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.Beta; 023import com.google.common.annotations.GwtIncompatible; 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@GwtIncompatible 077@ElementTypesAreNonnullByDefault 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 @Beta 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 @Beta 318 @CanIgnoreReturnValue // some processors won't return a useful result 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 ByteSource { 575 576 final byte[] bytes; 577 final int offset; 578 final int length; 579 580 ByteArrayByteSource(byte[] bytes) { 581 this(bytes, 0, bytes.length); 582 } 583 584 // NOTE: Preconditions are enforced by slice, the only non-trivial caller. 585 ByteArrayByteSource(byte[] bytes, int offset, int length) { 586 this.bytes = bytes; 587 this.offset = offset; 588 this.length = length; 589 } 590 591 @Override 592 public InputStream openStream() { 593 return new ByteArrayInputStream(bytes, offset, length); 594 } 595 596 @Override 597 public InputStream openBufferedStream() throws IOException { 598 return openStream(); 599 } 600 601 @Override 602 public boolean isEmpty() { 603 return length == 0; 604 } 605 606 @Override 607 public long size() { 608 return length; 609 } 610 611 @Override 612 public Optional<Long> sizeIfKnown() { 613 return Optional.of((long) length); 614 } 615 616 @Override 617 public byte[] read() { 618 return Arrays.copyOfRange(bytes, offset, offset + length); 619 } 620 621 @SuppressWarnings("CheckReturnValue") // it doesn't matter what processBytes returns here 622 @Override 623 @ParametricNullness 624 public <T extends @Nullable Object> T read(ByteProcessor<T> processor) throws IOException { 625 processor.processBytes(bytes, offset, length); 626 return processor.getResult(); 627 } 628 629 @Override 630 public long copyTo(OutputStream output) throws IOException { 631 output.write(bytes, offset, length); 632 return length; 633 } 634 635 @Override 636 public HashCode hash(HashFunction hashFunction) throws IOException { 637 return hashFunction.hashBytes(bytes, offset, length); 638 } 639 640 @Override 641 public ByteSource slice(long offset, long length) { 642 checkArgument(offset >= 0, "offset (%s) may not be negative", offset); 643 checkArgument(length >= 0, "length (%s) may not be negative", length); 644 645 offset = Math.min(offset, this.length); 646 length = Math.min(length, this.length - offset); 647 int newOffset = this.offset + (int) offset; 648 return new ByteArrayByteSource(bytes, newOffset, (int) length); 649 } 650 651 @Override 652 public String toString() { 653 return "ByteSource.wrap(" 654 + Ascii.truncate(BaseEncoding.base16().encode(bytes, offset, length), 30, "...") 655 + ")"; 656 } 657 } 658 659 private static final class EmptyByteSource extends ByteArrayByteSource { 660 661 static final EmptyByteSource INSTANCE = new EmptyByteSource(); 662 663 EmptyByteSource() { 664 super(new byte[0]); 665 } 666 667 @Override 668 public CharSource asCharSource(Charset charset) { 669 checkNotNull(charset); 670 return CharSource.empty(); 671 } 672 673 @Override 674 public byte[] read() { 675 return bytes; // length is 0, no need to clone 676 } 677 678 @Override 679 public String toString() { 680 return "ByteSource.empty()"; 681 } 682 } 683 684 private static final class ConcatenatedByteSource extends ByteSource { 685 686 final Iterable<? extends ByteSource> sources; 687 688 ConcatenatedByteSource(Iterable<? extends ByteSource> sources) { 689 this.sources = checkNotNull(sources); 690 } 691 692 @Override 693 public InputStream openStream() throws IOException { 694 return new MultiInputStream(sources.iterator()); 695 } 696 697 @Override 698 public boolean isEmpty() throws IOException { 699 for (ByteSource source : sources) { 700 if (!source.isEmpty()) { 701 return false; 702 } 703 } 704 return true; 705 } 706 707 @Override 708 public Optional<Long> sizeIfKnown() { 709 if (!(sources instanceof Collection)) { 710 // Infinite Iterables can cause problems here. Of course, it's true that most of the other 711 // methods on this class also have potential problems with infinite Iterables. But unlike 712 // those, this method can cause issues even if the user is dealing with a (finite) slice() 713 // of this source, since the slice's sizeIfKnown() method needs to know the size of the 714 // underlying source to know what its size actually is. 715 return Optional.absent(); 716 } 717 long result = 0L; 718 for (ByteSource source : sources) { 719 Optional<Long> sizeIfKnown = source.sizeIfKnown(); 720 if (!sizeIfKnown.isPresent()) { 721 return Optional.absent(); 722 } 723 result += sizeIfKnown.get(); 724 if (result < 0) { 725 // Overflow (or one or more sources that returned a negative size, but all bets are off in 726 // that case) 727 // Can't represent anything higher, and realistically there probably isn't anything that 728 // can actually be done anyway with the supposed 8+ exbibytes of data the source is 729 // claiming to have if we get here, so just stop. 730 return Optional.of(Long.MAX_VALUE); 731 } 732 } 733 return Optional.of(result); 734 } 735 736 @Override 737 public long size() throws IOException { 738 long result = 0L; 739 for (ByteSource source : sources) { 740 result += source.size(); 741 if (result < 0) { 742 // Overflow (or one or more sources that returned a negative size, but all bets are off in 743 // that case) 744 // Can't represent anything higher, and realistically there probably isn't anything that 745 // can actually be done anyway with the supposed 8+ exbibytes of data the source is 746 // claiming to have if we get here, so just stop. 747 return Long.MAX_VALUE; 748 } 749 } 750 return result; 751 } 752 753 @Override 754 public String toString() { 755 return "ByteSource.concat(" + sources + ")"; 756 } 757 } 758}