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