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}