• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
18 package java.io;
19 
20 import 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  */
29 public 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      */
PushbackInputStream(InputStream in)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      */
PushbackInputStream(InputStream in, int size)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
available()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
close()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
markSupported()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
read()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 at most {@code length} bytes from this stream and stores them in
151      * the byte array {@code buffer} starting at {@code offset}. Bytes are read
152      * from the pushback buffer first, then from the source stream if more bytes
153      * are required. Blocks until {@code count} bytes have been read, the end of
154      * the source stream is detected or an exception is thrown.
155      *
156      * @param buffer
157      *            the array in which to store the bytes read from this stream.
158      * @param offset
159      *            the initial position in {@code buffer} to store the bytes read
160      *            from this stream.
161      * @param length
162      *            the maximum number of bytes to store in {@code buffer}.
163      * @return the number of bytes read or -1 if the end of the source stream
164      *         has been reached.
165      * @throws IndexOutOfBoundsException
166      *             if {@code offset < 0} or {@code length < 0}, or if
167      *             {@code offset + length} is greater than the length of
168      *             {@code buffer}.
169      * @throws IOException
170      *             if this stream is closed or another I/O error occurs while
171      *             reading from this stream.
172      * @throws NullPointerException
173      *             if {@code buffer} is {@code null}.
174      */
175     @Override
read(byte[] buffer, int offset, int length)176     public int read(byte[] buffer, int offset, int length) throws IOException {
177         if (buf == null) {
178             throw streamClosed();
179         }
180         Arrays.checkOffsetAndCount(buffer.length, offset, length);
181         int copiedBytes = 0, copyLength = 0, newOffset = offset;
182         // Are there pushback bytes available?
183         if (pos < buf.length) {
184             copyLength = (buf.length - pos >= length) ? length : buf.length
185                     - pos;
186             System.arraycopy(buf, pos, buffer, newOffset, copyLength);
187             newOffset += copyLength;
188             copiedBytes += copyLength;
189             // Use up the bytes in the local buffer
190             pos += copyLength;
191         }
192         // Have we copied enough?
193         if (copyLength == length) {
194             return length;
195         }
196         int inCopied = in.read(buffer, newOffset, length - copiedBytes);
197         if (inCopied > 0) {
198             return inCopied + copiedBytes;
199         }
200         if (copiedBytes == 0) {
201             return inCopied;
202         }
203         return copiedBytes;
204     }
205 
streamClosed()206     private IOException streamClosed() throws IOException  {
207         throw new IOException("PushbackInputStream is closed");
208     }
209 
210     /**
211      * Skips {@code byteCount} bytes in this stream. This implementation skips bytes
212      * in the pushback buffer first and then in the source stream if necessary.
213      *
214      * @return the number of bytes actually skipped.
215      * @throws IOException
216      *             if this stream is closed or another I/O error occurs.
217      */
218     @Override
skip(long byteCount)219     public long skip(long byteCount) throws IOException {
220         if (in == null) {
221             throw streamClosed();
222         }
223         if (byteCount <= 0) {
224             return 0;
225         }
226         int numSkipped = 0;
227         if (pos < buf.length) {
228             numSkipped += (byteCount < buf.length - pos) ? byteCount : buf.length - pos;
229             pos += numSkipped;
230         }
231         if (numSkipped < byteCount) {
232             numSkipped += in.skip(byteCount - numSkipped);
233         }
234         return numSkipped;
235     }
236 
237     /**
238      * Pushes all the bytes in {@code buffer} back to this stream. The bytes are
239      * pushed back in such a way that the next byte read from this stream is
240      * buffer[0], then buffer[1] and so on.
241      * <p>
242      * If this stream's internal pushback buffer cannot store the entire
243      * contents of {@code buffer}, an {@code IOException} is thrown. Parts of
244      * {@code buffer} may have already been copied to the pushback buffer when
245      * the exception is thrown.
246      *
247      * @param buffer
248      *            the buffer containing the bytes to push back to this stream.
249      * @throws IOException
250      *             if the free space in the internal pushback buffer is not
251      *             sufficient to store the contents of {@code buffer}.
252      */
unread(byte[] buffer)253     public void unread(byte[] buffer) throws IOException {
254         unread(buffer, 0, buffer.length);
255     }
256 
257     /**
258      * Pushes a subset of the bytes in {@code buffer} back to this stream. The
259      * subset is defined by the start position {@code offset} within
260      * {@code buffer} and the number of bytes specified by {@code length}. The
261      * bytes are pushed back in such a way that the next byte read from this
262      * stream is {@code buffer[offset]}, then {@code buffer[1]} and so on.
263      * <p>
264      * If this stream's internal pushback buffer cannot store the selected
265      * subset of {@code buffer}, an {@code IOException} is thrown. Parts of
266      * {@code buffer} may have already been copied to the pushback buffer when
267      * the exception is thrown.
268      *
269      * @param buffer
270      *            the buffer containing the bytes to push back to this stream.
271      * @param offset
272      *            the index of the first byte in {@code buffer} to push back.
273      * @param length
274      *            the number of bytes to push back.
275      * @throws IndexOutOfBoundsException
276      *             if {@code offset < 0} or {@code length < 0}, or if
277      *             {@code offset + length} is greater than the length of
278      *             {@code buffer}.
279      * @throws IOException
280      *             if the free space in the internal pushback buffer is not
281      *             sufficient to store the selected contents of {@code buffer}.
282      */
unread(byte[] buffer, int offset, int length)283     public void unread(byte[] buffer, int offset, int length) throws IOException {
284         if (length > pos) {
285             throw new IOException("Pushback buffer full");
286         }
287         Arrays.checkOffsetAndCount(buffer.length, offset, length);
288         if (buf == null) {
289             throw streamClosed();
290         }
291 
292         System.arraycopy(buffer, offset, buf, pos - length, length);
293         pos = pos - length;
294     }
295 
296     /**
297      * Pushes the specified byte {@code oneByte} back to this stream. Only the
298      * least significant byte of the integer {@code oneByte} is pushed back.
299      * This is done in such a way that the next byte read from this stream is
300      * {@code (byte) oneByte}.
301      * <p>
302      * If this stream's internal pushback buffer cannot store the byte, an
303      * {@code IOException} is thrown.
304      *
305      * @param oneByte
306      *            the byte to push back to this stream.
307      * @throws IOException
308      *             if this stream is closed or the internal pushback buffer is
309      *             full.
310      */
unread(int oneByte)311     public void unread(int oneByte) throws IOException {
312         if (buf == null) {
313             throw new IOException();
314         }
315         if (pos == 0) {
316             throw new IOException("Pushback buffer full");
317         }
318         buf[--pos] = (byte) oneByte;
319     }
320 
321     /**
322      * Marks the current position in this stream. Setting a mark is not
323      * supported in this class; this implementation does nothing.
324      *
325      * @param readlimit
326      *            the number of bytes that can be read from this stream before
327      *            the mark is invalidated; this parameter is ignored.
328      */
mark(int readlimit)329     @Override public void mark(int readlimit) {
330     }
331 
332     /**
333      * Resets this stream to the last marked position. Resetting the stream is
334      * not supported in this class; this implementation always throws an
335      * {@code IOException}.
336      *
337      * @throws IOException
338      *             if this method is called.
339      */
340     @Override
reset()341     public void reset() throws IOException {
342         throw new IOException();
343     }
344 }
345