• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package java.io;
27 
28 
29 /**
30  * A buffered character-input stream that keeps track of line numbers.  This
31  * class defines methods {@link #setLineNumber(int)} and {@link
32  * #getLineNumber()} for setting and getting the current line number
33  * respectively.
34  *
35  * <p> By default, line numbering begins at 0. This number increments at every
36  * <a href="#lt">line terminator</a> as the data is read, and can be changed
37  * with a call to {@code setLineNumber(int)}.  Note however, that
38  * {@code setLineNumber(int)} does not actually change the current position in
39  * the stream; it only changes the value that will be returned by
40  * {@code getLineNumber()}.
41  *
42  * <p> A line is considered to be <a id="lt">terminated</a> by any one of a
43  * line feed ('\n'), a carriage return ('\r'), or a carriage return followed
44  * immediately by a linefeed.
45  *
46  * @author      Mark Reinhold
47  * @since       1.1
48  */
49 
50 public class LineNumberReader extends BufferedReader {
51 
52     /** The current line number */
53     private int lineNumber = 0;
54 
55     /** The line number of the mark, if any */
56     private int markedLineNumber; // Defaults to 0
57 
58     /** If the next character is a line feed, skip it */
59     private boolean skipLF;
60 
61     /** The skipLF flag when the mark was set */
62     private boolean markedSkipLF;
63 
64     /**
65      * Create a new line-numbering reader, using the default input-buffer
66      * size.
67      *
68      * @param  in
69      *         A Reader object to provide the underlying stream
70      */
LineNumberReader(Reader in)71     public LineNumberReader(Reader in) {
72         super(in);
73     }
74 
75     /**
76      * Create a new line-numbering reader, reading characters into a buffer of
77      * the given size.
78      *
79      * @param  in
80      *         A Reader object to provide the underlying stream
81      *
82      * @param  sz
83      *         An int specifying the size of the buffer
84      */
LineNumberReader(Reader in, int sz)85     public LineNumberReader(Reader in, int sz) {
86         super(in, sz);
87     }
88 
89     /**
90      * Set the current line number.
91      *
92      * @param  lineNumber
93      *         An int specifying the line number
94      *
95      * @see #getLineNumber
96      */
setLineNumber(int lineNumber)97     public void setLineNumber(int lineNumber) {
98         this.lineNumber = lineNumber;
99     }
100 
101     /**
102      * Get the current line number.
103      *
104      * @return  The current line number
105      *
106      * @see #setLineNumber
107      */
getLineNumber()108     public int getLineNumber() {
109         return lineNumber;
110     }
111 
112     /**
113      * Read a single character.  <a href="#lt">Line terminators</a> are
114      * compressed into single newline ('\n') characters.  Whenever a line
115      * terminator is read the current line number is incremented.
116      *
117      * @return  The character read, or -1 if the end of the stream has been
118      *          reached
119      *
120      * @throws  IOException
121      *          If an I/O error occurs
122      */
123     @SuppressWarnings("fallthrough")
read()124     public int read() throws IOException {
125         synchronized (lock) {
126             int c = super.read();
127             if (skipLF) {
128                 if (c == '\n')
129                     c = super.read();
130                 skipLF = false;
131             }
132             switch (c) {
133             case '\r':
134                 skipLF = true;
135             case '\n':          /* Fall through */
136                 lineNumber++;
137                 return '\n';
138             }
139             return c;
140         }
141     }
142 
143     /**
144      * Read characters into a portion of an array.  Whenever a <a
145      * href="#lt">line terminator</a> is read the current line number is
146      * incremented.
147      *
148      * @param  cbuf
149      *         Destination buffer
150      *
151      * @param  off
152      *         Offset at which to start storing characters
153      *
154      * @param  len
155      *         Maximum number of characters to read
156      *
157      * @return  The number of bytes read, or -1 if the end of the stream has
158      *          already been reached
159      *
160      * @throws  IOException
161      *          If an I/O error occurs
162      *
163      * @throws  IndexOutOfBoundsException {@inheritDoc}
164      */
165     @SuppressWarnings("fallthrough")
read(char cbuf[], int off, int len)166     public int read(char cbuf[], int off, int len) throws IOException {
167         synchronized (lock) {
168             int n = super.read(cbuf, off, len);
169 
170             for (int i = off; i < off + n; i++) {
171                 int c = cbuf[i];
172                 if (skipLF) {
173                     skipLF = false;
174                     if (c == '\n')
175                         continue;
176                 }
177                 switch (c) {
178                 case '\r':
179                     skipLF = true;
180                 case '\n':      /* Fall through */
181                     lineNumber++;
182                     break;
183                 }
184             }
185 
186             return n;
187         }
188     }
189 
190     /**
191      * Read a line of text.  Whenever a <a href="#lt">line terminator</a> is
192      * read the current line number is incremented.
193      *
194      * @return  A String containing the contents of the line, not including
195      *          any <a href="#lt">line termination characters</a>, or
196      *          {@code null} if the end of the stream has been reached
197      *
198      * @throws  IOException
199      *          If an I/O error occurs
200      */
readLine()201     public String readLine() throws IOException {
202         synchronized (lock) {
203             String l = super.readLine(skipLF);
204             skipLF = false;
205             if (l != null)
206                 lineNumber++;
207             return l;
208         }
209     }
210 
211     /** Maximum skip-buffer size */
212     private static final int maxSkipBufferSize = 8192;
213 
214     /** Skip buffer, null until allocated */
215     private char skipBuffer[] = null;
216 
217     /**
218      * Skip characters.
219      *
220      * @param  n
221      *         The number of characters to skip
222      *
223      * @return  The number of characters actually skipped
224      *
225      * @throws  IOException
226      *          If an I/O error occurs
227      *
228      * @throws  IllegalArgumentException
229      *          If {@code n} is negative
230      */
skip(long n)231     public long skip(long n) throws IOException {
232         if (n < 0)
233             throw new IllegalArgumentException("skip() value is negative");
234         int nn = (int) Math.min(n, maxSkipBufferSize);
235         synchronized (lock) {
236             if ((skipBuffer == null) || (skipBuffer.length < nn))
237                 skipBuffer = new char[nn];
238             long r = n;
239             while (r > 0) {
240                 int nc = read(skipBuffer, 0, (int) Math.min(r, nn));
241                 if (nc == -1)
242                     break;
243                 r -= nc;
244             }
245             return n - r;
246         }
247     }
248 
249     /**
250      * Mark the present position in the stream.  Subsequent calls to reset()
251      * will attempt to reposition the stream to this point, and will also reset
252      * the line number appropriately.
253      *
254      * @param  readAheadLimit
255      *         Limit on the number of characters that may be read while still
256      *         preserving the mark.  After reading this many characters,
257      *         attempting to reset the stream may fail.
258      *
259      * @throws  IOException
260      *          If an I/O error occurs
261      */
mark(int readAheadLimit)262     public void mark(int readAheadLimit) throws IOException {
263         synchronized (lock) {
264             // If the most recently read character is '\r', then increment the
265             // read ahead limit as in this case if the next character is '\n',
266             // two characters would actually be read by the next read().
267             if (skipLF)
268                 readAheadLimit++;
269             super.mark(readAheadLimit);
270             markedLineNumber = lineNumber;
271             markedSkipLF     = skipLF;
272         }
273     }
274 
275     /**
276      * Reset the stream to the most recent mark.
277      *
278      * @throws  IOException
279      *          If the stream has not been marked, or if the mark has been
280      *          invalidated
281      */
reset()282     public void reset() throws IOException {
283         synchronized (lock) {
284             super.reset();
285             lineNumber = markedLineNumber;
286             skipLF     = markedSkipLF;
287         }
288     }
289 
290 }
291