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