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