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 at most {@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 * <p> 140 * 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 * @param buffer 145 * the array in which to store the characters read. 146 * @param offset 147 * the initial position in {@code buffer} to store the characters 148 * read from this reader. 149 * @param count 150 * the maximum number of characters to store in {@code buffer}. 151 * @return the number of characters actually read or -1 if the end of the 152 * source reader has been reached while reading. 153 * @throws IOException 154 * if this reader is closed or another IOException occurs. 155 */ 156 @Override 157 public int read(char[] buffer, int offset, int count) throws IOException { 158 synchronized (lock) { 159 int read = super.read(buffer, offset, count); 160 if (read == -1) { 161 return -1; 162 } 163 for (int i = 0; i < read; i++) { 164 char ch = buffer[offset + i]; 165 if (ch == '\r') { 166 lineNumber++; 167 lastWasCR = true; 168 } else if (ch == '\n') { 169 if (!lastWasCR) { 170 lineNumber++; 171 } 172 lastWasCR = false; 173 } else { 174 lastWasCR = false; 175 } 176 } 177 return read; 178 } 179 } 180 181 /** 182 * Returns the next line of text available from this reader. A line is 183 * represented by 0 or more characters followed by {@code '\r'}, 184 * {@code '\n'}, {@code "\r\n"} or the end of the stream. The returned 185 * string does not include the newline sequence. 186 * 187 * @return the contents of the line or {@code null} if no characters have 188 * been read before the end of the stream has been reached. 189 * @throws IOException 190 * if this reader is closed or another IOException occurs. 191 */ 192 @Override 193 public String readLine() throws IOException { 194 synchronized (lock) { 195 if (lastWasCR) { 196 chompNewline(); 197 lastWasCR = false; 198 } 199 String result = super.readLine(); 200 if (result != null) { 201 lineNumber++; 202 } 203 return result; 204 } 205 } 206 207 /** 208 * Resets this reader to the last marked location. It also resets the line 209 * count to what is was when this reader was marked. This implementation 210 * resets the source reader. 211 * 212 * @throws IOException 213 * if this reader is already closed, no mark has been set or the 214 * mark is no longer valid because more than {@code readlimit} 215 * bytes have been read since setting the mark. 216 * @see #mark(int) 217 * @see #markSupported() 218 */ 219 @Override 220 public void reset() throws IOException { 221 synchronized (lock) { 222 super.reset(); 223 lineNumber = markedLineNumber; 224 lastWasCR = markedLastWasCR; 225 } 226 } 227 228 /** 229 * Sets the line number of this reader to the specified {@code lineNumber}. 230 * Note that this may have side effects on the line number associated with 231 * the last marked position. 232 * 233 * @param lineNumber 234 * the new line number value. 235 * @see #mark(int) 236 * @see #reset() 237 */ 238 public void setLineNumber(int lineNumber) { 239 synchronized (lock) { 240 this.lineNumber = lineNumber; 241 } 242 } 243 244 /** 245 * Skips {@code count} number of characters in this reader. Subsequent 246 * {@code read()}'s will not return these characters unless {@code reset()} 247 * is used. This implementation skips {@code count} number of characters in 248 * the source reader and increments the line number count whenever line 249 * terminator sequences are skipped. 250 * 251 * @param count 252 * the number of characters to skip. 253 * @return the number of characters actually skipped. 254 * @throws IllegalArgumentException 255 * if {@code count < 0}. 256 * @throws IOException 257 * if this reader is closed or another IOException occurs. 258 * @see #mark(int) 259 * @see #read() 260 * @see #reset() 261 */ 262 @Override 263 public long skip(long count) throws IOException { 264 if (count < 0) { 265 throw new IllegalArgumentException(); 266 } 267 synchronized (lock) { 268 for (int i = 0; i < count; i++) { 269 if (read() == -1) { 270 return i; 271 } 272 } 273 return count; 274 } 275 } 276} 277