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 org.apache.harmony.luni.util.Msg;
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(Msg.getString("K0058")); //$NON-NLS-1$
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(Msg.getString("K007f")); //$NON-NLS-1$
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            if (buf == null) {
139                throw new IOException(Msg.getString("K0059")); //$NON-NLS-1$
140            }
141            /* Is there a pushback character available? */
142            if (pos < buf.length) {
143                return buf[pos++];
144            }
145            /**
146             * Assume read() in the InputStream will return 2 lowest-order bytes
147             * or -1 if end of stream.
148             */
149            return in.read();
150        }
151    }
152
153    /**
154     * Reads at most {@code length} bytes from this reader and stores them in
155     * byte array {@code buffer} starting at {@code offset}. Characters are
156     * read from the pushback buffer first, then from the source reader if more
157     * bytes are required. Blocks until {@code count} characters have been read,
158     * the end of the source reader is detected or an exception is thrown.
159     *
160     * @param buffer
161     *            the array in which to store the characters read from this
162     *            reader.
163     * @param offset
164     *            the initial position in {@code buffer} to store the characters
165     *            read from this reader.
166     * @param count
167     *            the maximum number of bytes to store in {@code buffer}.
168     * @return the number of bytes read or -1 if the end of the source reader
169     *         has been reached.
170     * @throws IndexOutOfBoundsException
171     *             if {@code offset < 0} or {@code count < 0}, or if
172     *             {@code offset + count} is greater than the length of
173     *             {@code buffer}.
174     * @throws IOException
175     *             if this reader is closed or another I/O error occurs while
176     *             reading from this reader.
177     */
178    @Override
179    public int read(char[] buffer, int offset, int count) throws IOException {
180        synchronized (lock) {
181            if (null == buf) {
182                throw new IOException(Msg.getString("K0059")); //$NON-NLS-1$
183            }
184            // avoid int overflow
185            // BEGIN android-changed
186            // Exception priorities (in case of multiple errors) differ from
187            // RI, but are spec-compliant.
188            // made implicit null check explicit, used (offset | count) < 0
189            // instead of (offset < 0) || (count < 0) to safe one operation
190            if (buffer == null) {
191                throw new NullPointerException(Msg.getString("K0047")); //$NON-NLS-1$
192            }
193            if ((offset | count) < 0 || offset > buffer.length - count) {
194                throw new IndexOutOfBoundsException(Msg.getString("K002f")); //$NON-NLS-1$
195            }
196            // END android-changed
197
198            int copiedChars = 0;
199            int copyLength = 0;
200            int newOffset = offset;
201            /* Are there pushback chars available? */
202            if (pos < buf.length) {
203                copyLength = (buf.length - pos >= count) ? count : buf.length
204                        - pos;
205                System.arraycopy(buf, pos, buffer, newOffset, copyLength);
206                newOffset += copyLength;
207                copiedChars += copyLength;
208                /* Use up the chars in the local buffer */
209                pos += copyLength;
210            }
211            /* Have we copied enough? */
212            if (copyLength == count) {
213                return count;
214            }
215            int inCopied = in.read(buffer, newOffset, count - copiedChars);
216            if (inCopied > 0) {
217                return inCopied + copiedChars;
218            }
219            if (copiedChars == 0) {
220                return inCopied;
221            }
222            return copiedChars;
223        }
224    }
225
226    /**
227     * Indicates whether this reader is ready to be read without blocking.
228     * Returns {@code true} if this reader will not block when {@code read} is
229     * called, {@code false} if unknown or blocking will occur.
230     *
231     * @return {@code true} if the receiver will not block when
232     *         {@code read()} is called, {@code false} if unknown
233     *         or blocking will occur.
234     * @throws IOException
235     *             if this reader is closed or some other I/O error occurs.
236     * @see #read()
237     * @see #read(char[], int, int)
238     */
239    @Override
240    public boolean ready() throws IOException {
241        synchronized (lock) {
242            if (buf == null) {
243                throw new IOException(Msg.getString("K0080")); //$NON-NLS-1$
244            }
245            return (buf.length - pos > 0 || in.ready());
246        }
247    }
248
249    /**
250     * Resets this reader to the last marked position. Resetting the reader is
251     * not supported in this class; this implementation always throws an
252     * {@code IOException}.
253     *
254     * @throws IOException
255     *             if this method is called.
256     */
257    @Override
258    public void reset() throws IOException {
259        throw new IOException(Msg.getString("K007f")); //$NON-NLS-1$
260    }
261
262    /**
263     * Pushes all the characters in {@code buffer} back to this reader. The
264     * characters are pushed back in such a way that the next character read
265     * from this reader is buffer[0], then buffer[1] and so on.
266     * <p>
267     * If this reader's internal pushback buffer cannot store the entire
268     * contents of {@code buffer}, an {@code IOException} is thrown. Parts of
269     * {@code buffer} may have already been copied to the pushback buffer when
270     * the exception is thrown.
271     *
272     * @param buffer
273     *            the buffer containing the characters to push back to this
274     *            reader.
275     * @throws IOException
276     *             if this reader is closed or the free space in the internal
277     *             pushback buffer is not sufficient to store the contents of
278     *             {@code buffer}.
279     */
280    public void unread(char[] buffer) throws IOException {
281        unread(buffer, 0, buffer.length);
282    }
283
284    /**
285     * Pushes a subset of the characters in {@code buffer} back to this reader.
286     * The subset is defined by the start position {@code offset} within
287     * {@code buffer} and the number of characters specified by {@code length}.
288     * The bytes are pushed back in such a way that the next byte read from this
289     * stream is {@code buffer[offset]}, then {@code buffer[1]} and so on.
290     * <p>
291     * If this stream's internal pushback buffer cannot store the selected
292     * subset of {@code buffer}, an {@code IOException} is thrown. Parts of
293     * {@code buffer} may have already been copied to the pushback buffer when
294     * the exception is thrown.
295     *
296     * @param buffer
297     *            the buffer containing the characters to push back to this
298     *            reader.
299     * @param offset
300     *            the index of the first byte in {@code buffer} to push back.
301     * @param length
302     *            the number of bytes to push back.
303     * @throws IndexOutOfBoundsException
304     *             if {@code offset < 0} or {@code count < 0}, or if
305     *             {@code offset + count} is greater than the length of
306     *             {@code buffer}.
307     * @throws IOException
308     *             if this reader is closed or the free space in the internal
309     *             pushback buffer is not sufficient to store the selected
310     *             contents of {@code buffer}.
311     * @throws NullPointerException
312     *             if {@code buffer} is {@code null}.
313     */
314    public void unread(char[] buffer, int offset, int length) throws IOException {
315        synchronized (lock) {
316            if (buf == null) {
317                // K0059=Stream is closed
318                throw new IOException(Msg.getString("K0059")); //$NON-NLS-1$
319            }
320            if (length > pos) {
321                // K007e=Pushback buffer full
322                throw new IOException(Msg.getString("K007e")); //$NON-NLS-1$
323            }
324            // Force buffer null check first!
325            if (offset > buffer.length - length || offset < 0) {
326                // K002e=Offset out of bounds \: {0}
327                throw new ArrayIndexOutOfBoundsException(Msg.getString("K002e", offset)); //$NON-NLS-1$
328            }
329            if (length < 0) {
330                // K0031=Length out of bounds \: {0}
331                throw new ArrayIndexOutOfBoundsException(Msg.getString("K0031", length)); //$NON-NLS-1$
332            }
333
334            for (int i = offset + length - 1; i >= offset; i--) {
335                unread(buffer[i]);
336            }
337        }
338    }
339
340    /**
341     * Pushes the specified character {@code oneChar} back to this reader. This
342     * is done in such a way that the next character read from this reader is
343     * {@code (char) oneChar}.
344     * <p>
345     * If this reader's internal pushback buffer cannot store the character, an
346     * {@code IOException} is thrown.
347     *
348     * @param oneChar
349     *            the character to push back to this stream.
350     * @throws IOException
351     *             if this reader is closed or the internal pushback buffer is
352     *             full.
353     */
354    public void unread(int oneChar) throws IOException {
355        synchronized (lock) {
356            if (buf == null) {
357                throw new IOException(Msg.getString("K0059")); //$NON-NLS-1$
358            }
359            if (pos == 0) {
360                throw new IOException(Msg.getString("K007e")); //$NON-NLS-1$
361            }
362            buf[--pos] = (char) oneChar;
363        }
364    }
365
366    /**
367     * Skips {@code count} characters in this reader. This implementation skips
368     * characters in the pushback buffer first and then in the source reader if
369     * necessary.
370     *
371     * @param count
372     *            the number of characters to skip.
373     * @return the number of characters actually skipped.
374     * @throws IllegalArgumentException if {@code count < 0}.
375     * @throws IOException
376     *             if this reader is closed or another I/O error occurs.
377     */
378    @Override
379    public long skip(long count) throws IOException {
380        if (count < 0) {
381            throw new IllegalArgumentException();
382        }
383        synchronized (lock) {
384            if (buf == null) {
385                throw new IOException(Msg.getString("K0059")); //$NON-NLS-1$
386            }
387            if (count == 0) {
388                return 0;
389            }
390            long inSkipped;
391            int availableFromBuffer = buf.length - pos;
392            if (availableFromBuffer > 0) {
393                long requiredFromIn = count - availableFromBuffer;
394                if (requiredFromIn <= 0) {
395                    pos += count;
396                    return count;
397                }
398                pos += availableFromBuffer;
399                inSkipped = in.skip(requiredFromIn);
400            } else {
401                inSkipped = in.skip(count);
402            }
403            return inSkipped + availableFromBuffer;
404        }
405    }
406}
407