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 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, 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 = min(offset, unslicedSize); 563 return Optional.of(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 = min(offset, this.length); 648 length = 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}