Source for java.io.LineNumberReader

   1: /* LineNumberReader.java -- A character input stream which counts line numbers
   2:    Copyright (C) 1998, 1999, 2001, 2003, 2005  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: package java.io;
  39: 
  40: /**
  41:  * This class functions like a standard <code>Reader</code> except that it
  42:  * counts line numbers, and canonicalizes newline characters.  As data
  43:  * is read, whenever the char sequences "\r", "\n", or "\r\n" are encountered,
  44:  * the running line count is incremeted by one.  Additionally, the whatever
  45:  * line termination sequence was encountered will be converted to a "\n"
  46:  * char.  Note that this class numbers lines from 0.  When the first
  47:  * line terminator is encountered, the line number is incremented to 1, and
  48:  * so on.  Also note that actual "\r" and "\n" characters are looked for.
  49:  * The system dependent line separator sequence is ignored.
  50:  * <p>
  51:  * This class counts only line termination characters.  If the last line
  52:  * read from the stream does not end in a line termination sequence, it
  53:  * will not be counted as a line.
  54:  *
  55:  * @author Per Bothner (bothner@cygnus.com)
  56:  * @author Aaron M. Renn (arenn@urbanophile.com)
  57:  * @author Guilhem Lavaux (guilhem@kaffe.org)
  58:  * @date December 28, 2003.
  59:  */
  60: /* Written using "Java Class Libraries", 2nd edition, plus online
  61:  * API docs for JDK 1.2 beta from http://www.javasoft.com.
  62:  * Status:  Believed complete and correct.
  63:  *
  64:  * This implementation has the feature that if '\r' is read, it
  65:  * does not look for a '\n', but immediately returns '\n'.
  66:  * On the next read(), if a '\n' is read, it is skipped.
  67:  * This has the advantage that we do not read (and hang) unnecessarily.
  68:  *
  69:  * This implementation is also minimal in the number of fields it uses.
  70:  */
  71: public class LineNumberReader extends BufferedReader
  72: {
  73:   /** The current line number. */
  74:   private int lineNumber;
  75:   /** Whether we already found a new line in the former call. */
  76:   private boolean matchedNewLine;
  77:   /** The saved line number when calling mark() */
  78:   private int savedLineNumber;
  79: 
  80:   /**
  81:     * Create a new <code>LineNumberReader</code> that reads from the
  82:     * specified subordinate <code>Reader</code>.  A default 8K char sized
  83:     * buffer will be used for reads.
  84:     *
  85:     * @param in The subordinate <code>Reader</code> to read from
  86:     */
  87:   public LineNumberReader(Reader in)
  88:   {
  89:     super(in, DEFAULT_BUFFER_SIZE);
  90:   }
  91: 
  92:   /**
  93:     * This method initializes a new <code>LineNumberReader</code> to read
  94:     * from the specified subordinate <code>Reader</code> using the specified
  95:     * read buffer size.
  96:     *
  97:     * @param in The subordinate <code>Reader</code> to read from
  98:     * @param size The buffer size to use for reading
  99:     */
 100:   public LineNumberReader(Reader in, int size)
 101:   {
 102:     super(in, size);
 103:   }
 104: 
 105:   /**
 106:     * This method returns the current line number
 107:     *
 108:     * @return The current line number
 109:     */
 110:   public int getLineNumber()
 111:   {
 112:     return lineNumber;
 113:   }
 114: 
 115:   /**
 116:     * This method sets the current line number to the specified value.
 117:     *
 118:     * @param lineNumber The new line number
 119:     */
 120:   public void setLineNumber(int lineNumber)
 121:   {
 122:     this.lineNumber = lineNumber;
 123:   }
 124: 
 125:   /**
 126:     * This method marks a position in the input to which the stream can be
 127:     * "reset" char calling the <code>reset()</code> method.  The parameter
 128:     * <code>readlimit</code> is the number of chars that can be read from the
 129:     * stream after setting the mark before the mark becomes invalid.   For
 130:     * example, if <code>mark()</code> is called with a read limit of 10,
 131:     * then when
 132:     * 11 chars of data are read from the stream before the <code>reset()</code>
 133:     * method is called, then the mark is invalid and the stream object
 134:     * instance is not required to remember the mark.
 135:     * <p>
 136:     * In this class, this method will remember the current line number as well
 137:     * as the current position in the stream.  When the <code>reset()</code>
 138:     * method
 139:     * is called, the line number will be restored to the saved line number in
 140:     * addition to the stream position.
 141:     *
 142:     * @param readLimit The number of chars that can be read before the
 143:     * mark becomes invalid
 144:     *
 145:     * @exception IOException If an error occurs
 146:     */
 147:   public void mark(int readLimit) throws IOException
 148:   {
 149:     if (readLimit < 0)
 150:       throw new IllegalArgumentException("Read-ahead limit is negative");
 151: 
 152:     synchronized (lock)
 153:       {
 154:         // This is basically the same as BufferedReader.mark.
 155:         // However, if the previous character was a '\r', we need to
 156:         // save that 'r', in case the next character is a '\n'.
 157:         if (pos + readLimit > limit)
 158:           {
 159:             int saveCR = matchedNewLine ? 1 : 0;
 160:             char[] old_buffer = buffer;
 161:             if (readLimit > limit)
 162:               buffer = new char[saveCR + readLimit];
 163:             int copy_start = pos - saveCR;
 164:             savedLineNumber = lineNumber;
 165:             limit -= copy_start;
 166:             System.arraycopy(old_buffer, copy_start, buffer, 0, limit);
 167:             pos = saveCR;
 168:           }
 169:         markPos = pos;
 170:       }
 171:   }
 172: 
 173:   /**
 174:     * This method resets a stream to the point where the <code>mark()</code>
 175:     * method
 176:     * was called.  Any chars that were read after the mark point was set will
 177:     * be re-read during subsequent reads.
 178:     * <p>
 179:     * In this class, this method will also restore the line number that was
 180:     * current when the <code>mark()</code> method was called.
 181:     *
 182:     * @exception IOException If an error occurs
 183:     */
 184:   public void reset() throws IOException
 185:   {
 186:     synchronized (lock)
 187:       {
 188:         if (markPos < 0)
 189:           throw new IOException("mark never set or invalidated");
 190:         lineNumber = savedLineNumber;
 191:         pos = markPos;
 192:         matchedNewLine = (markPos > 0 && buffer[markPos-1] == '\r');
 193:       }
 194:   }
 195: 
 196:   /**
 197:    * This private method fills the input buffer whatever pos is.
 198:    * Consequently pos should be checked before calling this method.
 199:    *
 200:    * @return the number of bytes actually read from the input stream or
 201:    * -1 if end of stream.
 202:    * @exception IOException If an error occurs.
 203:    */
 204:   private int fill() throws IOException
 205:   {
 206:     if (markPos >= 0 && limit == buffer.length)
 207:       markPos = -1;
 208:     if (markPos < 0)
 209:       pos = limit = 0;
 210:     int count = in.read(buffer, limit, buffer.length - limit);
 211:     if (count <= 0)
 212:       return -1;
 213:     limit += count;
 214: 
 215:     return count;
 216:   }
 217: 
 218:   /**
 219:     * This method reads an unsigned char from the input stream and returns it
 220:     * as an int in the range of 0-65535.  This method will return -1 if the
 221:     * end of the stream has been reached.
 222:     * <p>
 223:     * Note that if a line termination sequence is encountered (ie, "\r",
 224:     * "\n", or "\r\n") then that line termination sequence is converted to
 225:     * a single "\n" value which is returned from this method.  This means
 226:     * that it is possible this method reads two chars from the subordinate
 227:     * stream instead of just one.
 228:     * <p>
 229:     * Note that this method will block until a char of data is available
 230:     * to be read.
 231:     *
 232:     * @return The char read or -1 if end of stream
 233:     *
 234:     * @exception IOException If an error occurs
 235:     */
 236:   public int read() throws IOException
 237:   {
 238:     synchronized (lock)
 239:       {
 240:         skipRedundantLF();
 241:         if (pos >= limit && fill() < 0)
 242:           return -1;
 243:         char ch = buffer[pos++];
 244: 
 245:         if ((matchedNewLine = (ch == '\r')) || ch == '\n')
 246:           {
 247:             lineNumber++;
 248:             return '\n';
 249:           }
 250:         matchedNewLine = false;
 251:         return (int) ch;
 252:       }
 253:   }
 254: 
 255:   /**
 256:     * This method reads chars from a stream and stores them into a caller
 257:     * supplied buffer.  It starts storing data at index <code>offset</code> into
 258:     * the buffer and attemps to read <code>len</code> chars.  This method can
 259:     * return before reading the number of chars requested.  The actual number
 260:     * of chars read is returned as an int.  A -1 is returned to indicated the
 261:     * end of the stream.
 262:     * <p>
 263:     * This method will block until some data can be read.
 264:     * <p>
 265:     * Note that if a line termination sequence is encountered (ie, "\r",
 266:     * "\n", or "\r\n") then that line termination sequence is converted to
 267:     * a single "\n" value which is stored in the buffer.  Only a single
 268:     * char is counted towards the number of chars read in this case.
 269:     *
 270:     * @param buf The array into which the chars read should be stored
 271:     * @param offset The offset into the array to start storing chars
 272:     * @param count The requested number of chars to read
 273:     *
 274:     * @return The actual number of chars read, or -1 if end of stream
 275:     *
 276:     * @exception IOException If an error occurs.
 277:     * @exception NullPointerException If buf is null (in any case).
 278:     * @exception IndexOutOfBoundsException If buffer parameters (offset and
 279:     * count) lies outside of the buffer capacity.
 280:     */
 281:   public int read(char[] buf, int offset, int count) throws IOException
 282:   {
 283:     if (buf == null)
 284:       throw new NullPointerException();
 285: 
 286:     if (offset + count > buf.length || offset < 0)
 287:       throw new IndexOutOfBoundsException();
 288: 
 289:     if (count <= 0)
 290:       {
 291:         if (count < 0)
 292:           throw new IndexOutOfBoundsException();
 293:         return 0;
 294:       }
 295: 
 296:     synchronized (lock)
 297:       {
 298:         if (pos >= limit && fill() < 0)
 299:           return -1;
 300: 
 301:         int start_offset = offset;
 302:         boolean matched = matchedNewLine;
 303: 
 304:         while (count-- > 0 && pos < limit)
 305:           {
 306:             char ch = buffer[pos++];
 307:             if (ch == '\r')
 308:               {
 309:                 lineNumber++;
 310:                 matched = true;
 311:               }
 312:             else if (ch == '\n' && !matched)
 313:               lineNumber++;
 314:             else
 315:               matched = false;
 316: 
 317:             buf[offset++] = ch;
 318:           }
 319: 
 320:         matchedNewLine = matched;
 321:         return offset - start_offset;
 322:       }
 323:   }
 324: 
 325:   private void skipRedundantLF() throws IOException
 326:   {
 327:     if (pos > 0 && matchedNewLine)
 328:       {
 329:         if (pos < limit)
 330:           { // fast case
 331:             if (buffer[pos] == '\n')
 332:               pos++;
 333:           }
 334:         else
 335:           { // check whether the next buffer begins with '\n'.
 336:             // in that case kill the '\n'.
 337:             if (fill() <= 0)
 338:               return;
 339:             if (buffer[pos] == '\n')
 340:               pos++;
 341:           }
 342:         matchedNewLine = true;
 343:       }
 344:   }
 345: 
 346:   /**
 347:     * This method reads a line of text from the input stream and returns
 348:     * it as a <code>String</code>.  A line is considered to be terminated
 349:     * by a "\r", "\n", or "\r\n" sequence, not by the system dependent line
 350:     * separator.
 351:     *
 352:     * @return The line read as a <code>String</code> or <code>null</code>
 353:     * if end of stream.
 354:     *
 355:     * @exception IOException If an error occurs
 356:     */
 357:   public String readLine() throws IOException
 358:   {
 359:     // BufferedReader.readLine already does this.  Shouldn't need to keep
 360:     // track of newlines (since the read method deals with this for us).
 361:     // But if the buffer is large, we may not call the read method at all
 362:     // and super.readLine can't increment lineNumber itself.
 363:     // Though it may seem kludgy, the safest thing to do is to save off
 364:     // lineNumber and increment it explicitly when we're done (iff we
 365:     // ended with a '\n' or '\r' as opposed to EOF).
 366:     //
 367:     // Also, we need to undo the special casing done by BufferedReader.readLine
 368:     // when a '\r' is the last char in the buffer.  That situation is marked
 369:     // by 'pos > limit'.
 370:     int tmpLineNumber = lineNumber;
 371:     skipRedundantLF();
 372:     String str = super.readLine();
 373:     if (pos > limit)
 374:       --pos;
 375: 
 376:     // The only case where you mustn't increment the line number is you are
 377:     // at the EOS.
 378:     if (str != null)
 379:       lineNumber = tmpLineNumber + 1;
 380: 
 381:     return str;
 382:   }
 383: 
 384:   /**
 385:     * This method skips over characters in the stream.  This method will
 386:     * skip the specified number of characters if possible, but is not required
 387:     * to skip them all.  The actual number of characters skipped is returned.
 388:     * This method returns 0 if the specified number of chars is less than 1.
 389:     *
 390:     * @param count The specified number of chars to skip.
 391:     *
 392:     * @return The actual number of chars skipped.
 393:     *
 394:     * @exception IOException If an error occurs
 395:     */
 396:   public long skip (long count) throws IOException
 397:   {
 398:     if (count < 0)
 399:       throw new IllegalArgumentException("skip() value is negative");
 400:     if (count == 0)
 401:       return 0;
 402: 
 403:     int skipped;
 404:     char[] buf = new char[1];
 405: 
 406:     for (skipped = 0; skipped < count; skipped++)
 407:       {
 408:         int ch = read(buf, 0, 1);
 409: 
 410:         if (ch < 0)
 411:           break;
 412:       }
 413: 
 414:     return skipped;
 415:   }
 416: }