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