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 {@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
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     * @throws  IndexOutOfBoundsException {@inheritDoc}
164     */
165    @SuppressWarnings("fallthrough")
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     */
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     */
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     */
262    public void mark(int readAheadLimit) throws IOException {
263        synchronized (lock) {
264            super.mark(readAheadLimit);
265            markedLineNumber = lineNumber;
266            markedSkipLF     = skipLF;
267        }
268    }
269
270    /**
271     * Reset the stream to the most recent mark.
272     *
273     * @throws  IOException
274     *          If the stream has not been marked, or if the mark has been
275     *          invalidated
276     */
277    public void reset() throws IOException {
278        synchronized (lock) {
279            super.reset();
280            lineNumber = markedLineNumber;
281            skipLF     = markedSkipLF;
282        }
283    }
284
285}
286