View Javadoc
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  
11  package org.eclipse.jgit.util.io;
12  
13  import java.io.IOException;
14  import java.io.InputStream;
15  import java.util.Iterator;
16  import java.util.LinkedList;
17  
18  /**
19   * An InputStream which reads from one or more InputStreams.
20   * <p>
21   * This stream may enter into an EOF state, returning -1 from any of the read
22   * methods, and then later successfully read additional bytes if a new
23   * InputStream is added after reaching EOF.
24   * <p>
25   * Currently this stream does not support the mark/reset APIs. If mark and later
26   * reset functionality is needed the caller should wrap this stream with a
27   * {@link java.io.BufferedInputStream}.
28   */
29  public class UnionInputStream extends InputStream {
30  	private static final InputStream EOF = new InputStream() {
31  		@Override
32  		public int read() throws IOException {
33  			return -1;
34  		}
35  	};
36  
37  	private final LinkedList<InputStream> streams = new LinkedList<>();
38  
39  	/**
40  	 * Create an empty InputStream that is currently at EOF state.
41  	 */
42  	public UnionInputStream() {
43  		// Do nothing.
44  	}
45  
46  	/**
47  	 * Create an InputStream that is a union of the individual streams.
48  	 * <p>
49  	 * As each stream reaches EOF, it will be automatically closed before bytes
50  	 * from the next stream are read.
51  	 *
52  	 * @param inputStreams
53  	 *            streams to be pushed onto this stream.
54  	 */
55  	public UnionInputStream(InputStream... inputStreams) {
56  		for (InputStream i : inputStreams)
57  			add(i);
58  	}
59  
60  	private InputStream head() {
61  		return streams.isEmpty() ? EOF : streams.getFirst();
62  	}
63  
64  	private void pop() throws IOException {
65  		if (!streams.isEmpty())
66  			streams.removeFirst().close();
67  	}
68  
69  	/**
70  	 * Add the given InputStream onto the end of the stream queue.
71  	 * <p>
72  	 * When the stream reaches EOF it will be automatically closed.
73  	 *
74  	 * @param in
75  	 *            the stream to add; must not be null.
76  	 */
77  	public void add(InputStream in) {
78  		streams.add(in);
79  	}
80  
81  	/**
82  	 * Returns true if there are no more InputStreams in the stream queue.
83  	 * <p>
84  	 * If this method returns {@code true} then all read methods will signal EOF
85  	 * by returning -1, until another InputStream has been pushed into the queue
86  	 * with {@link #add(InputStream)}.
87  	 *
88  	 * @return true if there are no more streams to read from.
89  	 */
90  	public boolean isEmpty() {
91  		return streams.isEmpty();
92  	}
93  
94  	/** {@inheritDoc} */
95  	@Override
96  	public int read() throws IOException {
97  		for (;;) {
98  			final InputStream in = head();
99  			final int r = in.read();
100 			if (0 <= r)
101 				return r;
102 			else if (in == EOF)
103 				return -1;
104 			else
105 				pop();
106 		}
107 	}
108 
109 	/** {@inheritDoc} */
110 	@Override
111 	public int read(byte[] b, int off, int len) throws IOException {
112 		if (len == 0)
113 			return 0;
114 		for (;;) {
115 			final InputStream in = head();
116 			final int n = in.read(b, off, len);
117 			if (0 < n)
118 				return n;
119 			else if (in == EOF)
120 				return -1;
121 			else
122 				pop();
123 		}
124 	}
125 
126 	/** {@inheritDoc} */
127 	@Override
128 	public int available() throws IOException {
129 		return head().available();
130 	}
131 
132 	/** {@inheritDoc} */
133 	@Override
134 	public long skip(long count) throws IOException {
135 		long skipped = 0;
136 		long cnt = count;
137 		while (0 < cnt) {
138 			final InputStream in = head();
139 			final long n = in.skip(cnt);
140 			if (0 < n) {
141 				skipped += n;
142 				cnt -= n;
143 
144 			} else if (in == EOF) {
145 				return skipped;
146 
147 			} else {
148 				// Is this stream at EOF? We can't tell from skip alone.
149 				// Read one byte to test for EOF, discard it if we aren't
150 				// yet at EOF.
151 				//
152 				final int r = in.read();
153 				if (r < 0) {
154 					pop();
155 					if (0 < skipped)
156 						break;
157 				} else {
158 					skipped += 1;
159 					cnt -= 1;
160 				}
161 			}
162 		}
163 		return skipped;
164 	}
165 
166 	/** {@inheritDoc} */
167 	@Override
168 	public void close() throws IOException {
169 		IOException err = null;
170 
171 		for (Iterator<InputStream> i = streams.iterator(); i.hasNext();) {
172 			try {
173 				i.next().close();
174 			} catch (IOException closeError) {
175 				err = closeError;
176 			}
177 			i.remove();
178 		}
179 
180 		if (err != null)
181 			throw err;
182 	}
183 }