Home | History | Annotate | Download | only in io
      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 
     18 package 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  */
     26 public 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 charCount} characters in this reader. Subsequent calls to
    246      * {@code read} will not return these characters unless {@code reset}
    247      * is used. This implementation skips {@code charCount} number of characters in
    248      * the source reader and increments the line number count whenever line
    249      * terminator sequences are skipped.
    250      *
    251      * @return the number of characters actually skipped.
    252      * @throws IllegalArgumentException
    253      *             if {@code charCount < 0}.
    254      * @throws IOException
    255      *             if this reader is closed or another IOException occurs.
    256      * @see #mark(int)
    257      * @see #read()
    258      * @see #reset()
    259      */
    260     @Override
    261     public long skip(long charCount) throws IOException {
    262         if (charCount < 0) {
    263             throw new IllegalArgumentException("charCount < 0: " + charCount);
    264         }
    265         synchronized (lock) {
    266             for (int i = 0; i < charCount; i++) {
    267                 if (read() == -1) {
    268                     return i;
    269                 }
    270             }
    271             return charCount;
    272         }
    273     }
    274 }
    275