001: /*
002: * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package java.io;
027:
028: /**
029: * A buffered character-input stream that keeps track of line numbers. This
030: * class defines methods {@link #setLineNumber(int)} and {@link
031: * #getLineNumber()} for setting and getting the current line number
032: * respectively.
033: *
034: * <p> By default, line numbering begins at 0. This number increments at every
035: * <a href="#lt">line terminator</a> as the data is read, and can be changed
036: * with a call to <tt>setLineNumber(int)</tt>. Note however, that
037: * <tt>setLineNumber(int)</tt> does not actually change the current position in
038: * the stream; it only changes the value that will be returned by
039: * <tt>getLineNumber()</tt>.
040: *
041: * <p> A line is considered to be <a name="lt">terminated</a> by any one of a
042: * line feed ('\n'), a carriage return ('\r'), or a carriage return followed
043: * immediately by a linefeed.
044: *
045: * @version 1.29, 07/05/05
046: * @author Mark Reinhold
047: * @since JDK1.1
048: */
049:
050: public class LineNumberReader extends BufferedReader {
051:
052: /** The current line number */
053: private int lineNumber = 0;
054:
055: /** The line number of the mark, if any */
056: private int markedLineNumber; // Defaults to 0
057:
058: /** If the next character is a line feed, skip it */
059: private boolean skipLF;
060:
061: /** The skipLF flag when the mark was set */
062: private boolean markedSkipLF;
063:
064: /**
065: * Create a new line-numbering reader, using the default input-buffer
066: * size.
067: *
068: * @param in
069: * A Reader object to provide the underlying stream
070: */
071: public LineNumberReader(Reader in) {
072: super (in);
073: }
074:
075: /**
076: * Create a new line-numbering reader, reading characters into a buffer of
077: * the given size.
078: *
079: * @param in
080: * A Reader object to provide the underlying stream
081: *
082: * @param sz
083: * An int specifying the size of the buffer
084: */
085: public LineNumberReader(Reader in, int sz) {
086: super (in, sz);
087: }
088:
089: /**
090: * Set the current line number.
091: *
092: * @param lineNumber
093: * An int specifying the line number
094: *
095: * @see #getLineNumber
096: */
097: public void setLineNumber(int lineNumber) {
098: this .lineNumber = lineNumber;
099: }
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: public int read() throws IOException {
124: synchronized (lock) {
125: int c = super .read();
126: if (skipLF) {
127: if (c == '\n')
128: c = super .read();
129: skipLF = false;
130: }
131: switch (c) {
132: case '\r':
133: skipLF = true;
134: case '\n': /* Fall through */
135: lineNumber++;
136: return '\n';
137: }
138: return c;
139: }
140: }
141:
142: /**
143: * Read characters into a portion of an array. Whenever a <a
144: * href="#lt">line terminator</a> is read the current line number is
145: * incremented.
146: *
147: * @param cbuf
148: * Destination buffer
149: *
150: * @param off
151: * Offset at which to start storing characters
152: *
153: * @param len
154: * Maximum number of characters to read
155: *
156: * @return The number of bytes read, or -1 if the end of the stream has
157: * already been reached
158: *
159: * @throws IOException
160: * If an I/O error occurs
161: */
162: public int read(char cbuf[], int off, int len) throws IOException {
163: synchronized (lock) {
164: int n = super .read(cbuf, off, len);
165:
166: for (int i = off; i < off + n; i++) {
167: int c = cbuf[i];
168: if (skipLF) {
169: skipLF = false;
170: if (c == '\n')
171: continue;
172: }
173: switch (c) {
174: case '\r':
175: skipLF = true;
176: case '\n': /* Fall through */
177: lineNumber++;
178: break;
179: }
180: }
181:
182: return n;
183: }
184: }
185:
186: /**
187: * Read a line of text. Whenever a <a href="#lt">line terminator</a> is
188: * read the current line number is incremented.
189: *
190: * @return A String containing the contents of the line, not including
191: * any <a href="#lt">line termination characters</a>, or
192: * <tt>null</tt> if the end of the stream has been reached
193: *
194: * @throws IOException
195: * If an I/O error occurs
196: */
197: public String readLine() throws IOException {
198: synchronized (lock) {
199: String l = super .readLine(skipLF);
200: skipLF = false;
201: if (l != null)
202: lineNumber++;
203: return l;
204: }
205: }
206:
207: /** Maximum skip-buffer size */
208: private static final int maxSkipBufferSize = 8192;
209:
210: /** Skip buffer, null until allocated */
211: private char skipBuffer[] = null;
212:
213: /**
214: * Skip characters.
215: *
216: * @param n
217: * The number of characters to skip
218: *
219: * @return The number of characters actually skipped
220: *
221: * @throws IOException
222: * If an I/O error occurs
223: *
224: * @throws IllegalArgumentException
225: * If <tt>n</tt> is negative
226: */
227: public long skip(long n) throws IOException {
228: if (n < 0)
229: throw new IllegalArgumentException(
230: "skip() value is negative");
231: int nn = (int) Math.min(n, maxSkipBufferSize);
232: synchronized (lock) {
233: if ((skipBuffer == null) || (skipBuffer.length < nn))
234: skipBuffer = new char[nn];
235: long r = n;
236: while (r > 0) {
237: int nc = read(skipBuffer, 0, (int) Math.min(r, nn));
238: if (nc == -1)
239: break;
240: r -= nc;
241: }
242: return n - r;
243: }
244: }
245:
246: /**
247: * Mark the present position in the stream. Subsequent calls to reset()
248: * will attempt to reposition the stream to this point, and will also reset
249: * the line number appropriately.
250: *
251: * @param readAheadLimit
252: * Limit on the number of characters that may be read while still
253: * preserving the mark. After reading this many characters,
254: * attempting to reset the stream may fail.
255: *
256: * @throws IOException
257: * If an I/O error occurs
258: */
259: public void mark(int readAheadLimit) throws IOException {
260: synchronized (lock) {
261: super .mark(readAheadLimit);
262: markedLineNumber = lineNumber;
263: markedSkipLF = skipLF;
264: }
265: }
266:
267: /**
268: * Reset the stream to the most recent mark.
269: *
270: * @throws IOException
271: * If the stream has not been marked, or if the mark has been
272: * invalidated
273: */
274: public void reset() throws IOException {
275: synchronized (lock) {
276: super.reset();
277: lineNumber = markedLineNumber;
278: skipLF = markedSkipLF;
279: }
280: }
281:
282: }
|