001/*
002 * Copyright (C) 2012 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License
010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011 * or implied. See the License for the specific language governing permissions and limitations under
012 * the License.
013 */
014
015package com.google.common.io;
016
017import static com.google.common.base.Preconditions.checkArgument;
018import static com.google.common.base.Preconditions.checkNotNull;
019import static com.google.common.io.ByteStreams.skipUpTo;
020import static java.lang.Math.min;
021
022import com.google.common.annotations.GwtIncompatible;
023import com.google.common.annotations.J2ktIncompatible;
024import com.google.common.base.Ascii;
025import com.google.common.base.Optional;
026import com.google.common.collect.ImmutableList;
027import com.google.common.hash.Funnels;
028import com.google.common.hash.HashCode;
029import com.google.common.hash.HashFunction;
030import com.google.common.hash.Hasher;
031import com.google.errorprone.annotations.CanIgnoreReturnValue;
032import java.io.BufferedInputStream;
033import java.io.ByteArrayInputStream;
034import java.io.IOException;
035import java.io.InputStream;
036import java.io.InputStreamReader;
037import java.io.OutputStream;
038import java.io.Reader;
039import java.nio.charset.Charset;
040import java.util.Arrays;
041import java.util.Collection;
042import java.util.Iterator;
043import org.jspecify.annotations.Nullable;
044
045/**
046 * A readable source of bytes, such as a file. Unlike an {@link InputStream}, a {@code ByteSource}
047 * is not an open, stateful stream for input that can be read and closed. Instead, it is an
048 * immutable <i>supplier</i> of {@code InputStream} instances.
049 *
050 * <p>{@code ByteSource} provides two kinds of methods:
051 *
052 * <ul>
053 *   <li><b>Methods that return a stream:</b> These methods should return a <i>new</i>, independent
054 *       instance each time they are called. The caller is responsible for ensuring that the
055 *       returned stream is closed.
056 *   <li><b>Convenience methods:</b> These are implementations of common operations that are
057 *       typically implemented by opening a stream using one of the methods in the first category,
058 *       doing something and finally closing the stream that was opened.
059 * </ul>
060 *
061 * <p><b>Note:</b> In general, {@code ByteSource} is intended to be used for "file-like" sources
062 * that provide streams that are:
063 *
064 * <ul>
065 *   <li><b>Finite:</b> Many operations, such as {@link #size()} and {@link #read()}, will either
066 *       block indefinitely or fail if the source creates an infinite stream.
067 *   <li><b>Non-destructive:</b> A <i>destructive</i> stream will consume or otherwise alter the
068 *       bytes of the source as they are read from it. A source that provides such streams will not
069 *       be reusable, and operations that read from the stream (including {@link #size()}, in some
070 *       implementations) will prevent further operations from completing as expected.
071 * </ul>
072 *
073 * @since 14.0
074 * @author Colin Decker
075 */
076@J2ktIncompatible
077@GwtIncompatible
078public abstract class ByteSource {
079
080  /** Constructor for use by subclasses. */
081  protected ByteSource() {}
082
083  /**
084   * Returns a {@link CharSource} view of this byte source that decodes bytes read from this source
085   * as characters using the given {@link Charset}.
086   *
087   * <p>If {@link CharSource#asByteSource} is called on the returned source with the same charset,
088   * the default implementation of this method will ensure that the original {@code ByteSource} is
089   * returned, rather than round-trip encoding. Subclasses that override this method should behave
090   * the same way.
091   */
092  public CharSource asCharSource(Charset charset) {
093    return new AsCharSource(charset);
094  }
095
096  /**
097   * Opens a new {@link InputStream} for reading from this source. This method returns a new,
098   * independent stream each time it is called.
099   *
100   * <p>The caller is responsible for ensuring that the returned stream is closed.
101   *
102   * @throws IOException if an I/O error occurs while opening the stream
103   */
104  public abstract InputStream openStream() throws IOException;
105
106  /**
107   * Opens a new buffered {@link InputStream} for reading from this source. The returned stream is
108   * not required to be a {@link BufferedInputStream} in order to allow implementations to simply
109   * delegate to {@link #openStream()} when the stream returned by that method does not benefit from
110   * additional buffering (for example, a {@code ByteArrayInputStream}). This method returns a new,
111   * independent stream each time it is called.
112   *
113   * <p>The caller is responsible for ensuring that the returned stream is closed.
114   *
115   * @throws IOException if an I/O error occurs while opening the stream
116   * @since 15.0 (in 14.0 with return type {@link BufferedInputStream})
117   */
118  public InputStream openBufferedStream() throws IOException {
119    InputStream in = openStream();
120    return (in instanceof BufferedInputStream)
121        ? (BufferedInputStream) in
122        : new BufferedInputStream(in);
123  }
124
125  /**
126   * Returns a view of a slice of this byte source that is at most {@code length} bytes long
127   * starting at the given {@code offset}. If {@code offset} is greater than the size of this
128   * source, the returned source will be empty. If {@code offset + length} is greater than the size
129   * of this source, the returned source will contain the slice starting at {@code offset} and
130   * ending at the end of this source.
131   *
132   * @throws IllegalArgumentException if {@code offset} or {@code length} is negative
133   */
134  public ByteSource slice(long offset, long length) {
135    return new SlicedByteSource(offset, length);
136  }
137
138  /**
139   * Returns whether the source has zero bytes. The default implementation first checks {@link
140   * #sizeIfKnown}, returning true if it's known to be zero and false if it's known to be non-zero.
141   * If the size is not known, it falls back to opening a stream and checking for EOF.
142   *
143   * <p>Note that, in cases where {@code sizeIfKnown} returns zero, it is <i>possible</i> that bytes
144   * are actually available for reading. (For example, some special files may return a size of 0
145   * despite actually having content when read.) This means that a source may return {@code true}
146   * from {@code isEmpty()} despite having readable content.
147   *
148   * @throws IOException if an I/O error occurs
149   * @since 15.0
150   */
151  public boolean isEmpty() throws IOException {
152    Optional<Long> sizeIfKnown = sizeIfKnown();
153    if (sizeIfKnown.isPresent()) {
154      return sizeIfKnown.get() == 0L;
155    }
156    Closer closer = Closer.create();
157    try {
158      InputStream in = closer.register(openStream());
159      return in.read() == -1;
160    } catch (Throwable e) {
161      throw closer.rethrow(e);
162    } finally {
163      closer.close();
164    }
165  }
166
167  /**
168   * Returns the size of this source in bytes, if the size can be easily determined without actually
169   * opening the data stream.
170   *
171   * <p>The default implementation returns {@link Optional#absent}. Some sources, such as a file,
172   * may return a non-absent value. Note that in such cases, it is <i>possible</i> that this method
173   * will return a different number of bytes than would be returned by reading all of the bytes (for
174   * example, some special files may return a size of 0 despite actually having content when read).
175   *
176   * <p>Additionally, for mutable sources such as files, a subsequent read may return a different
177   * number of bytes if the contents are changed.
178   *
179   * @since 19.0
180   */
181  public Optional<Long> sizeIfKnown() {
182    return Optional.absent();
183  }
184
185  /**
186   * Returns the size of this source in bytes, even if doing so requires opening and traversing an
187   * entire stream. To avoid a potentially expensive operation, see {@link #sizeIfKnown}.
188   *
189   * <p>The default implementation calls {@link #sizeIfKnown} and returns the value if present. If
190   * absent, it will fall back to a heavyweight operation that will open a stream, read (or {@link
191   * InputStream#skip(long) skip}, if possible) to the end of the stream and return the total number
192   * of bytes that were read.
193   *
194   * <p>Note that for some sources that implement {@link #sizeIfKnown} to provide a more efficient
195   * implementation, it is <i>possible</i> that this method will return a different number of bytes
196   * than would be returned by reading all of the bytes (for example, some special files may return
197   * a size of 0 despite actually having content when read).
198   *
199   * <p>In either case, for mutable sources such as files, a subsequent read may return a different
200   * number of bytes if the contents are changed.
201   *
202   * @throws IOException if an I/O error occurs while reading the size of this source
203   */
204  public long size() throws IOException {
205    Optional<Long> sizeIfKnown = sizeIfKnown();
206    if (sizeIfKnown.isPresent()) {
207      return sizeIfKnown.get();
208    }
209
210    Closer closer = Closer.create();
211    try {
212      InputStream in = closer.register(openStream());
213      return countBySkipping(in);
214    } catch (IOException e) {
215      // skip may not be supported... at any rate, try reading
216    } finally {
217      closer.close();
218    }
219
220    closer = Closer.create();
221    try {
222      InputStream in = closer.register(openStream());
223      return ByteStreams.exhaust(in);
224    } catch (Throwable e) {
225      throw closer.rethrow(e);
226    } finally {
227      closer.close();
228    }
229  }
230
231  /** Counts the bytes in the given input stream using skip if possible. */
232  private long countBySkipping(InputStream in) throws IOException {
233    long count = 0;
234    long skipped;
235    while ((skipped = skipUpTo(in, Integer.MAX_VALUE)) > 0) {
236      count += skipped;
237    }
238    return count;
239  }
240
241  /**
242   * Copies the contents of this byte source to the given {@code OutputStream}. Does not close
243   * {@code output}.
244   *
245   * @return the number of bytes copied
246   * @throws IOException if an I/O error occurs while reading from this source or writing to {@code
247   *     output}
248   */
249  @CanIgnoreReturnValue
250  public long copyTo(OutputStream output) throws IOException {
251    checkNotNull(output);
252
253    Closer closer = Closer.create();
254    try {
255      InputStream in = closer.register(openStream());
256      return ByteStreams.copy(in, output);
257    } catch (Throwable e) {
258      throw closer.rethrow(e);
259    } finally {
260      closer.close();
261    }
262  }
263
264  /**
265   * Copies the contents of this byte source to the given {@code ByteSink}.
266   *
267   * @return the number of bytes copied
268   * @throws IOException if an I/O error occurs while reading from this source or writing to {@code
269   *     sink}
270   */
271  @CanIgnoreReturnValue
272  public long copyTo(ByteSink sink) throws IOException {
273    checkNotNull(sink);
274
275    Closer closer = Closer.create();
276    try {
277      InputStream in = closer.register(openStream());
278      OutputStream out = closer.register(sink.openStream());
279      return ByteStreams.copy(in, out);
280    } catch (Throwable e) {
281      throw closer.rethrow(e);
282    } finally {
283      closer.close();
284    }
285  }
286
287  /**
288   * Reads the full contents of this byte source as a byte array.
289   *
290   * @throws IOException if an I/O error occurs while reading from this source
291   */
292  public byte[] read() throws IOException {
293    Closer closer = Closer.create();
294    try {
295      InputStream in = closer.register(openStream());
296      Optional<Long> size = sizeIfKnown();
297      return size.isPresent()
298          ? ByteStreams.toByteArray(in, size.get())
299          : ByteStreams.toByteArray(in);
300    } catch (Throwable e) {
301      throw closer.rethrow(e);
302    } finally {
303      closer.close();
304    }
305  }
306
307  /**
308   * Reads the contents of this byte source using the given {@code processor} to process bytes as
309   * they are read. Stops when all bytes have been read or the consumer returns {@code false}.
310   * Returns the result produced by the processor.
311   *
312   * @throws IOException if an I/O error occurs while reading from this source or if {@code
313   *     processor} throws an {@code IOException}
314   * @since 16.0
315   */
316  @CanIgnoreReturnValue // some processors won't return a useful result
317  @ParametricNullness
318  public <T extends @Nullable Object> T read(ByteProcessor<T> processor) throws IOException {
319    checkNotNull(processor);
320
321    Closer closer = Closer.create();
322    try {
323      InputStream in = closer.register(openStream());
324      return ByteStreams.readBytes(in, processor);
325    } catch (Throwable e) {
326      throw closer.rethrow(e);
327    } finally {
328      closer.close();
329    }
330  }
331
332  /**
333   * Hashes the contents of this byte source using the given hash function.
334   *
335   * @throws IOException if an I/O error occurs while reading from this source
336   */
337  public HashCode hash(HashFunction hashFunction) throws IOException {
338    Hasher hasher = hashFunction.newHasher();
339    copyTo(Funnels.asOutputStream(hasher));
340    return hasher.hash();
341  }
342
343  /**
344   * Checks that the contents of this byte source are equal to the contents of the given byte
345   * source.
346   *
347   * @throws IOException if an I/O error occurs while reading from this source or {@code other}
348   */
349  public boolean contentEquals(ByteSource other) throws IOException {
350    checkNotNull(other);
351
352    Closer closer = Closer.create();
353    try {
354      return ByteStreams.contentsEqual(
355          closer.register(openStream()), closer.register(other.openStream()));
356    } catch (Throwable e) {
357      throw closer.rethrow(e);
358    } finally {
359      closer.close();
360    }
361  }
362
363  /**
364   * Concatenates multiple {@link ByteSource} instances into a single source. Streams returned from
365   * the source will contain the concatenated data from the streams of the underlying sources.
366   *
367   * <p>Only one underlying stream will be open at a time. Closing the concatenated stream will
368   * close the open underlying stream.
369   *
370   * @param sources the sources to concatenate
371   * @return a {@code ByteSource} containing the concatenated data
372   * @since 15.0
373   */
374  public static ByteSource concat(Iterable<? extends ByteSource> sources) {
375    return new ConcatenatedByteSource(sources);
376  }
377
378  /**
379   * Concatenates multiple {@link ByteSource} instances into a single source. Streams returned from
380   * the source will contain the concatenated data from the streams of the underlying sources.
381   *
382   * <p>Only one underlying stream will be open at a time. Closing the concatenated stream will
383   * close the open underlying stream.
384   *
385   * <p>Note: The input {@code Iterator} will be copied to an {@code ImmutableList} when this method
386   * is called. This will fail if the iterator is infinite and may cause problems if the iterator
387   * eagerly fetches data for each source when iterated (rather than producing sources that only
388   * load data through their streams). Prefer using the {@link #concat(Iterable)} overload if
389   * possible.
390   *
391   * @param sources the sources to concatenate
392   * @return a {@code ByteSource} containing the concatenated data
393   * @throws NullPointerException if any of {@code sources} is {@code null}
394   * @since 15.0
395   */
396  public static ByteSource concat(Iterator<? extends ByteSource> sources) {
397    return concat(ImmutableList.copyOf(sources));
398  }
399
400  /**
401   * Concatenates multiple {@link ByteSource} instances into a single source. Streams returned from
402   * the source will contain the concatenated data from the streams of the underlying sources.
403   *
404   * <p>Only one underlying stream will be open at a time. Closing the concatenated stream will
405   * close the open underlying stream.
406   *
407   * @param sources the sources to concatenate
408   * @return a {@code ByteSource} containing the concatenated data
409   * @throws NullPointerException if any of {@code sources} is {@code null}
410   * @since 15.0
411   */
412  public static ByteSource concat(ByteSource... sources) {
413    return concat(ImmutableList.copyOf(sources));
414  }
415
416  /**
417   * Returns a view of the given byte array as a {@link ByteSource}. To view only a specific range
418   * in the array, use {@code ByteSource.wrap(b).slice(offset, length)}.
419   *
420   * <p>Note that the given byte array may be passed directly to methods on, for example, {@code
421   * OutputStream} (when {@code copyTo(OutputStream)} is called on the resulting {@code
422   * ByteSource}). This could allow a malicious {@code OutputStream} implementation to modify the
423   * contents of the array, but provides better performance in the normal case.
424   *
425   * @since 15.0 (since 14.0 as {@code ByteStreams.asByteSource(byte[])}).
426   */
427  public static ByteSource wrap(byte[] b) {
428    return new ByteArrayByteSource(b);
429  }
430
431  /**
432   * Returns an immutable {@link ByteSource} that contains no bytes.
433   *
434   * @since 15.0
435   */
436  public static ByteSource empty() {
437    return EmptyByteSource.INSTANCE;
438  }
439
440  /**
441   * A char source that reads bytes from this source and decodes them as characters using a charset.
442   */
443  class AsCharSource extends CharSource {
444
445    final Charset charset;
446
447    AsCharSource(Charset charset) {
448      this.charset = checkNotNull(charset);
449    }
450
451    @Override
452    public ByteSource asByteSource(Charset charset) {
453      if (charset.equals(this.charset)) {
454        return ByteSource.this;
455      }
456      return super.asByteSource(charset);
457    }
458
459    @Override
460    public Reader openStream() throws IOException {
461      return new InputStreamReader(ByteSource.this.openStream(), charset);
462    }
463
464    @Override
465    public String read() throws IOException {
466      // Reading all the data as a byte array is more efficient than the default read()
467      // implementation because:
468      // 1. the string constructor can avoid an extra copy most of the time by correctly sizing the
469      //    internal char array (hard to avoid using StringBuilder)
470      // 2. we avoid extra copies into temporary buffers altogether
471      // The downside is that this will cause us to store the file bytes in memory twice for a short
472      // amount of time.
473      return new String(ByteSource.this.read(), charset);
474    }
475
476    @Override
477    public String toString() {
478      return ByteSource.this.toString() + ".asCharSource(" + charset + ")";
479    }
480  }
481
482  /** A view of a subsection of the containing byte source. */
483  private final class SlicedByteSource extends ByteSource {
484
485    final long offset;
486    final long length;
487
488    SlicedByteSource(long offset, long length) {
489      checkArgument(offset >= 0, "offset (%s) may not be negative", offset);
490      checkArgument(length >= 0, "length (%s) may not be negative", length);
491      this.offset = offset;
492      this.length = length;
493    }
494
495    @Override
496    public InputStream openStream() throws IOException {
497      return sliceStream(ByteSource.this.openStream());
498    }
499
500    @Override
501    public InputStream openBufferedStream() throws IOException {
502      return sliceStream(ByteSource.this.openBufferedStream());
503    }
504
505    private InputStream sliceStream(InputStream in) throws IOException {
506      if (offset > 0) {
507        long skipped;
508        try {
509          skipped = ByteStreams.skipUpTo(in, offset);
510        } catch (Throwable e) {
511          Closer closer = Closer.create();
512          closer.register(in);
513          try {
514            throw closer.rethrow(e);
515          } finally {
516            closer.close();
517          }
518        }
519
520        if (skipped < offset) {
521          // offset was beyond EOF
522          in.close();
523          return new ByteArrayInputStream(new byte[0]);
524        }
525      }
526      return ByteStreams.limit(in, length);
527    }
528
529    @Override
530    public ByteSource slice(long offset, long length) {
531      checkArgument(offset >= 0, "offset (%s) may not be negative", offset);
532      checkArgument(length >= 0, "length (%s) may not be negative", length);
533      long maxLength = this.length - offset;
534      return maxLength <= 0
535          ? ByteSource.empty()
536          : ByteSource.this.slice(this.offset + offset, min(length, maxLength));
537    }
538
539    @Override
540    public boolean isEmpty() throws IOException {
541      return length == 0 || super.isEmpty();
542    }
543
544    @Override
545    public Optional<Long> sizeIfKnown() {
546      Optional<Long> optionalUnslicedSize = ByteSource.this.sizeIfKnown();
547      if (optionalUnslicedSize.isPresent()) {
548        long unslicedSize = optionalUnslicedSize.get();
549        long off = min(offset, unslicedSize);
550        return Optional.of(min(length, unslicedSize - off));
551      }
552      return Optional.absent();
553    }
554
555    @Override
556    public String toString() {
557      return ByteSource.this.toString() + ".slice(" + offset + ", " + length + ")";
558    }
559  }
560
561  private static class ByteArrayByteSource extends
562      ByteSource
563  {
564
565    final byte[] bytes;
566    final int offset;
567    final int length;
568
569    ByteArrayByteSource(byte[] bytes) {
570      this(bytes, 0, bytes.length);
571    }
572
573    // NOTE: Preconditions are enforced by slice, the only non-trivial caller.
574    ByteArrayByteSource(byte[] bytes, int offset, int length) {
575      this.bytes = bytes;
576      this.offset = offset;
577      this.length = length;
578    }
579
580    @Override
581    public InputStream openStream() {
582      return new ByteArrayInputStream(bytes, offset, length);
583    }
584
585    @Override
586    public InputStream openBufferedStream() {
587      return openStream();
588    }
589
590    @Override
591    public boolean isEmpty() {
592      return length == 0;
593    }
594
595    @Override
596    public long size() {
597      return length;
598    }
599
600    @Override
601    public Optional<Long> sizeIfKnown() {
602      return Optional.of((long) length);
603    }
604
605    @Override
606    public byte[] read() {
607      return Arrays.copyOfRange(bytes, offset, offset + length);
608    }
609
610    @SuppressWarnings("CheckReturnValue") // it doesn't matter what processBytes returns here
611    @Override
612    @ParametricNullness
613    public <T extends @Nullable Object> T read(ByteProcessor<T> processor) throws IOException {
614      processor.processBytes(bytes, offset, length);
615      return processor.getResult();
616    }
617
618    @Override
619    public long copyTo(OutputStream output) throws IOException {
620      output.write(bytes, offset, length);
621      return length;
622    }
623
624    @Override
625    public HashCode hash(HashFunction hashFunction) throws IOException {
626      return hashFunction.hashBytes(bytes, offset, length);
627    }
628
629    @Override
630    public ByteSource slice(long offset, long length) {
631      checkArgument(offset >= 0, "offset (%s) may not be negative", offset);
632      checkArgument(length >= 0, "length (%s) may not be negative", length);
633
634      offset = min(offset, this.length);
635      length = min(length, this.length - offset);
636      int newOffset = this.offset + (int) offset;
637      return new ByteArrayByteSource(bytes, newOffset, (int) length);
638    }
639
640    @Override
641    public String toString() {
642      return "ByteSource.wrap("
643          + Ascii.truncate(BaseEncoding.base16().encode(bytes, offset, length), 30, "...")
644          + ")";
645    }
646  }
647
648  private static final class EmptyByteSource extends ByteArrayByteSource {
649
650    static final EmptyByteSource INSTANCE = new EmptyByteSource();
651
652    EmptyByteSource() {
653      super(new byte[0]);
654    }
655
656    @Override
657    public CharSource asCharSource(Charset charset) {
658      checkNotNull(charset);
659      return CharSource.empty();
660    }
661
662    @Override
663    public byte[] read() {
664      return bytes; // length is 0, no need to clone
665    }
666
667    @Override
668    public String toString() {
669      return "ByteSource.empty()";
670    }
671  }
672
673  private static final class ConcatenatedByteSource extends ByteSource {
674
675    final Iterable<? extends ByteSource> sources;
676
677    ConcatenatedByteSource(Iterable<? extends ByteSource> sources) {
678      this.sources = checkNotNull(sources);
679    }
680
681    @Override
682    public InputStream openStream() throws IOException {
683      return new MultiInputStream(sources.iterator());
684    }
685
686    @Override
687    public boolean isEmpty() throws IOException {
688      for (ByteSource source : sources) {
689        if (!source.isEmpty()) {
690          return false;
691        }
692      }
693      return true;
694    }
695
696    @Override
697    public Optional<Long> sizeIfKnown() {
698      if (!(sources instanceof Collection)) {
699        // Infinite Iterables can cause problems here. Of course, it's true that most of the other
700        // methods on this class also have potential problems with infinite  Iterables. But unlike
701        // those, this method can cause issues even if the user is dealing with a (finite) slice()
702        // of this source, since the slice's sizeIfKnown() method needs to know the size of the
703        // underlying source to know what its size actually is.
704        return Optional.absent();
705      }
706      long result = 0L;
707      for (ByteSource source : sources) {
708        Optional<Long> sizeIfKnown = source.sizeIfKnown();
709        if (!sizeIfKnown.isPresent()) {
710          return Optional.absent();
711        }
712        result += sizeIfKnown.get();
713        if (result < 0) {
714          // Overflow (or one or more sources that returned a negative size, but all bets are off in
715          // that case)
716          // Can't represent anything higher, and realistically there probably isn't anything that
717          // can actually be done anyway with the supposed 8+ exbibytes of data the source is
718          // claiming to have if we get here, so just stop.
719          return Optional.of(Long.MAX_VALUE);
720        }
721      }
722      return Optional.of(result);
723    }
724
725    @Override
726    public long size() throws IOException {
727      long result = 0L;
728      for (ByteSource source : sources) {
729        result += source.size();
730        if (result < 0) {
731          // Overflow (or one or more sources that returned a negative size, but all bets are off in
732          // that case)
733          // Can't represent anything higher, and realistically there probably isn't anything that
734          // can actually be done anyway with the supposed 8+ exbibytes of data the source is
735          // claiming to have if we get here, so just stop.
736          return Long.MAX_VALUE;
737        }
738      }
739      return result;
740    }
741
742    @Override
743    public String toString() {
744      return "ByteSource.concat(" + sources + ")";
745    }
746  }
747}