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