View Javadoc
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 org.apache.commons.csv;
19  
20  import static org.apache.commons.csv.Constants.CR;
21  import static org.apache.commons.csv.Constants.END_OF_STREAM;
22  import static org.apache.commons.csv.Constants.LF;
23  import static org.apache.commons.csv.Constants.UNDEFINED;
24  
25  import java.io.BufferedReader;
26  import java.io.IOException;
27  import java.io.Reader;
28  
29  /**
30   * A special buffered reader which supports sophisticated read access.
31   * <p>
32   * In particular the reader supports a look-ahead option, which allows you to see the next char returned by
33   * {@link #read()}.
34   *
35   * @version $Id: ExtendedBufferedReader.java 1512625 2013-08-10 11:07:15Z britter $
36   */
37  final class ExtendedBufferedReader extends BufferedReader {
38  
39      /** The last char returned */
40      private int lastChar = UNDEFINED;
41  
42      /** The count of EOLs (CR/LF/CRLF) seen so far */
43      private long eolCounter = 0;
44  
45      private boolean closed;
46  
47      /**
48       * Created extended buffered reader using default buffer-size
49       */
50      ExtendedBufferedReader(final Reader reader) {
51          super(reader);
52      }
53  
54      @Override
55      public int read() throws IOException {
56          final int current = super.read();
57          if (current == CR || (current == LF && lastChar != CR)) {
58              eolCounter++;
59          }
60          lastChar = current;
61          return lastChar;
62      }
63  
64      /**
65       * Returns the last character that was read as an integer (0 to 65535). This will be the last character returned by
66       * any of the read methods. This will not include a character read using the {@link #lookAhead()} method. If no
67       * character has been read then this will return {@link Constants#UNDEFINED}. If the end of the stream was reached
68       * on the last read then this will return {@link Constants#END_OF_STREAM}.
69       *
70       * @return the last character that was read
71       */
72      int getLastChar() {
73          return lastChar;
74      }
75  
76      @Override
77      public int read(final char[] buf, final int offset, final int length) throws IOException {
78          if (length == 0) {
79              return 0;
80          }
81  
82          final int len = super.read(buf, offset, length);
83  
84          if (len > 0) {
85  
86              for (int i = offset; i < offset + len; i++) {
87                  final char ch = buf[i];
88                  if (ch == LF) {
89                      if (CR != (i > 0 ? buf[i - 1] : lastChar)) {
90                          eolCounter++;
91                      }
92                  } else if (ch == CR) {
93                      eolCounter++;
94                  }
95              }
96  
97              lastChar = buf[offset + len - 1];
98  
99          } else if (len == -1) {
100             lastChar = END_OF_STREAM;
101         }
102 
103         return len;
104     }
105 
106     /**
107      * Calls {@link BufferedReader#readLine()} which drops the line terminator(s). This method should only be called
108      * when processing a comment, otherwise information can be lost.
109      * <p>
110      * Increments {@link #eolCounter}
111      * <p>
112      * Sets {@link #lastChar} to {@link Constants#END_OF_STREAM} at EOF, otherwise to LF
113      *
114      * @return the line that was read, or null if reached EOF.
115      */
116     @Override
117     public String readLine() throws IOException {
118         final String line = super.readLine();
119 
120         if (line != null) {
121             lastChar = LF; // needed for detecting start of line
122             eolCounter++;
123         } else {
124             lastChar = END_OF_STREAM;
125         }
126 
127         return line;
128     }
129 
130     /**
131      * Returns the next character in the current reader without consuming it. So the next call to {@link #read()} will
132      * still return this value. Does not affect line number or last character.
133      *
134      * @return the next character
135      *
136      * @throws IOException
137      *             if there is an error in reading
138      */
139     int lookAhead() throws IOException {
140         super.mark(1);
141         final int c = super.read();
142         super.reset();
143 
144         return c;
145     }
146 
147     /**
148      * Returns the current line number
149      *
150      * @return the current line number
151      */
152     long getCurrentLineNumber() {
153         // Check if we are at EOL or EOF or just starting
154         if (lastChar == CR || lastChar == LF || lastChar == UNDEFINED || lastChar == END_OF_STREAM) {
155             return eolCounter; // counter is accurate
156         }
157         return eolCounter + 1; // Allow for counter being incremented only at EOL
158     }
159 
160     public boolean isClosed() {
161         return closed;
162     }
163 
164     /**
165      * Closes the stream.
166      *
167      * @throws IOException
168      *             If an I/O error occurs
169      */
170     @Override
171     public void close() throws IOException {
172         // Set ivars before calling super close() in case close() throws an IOException.
173         closed = true;
174         lastChar = END_OF_STREAM;
175         super.close();
176     }
177 
178 }