jaroslav@557: /* jaroslav@557: * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. jaroslav@557: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jaroslav@557: * jaroslav@557: * This code is free software; you can redistribute it and/or modify it jaroslav@557: * under the terms of the GNU General Public License version 2 only, as jaroslav@557: * published by the Free Software Foundation. Oracle designates this jaroslav@557: * particular file as subject to the "Classpath" exception as provided jaroslav@557: * by Oracle in the LICENSE file that accompanied this code. jaroslav@557: * jaroslav@557: * This code is distributed in the hope that it will be useful, but WITHOUT jaroslav@557: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jaroslav@557: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jaroslav@557: * version 2 for more details (a copy is included in the LICENSE file that jaroslav@557: * accompanied this code). jaroslav@557: * jaroslav@557: * You should have received a copy of the GNU General Public License version jaroslav@557: * 2 along with this work; if not, write to the Free Software Foundation, jaroslav@557: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jaroslav@557: * jaroslav@557: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA jaroslav@557: * or visit www.oracle.com if you need additional information or have any jaroslav@557: * questions. jaroslav@557: */ jaroslav@557: jaroslav@557: package java.io; jaroslav@557: jaroslav@560: jaroslav@557: jaroslav@557: /** jaroslav@557: * Reads text from a character-input stream, buffering characters so as to jaroslav@557: * provide for the efficient reading of characters, arrays, and lines. jaroslav@557: * jaroslav@557: *

The buffer size may be specified, or the default size may be used. The jaroslav@557: * default is large enough for most purposes. jaroslav@557: * jaroslav@557: *

In general, each read request made of a Reader causes a corresponding jaroslav@557: * read request to be made of the underlying character or byte stream. It is jaroslav@557: * therefore advisable to wrap a BufferedReader around any Reader whose read() jaroslav@557: * operations may be costly, such as FileReaders and InputStreamReaders. For jaroslav@557: * example, jaroslav@557: * jaroslav@557: *

jaroslav@557:  * BufferedReader in
jaroslav@557:  *   = new BufferedReader(new FileReader("foo.in"));
jaroslav@557:  * 
jaroslav@557: * jaroslav@557: * will buffer the input from the specified file. Without buffering, each jaroslav@557: * invocation of read() or readLine() could cause bytes to be read from the jaroslav@557: * file, converted into characters, and then returned, which can be very jaroslav@557: * inefficient. jaroslav@557: * jaroslav@557: *

Programs that use DataInputStreams for textual input can be localized by jaroslav@557: * replacing each DataInputStream with an appropriate BufferedReader. jaroslav@557: * jaroslav@557: * @see FileReader jaroslav@557: * @see InputStreamReader jaroslav@557: * @see java.nio.file.Files#newBufferedReader jaroslav@557: * jaroslav@557: * @author Mark Reinhold jaroslav@557: * @since JDK1.1 jaroslav@557: */ jaroslav@557: jaroslav@557: public class BufferedReader extends Reader { jaroslav@557: jaroslav@557: private Reader in; jaroslav@557: jaroslav@557: private char cb[]; jaroslav@557: private int nChars, nextChar; jaroslav@557: jaroslav@557: private static final int INVALIDATED = -2; jaroslav@557: private static final int UNMARKED = -1; jaroslav@557: private int markedChar = UNMARKED; jaroslav@557: private int readAheadLimit = 0; /* Valid only when markedChar > 0 */ jaroslav@557: jaroslav@557: /** If the next character is a line feed, skip it */ jaroslav@557: private boolean skipLF = false; jaroslav@557: jaroslav@557: /** The skipLF flag when the mark was set */ jaroslav@557: private boolean markedSkipLF = false; jaroslav@557: jaroslav@557: private static int defaultCharBufferSize = 8192; jaroslav@557: private static int defaultExpectedLineLength = 80; jaroslav@557: jaroslav@557: /** jaroslav@557: * Creates a buffering character-input stream that uses an input buffer of jaroslav@557: * the specified size. jaroslav@557: * jaroslav@557: * @param in A Reader jaroslav@557: * @param sz Input-buffer size jaroslav@557: * jaroslav@557: * @exception IllegalArgumentException If sz is <= 0 jaroslav@557: */ jaroslav@557: public BufferedReader(Reader in, int sz) { jaroslav@557: super(in); jaroslav@557: if (sz <= 0) jaroslav@557: throw new IllegalArgumentException("Buffer size <= 0"); jaroslav@557: this.in = in; jaroslav@557: cb = new char[sz]; jaroslav@557: nextChar = nChars = 0; jaroslav@557: } jaroslav@557: jaroslav@557: /** jaroslav@557: * Creates a buffering character-input stream that uses a default-sized jaroslav@557: * input buffer. jaroslav@557: * jaroslav@557: * @param in A Reader jaroslav@557: */ jaroslav@557: public BufferedReader(Reader in) { jaroslav@557: this(in, defaultCharBufferSize); jaroslav@557: } jaroslav@557: jaroslav@557: /** Checks to make sure that the stream has not been closed */ jaroslav@557: private void ensureOpen() throws IOException { jaroslav@557: if (in == null) jaroslav@557: throw new IOException("Stream closed"); jaroslav@557: } jaroslav@557: jaroslav@557: /** jaroslav@557: * Fills the input buffer, taking the mark into account if it is valid. jaroslav@557: */ jaroslav@557: private void fill() throws IOException { jaroslav@557: int dst; jaroslav@557: if (markedChar <= UNMARKED) { jaroslav@557: /* No mark */ jaroslav@557: dst = 0; jaroslav@557: } else { jaroslav@557: /* Marked */ jaroslav@557: int delta = nextChar - markedChar; jaroslav@557: if (delta >= readAheadLimit) { jaroslav@557: /* Gone past read-ahead limit: Invalidate mark */ jaroslav@557: markedChar = INVALIDATED; jaroslav@557: readAheadLimit = 0; jaroslav@557: dst = 0; jaroslav@557: } else { jaroslav@557: if (readAheadLimit <= cb.length) { jaroslav@557: /* Shuffle in the current buffer */ jaroslav@557: System.arraycopy(cb, markedChar, cb, 0, delta); jaroslav@557: markedChar = 0; jaroslav@557: dst = delta; jaroslav@557: } else { jaroslav@557: /* Reallocate buffer to accommodate read-ahead limit */ jaroslav@557: char ncb[] = new char[readAheadLimit]; jaroslav@557: System.arraycopy(cb, markedChar, ncb, 0, delta); jaroslav@557: cb = ncb; jaroslav@557: markedChar = 0; jaroslav@557: dst = delta; jaroslav@557: } jaroslav@557: nextChar = nChars = delta; jaroslav@557: } jaroslav@557: } jaroslav@557: jaroslav@557: int n; jaroslav@557: do { jaroslav@557: n = in.read(cb, dst, cb.length - dst); jaroslav@557: } while (n == 0); jaroslav@557: if (n > 0) { jaroslav@557: nChars = dst + n; jaroslav@557: nextChar = dst; jaroslav@557: } jaroslav@557: } jaroslav@557: jaroslav@557: /** jaroslav@557: * Reads a single character. jaroslav@557: * jaroslav@557: * @return The character read, as an integer in the range jaroslav@557: * 0 to 65535 (0x00-0xffff), or -1 if the jaroslav@557: * end of the stream has been reached jaroslav@557: * @exception IOException If an I/O error occurs jaroslav@557: */ jaroslav@557: public int read() throws IOException { jaroslav@557: synchronized (lock) { jaroslav@557: ensureOpen(); jaroslav@557: for (;;) { jaroslav@557: if (nextChar >= nChars) { jaroslav@557: fill(); jaroslav@557: if (nextChar >= nChars) jaroslav@557: return -1; jaroslav@557: } jaroslav@557: if (skipLF) { jaroslav@557: skipLF = false; jaroslav@557: if (cb[nextChar] == '\n') { jaroslav@557: nextChar++; jaroslav@557: continue; jaroslav@557: } jaroslav@557: } jaroslav@557: return cb[nextChar++]; jaroslav@557: } jaroslav@557: } jaroslav@557: } jaroslav@557: jaroslav@557: /** jaroslav@557: * Reads characters into a portion of an array, reading from the underlying jaroslav@557: * stream if necessary. jaroslav@557: */ jaroslav@557: private int read1(char[] cbuf, int off, int len) throws IOException { jaroslav@557: if (nextChar >= nChars) { jaroslav@557: /* If the requested length is at least as large as the buffer, and jaroslav@557: if there is no mark/reset activity, and if line feeds are not jaroslav@557: being skipped, do not bother to copy the characters into the jaroslav@557: local buffer. In this way buffered streams will cascade jaroslav@557: harmlessly. */ jaroslav@557: if (len >= cb.length && markedChar <= UNMARKED && !skipLF) { jaroslav@557: return in.read(cbuf, off, len); jaroslav@557: } jaroslav@557: fill(); jaroslav@557: } jaroslav@557: if (nextChar >= nChars) return -1; jaroslav@557: if (skipLF) { jaroslav@557: skipLF = false; jaroslav@557: if (cb[nextChar] == '\n') { jaroslav@557: nextChar++; jaroslav@557: if (nextChar >= nChars) jaroslav@557: fill(); jaroslav@557: if (nextChar >= nChars) jaroslav@557: return -1; jaroslav@557: } jaroslav@557: } jaroslav@557: int n = Math.min(len, nChars - nextChar); jaroslav@557: System.arraycopy(cb, nextChar, cbuf, off, n); jaroslav@557: nextChar += n; jaroslav@557: return n; jaroslav@557: } jaroslav@557: jaroslav@557: /** jaroslav@557: * Reads characters into a portion of an array. jaroslav@557: * jaroslav@557: *

This method implements the general contract of the corresponding jaroslav@557: * {@link Reader#read(char[], int, int) read} method of the jaroslav@557: * {@link Reader} class. As an additional convenience, it jaroslav@557: * attempts to read as many characters as possible by repeatedly invoking jaroslav@557: * the read method of the underlying stream. This iterated jaroslav@557: * read continues until one of the following conditions becomes jaroslav@557: * true:

If the first read on the underlying stream returns jaroslav@557: * -1 to indicate end-of-file then this method returns jaroslav@557: * -1. Otherwise this method returns the number of characters jaroslav@557: * actually read. jaroslav@557: * jaroslav@557: *

Subclasses of this class are encouraged, but not required, to jaroslav@557: * attempt to read as many characters as possible in the same fashion. jaroslav@557: * jaroslav@557: *

Ordinarily this method takes characters from this stream's character jaroslav@557: * buffer, filling it from the underlying stream as necessary. If, jaroslav@557: * however, the buffer is empty, the mark is not valid, and the requested jaroslav@557: * length is at least as large as the buffer, then this method will read jaroslav@557: * characters directly from the underlying stream into the given array. jaroslav@557: * Thus redundant BufferedReaders will not copy data jaroslav@557: * unnecessarily. jaroslav@557: * jaroslav@557: * @param cbuf Destination buffer jaroslav@557: * @param off Offset at which to start storing characters jaroslav@557: * @param len Maximum number of characters to read jaroslav@557: * jaroslav@557: * @return The number of characters read, or -1 if the end of the jaroslav@557: * stream has been reached jaroslav@557: * jaroslav@557: * @exception IOException If an I/O error occurs jaroslav@557: */ jaroslav@557: public int read(char cbuf[], int off, int len) throws IOException { jaroslav@557: synchronized (lock) { jaroslav@557: ensureOpen(); jaroslav@557: if ((off < 0) || (off > cbuf.length) || (len < 0) || jaroslav@557: ((off + len) > cbuf.length) || ((off + len) < 0)) { jaroslav@557: throw new IndexOutOfBoundsException(); jaroslav@557: } else if (len == 0) { jaroslav@557: return 0; jaroslav@557: } jaroslav@557: jaroslav@557: int n = read1(cbuf, off, len); jaroslav@557: if (n <= 0) return n; jaroslav@557: while ((n < len) && in.ready()) { jaroslav@557: int n1 = read1(cbuf, off + n, len - n); jaroslav@557: if (n1 <= 0) break; jaroslav@557: n += n1; jaroslav@557: } jaroslav@557: return n; jaroslav@557: } jaroslav@557: } jaroslav@557: jaroslav@557: /** jaroslav@557: * Reads a line of text. A line is considered to be terminated by any one jaroslav@557: * of a line feed ('\n'), a carriage return ('\r'), or a carriage return jaroslav@557: * followed immediately by a linefeed. jaroslav@557: * jaroslav@557: * @param ignoreLF If true, the next '\n' will be skipped jaroslav@557: * jaroslav@557: * @return A String containing the contents of the line, not including jaroslav@557: * any line-termination characters, or null if the end of the jaroslav@557: * stream has been reached jaroslav@557: * jaroslav@557: * @see java.io.LineNumberReader#readLine() jaroslav@557: * jaroslav@557: * @exception IOException If an I/O error occurs jaroslav@557: */ jaroslav@557: String readLine(boolean ignoreLF) throws IOException { jaroslav@557: StringBuffer s = null; jaroslav@557: int startChar; jaroslav@557: jaroslav@557: synchronized (lock) { jaroslav@557: ensureOpen(); jaroslav@557: boolean omitLF = ignoreLF || skipLF; jaroslav@557: jaroslav@557: bufferLoop: jaroslav@557: for (;;) { jaroslav@557: jaroslav@557: if (nextChar >= nChars) jaroslav@557: fill(); jaroslav@557: if (nextChar >= nChars) { /* EOF */ jaroslav@557: if (s != null && s.length() > 0) jaroslav@557: return s.toString(); jaroslav@557: else jaroslav@557: return null; jaroslav@557: } jaroslav@557: boolean eol = false; jaroslav@557: char c = 0; jaroslav@557: int i; jaroslav@557: jaroslav@557: /* Skip a leftover '\n', if necessary */ jaroslav@557: if (omitLF && (cb[nextChar] == '\n')) jaroslav@557: nextChar++; jaroslav@557: skipLF = false; jaroslav@557: omitLF = false; jaroslav@557: jaroslav@557: charLoop: jaroslav@557: for (i = nextChar; i < nChars; i++) { jaroslav@557: c = cb[i]; jaroslav@557: if ((c == '\n') || (c == '\r')) { jaroslav@557: eol = true; jaroslav@557: break charLoop; jaroslav@557: } jaroslav@557: } jaroslav@557: jaroslav@557: startChar = nextChar; jaroslav@557: nextChar = i; jaroslav@557: jaroslav@557: if (eol) { jaroslav@557: String str; jaroslav@557: if (s == null) { jaroslav@557: str = new String(cb, startChar, i - startChar); jaroslav@557: } else { jaroslav@557: s.append(cb, startChar, i - startChar); jaroslav@557: str = s.toString(); jaroslav@557: } jaroslav@557: nextChar++; jaroslav@557: if (c == '\r') { jaroslav@557: skipLF = true; jaroslav@557: } jaroslav@557: return str; jaroslav@557: } jaroslav@557: jaroslav@557: if (s == null) jaroslav@557: s = new StringBuffer(defaultExpectedLineLength); jaroslav@557: s.append(cb, startChar, i - startChar); jaroslav@557: } jaroslav@557: } jaroslav@557: } jaroslav@557: jaroslav@557: /** jaroslav@557: * Reads a line of text. A line is considered to be terminated by any one jaroslav@557: * of a line feed ('\n'), a carriage return ('\r'), or a carriage return jaroslav@557: * followed immediately by a linefeed. jaroslav@557: * jaroslav@557: * @return A String containing the contents of the line, not including jaroslav@557: * any line-termination characters, or null if the end of the jaroslav@557: * stream has been reached jaroslav@557: * jaroslav@557: * @exception IOException If an I/O error occurs jaroslav@557: * jaroslav@557: * @see java.nio.file.Files#readAllLines jaroslav@557: */ jaroslav@557: public String readLine() throws IOException { jaroslav@557: return readLine(false); jaroslav@557: } jaroslav@557: jaroslav@557: /** jaroslav@557: * Skips characters. jaroslav@557: * jaroslav@557: * @param n The number of characters to skip jaroslav@557: * jaroslav@557: * @return The number of characters actually skipped jaroslav@557: * jaroslav@557: * @exception IllegalArgumentException If n is negative. jaroslav@557: * @exception IOException If an I/O error occurs jaroslav@557: */ jaroslav@557: public long skip(long n) throws IOException { jaroslav@557: if (n < 0L) { jaroslav@557: throw new IllegalArgumentException("skip value is negative"); jaroslav@557: } jaroslav@557: synchronized (lock) { jaroslav@557: ensureOpen(); jaroslav@557: long r = n; jaroslav@557: while (r > 0) { jaroslav@557: if (nextChar >= nChars) jaroslav@557: fill(); jaroslav@557: if (nextChar >= nChars) /* EOF */ jaroslav@557: break; jaroslav@557: if (skipLF) { jaroslav@557: skipLF = false; jaroslav@557: if (cb[nextChar] == '\n') { jaroslav@557: nextChar++; jaroslav@557: } jaroslav@557: } jaroslav@557: long d = nChars - nextChar; jaroslav@557: if (r <= d) { jaroslav@557: nextChar += r; jaroslav@557: r = 0; jaroslav@557: break; jaroslav@557: } jaroslav@557: else { jaroslav@557: r -= d; jaroslav@557: nextChar = nChars; jaroslav@557: } jaroslav@557: } jaroslav@557: return n - r; jaroslav@557: } jaroslav@557: } jaroslav@557: jaroslav@557: /** jaroslav@557: * Tells whether this stream is ready to be read. A buffered character jaroslav@557: * stream is ready if the buffer is not empty, or if the underlying jaroslav@557: * character stream is ready. jaroslav@557: * jaroslav@557: * @exception IOException If an I/O error occurs jaroslav@557: */ jaroslav@557: public boolean ready() throws IOException { jaroslav@557: synchronized (lock) { jaroslav@557: ensureOpen(); jaroslav@557: jaroslav@557: /* jaroslav@557: * If newline needs to be skipped and the next char to be read jaroslav@557: * is a newline character, then just skip it right away. jaroslav@557: */ jaroslav@557: if (skipLF) { jaroslav@557: /* Note that in.ready() will return true if and only if the next jaroslav@557: * read on the stream will not block. jaroslav@557: */ jaroslav@557: if (nextChar >= nChars && in.ready()) { jaroslav@557: fill(); jaroslav@557: } jaroslav@557: if (nextChar < nChars) { jaroslav@557: if (cb[nextChar] == '\n') jaroslav@557: nextChar++; jaroslav@557: skipLF = false; jaroslav@557: } jaroslav@557: } jaroslav@557: return (nextChar < nChars) || in.ready(); jaroslav@557: } jaroslav@557: } jaroslav@557: jaroslav@557: /** jaroslav@557: * Tells whether this stream supports the mark() operation, which it does. jaroslav@557: */ jaroslav@557: public boolean markSupported() { jaroslav@557: return true; jaroslav@557: } jaroslav@557: jaroslav@557: /** jaroslav@557: * Marks the present position in the stream. Subsequent calls to reset() jaroslav@557: * will attempt to reposition the stream to this point. jaroslav@557: * jaroslav@557: * @param readAheadLimit Limit on the number of characters that may be jaroslav@557: * read while still preserving the mark. An attempt jaroslav@557: * to reset the stream after reading characters jaroslav@557: * up to this limit or beyond may fail. jaroslav@557: * A limit value larger than the size of the input jaroslav@557: * buffer will cause a new buffer to be allocated jaroslav@557: * whose size is no smaller than limit. jaroslav@557: * Therefore large values should be used with care. jaroslav@557: * jaroslav@557: * @exception IllegalArgumentException If readAheadLimit is < 0 jaroslav@557: * @exception IOException If an I/O error occurs jaroslav@557: */ jaroslav@557: public void mark(int readAheadLimit) throws IOException { jaroslav@557: if (readAheadLimit < 0) { jaroslav@557: throw new IllegalArgumentException("Read-ahead limit < 0"); jaroslav@557: } jaroslav@557: synchronized (lock) { jaroslav@557: ensureOpen(); jaroslav@557: this.readAheadLimit = readAheadLimit; jaroslav@557: markedChar = nextChar; jaroslav@557: markedSkipLF = skipLF; jaroslav@557: } jaroslav@557: } jaroslav@557: jaroslav@557: /** jaroslav@557: * Resets the stream to the most recent mark. jaroslav@557: * jaroslav@557: * @exception IOException If the stream has never been marked, jaroslav@557: * or if the mark has been invalidated jaroslav@557: */ jaroslav@557: public void reset() throws IOException { jaroslav@557: synchronized (lock) { jaroslav@557: ensureOpen(); jaroslav@557: if (markedChar < 0) jaroslav@557: throw new IOException((markedChar == INVALIDATED) jaroslav@557: ? "Mark invalid" jaroslav@557: : "Stream not marked"); jaroslav@557: nextChar = markedChar; jaroslav@557: skipLF = markedSkipLF; jaroslav@557: } jaroslav@557: } jaroslav@557: jaroslav@557: public void close() throws IOException { jaroslav@557: synchronized (lock) { jaroslav@557: if (in == null) jaroslav@557: return; jaroslav@557: in.close(); jaroslav@557: in = null; jaroslav@557: cb = null; jaroslav@557: } jaroslav@557: } jaroslav@557: }