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 up to {@code count} characters from this reader and stores them in
159     * character 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     * Returns the number of bytes read or -1 if the end of the source reader has been reached.
164     *
165     * @throws IndexOutOfBoundsException
166     *     if {@code offset < 0 || count < 0 || offset + count > buffer.length}.
167     * @throws IOException
168     *             if this reader is closed or another I/O error occurs while
169     *             reading from this reader.
170     */
171    @Override
172    public int read(char[] buffer, int offset, int count) throws IOException {
173        synchronized (lock) {
174            checkNotClosed();
175            Arrays.checkOffsetAndCount(buffer.length, offset, count);
176
177            int copiedChars = 0;
178            int copyLength = 0;
179            int newOffset = offset;
180            /* Are there pushback chars available? */
181            if (pos < buf.length) {
182                copyLength = (buf.length - pos >= count) ? count : buf.length
183                        - pos;
184                System.arraycopy(buf, pos, buffer, newOffset, copyLength);
185                newOffset += copyLength;
186                copiedChars += copyLength;
187                /* Use up the chars in the local buffer */
188                pos += copyLength;
189            }
190            /* Have we copied enough? */
191            if (copyLength == count) {
192                return count;
193            }
194            int inCopied = in.read(buffer, newOffset, count - copiedChars);
195            if (inCopied > 0) {
196                return inCopied + copiedChars;
197            }
198            if (copiedChars == 0) {
199                return inCopied;
200            }
201            return copiedChars;
202        }
203    }
204
205    /**
206     * Indicates whether this reader is ready to be read without blocking.
207     * Returns {@code true} if this reader will not block when {@code read} is
208     * called, {@code false} if unknown or blocking will occur.
209     *
210     * @return {@code true} if the receiver will not block when
211     *         {@code read()} is called, {@code false} if unknown
212     *         or blocking will occur.
213     * @throws IOException
214     *             if this reader is closed or some other I/O error occurs.
215     * @see #read()
216     * @see #read(char[], int, int)
217     */
218    @Override
219    public boolean ready() throws IOException {
220        synchronized (lock) {
221            if (buf == null) {
222                throw new IOException("Reader is closed");
223            }
224            return (buf.length - pos > 0 || in.ready());
225        }
226    }
227
228    /**
229     * Resets this reader to the last marked position. Resetting the reader is
230     * not supported in this class; this implementation always throws an
231     * {@code IOException}.
232     *
233     * @throws IOException
234     *             if this method is called.
235     */
236    @Override
237    public void reset() throws IOException {
238        throw new IOException("mark/reset not supported");
239    }
240
241    /**
242     * Pushes all the characters in {@code buffer} back to this reader. The
243     * characters are pushed back in such a way that the next character read
244     * from this reader is buffer[0], then buffer[1] and so on.
245     * <p>
246     * If this reader's internal pushback buffer cannot store the entire
247     * contents of {@code buffer}, an {@code IOException} is thrown. Parts of
248     * {@code buffer} may have already been copied to the pushback buffer when
249     * the exception is thrown.
250     *
251     * @param buffer
252     *            the buffer containing the characters to push back to this
253     *            reader.
254     * @throws IOException
255     *             if this reader is closed or the free space in the internal
256     *             pushback buffer is not sufficient to store the contents of
257     *             {@code buffer}.
258     */
259    public void unread(char[] buffer) throws IOException {
260        unread(buffer, 0, buffer.length);
261    }
262
263    /**
264     * Pushes a subset of the characters in {@code buffer} back to this reader.
265     * The subset is defined by the start position {@code offset} within
266     * {@code buffer} and the number of characters specified by {@code length}.
267     * The bytes are pushed back in such a way that the next byte read from this
268     * stream is {@code buffer[offset]}, then {@code buffer[1]} and so on.
269     * <p>
270     * If this stream's internal pushback buffer cannot store the selected
271     * subset of {@code buffer}, an {@code IOException} is thrown. Parts of
272     * {@code buffer} may have already been copied to the pushback buffer when
273     * the exception is thrown.
274     *
275     * @param buffer
276     *            the buffer containing the characters to push back to this
277     *            reader.
278     * @param offset
279     *            the index of the first byte in {@code buffer} to push back.
280     * @param length
281     *            the number of bytes to push back.
282     * @throws IndexOutOfBoundsException
283     *             if {@code offset < 0} or {@code count < 0}, or if
284     *             {@code offset + count} is greater than the length of
285     *             {@code buffer}.
286     * @throws IOException
287     *             if this reader is closed or the free space in the internal
288     *             pushback buffer is not sufficient to store the selected
289     *             contents of {@code buffer}.
290     * @throws NullPointerException
291     *             if {@code buffer} is {@code null}.
292     */
293    public void unread(char[] buffer, int offset, int length) throws IOException {
294        synchronized (lock) {
295            checkNotClosed();
296            if (length > pos) {
297                throw new IOException("Pushback buffer full");
298            }
299            Arrays.checkOffsetAndCount(buffer.length, offset, length);
300            for (int i = offset + length - 1; i >= offset; i--) {
301                unread(buffer[i]);
302            }
303        }
304    }
305
306    /**
307     * Pushes the specified character {@code oneChar} back to this reader. This
308     * is done in such a way that the next character read from this reader is
309     * {@code (char) oneChar}.
310     * <p>
311     * If this reader's internal pushback buffer cannot store the character, an
312     * {@code IOException} is thrown.
313     *
314     * @param oneChar
315     *            the character to push back to this stream.
316     * @throws IOException
317     *             if this reader is closed or the internal pushback buffer is
318     *             full.
319     */
320    public void unread(int oneChar) throws IOException {
321        synchronized (lock) {
322            checkNotClosed();
323            if (pos == 0) {
324                throw new IOException("Pushback buffer full");
325            }
326            buf[--pos] = (char) oneChar;
327        }
328    }
329
330    /**
331     * Skips {@code charCount} characters in this reader. This implementation skips
332     * characters in the pushback buffer first and then in the source reader if
333     * necessary.
334     *
335     * @return the number of characters actually skipped.
336     * @throws IllegalArgumentException if {@code charCount < 0}.
337     * @throws IOException
338     *             if this reader is closed or another I/O error occurs.
339     */
340    @Override
341    public long skip(long charCount) throws IOException {
342        if (charCount < 0) {
343            throw new IllegalArgumentException("charCount < 0: " + charCount);
344        }
345        synchronized (lock) {
346            checkNotClosed();
347            if (charCount == 0) {
348                return 0;
349            }
350            long inSkipped;
351            int availableFromBuffer = buf.length - pos;
352            if (availableFromBuffer > 0) {
353                long requiredFromIn = charCount - availableFromBuffer;
354                if (requiredFromIn <= 0) {
355                    pos += charCount;
356                    return charCount;
357                }
358                pos += availableFromBuffer;
359                inSkipped = in.skip(requiredFromIn);
360            } else {
361                inSkipped = in.skip(charCount);
362            }
363            return inSkipped + availableFromBuffer;
364        }
365    }
366}
367