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