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  	/** Create an empty InputStream that is currently at EOF state. */
73  	public UnionInputStream() {
74  		// Do nothing.
75  	}
76  
77  	/**
78  	 * Create an InputStream that is a union of the individual streams.
79  	 * <p>
80  	 * As each stream reaches EOF, it will be automatically closed before bytes
81  	 * from the next stream are read.
82  	 *
83  	 * @param inputStreams
84  	 *            streams to be pushed onto this stream.
85  	 */
86  	public UnionInputStream(InputStream... inputStreams) {
87  		for (InputStream i : inputStreams)
88  			add(i);
89  	}
90  
91  	private InputStream head() {
92  		return streams.isEmpty() ? EOF : streams.getFirst();
93  	}
94  
95  	private void pop() throws IOException {
96  		if (!streams.isEmpty())
97  			streams.removeFirst().close();
98  	}
99  
100 	/**
101 	 * Add the given InputStream onto the end of the stream queue.
102 	 * <p>
103 	 * When the stream reaches EOF it will be automatically closed.
104 	 *
105 	 * @param in
106 	 *            the stream to add; must not be null.
107 	 */
108 	public void add(final InputStream in) {
109 		streams.add(in);
110 	}
111 
112 	/**
113 	 * Returns true if there are no more InputStreams in the stream queue.
114 	 * <p>
115 	 * If this method returns {@code true} then all read methods will signal EOF
116 	 * by returning -1, until another InputStream has been pushed into the queue
117 	 * with {@link #add(InputStream)}.
118 	 *
119 	 * @return true if there are no more streams to read from.
120 	 */
121 	public boolean isEmpty() {
122 		return streams.isEmpty();
123 	}
124 
125 	@Override
126 	public int read() throws IOException {
127 		for (;;) {
128 			final InputStream in = head();
129 			final int r = in.read();
130 			if (0 <= r)
131 				return r;
132 			else if (in == EOF)
133 				return -1;
134 			else
135 				pop();
136 		}
137 	}
138 
139 	@Override
140 	public int read(byte[] b, int off, int len) throws IOException {
141 		if (len == 0)
142 			return 0;
143 		for (;;) {
144 			final InputStream in = head();
145 			final int n = in.read(b, off, len);
146 			if (0 < n)
147 				return n;
148 			else if (in == EOF)
149 				return -1;
150 			else
151 				pop();
152 		}
153 	}
154 
155 	@Override
156 	public int available() throws IOException {
157 		return head().available();
158 	}
159 
160 	@Override
161 	public long skip(final long count) throws IOException {
162 		long skipped = 0;
163 		long cnt = count;
164 		while (0 < cnt) {
165 			final InputStream in = head();
166 			final long n = in.skip(cnt);
167 			if (0 < n) {
168 				skipped += n;
169 				cnt -= n;
170 
171 			} else if (in == EOF) {
172 				return skipped;
173 
174 			} else {
175 				// Is this stream at EOF? We can't tell from skip alone.
176 				// Read one byte to test for EOF, discard it if we aren't
177 				// yet at EOF.
178 				//
179 				final int r = in.read();
180 				if (r < 0) {
181 					pop();
182 					if (0 < skipped)
183 						break;
184 				} else {
185 					skipped += 1;
186 					cnt -= 1;
187 				}
188 			}
189 		}
190 		return skipped;
191 	}
192 
193 	@Override
194 	public void close() throws IOException {
195 		IOException err = null;
196 
197 		for (Iterator<InputStream> i = streams.iterator(); i.hasNext();) {
198 			try {
199 				i.next().close();
200 			} catch (IOException closeError) {
201 				err = closeError;
202 			}
203 			i.remove();
204 		}
205 
206 		if (err != null)
207 			throw err;
208 	}
209 }