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;
21
22/**
23 * Wraps an existing {@link InputStream} and adds functionality to "push back"
24 * bytes that have been read, so that they can be read again. Parsers may find
25 * this useful. The number of bytes which may be pushed back can be specified
26 * during construction. If the buffer of pushed back bytes is empty, bytes are
27 * read from the underlying input stream.
28 */
29public class PushbackInputStream extends FilterInputStream {
30    /**
31     * The buffer that contains pushed-back bytes.
32     */
33    protected byte[] buf;
34
35    /**
36     * The current position within {@code buf}. A value equal to
37     * {@code buf.length} indicates that no bytes are available. A value of 0
38     * indicates that the buffer is full.
39     */
40    protected int pos;
41
42    /**
43     * Constructs a new {@code PushbackInputStream} with the specified input
44     * stream as source. The size of the pushback buffer is set to the default
45     * value of 1 byte.
46     *
47     * <p><strong>Warning:</strong> passing a null source creates an invalid
48     * {@code PushbackInputStream}. All read operations on such a stream will
49     * fail.
50     *
51     * @param in
52     *            the source input stream.
53     */
54    public PushbackInputStream(InputStream in) {
55        super(in);
56        buf = (in == null) ? null : new byte[1];
57        pos = 1;
58    }
59
60    /**
61     * Constructs a new {@code PushbackInputStream} with {@code in} as source
62     * input stream. The size of the pushback buffer is set to {@code size}.
63     *
64     * <p><strong>Warning:</strong> passing a null source creates an invalid
65     * {@code PushbackInputStream}. All read operations on such a stream will
66     * fail.
67     *
68     * @param in
69     *            the source input stream.
70     * @param size
71     *            the size of the pushback buffer.
72     * @throws IllegalArgumentException
73     *             if {@code size} is negative.
74     */
75    public PushbackInputStream(InputStream in, int size) {
76        super(in);
77        if (size <= 0) {
78            throw new IllegalArgumentException("size <= 0");
79        }
80        buf = (in == null) ? null : new byte[size];
81        pos = size;
82    }
83
84    @Override
85    public int available() throws IOException {
86        if (buf == null) {
87            throw new IOException();
88        }
89        return buf.length - pos + in.available();
90    }
91
92    /**
93     * Closes this stream. This implementation closes the source stream
94     * and releases the pushback buffer.
95     *
96     * @throws IOException
97     *             if an error occurs while closing this stream.
98     */
99    @Override
100    public void close() throws IOException {
101        if (in != null) {
102            in.close();
103            in = null;
104            buf = null;
105        }
106    }
107
108    /**
109     * Indicates whether this stream supports the {@code mark(int)} and
110     * {@code reset()} methods. {@code PushbackInputStream} does not support
111     * them, so it returns {@code false}.
112     *
113     * @return always {@code false}.
114     * @see #mark(int)
115     * @see #reset()
116     */
117    @Override
118    public boolean markSupported() {
119        return false;
120    }
121
122    /**
123     * Reads a single byte from this stream and returns it as an integer in the
124     * range from 0 to 255. If the pushback buffer does not contain any
125     * available bytes then a byte from the source input stream is returned.
126     * Blocks until one byte has been read, the end of the source stream is
127     * detected or an exception is thrown.
128     *
129     * @return the byte read or -1 if the end of the source stream has been
130     *         reached.
131     * @throws IOException
132     *             if this stream is closed or an I/O error occurs while reading
133     *             from this stream.
134     */
135    @Override
136    public int read() throws IOException {
137        if (buf == null) {
138            throw new IOException();
139        }
140        // Is there a pushback byte available?
141        if (pos < buf.length) {
142            return (buf[pos++] & 0xFF);
143        }
144        // Assume read() in the InputStream will return low-order byte or -1
145        // if end of stream.
146        return in.read();
147    }
148
149    /**
150     * Reads up to {@code byteCount} bytes from this stream and stores them in
151     * the byte array {@code buffer} starting at {@code byteOffset}. Bytes are read
152     * from the pushback buffer first, then from the source stream if more bytes
153     * are required. Blocks until {@code byteCount} bytes have been read, the end of
154     * the source stream is detected or an exception is thrown. Returns the number of bytes read,
155     * or -1 if the end of the source stream has been reached.
156     *
157     * @throws IndexOutOfBoundsException
158     *     if {@code byteOffset < 0 || byteCount < 0 || byteOffset + byteCount > buffer.length}.
159     * @throws IOException
160     *             if this stream is closed or another I/O error occurs while
161     *             reading from this stream.
162     * @throws NullPointerException
163     *             if {@code buffer == null}.
164     */
165    @Override
166    public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
167        if (buf == null) {
168            throw streamClosed();
169        }
170        Arrays.checkOffsetAndCount(buffer.length, byteOffset, byteCount);
171        int copiedBytes = 0, copyLength = 0, newOffset = byteOffset;
172        // Are there pushback bytes available?
173        if (pos < buf.length) {
174            copyLength = (buf.length - pos >= byteCount) ? byteCount : buf.length - pos;
175            System.arraycopy(buf, pos, buffer, newOffset, copyLength);
176            newOffset += copyLength;
177            copiedBytes += copyLength;
178            // Use up the bytes in the local buffer
179            pos += copyLength;
180        }
181        // Have we copied enough?
182        if (copyLength == byteCount) {
183            return byteCount;
184        }
185        int inCopied = in.read(buffer, newOffset, byteCount - copiedBytes);
186        if (inCopied > 0) {
187            return inCopied + copiedBytes;
188        }
189        if (copiedBytes == 0) {
190            return inCopied;
191        }
192        return copiedBytes;
193    }
194
195    private IOException streamClosed() throws IOException  {
196        throw new IOException("PushbackInputStream is closed");
197    }
198
199    /**
200     * Skips {@code byteCount} bytes in this stream. This implementation skips bytes
201     * in the pushback buffer first and then in the source stream if necessary.
202     *
203     * @return the number of bytes actually skipped.
204     * @throws IOException
205     *             if this stream is closed or another I/O error occurs.
206     */
207    @Override
208    public long skip(long byteCount) throws IOException {
209        if (in == null) {
210            throw streamClosed();
211        }
212        if (byteCount <= 0) {
213            return 0;
214        }
215        int numSkipped = 0;
216        if (pos < buf.length) {
217            numSkipped += (byteCount < buf.length - pos) ? byteCount : buf.length - pos;
218            pos += numSkipped;
219        }
220        if (numSkipped < byteCount) {
221            numSkipped += in.skip(byteCount - numSkipped);
222        }
223        return numSkipped;
224    }
225
226    /**
227     * Pushes all the bytes in {@code buffer} back to this stream. The bytes are
228     * pushed back in such a way that the next byte read from this stream is
229     * buffer[0], then buffer[1] and so on.
230     * <p>
231     * If this stream's internal pushback buffer cannot store the entire
232     * contents of {@code buffer}, an {@code IOException} is thrown. Parts of
233     * {@code buffer} may have already been copied to the pushback buffer when
234     * the exception is thrown.
235     *
236     * @param buffer
237     *            the buffer containing the bytes to push back to this stream.
238     * @throws IOException
239     *             if the free space in the internal pushback buffer is not
240     *             sufficient to store the contents of {@code buffer}.
241     */
242    public void unread(byte[] buffer) throws IOException {
243        unread(buffer, 0, buffer.length);
244    }
245
246    /**
247     * Pushes a subset of the bytes in {@code buffer} back to this stream. The
248     * subset is defined by the start position {@code offset} within
249     * {@code buffer} and the number of bytes specified by {@code length}. The
250     * bytes are pushed back in such a way that the next byte read from this
251     * stream is {@code buffer[offset]}, then {@code buffer[1]} and so on.
252     * <p>
253     * If this stream's internal pushback buffer cannot store the selected
254     * subset of {@code buffer}, an {@code IOException} is thrown. Parts of
255     * {@code buffer} may have already been copied to the pushback buffer when
256     * the exception is thrown.
257     *
258     * @param buffer
259     *            the buffer containing the bytes to push back to this stream.
260     * @param offset
261     *            the index of the first byte in {@code buffer} to push back.
262     * @param length
263     *            the number of bytes to push back.
264     * @throws IndexOutOfBoundsException
265     *             if {@code offset < 0} or {@code length < 0}, or if
266     *             {@code offset + length} is greater than the length of
267     *             {@code buffer}.
268     * @throws IOException
269     *             if the free space in the internal pushback buffer is not
270     *             sufficient to store the selected contents of {@code buffer}.
271     */
272    public void unread(byte[] buffer, int offset, int length) throws IOException {
273        if (length > pos) {
274            throw new IOException("Pushback buffer full");
275        }
276        Arrays.checkOffsetAndCount(buffer.length, offset, length);
277        if (buf == null) {
278            throw streamClosed();
279        }
280
281        System.arraycopy(buffer, offset, buf, pos - length, length);
282        pos = pos - length;
283    }
284
285    /**
286     * Pushes the specified byte {@code oneByte} back to this stream. Only the
287     * least significant byte of the integer {@code oneByte} is pushed back.
288     * This is done in such a way that the next byte read from this stream is
289     * {@code (byte) oneByte}.
290     * <p>
291     * If this stream's internal pushback buffer cannot store the byte, an
292     * {@code IOException} is thrown.
293     *
294     * @param oneByte
295     *            the byte to push back to this stream.
296     * @throws IOException
297     *             if this stream is closed or the internal pushback buffer is
298     *             full.
299     */
300    public void unread(int oneByte) throws IOException {
301        if (buf == null) {
302            throw new IOException();
303        }
304        if (pos == 0) {
305            throw new IOException("Pushback buffer full");
306        }
307        buf[--pos] = (byte) oneByte;
308    }
309
310    /**
311     * Marks the current position in this stream. Setting a mark is not
312     * supported in this class; this implementation does nothing.
313     *
314     * @param readlimit
315     *            the number of bytes that can be read from this stream before
316     *            the mark is invalidated; this parameter is ignored.
317     */
318    @Override public void mark(int readlimit) {
319    }
320
321    /**
322     * Resets this stream to the last marked position. Resetting the stream is
323     * not supported in this class; this implementation always throws an
324     * {@code IOException}.
325     *
326     * @throws IOException
327     *             if this method is called.
328     */
329    @Override
330    public void reset() throws IOException {
331        throw new IOException();
332    }
333}
334