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
017package com.google.common.io;
018
019import com.google.common.annotations.Beta;
020import com.google.common.base.Preconditions;
021import com.google.common.hash.Funnels;
022import com.google.common.hash.HashCode;
023import com.google.common.hash.HashFunction;
024import com.google.common.hash.Hasher;
025
026import java.io.ByteArrayInputStream;
027import java.io.ByteArrayOutputStream;
028import java.io.DataInput;
029import java.io.DataInputStream;
030import java.io.DataOutput;
031import java.io.DataOutputStream;
032import java.io.EOFException;
033import java.io.IOException;
034import java.io.InputStream;
035import java.io.OutputStream;
036import java.nio.ByteBuffer;
037import java.nio.channels.ReadableByteChannel;
038import java.nio.channels.WritableByteChannel;
039import java.util.Arrays;
040import 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
049public 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}