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