1 /*
2  * Copyright (c) 1996, 2011, 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 <tt>setLineNumber(int)</tt>.  Note however, that
38  * <tt>setLineNumber(int)</tt> does not actually change the current position in
39  * the stream; it only changes the value that will be returned by
40  * <tt>getLineNumber()</tt>.
41  *
42  * <p> A line is considered to be <a name="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       JDK1.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     @SuppressWarnings("fallthrough")
read(char cbuf[], int off, int len)164     public int read(char cbuf[], int off, int len) throws IOException {
165         synchronized (lock) {
166             int n = super.read(cbuf, off, len);
167 
168             for (int i = off; i < off + n; i++) {
169                 int c = cbuf[i];
170                 if (skipLF) {
171                     skipLF = false;
172                     if (c == '\n')
173                         continue;
174                 }
175                 switch (c) {
176                 case '\r':
177                     skipLF = true;
178                 case '\n':      /* Fall through */
179                     lineNumber++;
180                     break;
181                 }
182             }
183 
184             return n;
185         }
186     }
187 
188     /**
189      * Read a line of text.  Whenever a <a href="#lt">line terminator</a> is
190      * read the current line number is incremented.
191      *
192      * @return  A String containing the contents of the line, not including
193      *          any <a href="#lt">line termination characters</a>, or
194      *          <tt>null</tt> if the end of the stream has been reached
195      *
196      * @throws  IOException
197      *          If an I/O error occurs
198      */
readLine()199     public String readLine() throws IOException {
200         synchronized (lock) {
201             String l = super.readLine(skipLF);
202             skipLF = false;
203             if (l != null)
204                 lineNumber++;
205             return l;
206         }
207     }
208 
209     /** Maximum skip-buffer size */
210     private static final int maxSkipBufferSize = 8192;
211 
212     /** Skip buffer, null until allocated */
213     private char skipBuffer[] = null;
214 
215     /**
216      * Skip characters.
217      *
218      * @param  n
219      *         The number of characters to skip
220      *
221      * @return  The number of characters actually skipped
222      *
223      * @throws  IOException
224      *          If an I/O error occurs
225      *
226      * @throws  IllegalArgumentException
227      *          If <tt>n</tt> is negative
228      */
skip(long n)229     public long skip(long n) throws IOException {
230         if (n < 0)
231             throw new IllegalArgumentException("skip() value is negative");
232         int nn = (int) Math.min(n, maxSkipBufferSize);
233         synchronized (lock) {
234             if ((skipBuffer == null) || (skipBuffer.length < nn))
235                 skipBuffer = new char[nn];
236             long r = n;
237             while (r > 0) {
238                 int nc = read(skipBuffer, 0, (int) Math.min(r, nn));
239                 if (nc == -1)
240                     break;
241                 r -= nc;
242             }
243             return n - r;
244         }
245     }
246 
247     /**
248      * Mark the present position in the stream.  Subsequent calls to reset()
249      * will attempt to reposition the stream to this point, and will also reset
250      * the line number appropriately.
251      *
252      * @param  readAheadLimit
253      *         Limit on the number of characters that may be read while still
254      *         preserving the mark.  After reading this many characters,
255      *         attempting to reset the stream may fail.
256      *
257      * @throws  IOException
258      *          If an I/O error occurs
259      */
mark(int readAheadLimit)260     public void mark(int readAheadLimit) throws IOException {
261         synchronized (lock) {
262             super.mark(readAheadLimit);
263             markedLineNumber = lineNumber;
264             markedSkipLF     = skipLF;
265         }
266     }
267 
268     /**
269      * Reset the stream to the most recent mark.
270      *
271      * @throws  IOException
272      *          If the stream has not been marked, or if the mark has been
273      *          invalidated
274      */
reset()275     public void reset() throws IOException {
276         synchronized (lock) {
277             super.reset();
278             lineNumber = markedLineNumber;
279             skipLF     = markedSkipLF;
280         }
281     }
282 
283 }
284