| /* | |
| * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package java.io; | |
| import java.util.Iterator; | |
| import java.util.NoSuchElementException; | |
| import java.util.Objects; | |
| import java.util.Spliterator; | |
| import java.util.Spliterators; | |
| import java.util.stream.Stream; | |
| import java.util.stream.StreamSupport; | |
| /** | |
| * Reads text from a character-input stream, buffering characters so as to | |
| * provide for the efficient reading of characters, arrays, and lines. | |
| * | |
| * <p> The buffer size may be specified, or the default size may be used. The | |
| * default is large enough for most purposes. | |
| * | |
| * <p> In general, each read request made of a Reader causes a corresponding | |
| * read request to be made of the underlying character or byte stream. It is | |
| * therefore advisable to wrap a {@code BufferedReader} around any | |
| * {@code Reader} whose {@code read()} operations may be costly, such as | |
| * {@code FileReader}s and {@code InputStreamReader}s. For | |
| * example, | |
| * | |
| * {@snippet lang=java : | |
| * BufferedReader in = new BufferedReader(new FileReader("foo.in")); | |
| * } | |
| * | |
| * will buffer the input from the specified file. Without buffering, each | |
| * invocation of {@code read()} or {@code readLine()} could cause bytes to be | |
| * read from the file, converted into characters, and then returned, which can | |
| * be very inefficient. | |
| * | |
| * <p> Programs that use {@code DataInputStream}s for textual input can be | |
| * localized by replacing each {@code DataInputStream} with an appropriate | |
| * {@code BufferedReader}. | |
| * | |
| * @apiNote | |
| * Once wrapped in a {@code BufferedReader}, the underlying | |
| * {@code Reader} should not be used directly nor wrapped with | |
| * another reader. | |
| * | |
| * @see FileReader | |
| * @see InputStreamReader | |
| * @see java.nio.file.Files#newBufferedReader | |
| * | |
| * @author Mark Reinhold | |
| * @since 1.1 | |
| */ | |
| public class BufferedReader extends Reader { | |
| private Reader in; | |
| private char[] cb; | |
| private int nChars, nextChar; | |
| private static final int INVALIDATED = -2; | |
| private static final int UNMARKED = -1; | |
| private int markedChar = UNMARKED; | |
| private int readAheadLimit = 0; /* Valid only when markedChar > 0 */ | |
| /** If the next character is a line feed, skip it */ | |
| private boolean skipLF = false; | |
| /** The skipLF flag when the mark was set */ | |
| private boolean markedSkipLF = false; | |
| private static final int DEFAULT_CHAR_BUFFER_SIZE = 8192; | |
| private static final int DEFAULT_EXPECTED_LINE_LENGTH = 80; | |
| /** | |
| * Creates a buffering character-input stream that uses an input buffer of | |
| * the specified size. | |
| * | |
| * @param in A Reader | |
| * @param sz Input-buffer size | |
| * | |
| * @throws IllegalArgumentException If {@code sz <= 0} | |
| */ | |
| public BufferedReader(Reader in, int sz) { | |
| super(in); | |
| if (sz <= 0) | |
| throw new IllegalArgumentException("Buffer size <= 0"); | |
| this.in = in; | |
| cb = new char[sz]; | |
| nextChar = nChars = 0; | |
| } | |
| /** | |
| * Creates a buffering character-input stream that uses a default-sized | |
| * input buffer. | |
| * | |
| * @param in A Reader | |
| */ | |
| public BufferedReader(Reader in) { | |
| this(in, DEFAULT_CHAR_BUFFER_SIZE); | |
| } | |
| /** Checks to make sure that the stream has not been closed */ | |
| private void ensureOpen() throws IOException { | |
| if (in == null) | |
| throw new IOException("Stream closed"); | |
| } | |
| /** | |
| * Fills the input buffer, taking the mark into account if it is valid. | |
| */ | |
| private void fill() throws IOException { | |
| int dst; | |
| if (markedChar <= UNMARKED) { | |
| /* No mark */ | |
| dst = 0; | |
| } else { | |
| /* Marked */ | |
| int delta = nextChar - markedChar; | |
| if (delta >= readAheadLimit) { | |
| /* Gone past read-ahead limit: Invalidate mark */ | |
| markedChar = INVALIDATED; | |
| readAheadLimit = 0; | |
| dst = 0; | |
| } else { | |
| if (readAheadLimit <= cb.length) { | |
| /* Shuffle in the current buffer */ | |
| System.arraycopy(cb, markedChar, cb, 0, delta); | |
| markedChar = 0; | |
| dst = delta; | |
| } else { | |
| /* Reallocate buffer to accommodate read-ahead limit */ | |
| char[] ncb = new char[readAheadLimit]; | |
| System.arraycopy(cb, markedChar, ncb, 0, delta); | |
| cb = ncb; | |
| markedChar = 0; | |
| dst = delta; | |
| } | |
| nextChar = nChars = delta; | |
| } | |
| } | |
| int n; | |
| do { | |
| n = in.read(cb, dst, cb.length - dst); | |
| } while (n == 0); | |
| if (n > 0) { | |
| nChars = dst + n; | |
| nextChar = dst; | |
| } | |
| } | |
| /** | |
| * Reads a single character. | |
| * | |
| * @return The character read, as an integer in the range | |
| * 0 to 65535 ({@code 0x00-0xffff}), or -1 if the | |
| * end of the stream has been reached | |
| * @throws IOException If an I/O error occurs | |
| */ | |
| public int read() throws IOException { | |
| synchronized (lock) { | |
| ensureOpen(); | |
| for (;;) { | |
| if (nextChar >= nChars) { | |
| fill(); | |
| if (nextChar >= nChars) | |
| return -1; | |
| } | |
| if (skipLF) { | |
| skipLF = false; | |
| if (cb[nextChar] == '\n') { | |
| nextChar++; | |
| continue; | |
| } | |
| } | |
| return cb[nextChar++]; | |
| } | |
| } | |
| } | |
| /** | |
| * Reads characters into a portion of an array, reading from the underlying | |
| * stream if necessary. | |
| */ | |
| private int read1(char[] cbuf, int off, int len) throws IOException { | |
| if (nextChar >= nChars) { | |
| /* If the requested length is at least as large as the buffer, and | |
| if there is no mark/reset activity, and if line feeds are not | |
| being skipped, do not bother to copy the characters into the | |
| local buffer. In this way buffered streams will cascade | |
| harmlessly. */ | |
| if (len >= cb.length && markedChar <= UNMARKED && !skipLF) { | |
| return in.read(cbuf, off, len); | |
| } | |
| fill(); | |
| } | |
| if (nextChar >= nChars) return -1; | |
| if (skipLF) { | |
| skipLF = false; | |
| if (cb[nextChar] == '\n') { | |
| nextChar++; | |
| if (nextChar >= nChars) | |
| fill(); | |
| if (nextChar >= nChars) | |
| return -1; | |
| } | |
| } | |
| int n = Math.min(len, nChars - nextChar); | |
| System.arraycopy(cb, nextChar, cbuf, off, n); | |
| nextChar += n; | |
| return n; | |
| } | |
| /** | |
| * Reads characters into a portion of an array. | |
| * | |
| * <p> This method implements the general contract of the corresponding | |
| * {@link Reader#read(char[], int, int) read} method of the | |
| * {@link Reader} class. As an additional convenience, it | |
| * attempts to read as many characters as possible by repeatedly invoking | |
| * the {@code read} method of the underlying stream. This iterated | |
| * {@code read} continues until one of the following conditions becomes | |
| * true: | |
| * <ul> | |
| * | |
| * <li> The specified number of characters have been read, | |
| * | |
| * <li> The {@code read} method of the underlying stream returns | |
| * {@code -1}, indicating end-of-file, or | |
| * | |
| * <li> The {@code ready} method of the underlying stream | |
| * returns {@code false}, indicating that further input requests | |
| * would block. | |
| * | |
| * </ul> | |
| * If the first {@code read} on the underlying stream returns | |
| * {@code -1} to indicate end-of-file then this method returns | |
| * {@code -1}. Otherwise this method returns the number of characters | |
| * actually read. | |
| * | |
| * <p> Subclasses of this class are encouraged, but not required, to | |
| * attempt to read as many characters as possible in the same fashion. | |
| * | |
| * <p> Ordinarily this method takes characters from this stream's character | |
| * buffer, filling it from the underlying stream as necessary. If, | |
| * however, the buffer is empty, the mark is not valid, and the requested | |
| * length is at least as large as the buffer, then this method will read | |
| * characters directly from the underlying stream into the given array. | |
| * Thus redundant {@code BufferedReader}s will not copy data | |
| * unnecessarily. | |
| * | |
| * @param cbuf {@inheritDoc} | |
| * @param off {@inheritDoc} | |
| * @param len {@inheritDoc} | |
| * | |
| * @return {@inheritDoc} | |
| * | |
| * @throws IndexOutOfBoundsException {@inheritDoc} | |
| * @throws IOException {@inheritDoc} | |
| */ | |
| public int read(char[] cbuf, int off, int len) throws IOException { | |
| synchronized (lock) { | |
| ensureOpen(); | |
| Objects.checkFromIndexSize(off, len, cbuf.length); | |
| if (len == 0) { | |
| return 0; | |
| } | |
| int n = read1(cbuf, off, len); | |
| if (n <= 0) return n; | |
| while ((n < len) && in.ready()) { | |
| int n1 = read1(cbuf, off + n, len - n); | |
| if (n1 <= 0) break; | |
| n += n1; | |
| } | |
| return n; | |
| } | |
| } | |
| /** | |
| * Reads a line of text. A line is considered to be terminated by any one | |
| * of a line feed ('\n'), a carriage return ('\r'), a carriage return | |
| * followed immediately by a line feed, or by reaching the end-of-file | |
| * (EOF). | |
| * | |
| * @param ignoreLF If true, the next '\n' will be skipped | |
| * @param term Output: Whether a line terminator was encountered | |
| * while reading the line; may be {@code null}. | |
| * | |
| * @return A String containing the contents of the line, not including | |
| * any line-termination characters, or null if the end of the | |
| * stream has been reached without reading any characters | |
| * | |
| * @see java.io.LineNumberReader#readLine() | |
| * | |
| * @throws IOException If an I/O error occurs | |
| */ | |
| String readLine(boolean ignoreLF, boolean[] term) throws IOException { | |
| synchronized (lock) { | |
| StringBuilder s = null; | |
| int startChar; | |
| ensureOpen(); | |
| boolean omitLF = ignoreLF || skipLF; | |
| if (term != null) term[0] = false; | |
| bufferLoop: | |
| for (;;) { | |
| if (nextChar >= nChars) | |
| fill(); | |
| if (nextChar >= nChars) { /* EOF */ | |
| if (s != null && s.length() > 0) | |
| return s.toString(); | |
| else | |
| return null; | |
| } | |
| boolean eol = false; | |
| char c = 0; | |
| int i; | |
| /* Skip a leftover '\n', if necessary */ | |
| if (omitLF && (cb[nextChar] == '\n')) | |
| nextChar++; | |
| skipLF = false; | |
| omitLF = false; | |
| charLoop: | |
| for (i = nextChar; i < nChars; i++) { | |
| c = cb[i]; | |
| if ((c == '\n') || (c == '\r')) { | |
| if (term != null) term[0] = true; | |
| eol = true; | |
| break charLoop; | |
| } | |
| } | |
| startChar = nextChar; | |
| nextChar = i; | |
| if (eol) { | |
| String str; | |
| if (s == null) { | |
| str = new String(cb, startChar, i - startChar); | |
| } else { | |
| s.append(cb, startChar, i - startChar); | |
| str = s.toString(); | |
| } | |
| nextChar++; | |
| if (c == '\r') { | |
| skipLF = true; | |
| } | |
| return str; | |
| } | |
| if (s == null) | |
| s = new StringBuilder(DEFAULT_EXPECTED_LINE_LENGTH); | |
| s.append(cb, startChar, i - startChar); | |
| } | |
| } | |
| } | |
| /** | |
| * Reads a line of text. A line is considered to be terminated by any one | |
| * of a line feed ('\n'), a carriage return ('\r'), a carriage return | |
| * followed immediately by a line feed, or by reaching the end-of-file | |
| * (EOF). | |
| * | |
| * @return A String containing the contents of the line, not including | |
| * any line-termination characters, or null if the end of the | |
| * stream has been reached without reading any characters | |
| * | |
| * @throws IOException If an I/O error occurs | |
| * | |
| * @see java.nio.file.Files#readAllLines | |
| */ | |
| public String readLine() throws IOException { | |
| return readLine(false, null); | |
| } | |
| /** | |
| * {@inheritDoc} | |
| */ | |
| public long skip(long n) throws IOException { | |
| if (n < 0L) { | |
| throw new IllegalArgumentException("skip value is negative"); | |
| } | |
| synchronized (lock) { | |
| ensureOpen(); | |
| long r = n; | |
| while (r > 0) { | |
| if (nextChar >= nChars) | |
| fill(); | |
| if (nextChar >= nChars) /* EOF */ | |
| break; | |
| if (skipLF) { | |
| skipLF = false; | |
| if (cb[nextChar] == '\n') { | |
| nextChar++; | |
| } | |
| } | |
| long d = nChars - nextChar; | |
| if (r <= d) { | |
| nextChar += (int)r; | |
| r = 0; | |
| break; | |
| } | |
| else { | |
| r -= d; | |
| nextChar = nChars; | |
| } | |
| } | |
| return n - r; | |
| } | |
| } | |
| /** | |
| * Tells whether this stream is ready to be read. A buffered character | |
| * stream is ready if the buffer is not empty, or if the underlying | |
| * character stream is ready. | |
| * | |
| * @throws IOException If an I/O error occurs | |
| */ | |
| public boolean ready() throws IOException { | |
| synchronized (lock) { | |
| ensureOpen(); | |
| /* | |
| * If newline needs to be skipped and the next char to be read | |
| * is a newline character, then just skip it right away. | |
| */ | |
| if (skipLF) { | |
| /* Note that in.ready() will return true if and only if the next | |
| * read on the stream will not block. | |
| */ | |
| if (nextChar >= nChars && in.ready()) { | |
| fill(); | |
| } | |
| if (nextChar < nChars) { | |
| if (cb[nextChar] == '\n') | |
| nextChar++; | |
| skipLF = false; | |
| } | |
| } | |
| return (nextChar < nChars) || in.ready(); | |
| } | |
| } | |
| /** | |
| * Tells whether this stream supports the mark() operation, which it does. | |
| */ | |
| public boolean markSupported() { | |
| return true; | |
| } | |
| /** | |
| * Marks the present position in the stream. Subsequent calls to reset() | |
| * will attempt to reposition the stream to this point. | |
| * | |
| * @param readAheadLimit Limit on the number of characters that may be | |
| * read while still preserving the mark. An attempt | |
| * to reset the stream after reading characters | |
| * up to this limit or beyond may fail. | |
| * A limit value larger than the size of the input | |
| * buffer will cause a new buffer to be allocated | |
| * whose size is no smaller than limit. | |
| * Therefore large values should be used with care. | |
| * | |
| * @throws IllegalArgumentException If {@code readAheadLimit < 0} | |
| * @throws IOException If an I/O error occurs | |
| */ | |
| public void mark(int readAheadLimit) throws IOException { | |
| if (readAheadLimit < 0) { | |
| throw new IllegalArgumentException("Read-ahead limit < 0"); | |
| } | |
| synchronized (lock) { | |
| ensureOpen(); | |
| this.readAheadLimit = readAheadLimit; | |
| markedChar = nextChar; | |
| markedSkipLF = skipLF; | |
| } | |
| } | |
| /** | |
| * Resets the stream to the most recent mark. | |
| * | |
| * @throws IOException If the stream has never been marked, | |
| * or if the mark has been invalidated | |
| */ | |
| public void reset() throws IOException { | |
| synchronized (lock) { | |
| ensureOpen(); | |
| if (markedChar < 0) | |
| throw new IOException((markedChar == INVALIDATED) | |
| ? "Mark invalid" | |
| : "Stream not marked"); | |
| nextChar = markedChar; | |
| skipLF = markedSkipLF; | |
| } | |
| } | |
| public void close() throws IOException { | |
| synchronized (lock) { | |
| if (in == null) | |
| return; | |
| try { | |
| in.close(); | |
| } finally { | |
| in = null; | |
| cb = null; | |
| } | |
| } | |
| } | |
| /** | |
| * Returns a {@code Stream}, the elements of which are lines read from | |
| * this {@code BufferedReader}. The {@link Stream} is lazily populated, | |
| * i.e., read only occurs during the | |
| * <a href="../util/stream/package-summary.html#StreamOps">terminal | |
| * stream operation</a>. | |
| * | |
| * <p> The reader must not be operated on during the execution of the | |
| * terminal stream operation. Otherwise, the result of the terminal stream | |
| * operation is undefined. | |
| * | |
| * <p> After execution of the terminal stream operation there are no | |
| * guarantees that the reader will be at a specific position from which to | |
| * read the next character or line. | |
| * | |
| * <p> If an {@link IOException} is thrown when accessing the underlying | |
| * {@code BufferedReader}, it is wrapped in an {@link | |
| * UncheckedIOException} which will be thrown from the {@code Stream} | |
| * method that caused the read to take place. This method will return a | |
| * Stream if invoked on a BufferedReader that is closed. Any operation on | |
| * that stream that requires reading from the BufferedReader after it is | |
| * closed, will cause an UncheckedIOException to be thrown. | |
| * | |
| * @return a {@code Stream<String>} providing the lines of text | |
| * described by this {@code BufferedReader} | |
| * | |
| * @since 1.8 | |
| */ | |
| public Stream<String> lines() { | |
| Iterator<String> iter = new Iterator<>() { | |
| String nextLine = null; | |
| @Override | |
| public boolean hasNext() { | |
| if (nextLine != null) { | |
| return true; | |
| } else { | |
| try { | |
| nextLine = readLine(); | |
| return (nextLine != null); | |
| } catch (IOException e) { | |
| throw new UncheckedIOException(e); | |
| } | |
| } | |
| } | |
| @Override | |
| public String next() { | |
| if (nextLine != null || hasNext()) { | |
| String line = nextLine; | |
| nextLine = null; | |
| return line; | |
| } else { | |
| throw new NoSuchElementException(); | |
| } | |
| } | |
| }; | |
| return StreamSupport.stream(Spliterators.spliteratorUnknownSize( | |
| iter, Spliterator.ORDERED | Spliterator.NONNULL), false); | |
| } | |
| } |