View Javadoc
1   /*
2    * Copyright (C) 2009, 2013 Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.util.io;
45  
46  import java.io.IOException;
47  import java.io.InputStream;
48  import java.util.Iterator;
49  import java.util.LinkedList;
50  
51  /**
52   * An InputStream which reads from one or more InputStreams.
53   * <p>
54   * This stream may enter into an EOF state, returning -1 from any of the read
55   * methods, and then later successfully read additional bytes if a new
56   * InputStream is added after reaching EOF.
57   * <p>
58   * Currently this stream does not support the mark/reset APIs. If mark and later
59   * reset functionality is needed the caller should wrap this stream with a
60   * {@link java.io.BufferedInputStream}.
61   */
62  public class UnionInputStream extends InputStream {
63  	private static final InputStream EOF = new InputStream() {
64  		@Override
65  		public int read() throws IOException {
66  			return -1;
67  		}
68  	};
69  
70  	private final LinkedList<InputStream> streams = new LinkedList<>();
71  
72  	/**
73  	 * Create an empty InputStream that is currently at EOF state.
74  	 */
75  	public UnionInputStream() {
76  		// Do nothing.
77  	}
78  
79  	/**
80  	 * Create an InputStream that is a union of the individual streams.
81  	 * <p>
82  	 * As each stream reaches EOF, it will be automatically closed before bytes
83  	 * from the next stream are read.
84  	 *
85  	 * @param inputStreams
86  	 *            streams to be pushed onto this stream.
87  	 */
88  	public UnionInputStream(InputStream... inputStreams) {
89  		for (InputStream i : inputStreams)
90  			add(i);
91  	}
92  
93  	private InputStream head() {
94  		return streams.isEmpty() ? EOF : streams.getFirst();
95  	}
96  
97  	private void pop() throws IOException {
98  		if (!streams.isEmpty())
99  			streams.removeFirst().close();
100 	}
101 
102 	/**
103 	 * Add the given InputStream onto the end of the stream queue.
104 	 * <p>
105 	 * When the stream reaches EOF it will be automatically closed.
106 	 *
107 	 * @param in
108 	 *            the stream to add; must not be null.
109 	 */
110 	public void add(InputStream in) {
111 		streams.add(in);
112 	}
113 
114 	/**
115 	 * Returns true if there are no more InputStreams in the stream queue.
116 	 * <p>
117 	 * If this method returns {@code true} then all read methods will signal EOF
118 	 * by returning -1, until another InputStream has been pushed into the queue
119 	 * with {@link #add(InputStream)}.
120 	 *
121 	 * @return true if there are no more streams to read from.
122 	 */
123 	public boolean isEmpty() {
124 		return streams.isEmpty();
125 	}
126 
127 	/** {@inheritDoc} */
128 	@Override
129 	public int read() throws IOException {
130 		for (;;) {
131 			final InputStream in = head();
132 			final int r = in.read();
133 			if (0 <= r)
134 				return r;
135 			else if (in == EOF)
136 				return -1;
137 			else
138 				pop();
139 		}
140 	}
141 
142 	/** {@inheritDoc} */
143 	@Override
144 	public int read(byte[] b, int off, int len) throws IOException {
145 		if (len == 0)
146 			return 0;
147 		for (;;) {
148 			final InputStream in = head();
149 			final int n = in.read(b, off, len);
150 			if (0 < n)
151 				return n;
152 			else if (in == EOF)
153 				return -1;
154 			else
155 				pop();
156 		}
157 	}
158 
159 	/** {@inheritDoc} */
160 	@Override
161 	public int available() throws IOException {
162 		return head().available();
163 	}
164 
165 	/** {@inheritDoc} */
166 	@Override
167 	public long skip(long count) throws IOException {
168 		long skipped = 0;
169 		long cnt = count;
170 		while (0 < cnt) {
171 			final InputStream in = head();
172 			final long n = in.skip(cnt);
173 			if (0 < n) {
174 				skipped += n;
175 				cnt -= n;
176 
177 			} else if (in == EOF) {
178 				return skipped;
179 
180 			} else {
181 				// Is this stream at EOF? We can't tell from skip alone.
182 				// Read one byte to test for EOF, discard it if we aren't
183 				// yet at EOF.
184 				//
185 				final int r = in.read();
186 				if (r < 0) {
187 					pop();
188 					if (0 < skipped)
189 						break;
190 				} else {
191 					skipped += 1;
192 					cnt -= 1;
193 				}
194 			}
195 		}
196 		return skipped;
197 	}
198 
199 	/** {@inheritDoc} */
200 	@Override
201 	public void close() throws IOException {
202 		IOException err = null;
203 
204 		for (Iterator<InputStream> i = streams.iterator(); i.hasNext();) {
205 			try {
206 				i.next().close();
207 			} catch (IOException closeError) {
208 				err = closeError;
209 			}
210 			i.remove();
211 		}
212 
213 		if (err != null)
214 			throw err;
215 	}
216 }