001 /* 002 * Copyright (C) 2007 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package com.google.common.io; 018 019 import com.google.common.annotations.Beta; 020 import com.google.common.base.Preconditions; 021 import com.google.common.hash.Funnels; 022 import com.google.common.hash.HashCode; 023 import com.google.common.hash.HashFunction; 024 import com.google.common.hash.Hasher; 025 026 import java.io.ByteArrayInputStream; 027 import java.io.ByteArrayOutputStream; 028 import java.io.DataInput; 029 import java.io.DataInputStream; 030 import java.io.DataOutput; 031 import java.io.DataOutputStream; 032 import java.io.EOFException; 033 import java.io.IOException; 034 import java.io.InputStream; 035 import java.io.OutputStream; 036 import java.nio.ByteBuffer; 037 import java.nio.channels.ReadableByteChannel; 038 import java.nio.channels.WritableByteChannel; 039 import java.util.Arrays; 040 import java.util.zip.Checksum; 041 042 /** 043 * Provides utility methods for working with byte arrays and I/O streams. 044 * 045 * @author Chris Nokleberg 046 * @since 1.0 047 */ 048 @Beta 049 public final class ByteStreams { 050 private static final int BUF_SIZE = 0x1000; // 4K 051 052 private ByteStreams() {} 053 054 /** 055 * Returns a factory that will supply instances of 056 * {@link ByteArrayInputStream} that read from the given byte array. 057 * 058 * @param b the input buffer 059 * @return the factory 060 */ 061 public static InputSupplier<ByteArrayInputStream> newInputStreamSupplier( 062 byte[] b) { 063 return newInputStreamSupplier(b, 0, b.length); 064 } 065 066 /** 067 * Returns a factory that will supply instances of 068 * {@link ByteArrayInputStream} that read from the given byte array. 069 * 070 * @param b the input buffer 071 * @param off the offset in the buffer of the first byte to read 072 * @param len the maximum number of bytes to read from the buffer 073 * @return the factory 074 */ 075 public static InputSupplier<ByteArrayInputStream> newInputStreamSupplier( 076 final byte[] b, final int off, final int len) { 077 return new InputSupplier<ByteArrayInputStream>() { 078 @Override 079 public ByteArrayInputStream getInput() { 080 return new ByteArrayInputStream(b, off, len); 081 } 082 }; 083 } 084 085 /** 086 * Writes a byte array to an output stream from the given supplier. 087 * 088 * @param from the bytes to write 089 * @param to the output supplier 090 * @throws IOException if an I/O error occurs 091 */ 092 public static void write(byte[] from, 093 OutputSupplier<? extends OutputStream> to) throws IOException { 094 Preconditions.checkNotNull(from); 095 boolean threw = true; 096 OutputStream out = to.getOutput(); 097 try { 098 out.write(from); 099 threw = false; 100 } finally { 101 Closeables.close(out, threw); 102 } 103 } 104 105 /** 106 * Opens input and output streams from the given suppliers, copies all 107 * bytes from the input to the output, and closes the streams. 108 * 109 * @param from the input factory 110 * @param to the output factory 111 * @return the number of bytes copied 112 * @throws IOException if an I/O error occurs 113 */ 114 public static long copy(InputSupplier<? extends InputStream> from, 115 OutputSupplier<? extends OutputStream> to) throws IOException { 116 int successfulOps = 0; 117 InputStream in = from.getInput(); 118 try { 119 OutputStream out = to.getOutput(); 120 try { 121 long count = copy(in, out); 122 successfulOps++; 123 return count; 124 } finally { 125 Closeables.close(out, successfulOps < 1); 126 successfulOps++; 127 } 128 } finally { 129 Closeables.close(in, successfulOps < 2); 130 } 131 } 132 133 /** 134 * Opens an input stream from the supplier, copies all bytes from the 135 * input to the output, and closes the input stream. Does not close 136 * or flush the output stream. 137 * 138 * @param from the input factory 139 * @param to the output stream to write to 140 * @return the number of bytes copied 141 * @throws IOException if an I/O error occurs 142 */ 143 public static long copy(InputSupplier<? extends InputStream> from, 144 OutputStream to) throws IOException { 145 boolean threw = true; 146 InputStream in = from.getInput(); 147 try { 148 long count = copy(in, to); 149 threw = false; 150 return count; 151 } finally { 152 Closeables.close(in, threw); 153 } 154 } 155 156 /** 157 * Opens an output stream from the supplier, copies all bytes from the input 158 * to the output, and closes the output stream. Does not close or flush the 159 * input stream. 160 * 161 * @param from the input stream to read from 162 * @param to the output factory 163 * @return the number of bytes copied 164 * @throws IOException if an I/O error occurs 165 * @since 10.0 166 */ 167 public static long copy(InputStream from, 168 OutputSupplier<? extends OutputStream> to) throws IOException { 169 boolean threw = true; 170 OutputStream out = to.getOutput(); 171 try { 172 long count = copy(from, out); 173 threw = false; 174 return count; 175 } finally { 176 Closeables.close(out, threw); 177 } 178 } 179 180 /** 181 * Copies all bytes from the input stream to the output stream. 182 * Does not close or flush either stream. 183 * 184 * @param from the input stream to read from 185 * @param to the output stream to write to 186 * @return the number of bytes copied 187 * @throws IOException if an I/O error occurs 188 */ 189 public static long copy(InputStream from, OutputStream to) 190 throws IOException { 191 byte[] buf = new byte[BUF_SIZE]; 192 long total = 0; 193 while (true) { 194 int r = from.read(buf); 195 if (r == -1) { 196 break; 197 } 198 to.write(buf, 0, r); 199 total += r; 200 } 201 return total; 202 } 203 204 /** 205 * Copies all bytes from the readable channel to the writable channel. 206 * Does not close or flush either channel. 207 * 208 * @param from the readable channel to read from 209 * @param to the writable channel to write to 210 * @return the number of bytes copied 211 * @throws IOException if an I/O error occurs 212 */ 213 public static long copy(ReadableByteChannel from, 214 WritableByteChannel to) throws IOException { 215 ByteBuffer buf = ByteBuffer.allocate(BUF_SIZE); 216 long total = 0; 217 while (from.read(buf) != -1) { 218 buf.flip(); 219 while (buf.hasRemaining()) { 220 total += to.write(buf); 221 } 222 buf.clear(); 223 } 224 return total; 225 } 226 227 /** 228 * Reads all bytes from an input stream into a byte array. 229 * Does not close the stream. 230 * 231 * @param in the input stream to read from 232 * @return a byte array containing all the bytes from the stream 233 * @throws IOException if an I/O error occurs 234 */ 235 public static byte[] toByteArray(InputStream in) throws IOException { 236 ByteArrayOutputStream out = new ByteArrayOutputStream(); 237 copy(in, out); 238 return out.toByteArray(); 239 } 240 241 /** 242 * Returns the data from a {@link InputStream} factory as a byte array. 243 * 244 * @param supplier the factory 245 * @throws IOException if an I/O error occurs 246 */ 247 public static byte[] toByteArray( 248 InputSupplier<? extends InputStream> supplier) throws IOException { 249 boolean threw = true; 250 InputStream in = supplier.getInput(); 251 try { 252 byte[] result = toByteArray(in); 253 threw = false; 254 return result; 255 } finally { 256 Closeables.close(in, threw); 257 } 258 } 259 260 /** 261 * Returns a new {@link ByteArrayDataInput} instance to read from the {@code 262 * bytes} array from the beginning. 263 */ 264 public static ByteArrayDataInput newDataInput(byte[] bytes) { 265 return new ByteArrayDataInputStream(bytes); 266 } 267 268 /** 269 * Returns a new {@link ByteArrayDataInput} instance to read from the {@code 270 * bytes} array, starting at the given position. 271 * 272 * @throws IndexOutOfBoundsException if {@code start} is negative or greater 273 * than the length of the array 274 */ 275 public static ByteArrayDataInput newDataInput(byte[] bytes, int start) { 276 Preconditions.checkPositionIndex(start, bytes.length); 277 return new ByteArrayDataInputStream(bytes, start); 278 } 279 280 private static class ByteArrayDataInputStream implements ByteArrayDataInput { 281 final DataInput input; 282 283 ByteArrayDataInputStream(byte[] bytes) { 284 this.input = new DataInputStream(new ByteArrayInputStream(bytes)); 285 } 286 287 ByteArrayDataInputStream(byte[] bytes, int start) { 288 this.input = new DataInputStream( 289 new ByteArrayInputStream(bytes, start, bytes.length - start)); 290 } 291 292 @Override public void readFully(byte b[]) { 293 try { 294 input.readFully(b); 295 } catch (IOException e) { 296 throw new IllegalStateException(e); 297 } 298 } 299 300 @Override public void readFully(byte b[], int off, int len) { 301 try { 302 input.readFully(b, off, len); 303 } catch (IOException e) { 304 throw new IllegalStateException(e); 305 } 306 } 307 308 @Override public int skipBytes(int n) { 309 try { 310 return input.skipBytes(n); 311 } catch (IOException e) { 312 throw new IllegalStateException(e); 313 } 314 } 315 316 @Override public boolean readBoolean() { 317 try { 318 return input.readBoolean(); 319 } catch (IOException e) { 320 throw new IllegalStateException(e); 321 } 322 } 323 324 @Override public byte readByte() { 325 try { 326 return input.readByte(); 327 } catch (EOFException e) { 328 throw new IllegalStateException(e); 329 } catch (IOException impossible) { 330 throw new AssertionError(impossible); 331 } 332 } 333 334 @Override public int readUnsignedByte() { 335 try { 336 return input.readUnsignedByte(); 337 } catch (IOException e) { 338 throw new IllegalStateException(e); 339 } 340 } 341 342 @Override public short readShort() { 343 try { 344 return input.readShort(); 345 } catch (IOException e) { 346 throw new IllegalStateException(e); 347 } 348 } 349 350 @Override public int readUnsignedShort() { 351 try { 352 return input.readUnsignedShort(); 353 } catch (IOException e) { 354 throw new IllegalStateException(e); 355 } 356 } 357 358 @Override public char readChar() { 359 try { 360 return input.readChar(); 361 } catch (IOException e) { 362 throw new IllegalStateException(e); 363 } 364 } 365 366 @Override public int readInt() { 367 try { 368 return input.readInt(); 369 } catch (IOException e) { 370 throw new IllegalStateException(e); 371 } 372 } 373 374 @Override public long readLong() { 375 try { 376 return input.readLong(); 377 } catch (IOException e) { 378 throw new IllegalStateException(e); 379 } 380 } 381 382 @Override public float readFloat() { 383 try { 384 return input.readFloat(); 385 } catch (IOException e) { 386 throw new IllegalStateException(e); 387 } 388 } 389 390 @Override public double readDouble() { 391 try { 392 return input.readDouble(); 393 } catch (IOException e) { 394 throw new IllegalStateException(e); 395 } 396 } 397 398 @Override public String readLine() { 399 try { 400 return input.readLine(); 401 } catch (IOException e) { 402 throw new IllegalStateException(e); 403 } 404 } 405 406 @Override public String readUTF() { 407 try { 408 return input.readUTF(); 409 } catch (IOException e) { 410 throw new IllegalStateException(e); 411 } 412 } 413 } 414 415 /** 416 * Returns a new {@link ByteArrayDataOutput} instance with a default size. 417 */ 418 public static ByteArrayDataOutput newDataOutput() { 419 return new ByteArrayDataOutputStream(); 420 } 421 422 /** 423 * Returns a new {@link ByteArrayDataOutput} instance sized to hold 424 * {@code size} bytes before resizing. 425 * 426 * @throws IllegalArgumentException if {@code size} is negative 427 */ 428 public static ByteArrayDataOutput newDataOutput(int size) { 429 Preconditions.checkArgument(size >= 0, "Invalid size: %s", size); 430 return new ByteArrayDataOutputStream(size); 431 } 432 433 @SuppressWarnings("deprecation") // for writeBytes 434 private static class ByteArrayDataOutputStream 435 implements ByteArrayDataOutput { 436 437 final DataOutput output; 438 final ByteArrayOutputStream byteArrayOutputSteam; 439 440 ByteArrayDataOutputStream() { 441 this(new ByteArrayOutputStream()); 442 } 443 444 ByteArrayDataOutputStream(int size) { 445 this(new ByteArrayOutputStream(size)); 446 } 447 448 ByteArrayDataOutputStream(ByteArrayOutputStream byteArrayOutputSteam) { 449 this.byteArrayOutputSteam = byteArrayOutputSteam; 450 output = new DataOutputStream(byteArrayOutputSteam); 451 } 452 453 @Override public void write(int b) { 454 try { 455 output.write(b); 456 } catch (IOException impossible) { 457 throw new AssertionError(impossible); 458 } 459 } 460 461 @Override public void write(byte[] b) { 462 try { 463 output.write(b); 464 } catch (IOException impossible) { 465 throw new AssertionError(impossible); 466 } 467 } 468 469 @Override public void write(byte[] b, int off, int len) { 470 try { 471 output.write(b, off, len); 472 } catch (IOException impossible) { 473 throw new AssertionError(impossible); 474 } 475 } 476 477 @Override public void writeBoolean(boolean v) { 478 try { 479 output.writeBoolean(v); 480 } catch (IOException impossible) { 481 throw new AssertionError(impossible); 482 } 483 } 484 485 @Override public void writeByte(int v) { 486 try { 487 output.writeByte(v); 488 } catch (IOException impossible) { 489 throw new AssertionError(impossible); 490 } 491 } 492 493 @Override public void writeBytes(String s) { 494 try { 495 output.writeBytes(s); 496 } catch (IOException impossible) { 497 throw new AssertionError(impossible); 498 } 499 } 500 501 @Override public void writeChar(int v) { 502 try { 503 output.writeChar(v); 504 } catch (IOException impossible) { 505 throw new AssertionError(impossible); 506 } 507 } 508 509 @Override public void writeChars(String s) { 510 try { 511 output.writeChars(s); 512 } catch (IOException impossible) { 513 throw new AssertionError(impossible); 514 } 515 } 516 517 @Override public void writeDouble(double v) { 518 try { 519 output.writeDouble(v); 520 } catch (IOException impossible) { 521 throw new AssertionError(impossible); 522 } 523 } 524 525 @Override public void writeFloat(float v) { 526 try { 527 output.writeFloat(v); 528 } catch (IOException impossible) { 529 throw new AssertionError(impossible); 530 } 531 } 532 533 @Override public void writeInt(int v) { 534 try { 535 output.writeInt(v); 536 } catch (IOException impossible) { 537 throw new AssertionError(impossible); 538 } 539 } 540 541 @Override public void writeLong(long v) { 542 try { 543 output.writeLong(v); 544 } catch (IOException impossible) { 545 throw new AssertionError(impossible); 546 } 547 } 548 549 @Override public void writeShort(int v) { 550 try { 551 output.writeShort(v); 552 } catch (IOException impossible) { 553 throw new AssertionError(impossible); 554 } 555 } 556 557 @Override public void writeUTF(String s) { 558 try { 559 output.writeUTF(s); 560 } catch (IOException impossible) { 561 throw new AssertionError(impossible); 562 } 563 } 564 565 @Override public byte[] toByteArray() { 566 return byteArrayOutputSteam.toByteArray(); 567 } 568 569 } 570 571 // TODO(chrisn): Not all streams support skipping. 572 /** Returns the length of a supplied input stream, in bytes. */ 573 public static long length(InputSupplier<? extends InputStream> supplier) 574 throws IOException { 575 long count = 0; 576 boolean threw = true; 577 InputStream in = supplier.getInput(); 578 try { 579 while (true) { 580 // We skip only Integer.MAX_VALUE due to JDK overflow bugs. 581 long amt = in.skip(Integer.MAX_VALUE); 582 if (amt == 0) { 583 if (in.read() == -1) { 584 threw = false; 585 return count; 586 } 587 count++; 588 } else { 589 count += amt; 590 } 591 } 592 } finally { 593 Closeables.close(in, threw); 594 } 595 } 596 597 /** 598 * Returns true if the supplied input streams contain the same bytes. 599 * 600 * @throws IOException if an I/O error occurs 601 */ 602 public static boolean equal(InputSupplier<? extends InputStream> supplier1, 603 InputSupplier<? extends InputStream> supplier2) throws IOException { 604 byte[] buf1 = new byte[BUF_SIZE]; 605 byte[] buf2 = new byte[BUF_SIZE]; 606 607 boolean threw = true; 608 InputStream in1 = supplier1.getInput(); 609 try { 610 InputStream in2 = supplier2.getInput(); 611 try { 612 while (true) { 613 int read1 = read(in1, buf1, 0, BUF_SIZE); 614 int read2 = read(in2, buf2, 0, BUF_SIZE); 615 if (read1 != read2 || !Arrays.equals(buf1, buf2)) { 616 threw = false; 617 return false; 618 } else if (read1 != BUF_SIZE) { 619 threw = false; 620 return true; 621 } 622 } 623 } finally { 624 Closeables.close(in2, threw); 625 } 626 } finally { 627 Closeables.close(in1, threw); 628 } 629 } 630 631 /** 632 * Attempts to read enough bytes from the stream to fill the given byte array, 633 * with the same behavior as {@link DataInput#readFully(byte[])}. 634 * Does not close the stream. 635 * 636 * @param in the input stream to read from. 637 * @param b the buffer into which the data is read. 638 * @throws EOFException if this stream reaches the end before reading all 639 * the bytes. 640 * @throws IOException if an I/O error occurs. 641 */ 642 public static void readFully(InputStream in, byte[] b) throws IOException { 643 readFully(in, b, 0, b.length); 644 } 645 646 /** 647 * Attempts to read {@code len} bytes from the stream into the given array 648 * starting at {@code off}, with the same behavior as 649 * {@link DataInput#readFully(byte[], int, int)}. Does not close the 650 * stream. 651 * 652 * @param in the input stream to read from. 653 * @param b the buffer into which the data is read. 654 * @param off an int specifying the offset into the data. 655 * @param len an int specifying the number of bytes to read. 656 * @throws EOFException if this stream reaches the end before reading all 657 * the bytes. 658 * @throws IOException if an I/O error occurs. 659 */ 660 public static void readFully(InputStream in, byte[] b, int off, int len) 661 throws IOException { 662 if (read(in, b, off, len) != len) { 663 throw new EOFException(); 664 } 665 } 666 667 /** 668 * Discards {@code n} bytes of data from the input stream. This method 669 * will block until the full amount has been skipped. Does not close the 670 * stream. 671 * 672 * @param in the input stream to read from 673 * @param n the number of bytes to skip 674 * @throws EOFException if this stream reaches the end before skipping all 675 * the bytes 676 * @throws IOException if an I/O error occurs, or the stream does not 677 * support skipping 678 */ 679 public static void skipFully(InputStream in, long n) throws IOException { 680 while (n > 0) { 681 long amt = in.skip(n); 682 if (amt == 0) { 683 // Force a blocking read to avoid infinite loop 684 if (in.read() == -1) { 685 throw new EOFException(); 686 } 687 n--; 688 } else { 689 n -= amt; 690 } 691 } 692 } 693 694 /** 695 * Process the bytes of a supplied stream 696 * 697 * @param supplier the input stream factory 698 * @param processor the object to which to pass the bytes of the stream 699 * @return the result of the byte processor 700 * @throws IOException if an I/O error occurs 701 */ 702 public static <T> T readBytes(InputSupplier<? extends InputStream> supplier, 703 ByteProcessor<T> processor) throws IOException { 704 byte[] buf = new byte[BUF_SIZE]; 705 boolean threw = true; 706 InputStream in = supplier.getInput(); 707 try { 708 int amt; 709 do { 710 amt = in.read(buf); 711 if (amt == -1) { 712 threw = false; 713 break; 714 } 715 } while (processor.processBytes(buf, 0, amt)); 716 return processor.getResult(); 717 } finally { 718 Closeables.close(in, threw); 719 } 720 } 721 722 /** 723 * Computes and returns the checksum value for a supplied input stream. 724 * The checksum object is reset when this method returns successfully. 725 * 726 * @param supplier the input stream factory 727 * @param checksum the checksum object 728 * @return the result of {@link Checksum#getValue} after updating the 729 * checksum object with all of the bytes in the stream 730 * @throws IOException if an I/O error occurs 731 */ 732 public static long getChecksum(InputSupplier<? extends InputStream> supplier, 733 final Checksum checksum) throws IOException { 734 return readBytes(supplier, new ByteProcessor<Long>() { 735 @Override 736 public boolean processBytes(byte[] buf, int off, int len) { 737 checksum.update(buf, off, len); 738 return true; 739 } 740 741 @Override 742 public Long getResult() { 743 long result = checksum.getValue(); 744 checksum.reset(); 745 return result; 746 } 747 }); 748 } 749 750 /** 751 * Computes the hash code of the data supplied by {@code supplier} using {@code 752 * hashFunction}. 753 * 754 * @param supplier the input stream factory 755 * @param hashFunction the hash function to use to hash the data 756 * @return the {@link HashCode} of all of the bytes in the input stream 757 * @throws IOException if an I/O error occurs 758 * @since 12.0 759 */ 760 public static HashCode hash( 761 InputSupplier<? extends InputStream> supplier, HashFunction hashFunction) 762 throws IOException { 763 Hasher hasher = hashFunction.newHasher(); 764 copy(supplier, Funnels.asOutputStream(hasher)); 765 return hasher.hash(); 766 } 767 768 /** 769 * Reads some bytes from an input stream and stores them into the buffer array 770 * {@code b}. This method blocks until {@code len} bytes of input data have 771 * been read into the array, or end of file is detected. The number of bytes 772 * read is returned, possibly zero. Does not close the stream. 773 * 774 * <p>A caller can detect EOF if the number of bytes read is less than 775 * {@code len}. All subsequent calls on the same stream will return zero. 776 * 777 * <p>If {@code b} is null, a {@code NullPointerException} is thrown. If 778 * {@code off} is negative, or {@code len} is negative, or {@code off+len} is 779 * greater than the length of the array {@code b}, then an 780 * {@code IndexOutOfBoundsException} is thrown. If {@code len} is zero, then 781 * no bytes are read. Otherwise, the first byte read is stored into element 782 * {@code b[off]}, the next one into {@code b[off+1]}, and so on. The number 783 * of bytes read is, at most, equal to {@code len}. 784 * 785 * @param in the input stream to read from 786 * @param b the buffer into which the data is read 787 * @param off an int specifying the offset into the data 788 * @param len an int specifying the number of bytes to read 789 * @return the number of bytes read 790 * @throws IOException if an I/O error occurs 791 */ 792 public static int read(InputStream in, byte[] b, int off, int len) 793 throws IOException { 794 if (len < 0) { 795 throw new IndexOutOfBoundsException("len is negative"); 796 } 797 int total = 0; 798 while (total < len) { 799 int result = in.read(b, off + total, len - total); 800 if (result == -1) { 801 break; 802 } 803 total += result; 804 } 805 return total; 806 } 807 808 /** 809 * Returns an {@link InputSupplier} that returns input streams from the 810 * an underlying supplier, where each stream starts at the given 811 * offset and is limited to the specified number of bytes. 812 * 813 * @param supplier the supplier from which to get the raw streams 814 * @param offset the offset in bytes into the underlying stream where 815 * the returned streams will start 816 * @param length the maximum length of the returned streams 817 * @throws IllegalArgumentException if offset or length are negative 818 */ 819 public static InputSupplier<InputStream> slice( 820 final InputSupplier<? extends InputStream> supplier, 821 final long offset, 822 final long length) { 823 Preconditions.checkNotNull(supplier); 824 Preconditions.checkArgument(offset >= 0, "offset is negative"); 825 Preconditions.checkArgument(length >= 0, "length is negative"); 826 return new InputSupplier<InputStream>() { 827 @Override public InputStream getInput() throws IOException { 828 InputStream in = supplier.getInput(); 829 if (offset > 0) { 830 try { 831 skipFully(in, offset); 832 } catch (IOException e) { 833 Closeables.closeQuietly(in); 834 throw e; 835 } 836 } 837 return new LimitInputStream(in, length); 838 } 839 }; 840 } 841 842 /** 843 * Joins multiple {@link InputStream} suppliers into a single supplier. 844 * Streams returned from the supplier will contain the concatenated data from 845 * the streams of the underlying suppliers. 846 * 847 * <p>Only one underlying input stream will be open at a time. Closing the 848 * joined stream will close the open underlying stream. 849 * 850 * <p>Reading from the joined stream will throw a {@link NullPointerException} 851 * if any of the suppliers are null or return null. 852 * 853 * @param suppliers the suppliers to concatenate 854 * @return a supplier that will return a stream containing the concatenated 855 * stream data 856 */ 857 public static InputSupplier<InputStream> join(final 858 Iterable<? extends InputSupplier<? extends InputStream>> suppliers) { 859 return new InputSupplier<InputStream>() { 860 @Override public InputStream getInput() throws IOException { 861 return new MultiInputStream(suppliers.iterator()); 862 } 863 }; 864 } 865 866 /** Varargs form of {@link #join(Iterable)}. */ 867 public static InputSupplier<InputStream> join( 868 InputSupplier<? extends InputStream>... suppliers) { 869 return join(Arrays.asList(suppliers)); 870 } 871 }