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