BufferedReader.java revision dd828f42a5c83b4270d4fbf6fce2da1878f1e84a
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// BEGIN android-added
23import java.util.logging.Logger;
24// END android-added
25
26/**
27 * Wraps an existing {@link Reader} and <em>buffers</em> the input. Expensive
28 * interaction with the underlying reader is minimized, since most (smaller)
29 * requests can be satisfied by accessing the buffer alone. The drawback is that
30 * some extra space is required to hold the buffer and that copying takes place
31 * when filling that buffer, but this is usually outweighed by the performance
32 * benefits.
33 *
34 * <p/>A typical application pattern for the class looks like this:<p/>
35 *
36 * <pre>
37 * BufferedReader buf = new BufferedReader(new FileReader(&quot;file.java&quot;));
38 * </pre>
39 *
40 * @see BufferedWriter
41 *
42 * @since Android 1.0
43 */
44public class BufferedReader extends Reader {
45
46    private Reader in;
47
48    private char[] buf;
49
50    private int marklimit = -1;
51
52    private int count;
53
54    private int markpos = -1;
55
56    private int pos;
57
58    /**
59     * Constructs a new BufferedReader on the Reader {@code in}. The
60     * buffer gets the default size (8 KB).
61     *
62     * @param in
63     *            the Reader that is buffered.
64     * @since Android 1.0
65     */
66    public BufferedReader(Reader in) {
67        super(in);
68        this.in = in;
69        buf = new char[8192];
70
71        // BEGIN android-added
72        /*
73         * For Android, we want to discourage the use of this
74         * constructor (with its arguably too-large default), so we
75         * note its use in the log. We don't disable it, nor do we
76         * alter the default, however, because we still aim to behave
77         * compatibly, and the default value, though not documented,
78         * is established by convention.
79         */
80        Logger.global.info(
81                "Default buffer size used in BufferedReader " +
82                "constructor. It would be " +
83                "better to be explicit if a 8k-char buffer is required.");
84        // END android-added
85    }
86
87    /**
88     * Constructs a new BufferedReader on the Reader {@code in}. The buffer
89     * size is specified by the parameter {@code size}.
90     *
91     * @param in
92     *            the Reader that is buffered.
93     * @param size
94     *            the size of the buffer to allocate.
95     * @throws IllegalArgumentException
96     *             if {@code size <= 0}.
97     * @since Android 1.0
98     */
99    public BufferedReader(Reader in, int size) {
100        super(in);
101        if (size <= 0) {
102            throw new IllegalArgumentException(Msg.getString("K0058")); //$NON-NLS-1$
103        }
104        this.in = in;
105        buf = new char[size];
106    }
107
108    /**
109     * Closes this reader. This implementation closes the buffered source reader
110     * and releases the buffer. Nothing is done if this reader has already been
111     * closed.
112     *
113     * @throws IOException
114     *             if an error occurs while closing this reader.
115     * @since Android 1.0
116     */
117    @Override
118    public void close() throws IOException {
119        synchronized (lock) {
120            if (!isClosed()) {
121                in.close();
122                buf = null;
123            }
124        }
125    }
126
127    private int fillbuf() throws IOException {
128        if (markpos == -1 || (pos - markpos >= marklimit)) {
129            /* Mark position not set or exceeded readlimit */
130            int result = in.read(buf, 0, buf.length);
131            if (result > 0) {
132                markpos = -1;
133                pos = 0;
134                count = result == -1 ? 0 : result;
135            }
136            return result;
137        }
138        if (markpos == 0 && marklimit > buf.length) {
139            /* Increase buffer size to accommodate the readlimit */
140            int newLength = buf.length * 2;
141            if (newLength > marklimit) {
142                newLength = marklimit;
143            }
144            char[] newbuf = new char[newLength];
145            System.arraycopy(buf, 0, newbuf, 0, buf.length);
146            buf = newbuf;
147        } else if (markpos > 0) {
148            System.arraycopy(buf, markpos, buf, 0, buf.length - markpos);
149        }
150
151        /* Set the new position and mark position */
152        pos -= markpos;
153        count = markpos = 0;
154        int charsread = in.read(buf, pos, buf.length - pos);
155        count = charsread == -1 ? pos : pos + charsread;
156        return charsread;
157    }
158
159    /**
160     * Indicates whether or not this reader is closed.
161     *
162     * @return {@code true} if this reader is closed, {@code false}
163     *         otherwise.
164     */
165    private boolean isClosed() {
166        return buf == null;
167    }
168
169    /**
170     * Sets a mark position in this reader. The parameter {@code readlimit}
171     * indicates how many characters can be read before the mark is invalidated.
172     * Calling {@code reset()} will reposition the reader back to the marked
173     * position if {@code readlimit} has not been surpassed.
174     *
175     * @param readlimit
176     *            the number of characters that can be read before the mark is
177     *            invalidated.
178     * @throws IllegalArgumentException
179     *             if {@code readlimit < 0}.
180     * @throws IOException
181     *             if an error occurs while setting a mark in this reader.
182     * @see #markSupported()
183     * @see #reset()
184     * @since Android 1.0
185     */
186    @Override
187    public void mark(int readlimit) throws IOException {
188        if (readlimit < 0) {
189            throw new IllegalArgumentException();
190        }
191        synchronized (lock) {
192            if (isClosed()) {
193                throw new IOException(Msg.getString("K005b")); //$NON-NLS-1$
194            }
195            marklimit = readlimit;
196            markpos = pos;
197        }
198    }
199
200    /**
201     * Indicates whether this reader supports the {@code mark()} and
202     * {@code reset()} methods. This implementation returns {@code true}.
203     *
204     * @return {@code true} for {@code BufferedReader}.
205     * @see #mark(int)
206     * @see #reset()
207     * @since Android 1.0
208     */
209    @Override
210    public boolean markSupported() {
211        return true;
212    }
213
214    /**
215     * Reads a single character from this reader and returns it with the two
216     * higher-order bytes set to 0. If possible, BufferedReader returns a
217     * character from the buffer. If there are no characters available in the
218     * buffer, it fills the buffer and then returns a character. It returns -1
219     * if there are no more characters in the source reader.
220     *
221     * @return the character read or -1 if the end of the source reader has been
222     *         reached.
223     * @throws IOException
224     *             if this reader is closed or some other I/O error occurs.
225     * @since Android 1.0
226     */
227    @Override
228    public int read() throws IOException {
229        synchronized (lock) {
230            if (isClosed()) {
231                throw new IOException(Msg.getString("K005b")); //$NON-NLS-1$
232            }
233            /* Are there buffered characters available? */
234            if (pos < count || fillbuf() != -1) {
235                return buf[pos++];
236            }
237            return -1;
238        }
239    }
240
241    /**
242     * Reads at most {@code length} characters from this reader and stores them
243     * at {@code offset} in the character array {@code buffer}. Returns the
244     * number of characters actually read or -1 if the end of the source reader
245     * has been reached. If all the buffered characters have been used, a mark
246     * has not been set and the requested number of characters is larger than
247     * this readers buffer size, BufferedReader bypasses the buffer and simply
248     * places the results directly into {@code buffer}.
249     *
250     * @param buffer
251     *            the character array to store the characters read.
252     * @param offset
253     *            the initial position in {@code buffer} to store the bytes read
254     *            from this reader.
255     * @param length
256     *            the maximum number of characters to read, must be
257     *            non-negative.
258     * @return number of characters read or -1 if the end of the source reader
259     *         has been reached.
260     * @throws IndexOutOfBoundsException
261     *             if {@code offset < 0} or {@code length < 0}, or if
262     *             {@code offset + length} is greater than the size of
263     *             {@code buffer}.
264     * @throws IOException
265     *             if this reader is closed or some other I/O error occurs.
266     * @since Android 1.0
267     */
268    @Override
269    public int read(char[] buffer, int offset, int length) throws IOException {
270        synchronized (lock) {
271            if (isClosed()) {
272                throw new IOException(Msg.getString("K005b")); //$NON-NLS-1$
273            }
274            // BEGIN android-changed
275            // Exception priorities (in case of multiple errors) differ from
276            // RI, but are spec-compliant.
277            // made implicit null check explicit, used (offset | length) < 0
278            // instead of (offset < 0) || (length < 0) to safe one operation
279            if (buffer == null) {
280                throw new NullPointerException(Msg.getString("K0047")); //$NON-NLS-1$
281            }
282            if ((offset | length) < 0 || offset > buffer.length - length) {
283                throw new IndexOutOfBoundsException(Msg.getString("K002f")); //$NON-NLS-1$
284            }
285            // END android-changed
286            if (length == 0) {
287                return 0;
288            }
289            int required;
290            if (pos < count) {
291                /* There are bytes available in the buffer. */
292                int copylength = count - pos >= length ? length : count - pos;
293                System.arraycopy(buf, pos, buffer, offset, copylength);
294                pos += copylength;
295                if (copylength == length || !in.ready()) {
296                    return copylength;
297                }
298                offset += copylength;
299                required = length - copylength;
300            } else {
301                required = length;
302            }
303
304            while (true) {
305                int read;
306                /*
307                 * If we're not marked and the required size is greater than the
308                 * buffer, simply read the bytes directly bypassing the buffer.
309                 */
310                if (markpos == -1 && required >= buf.length) {
311                    read = in.read(buffer, offset, required);
312                    if (read == -1) {
313                        return required == length ? -1 : length - required;
314                    }
315                } else {
316                    if (fillbuf() == -1) {
317                        return required == length ? -1 : length - required;
318                    }
319                    read = count - pos >= required ? required : count - pos;
320                    System.arraycopy(buf, pos, buffer, offset, read);
321                    pos += read;
322                }
323                required -= read;
324                if (required == 0) {
325                    return length;
326                }
327                if (!in.ready()) {
328                    return length - required;
329                }
330                offset += read;
331            }
332        }
333    }
334
335    /**
336     * Returns the next line of text available from this reader. A line is
337     * represented by zero or more characters followed by {@code '\n'},
338     * {@code '\r'}, {@code "\r\n"} or the end of the reader. The string does
339     * not include the newline sequence.
340     *
341     * @return the contents of the line or {@code null} if no characters were
342     *         read before the end of the reader has been reached.
343     * @throws IOException
344     *             if this reader is closed or some other I/O error occurs.
345     * @since Android 1.0
346     */
347    public String readLine() throws IOException {
348        synchronized (lock) {
349            if (isClosed()) {
350                throw new IOException(Msg.getString("K005b")); //$NON-NLS-1$
351            }
352            /* Are there buffered characters available? */
353            if ((pos >= count) && (fillbuf() == -1)) {
354                return null;
355            }
356            for (int charPos = pos; charPos < count; charPos++) {
357                char ch = buf[charPos];
358                if (ch > '\r') {
359                    continue;
360                }
361                if (ch == '\n') {
362                    String res = new String(buf, pos, charPos - pos);
363                    pos = charPos + 1;
364                    return res;
365                } else if (ch == '\r') {
366                    String res = new String(buf, pos, charPos - pos);
367                    pos = charPos + 1;
368                    if (((pos < count) || (fillbuf() != -1))
369                            && (buf[pos] == '\n')) {
370                        pos++;
371                    }
372                    return res;
373                }
374            }
375
376            char eol = '\0';
377            StringBuilder result = new StringBuilder(80);
378            /* Typical Line Length */
379
380            result.append(buf, pos, count - pos);
381            pos = count;
382            while (true) {
383                /* Are there buffered characters available? */
384                if (pos >= count) {
385                    if (eol == '\n') {
386                        return result.toString();
387                    }
388                    // attempt to fill buffer
389                    if (fillbuf() == -1) {
390                        // characters or null.
391                        return result.length() > 0 || eol != '\0' ? result
392                                .toString() : null;
393                    }
394                }
395                for (int charPos = pos; charPos < count; charPos++) {
396                    if (eol == '\0') {
397                        if ((buf[charPos] == '\n' || buf[charPos] == '\r')) {
398                            eol = buf[charPos];
399                        }
400                    } else if (eol == '\r' && (buf[charPos] == '\n')) {
401                        if (charPos > pos) {
402                            result.append(buf, pos, charPos - pos - 1);
403                        }
404                        pos = charPos + 1;
405                        return result.toString();
406                    } else if (eol != '\0') {
407                        if (charPos > pos) {
408                            result.append(buf, pos, charPos - pos - 1);
409                        }
410                        pos = charPos;
411                        return result.toString();
412                    }
413                }
414                if (eol == '\0') {
415                    result.append(buf, pos, count - pos);
416                } else {
417                    result.append(buf, pos, count - pos - 1);
418                }
419                pos = count;
420            }
421        }
422
423    }
424
425    /**
426     * Indicates whether this reader is ready to be read without blocking.
427     *
428     * @return {@code true} if this reader will not block when {@code read} is
429     *         called, {@code false} if unknown or blocking will occur.
430     * @throws IOException
431     *             if this reader is closed or some other I/O error occurs.
432     * @see #read()
433     * @see #read(char[], int, int)
434     * @see #readLine()
435     * @since Android 1.0
436     */
437    @Override
438    public boolean ready() throws IOException {
439        synchronized (lock) {
440            if (isClosed()) {
441                throw new IOException(Msg.getString("K005b")); //$NON-NLS-1$
442            }
443            return ((count - pos) > 0) || in.ready();
444        }
445    }
446
447    /**
448     * Resets this reader's position to the last {@code mark()} location.
449     * Invocations of {@code read()} and {@code skip()} will occur from this new
450     * location.
451     *
452     * @throws IOException
453     *             if this reader is closed or no mark has been set.
454     * @see #mark(int)
455     * @see #markSupported()
456     * @since Android 1.0
457     */
458    @Override
459    public void reset() throws IOException {
460        synchronized (lock) {
461            if (isClosed()) {
462                throw new IOException(Msg.getString("K005b")); //$NON-NLS-1$
463            }
464            if (markpos == -1) {
465                throw new IOException(Msg.getString("K005c")); //$NON-NLS-1$
466            }
467            pos = markpos;
468        }
469    }
470
471    /**
472     * Skips {@code amount} characters in this reader. Subsequent
473     * {@code read()}s will not return these characters unless {@code reset()}
474     * is used. Skipping characters may invalidate a mark if {@code readlimit}
475     * is surpassed.
476     *
477     * @param amount
478     *            the maximum number of characters to skip.
479     * @return the number of characters actually skipped.
480     * @throws IllegalArgumentException
481     *             if {@code amount < 0}.
482     * @throws IOException
483     *             if this reader is closed or some other I/O error occurs.
484     * @see #mark(int)
485     * @see #markSupported()
486     * @see #reset()
487     * @since Android 1.0
488     */
489    @Override
490    public long skip(long amount) throws IOException {
491        if (amount < 0) {
492            throw new IllegalArgumentException();
493        }
494        synchronized (lock) {
495            if (isClosed()) {
496                throw new IOException(Msg.getString("K005b")); //$NON-NLS-1$
497            }
498            if (amount < 1) {
499                return 0;
500            }
501            if (count - pos >= amount) {
502                pos += amount;
503                return amount;
504            }
505
506            long read = count - pos;
507            pos = count;
508            while (read < amount) {
509                if (fillbuf() == -1) {
510                    return read;
511                }
512                if (count - pos >= amount - read) {
513                    pos += amount - read;
514                    return amount;
515                }
516                // Couldn't get all the characters, skip what we read
517                read += (count - pos);
518                pos = count;
519            }
520            return amount;
521        }
522    }
523}
524