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