| /* | |
| * Copyright (c) 1996, 2021, 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; | |
| /** | |
| * A buffered character-input stream that keeps track of line numbers. This | |
| * class defines methods {@link #setLineNumber(int)} and {@link | |
| * #getLineNumber()} for setting and getting the current line number | |
| * respectively. | |
| * | |
| * <p> By default, line numbering begins at 0. This number increments at every | |
| * <a href="#lt">line terminator</a> as the data is read, and at the end of the | |
| * stream if the last character in the stream is not a line terminator. This | |
| * number can be changed with a call to {@code setLineNumber(int)}. Note | |
| * however, that {@code setLineNumber(int)} does not actually change the current | |
| * position in the stream; it only changes the value that will be returned by | |
| * {@code getLineNumber()}. | |
| * | |
| * <p> A line is considered to be <a id="lt">terminated</a> by any one of a | |
| * line feed ('\n'), a carriage return ('\r'), or a carriage return followed | |
| * immediately by a linefeed, or any of the previous terminators followed by | |
| * end of stream, or end of stream not preceded by another terminator. | |
| * | |
| * @author Mark Reinhold | |
| * @since 1.1 | |
| */ | |
| public class LineNumberReader extends BufferedReader { | |
| /** Previous character types */ | |
| private static final int NONE = 0; // no previous character | |
| private static final int CHAR = 1; // non-line terminator | |
| private static final int EOL = 2; // line terminator | |
| private static final int EOF = 3; // end-of-file | |
| /** The previous character type */ | |
| private int prevChar = NONE; | |
| /** The current line number */ | |
| private int lineNumber = 0; | |
| /** The line number of the mark, if any */ | |
| private int markedLineNumber; // Defaults to 0 | |
| /** If the next character is a line feed, skip it */ | |
| private boolean skipLF; | |
| /** The skipLF flag when the mark was set */ | |
| private boolean markedSkipLF; | |
| /** | |
| * Create a new line-numbering reader, using the default input-buffer | |
| * size. | |
| * | |
| * @param in | |
| * A Reader object to provide the underlying stream | |
| */ | |
| public LineNumberReader(Reader in) { | |
| super(in); | |
| } | |
| /** | |
| * Create a new line-numbering reader, reading characters into a buffer of | |
| * the given size. | |
| * | |
| * @param in | |
| * A Reader object to provide the underlying stream | |
| * | |
| * @param sz | |
| * An int specifying the size of the buffer | |
| */ | |
| public LineNumberReader(Reader in, int sz) { | |
| super(in, sz); | |
| } | |
| /** | |
| * Set the current line number. | |
| * | |
| * @param lineNumber | |
| * An int specifying the line number | |
| * | |
| * @see #getLineNumber | |
| */ | |
| public void setLineNumber(int lineNumber) { | |
| this.lineNumber = lineNumber; | |
| } | |
| /** | |
| * Get the current line number. | |
| * | |
| * @return The current line number | |
| * | |
| * @see #setLineNumber | |
| */ | |
| public int getLineNumber() { | |
| return lineNumber; | |
| } | |
| /** | |
| * Read a single character. <a href="#lt">Line terminators</a> are | |
| * compressed into single newline ('\n') characters. The current line | |
| * number is incremented whenever a line terminator is read, or when the | |
| * end of the stream is reached and the last character in the stream is | |
| * not a line terminator. | |
| * | |
| * @return The character read, or -1 if the end of the stream has been | |
| * reached | |
| * | |
| * @throws IOException | |
| * If an I/O error occurs | |
| */ | |
| @SuppressWarnings("fallthrough") | |
| public int read() throws IOException { | |
| synchronized (lock) { | |
| int c = super.read(); | |
| if (skipLF) { | |
| if (c == '\n') | |
| c = super.read(); | |
| skipLF = false; | |
| } | |
| switch (c) { | |
| case '\r': | |
| skipLF = true; | |
| case '\n': /* Fall through */ | |
| lineNumber++; | |
| prevChar = EOL; | |
| return '\n'; | |
| case -1: | |
| if (prevChar == CHAR) | |
| lineNumber++; | |
| prevChar = EOF; | |
| break; | |
| default: | |
| prevChar = CHAR; | |
| break; | |
| } | |
| return c; | |
| } | |
| } | |
| /** | |
| * Reads characters into a portion of an array. This method will block | |
| * until some input is available, an I/O error occurs, or the end of the | |
| * stream is reached. | |
| * | |
| * <p> If {@code len} is zero, then no characters are read and {@code 0} is | |
| * returned; otherwise, there is an attempt to read at least one character. | |
| * If no character is available because the stream is at its end, the value | |
| * {@code -1} is returned; otherwise, at least one character is read and | |
| * stored into {@code cbuf}. | |
| * | |
| * <p><a href="#lt">Line terminators</a> are compressed into single newline | |
| * ('\n') characters. The current line number is incremented whenever a | |
| * line terminator is read, or when the end of the stream is reached and | |
| * the last character in the stream is not a line terminator. | |
| * | |
| * @param cbuf {@inheritDoc} | |
| * @param off {@inheritDoc} | |
| * @param len {@inheritDoc} | |
| * | |
| * @return {@inheritDoc} | |
| * | |
| * @throws IndexOutOfBoundsException {@inheritDoc} | |
| * @throws IOException {@inheritDoc} | |
| */ | |
| @SuppressWarnings("fallthrough") | |
| public int read(char[] cbuf, int off, int len) throws IOException { | |
| synchronized (lock) { | |
| int n = super.read(cbuf, off, len); | |
| if (n == -1) { | |
| if (prevChar == CHAR) | |
| lineNumber++; | |
| prevChar = EOF; | |
| return -1; | |
| } | |
| for (int i = off; i < off + n; i++) { | |
| int c = cbuf[i]; | |
| if (skipLF) { | |
| skipLF = false; | |
| if (c == '\n') | |
| continue; | |
| } | |
| switch (c) { | |
| case '\r': | |
| skipLF = true; | |
| case '\n': /* Fall through */ | |
| lineNumber++; | |
| break; | |
| } | |
| } | |
| if (n > 0) { | |
| switch ((int)cbuf[off + n - 1]) { | |
| case '\r': | |
| case '\n': /* Fall through */ | |
| prevChar = EOL; | |
| break; | |
| default: | |
| prevChar = CHAR; | |
| break; | |
| } | |
| } | |
| return n; | |
| } | |
| } | |
| /** | |
| * Read a line of text. <a href="#lt">Line terminators</a> are compressed | |
| * into single newline ('\n') characters. The current line number is | |
| * incremented whenever a line terminator is read, or when the end of the | |
| * stream is reached and the last character in the stream is not a line | |
| * terminator. | |
| * | |
| * @return A String containing the contents of the line, not including | |
| * any <a href="#lt">line termination characters</a>, or | |
| * {@code null} if the end of the stream has been reached | |
| * | |
| * @throws IOException | |
| * If an I/O error occurs | |
| */ | |
| public String readLine() throws IOException { | |
| synchronized (lock) { | |
| boolean[] term = new boolean[1]; | |
| String l = super.readLine(skipLF, term); | |
| skipLF = false; | |
| if (l != null) { | |
| lineNumber++; | |
| prevChar = term[0] ? EOL : EOF; | |
| } else { // l == null | |
| if (prevChar == CHAR) | |
| lineNumber++; | |
| prevChar = EOF; | |
| } | |
| return l; | |
| } | |
| } | |
| /** Maximum skip-buffer size */ | |
| private static final int maxSkipBufferSize = 8192; | |
| /** Skip buffer, null until allocated */ | |
| private char skipBuffer[] = null; | |
| /** | |
| * {@inheritDoc} | |
| */ | |
| public long skip(long n) throws IOException { | |
| if (n < 0) | |
| throw new IllegalArgumentException("skip() value is negative"); | |
| int nn = (int) Math.min(n, maxSkipBufferSize); | |
| synchronized (lock) { | |
| if ((skipBuffer == null) || (skipBuffer.length < nn)) | |
| skipBuffer = new char[nn]; | |
| long r = n; | |
| while (r > 0) { | |
| int nc = read(skipBuffer, 0, (int) Math.min(r, nn)); | |
| if (nc == -1) | |
| break; | |
| r -= nc; | |
| } | |
| if (n - r > 0) { | |
| prevChar = NONE; | |
| } | |
| return n - r; | |
| } | |
| } | |
| /** | |
| * Mark the present position in the stream. Subsequent calls to reset() | |
| * will attempt to reposition the stream to this point, and will also reset | |
| * the line number appropriately. | |
| * | |
| * @param readAheadLimit | |
| * Limit on the number of characters that may be read while still | |
| * preserving the mark. After reading this many characters, | |
| * attempting to reset the stream may fail. | |
| * | |
| * @throws IOException | |
| * If an I/O error occurs | |
| */ | |
| public void mark(int readAheadLimit) throws IOException { | |
| synchronized (lock) { | |
| // If the most recently read character is '\r', then increment the | |
| // read ahead limit as in this case if the next character is '\n', | |
| // two characters would actually be read by the next read(). | |
| if (skipLF) | |
| readAheadLimit++; | |
| super.mark(readAheadLimit); | |
| markedLineNumber = lineNumber; | |
| markedSkipLF = skipLF; | |
| } | |
| } | |
| /** | |
| * Reset the stream to the most recent mark. | |
| * | |
| * @throws IOException | |
| * If the stream has not been marked, or if the mark has been | |
| * invalidated | |
| */ | |
| public void reset() throws IOException { | |
| synchronized (lock) { | |
| super.reset(); | |
| lineNumber = markedLineNumber; | |
| skipLF = markedSkipLF; | |
| } | |
| } | |
| } |