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
26package 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
50public 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     */
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     */
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     */
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     */
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")
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")
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     */
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     */
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     */
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     */
275    public void reset() throws IOException {
276        synchronized (lock) {
277            super.reset();
278            lineNumber = markedLineNumber;
279            skipLF     = markedSkipLF;
280        }
281    }
282
283}
284