UnionInputStream.java

  1. /*
  2.  * Copyright (C) 2009, 2013 Google Inc. and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.util.io;

  11. import java.io.IOException;
  12. import java.io.InputStream;
  13. import java.util.Iterator;
  14. import java.util.LinkedList;

  15. /**
  16.  * An InputStream which reads from one or more InputStreams.
  17.  * <p>
  18.  * This stream may enter into an EOF state, returning -1 from any of the read
  19.  * methods, and then later successfully read additional bytes if a new
  20.  * InputStream is added after reaching EOF.
  21.  * <p>
  22.  * Currently this stream does not support the mark/reset APIs. If mark and later
  23.  * reset functionality is needed the caller should wrap this stream with a
  24.  * {@link java.io.BufferedInputStream}.
  25.  */
  26. public class UnionInputStream extends InputStream {
  27.     private static final InputStream EOF = new InputStream() {
  28.         @Override
  29.         public int read() throws IOException {
  30.             return -1;
  31.         }
  32.     };

  33.     private final LinkedList<InputStream> streams = new LinkedList<>();

  34.     /**
  35.      * Create an empty InputStream that is currently at EOF state.
  36.      */
  37.     public UnionInputStream() {
  38.         // Do nothing.
  39.     }

  40.     /**
  41.      * Create an InputStream that is a union of the individual streams.
  42.      * <p>
  43.      * As each stream reaches EOF, it will be automatically closed before bytes
  44.      * from the next stream are read.
  45.      *
  46.      * @param inputStreams
  47.      *            streams to be pushed onto this stream.
  48.      */
  49.     public UnionInputStream(InputStream... inputStreams) {
  50.         for (InputStream i : inputStreams)
  51.             add(i);
  52.     }

  53.     private InputStream head() {
  54.         return streams.isEmpty() ? EOF : streams.getFirst();
  55.     }

  56.     private void pop() throws IOException {
  57.         if (!streams.isEmpty())
  58.             streams.removeFirst().close();
  59.     }

  60.     /**
  61.      * Add the given InputStream onto the end of the stream queue.
  62.      * <p>
  63.      * When the stream reaches EOF it will be automatically closed.
  64.      *
  65.      * @param in
  66.      *            the stream to add; must not be null.
  67.      */
  68.     public void add(InputStream in) {
  69.         streams.add(in);
  70.     }

  71.     /**
  72.      * Returns true if there are no more InputStreams in the stream queue.
  73.      * <p>
  74.      * If this method returns {@code true} then all read methods will signal EOF
  75.      * by returning -1, until another InputStream has been pushed into the queue
  76.      * with {@link #add(InputStream)}.
  77.      *
  78.      * @return true if there are no more streams to read from.
  79.      */
  80.     public boolean isEmpty() {
  81.         return streams.isEmpty();
  82.     }

  83.     /** {@inheritDoc} */
  84.     @Override
  85.     public int read() throws IOException {
  86.         for (;;) {
  87.             final InputStream in = head();
  88.             final int r = in.read();
  89.             if (0 <= r)
  90.                 return r;
  91.             else if (in == EOF)
  92.                 return -1;
  93.             else
  94.                 pop();
  95.         }
  96.     }

  97.     /** {@inheritDoc} */
  98.     @Override
  99.     public int read(byte[] b, int off, int len) throws IOException {
  100.         if (len == 0)
  101.             return 0;
  102.         for (;;) {
  103.             final InputStream in = head();
  104.             final int n = in.read(b, off, len);
  105.             if (0 < n)
  106.                 return n;
  107.             else if (in == EOF)
  108.                 return -1;
  109.             else
  110.                 pop();
  111.         }
  112.     }

  113.     /** {@inheritDoc} */
  114.     @Override
  115.     public int available() throws IOException {
  116.         return head().available();
  117.     }

  118.     /** {@inheritDoc} */
  119.     @Override
  120.     public long skip(long count) throws IOException {
  121.         long skipped = 0;
  122.         long cnt = count;
  123.         while (0 < cnt) {
  124.             final InputStream in = head();
  125.             final long n = in.skip(cnt);
  126.             if (0 < n) {
  127.                 skipped += n;
  128.                 cnt -= n;

  129.             } else if (in == EOF) {
  130.                 return skipped;

  131.             } else {
  132.                 // Is this stream at EOF? We can't tell from skip alone.
  133.                 // Read one byte to test for EOF, discard it if we aren't
  134.                 // yet at EOF.
  135.                 //
  136.                 final int r = in.read();
  137.                 if (r < 0) {
  138.                     pop();
  139.                     if (0 < skipped)
  140.                         break;
  141.                 } else {
  142.                     skipped += 1;
  143.                     cnt -= 1;
  144.                 }
  145.             }
  146.         }
  147.         return skipped;
  148.     }

  149.     /** {@inheritDoc} */
  150.     @Override
  151.     public void close() throws IOException {
  152.         IOException err = null;

  153.         for (Iterator<InputStream> i = streams.iterator(); i.hasNext();) {
  154.             try {
  155.                 i.next().close();
  156.             } catch (IOException closeError) {
  157.                 err = closeError;
  158.             }
  159.             i.remove();
  160.         }

  161.         if (err != null)
  162.             throw err;
  163.     }
  164. }