1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package java.io; 19 20/** 21 * Wraps an existing {@link Reader} and counts the line terminators encountered 22 * while reading the data. The line number starts at 0 and is incremented any 23 * time {@code '\r'}, {@code '\n'} or {@code "\r\n"} is read. The class has an 24 * internal buffer for its data. The size of the buffer defaults to 8 KB. 25 */ 26public class LineNumberReader extends BufferedReader { 27 28 private int lineNumber; 29 30 private int markedLineNumber = -1; 31 32 private boolean lastWasCR; 33 34 private boolean markedLastWasCR; 35 36 /** 37 * Constructs a new LineNumberReader on the Reader {@code in}. The internal 38 * buffer gets the default size (8 KB). 39 * 40 * @param in 41 * the Reader that is buffered. 42 */ 43 public LineNumberReader(Reader in) { 44 super(in); 45 } 46 47 /** 48 * Constructs a new LineNumberReader on the Reader {@code in}. The size of 49 * the internal buffer is specified by the parameter {@code size}. 50 * 51 * @param in 52 * the Reader that is buffered. 53 * @param size 54 * the size of the buffer to allocate. 55 * @throws IllegalArgumentException 56 * if {@code size <= 0}. 57 */ 58 public LineNumberReader(Reader in, int size) { 59 super(in, size); 60 } 61 62 /** 63 * Returns the current line number for this reader. Numbering starts at 0. 64 * 65 * @return the current line number. 66 */ 67 public int getLineNumber() { 68 synchronized (lock) { 69 return lineNumber; 70 } 71 } 72 73 /** 74 * Sets a mark position in this reader. The parameter {@code readlimit} 75 * indicates how many characters can be read before the mark is invalidated. 76 * Sending {@code reset()} will reposition this reader back to the marked 77 * position, provided that {@code readlimit} has not been surpassed. The 78 * line number associated with this marked position is also stored so that 79 * it can be restored when {@code reset()} is called. 80 * 81 * @param readlimit 82 * the number of characters that can be read from this stream 83 * before the mark is invalidated. 84 * @throws IOException 85 * if an error occurs while setting the mark in this reader. 86 * @see #markSupported() 87 * @see #reset() 88 */ 89 @Override 90 public void mark(int readlimit) throws IOException { 91 synchronized (lock) { 92 super.mark(readlimit); 93 markedLineNumber = lineNumber; 94 markedLastWasCR = lastWasCR; 95 } 96 } 97 98 /** 99 * Reads a single character from the source reader and returns it as an 100 * integer with the two higher-order bytes set to 0. Returns -1 if the end 101 * of the source reader has been reached. 102 * <p> 103 * The line number count is incremented if a line terminator is encountered. 104 * Recognized line terminator sequences are {@code '\r'}, {@code '\n'} and 105 * {@code "\r\n"}. Line terminator sequences are always translated into 106 * {@code '\n'}. 107 * 108 * @return the character read or -1 if the end of the source reader has been 109 * reached. 110 * @throws IOException 111 * if the reader is closed or another IOException occurs. 112 */ 113 @SuppressWarnings("fallthrough") 114 @Override 115 public int read() throws IOException { 116 synchronized (lock) { 117 int ch = super.read(); 118 if (ch == '\n' && lastWasCR) { 119 ch = super.read(); 120 } 121 lastWasCR = false; 122 switch (ch) { 123 case '\r': 124 ch = '\n'; 125 lastWasCR = true; 126 // fall through 127 case '\n': 128 lineNumber++; 129 } 130 return ch; 131 } 132 } 133 134 /** 135 * Reads up to {@code count} characters from the source reader and stores 136 * them in the character array {@code buffer} starting at {@code offset}. 137 * Returns the number of characters actually read or -1 if no characters 138 * have been read and the end of this reader has been reached. 139 * 140 * <p>The line number count is incremented if a line terminator is encountered. 141 * Recognized line terminator sequences are {@code '\r'}, {@code '\n'} and 142 * {@code "\r\n"}. 143 * 144 * @throws IOException 145 * if this reader is closed or another IOException occurs. 146 */ 147 @Override 148 public int read(char[] buffer, int offset, int count) throws IOException { 149 synchronized (lock) { 150 int read = super.read(buffer, offset, count); 151 if (read == -1) { 152 return -1; 153 } 154 for (int i = 0; i < read; i++) { 155 char ch = buffer[offset + i]; 156 if (ch == '\r') { 157 lineNumber++; 158 lastWasCR = true; 159 } else if (ch == '\n') { 160 if (!lastWasCR) { 161 lineNumber++; 162 } 163 lastWasCR = false; 164 } else { 165 lastWasCR = false; 166 } 167 } 168 return read; 169 } 170 } 171 172 /** 173 * Returns the next line of text available from this reader. A line is 174 * represented by 0 or more characters followed by {@code '\r'}, 175 * {@code '\n'}, {@code "\r\n"} or the end of the stream. The returned 176 * string does not include the newline sequence. 177 * 178 * @return the contents of the line or {@code null} if no characters have 179 * been read before the end of the stream has been reached. 180 * @throws IOException 181 * if this reader is closed or another IOException occurs. 182 */ 183 @Override 184 public String readLine() throws IOException { 185 synchronized (lock) { 186 if (lastWasCR) { 187 chompNewline(); 188 lastWasCR = false; 189 } 190 String result = super.readLine(); 191 if (result != null) { 192 lineNumber++; 193 } 194 return result; 195 } 196 } 197 198 /** 199 * Resets this reader to the last marked location. It also resets the line 200 * count to what is was when this reader was marked. This implementation 201 * resets the source reader. 202 * 203 * @throws IOException 204 * if this reader is already closed, no mark has been set or the 205 * mark is no longer valid because more than {@code readlimit} 206 * bytes have been read since setting the mark. 207 * @see #mark(int) 208 * @see #markSupported() 209 */ 210 @Override 211 public void reset() throws IOException { 212 synchronized (lock) { 213 super.reset(); 214 lineNumber = markedLineNumber; 215 lastWasCR = markedLastWasCR; 216 } 217 } 218 219 /** 220 * Sets the line number of this reader to the specified {@code lineNumber}. 221 * Note that this may have side effects on the line number associated with 222 * the last marked position. 223 * 224 * @param lineNumber 225 * the new line number value. 226 * @see #mark(int) 227 * @see #reset() 228 */ 229 public void setLineNumber(int lineNumber) { 230 synchronized (lock) { 231 this.lineNumber = lineNumber; 232 } 233 } 234 235 /** 236 * Skips {@code charCount} characters in this reader. Subsequent calls to 237 * {@code read} will not return these characters unless {@code reset} 238 * is used. This implementation skips {@code charCount} number of characters in 239 * the source reader and increments the line number count whenever line 240 * terminator sequences are skipped. 241 * 242 * @return the number of characters actually skipped. 243 * @throws IllegalArgumentException 244 * if {@code charCount < 0}. 245 * @throws IOException 246 * if this reader is closed or another IOException occurs. 247 * @see #mark(int) 248 * @see #read() 249 * @see #reset() 250 */ 251 @Override 252 public long skip(long charCount) throws IOException { 253 if (charCount < 0) { 254 throw new IllegalArgumentException("charCount < 0: " + charCount); 255 } 256 synchronized (lock) { 257 for (int i = 0; i < charCount; i++) { 258 if (read() == -1) { 259 return i; 260 } 261 } 262 return charCount; 263 } 264 } 265} 266