1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.io;
19
20import java.util.Arrays;
21import java.util.Enumeration;
22import java.util.Vector;
23
24/**
25 * Concatenates two or more existing {@link InputStream}s. Reads are taken from
26 * the first stream until it ends, then the next stream is used, until the last
27 * stream returns end of file.
28 */
29public class SequenceInputStream extends InputStream {
30    /**
31     * An enumeration which will return types of InputStream.
32     */
33    private Enumeration<? extends InputStream> e;
34
35    /**
36     * The current input stream.
37     */
38    private InputStream in;
39
40    /**
41     * Constructs a new {@code SequenceInputStream} using the two streams
42     * {@code s1} and {@code s2} as the sequence of streams to read from.
43     *
44     * @param s1
45     *            the first stream to get bytes from.
46     * @param s2
47     *            the second stream to get bytes from.
48     * @throws NullPointerException
49     *             if {@code s1} is {@code null}.
50     */
51    public SequenceInputStream(InputStream s1, InputStream s2) {
52        if (s1 == null) {
53            throw new NullPointerException("s1 == null");
54        }
55        Vector<InputStream> inVector = new Vector<InputStream>(1);
56        inVector.addElement(s2);
57        e = inVector.elements();
58        in = s1;
59    }
60
61    /**
62     * Constructs a new SequenceInputStream using the elements returned from
63     * Enumeration {@code e} as the stream sequence. The instances returned by
64     * {@code e.nextElement()} must be of type {@link InputStream}.
65     *
66     * @param e
67     *            the enumeration of {@code InputStreams} to get bytes from.
68     * @throws NullPointerException
69     *             if any of the elements in {@code e} is {@code null}.
70     */
71    public SequenceInputStream(Enumeration<? extends InputStream> e) {
72        this.e = e;
73        if (e.hasMoreElements()) {
74            in = e.nextElement();
75            if (in == null) {
76                throw new NullPointerException("element is null");
77            }
78        }
79    }
80
81    @Override
82    public int available() throws IOException {
83        if (e != null && in != null) {
84            return in.available();
85        }
86        return 0;
87    }
88
89    /**
90     * Closes all streams in this sequence of input stream.
91     *
92     * @throws IOException
93     *             if an error occurs while closing any of the input streams.
94     */
95    @Override
96    public void close() throws IOException {
97        while (in != null) {
98            nextStream();
99        }
100        e = null;
101    }
102
103    /**
104     * Sets up the next InputStream or leaves it alone if there are none left.
105     *
106     * @throws IOException
107     */
108    private void nextStream() throws IOException {
109        if (in != null) {
110            in.close();
111        }
112        if (e.hasMoreElements()) {
113            in = e.nextElement();
114            if (in == null) {
115                throw new NullPointerException("element is null");
116            }
117        } else {
118            in = null;
119        }
120    }
121
122    /**
123     * Reads a single byte from this sequence of input streams and returns it as
124     * an integer in the range from 0 to 255. It tries to read from the current
125     * stream first; if the end of this stream has been reached, it reads from
126     * the next one. Blocks until one byte has been read, the end of the last
127     * input stream in the sequence has been reached, or an exception is thrown.
128     *
129     * @return the byte read or -1 if either the end of the last stream in the
130     *         sequence has been reached or this input stream sequence is
131     *         closed.
132     * @throws IOException
133     *             if an error occurs while reading the current source input
134     *             stream.
135     */
136    @Override
137    public int read() throws IOException {
138        while (in != null) {
139            int result = in.read();
140            if (result >= 0) {
141                return result;
142            }
143            nextStream();
144        }
145        return -1;
146    }
147
148    /**
149     * Reads at most {@code count} bytes from this sequence of input streams and
150     * stores them in the byte array {@code buffer} starting at {@code offset}.
151     * Blocks only until at least 1 byte has been read, the end of the stream
152     * has been reached, or an exception is thrown.
153     * <p>
154     * This SequenceInputStream shows the same behavior as other InputStreams.
155     * To do this it will read only as many bytes as a call to read on the
156     * current substream returns. If that call does not return as many bytes as
157     * requested by {@code count}, it will not retry to read more on its own
158     * because subsequent reads might block. This would violate the rule that
159     * it will only block until at least one byte has been read.
160     * <p>
161     * If a substream has already reached the end when this call is made, it
162     * will close that substream and start with the next one. If there are no
163     * more substreams it will return -1.
164     *
165     * @param buffer
166     *            the array in which to store the bytes read.
167     * @param offset
168     *            the initial position in {@code buffer} to store the bytes read
169     *            from this stream.
170     * @param count
171     *            the maximum number of bytes to store in {@code buffer}.
172     * @return the number of bytes actually read; -1 if this sequence of streams
173     *         is closed or if the end of the last stream in the sequence has
174     *         been reached.
175     * @throws IndexOutOfBoundsException
176     *             if {@code offset < 0} or {@code count < 0}, or if {@code
177     *             offset + count} is greater than the size of {@code buffer}.
178     * @throws IOException
179     *             if an I/O error occurs.
180     * @throws NullPointerException
181     *             if {@code buffer} is {@code null}.
182     */
183    @Override
184    public int read(byte[] buffer, int offset, int count) throws IOException {
185        if (in == null) {
186            return -1;
187        }
188        Arrays.checkOffsetAndCount(buffer.length, offset, count);
189        while (in != null) {
190            int result = in.read(buffer, offset, count);
191            if (result >= 0) {
192                return result;
193            }
194            nextStream();
195        }
196        return -1;
197    }
198}
199