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  package org.apache.commons.csv;
18  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.Reader;
22  
23  /**
24   * ExtendedBufferedReader
25   *
26   * A special reader decorater which supports more
27   * sophisticated access to the underlying reader object.
28   * 
29   * In particular the reader supports a look-ahead option,
30   * which allows you to see the next char returned by
31   * next().
32   * Furthermore the skip-method supports skipping until
33   * (but excluding) a given char. Similar functionality
34   * is supported by the reader as well.
35   * 
36   */
37  class ExtendedBufferedReader extends BufferedReader  {
38  
39    
40    /** the end of stream symbol */
41    public static final int END_OF_STREAM = -1;
42    /** undefined state for the lookahead char */
43    public static final int UNDEFINED = -2;
44    
45    /** the lookahead chars */
46    private int lookaheadChar = UNDEFINED;
47    /** the last char returned */
48    private int lastChar = UNDEFINED;
49    /** the line counter */
50    private int lineCounter = 0;
51    private CharBuffer line = new CharBuffer();
52    
53    /**
54     * Created extended buffered reader using default buffer-size
55     *
56     */
57    public ExtendedBufferedReader(Reader r) {
58      super(r);
59      /* note uh: do not fetch the first char here,
60       *          because this might block the method!
61       */
62    }
63      
64    /**
65     * Create extended buffered reader using the given buffer-size
66     */
67    public ExtendedBufferedReader(Reader r, int bufSize) {
68      super(r, bufSize);
69      /* note uh: do not fetch the first char here,
70       *          because this might block the method!
71       */
72    }
73    
74    /**
75     * Reads the next char from the input stream.
76     * @return the next char or END_OF_STREAM if end of stream has been reached.
77     */
78    public int read() throws IOException {
79      // initalize the lookahead
80      if (lookaheadChar == UNDEFINED) {
81        lookaheadChar = super.read();
82      }
83      lastChar = lookaheadChar;
84      if (super.ready()) {
85        lookaheadChar = super.read();
86      } else {
87        lookaheadChar = UNDEFINED;
88      }
89      if (lastChar == '\n') {
90        lineCounter++;
91      } 
92      return lastChar;
93    }
94    
95    /**
96     * Returns the last read character again.
97     * 
98     * @return the last read char or UNDEFINED
99     */
100   public int readAgain() {
101     return lastChar;  
102   }
103   
104   /**
105    * Non-blocking reading of len chars into buffer buf starting
106    * at bufferposition off.
107    * 
108    * performs an iteratative read on the underlying stream
109    * as long as the following conditions hold:
110    *   - less than len chars have been read
111    *   - end of stream has not been reached
112    *   - next read is not blocking
113    * 
114    * @return nof chars actually read or END_OF_STREAM
115    */
116   public int read(char[] buf, int off, int len) throws IOException {
117     // do not claim if len == 0
118     if (len == 0) {
119       return 0;
120     } 
121     
122     // init lookahead, but do not block !!
123     if (lookaheadChar == UNDEFINED) {
124         if (ready()) {
125          lookaheadChar = super.read();
126         } else {
127           return -1;
128         }
129     }
130     // 'first read of underlying stream'
131     if (lookaheadChar == -1) {
132       return -1;
133     }
134     // continue until the lookaheadChar would block
135     int cOff = off;
136     while (len > 0 && ready()) {
137       if (lookaheadChar == -1) {
138         // eof stream reached, do not continue
139         return cOff - off;
140       } else {
141         buf[cOff++] = (char) lookaheadChar;
142         if (lookaheadChar == '\n') {
143           lineCounter++;
144         } 
145         lastChar = lookaheadChar;
146         lookaheadChar = super.read();
147         len--;
148       }
149     }
150     return cOff - off;
151   }
152  
153  /**
154   * Reads all characters up to (but not including) the given character.
155   * 
156   * @param c the character to read up to
157   * @return the string up to the character <code>c</code>
158   * @throws IOException
159   */
160  public String readUntil(char c) throws IOException {
161    if (lookaheadChar == UNDEFINED) {
162      lookaheadChar = super.read();
163    }
164    line.clear(); // reuse
165    while (lookaheadChar != c && lookaheadChar != END_OF_STREAM) {
166      line.append((char) lookaheadChar);
167      if (lookaheadChar == '\n') {
168        lineCounter++;
169      } 
170      lastChar = lookaheadChar;
171      lookaheadChar = super.read();
172    }
173    return line.toString();    
174  }
175  
176  /**
177   * @return A String containing the contents of the line, not 
178   *         including any line-termination characters, or null 
179   *         if the end of the stream has been reached
180   */
181   public String readLine() throws IOException {
182     
183     if (lookaheadChar == UNDEFINED) {
184       lookaheadChar = super.read(); 
185     }
186     
187     line.clear(); //reuse
188     
189     // return null if end of stream has been reached
190     if (lookaheadChar == END_OF_STREAM) {
191       return null;
192     }
193     // do we have a line termination already
194     char laChar = (char) lookaheadChar;
195     if (laChar == '\n' || laChar == '\r') {
196       lastChar = lookaheadChar;
197       lookaheadChar = super.read();
198       // ignore '\r\n' as well
199       if ((char) lookaheadChar == '\n') {
200         lastChar = lookaheadChar;
201         lookaheadChar = super.read();
202       }
203       lineCounter++;
204       return line.toString();
205     }
206     
207     // create the rest-of-line return and update the lookahead
208     line.append(laChar);
209     String restOfLine = super.readLine(); // TODO involves copying
210     lastChar = lookaheadChar;
211     lookaheadChar = super.read();
212     if (restOfLine != null) {
213       line.append(restOfLine);
214     }
215     lineCounter++;
216     return line.toString();
217   }
218   
219   /**
220    * Skips char in the stream
221    * 
222    * ATTENTION: invalidates the line-counter !!!!!
223    * 
224    * @return nof skiped chars
225    */
226   public long skip(long n) throws IllegalArgumentException, IOException  {
227     
228     if (lookaheadChar == UNDEFINED) {
229       lookaheadChar = super.read();   
230     }
231     
232     // illegal argument
233     if (n < 0) {
234       throw new IllegalArgumentException("negative argument not supported");  
235     }
236     
237     // no skipping
238     if (n == 0 || lookaheadChar == END_OF_STREAM) {
239       return 0;
240     } 
241     
242     // skip and reread the lookahead-char
243     long skiped = 0;
244     if (n > 1) {
245       skiped = super.skip(n - 1);
246     }
247     lookaheadChar = super.read();
248     // fixme uh: we should check the skiped sequence for line-terminations...
249     lineCounter = Integer.MIN_VALUE;
250     return skiped + 1;
251   }
252   
253   /**
254    * Skips all chars in the input until (but excluding) the given char
255    * 
256    * @param c
257    * @return
258    * @throws IllegalArgumentException
259    * @throws IOException
260    */
261   public long skipUntil(char c) throws IllegalArgumentException, IOException {
262     if (lookaheadChar == UNDEFINED) {
263       lookaheadChar = super.read();   
264     }
265     long counter = 0;
266     while (lookaheadChar != c && lookaheadChar != END_OF_STREAM) {
267       if (lookaheadChar == '\n') {
268         lineCounter++;
269       } 
270       lookaheadChar = super.read();
271       counter++;
272     }
273     return counter;
274   }
275   
276   /**
277    * Returns the next char in the stream without consuming it.
278    * 
279    * Remember the next char read by read(..) will always be
280    * identical to lookAhead().
281    * 
282    * @return the next char (without consuming it) or END_OF_STREAM
283    */
284   public int lookAhead() throws IOException {
285     if (lookaheadChar == UNDEFINED) {
286       lookaheadChar = super.read();
287     }
288     return lookaheadChar;
289   }
290   
291   
292   /**
293    * Returns the nof line read
294    * ATTENTION: the skip-method does invalidate the line-number counter
295    * 
296    * @return the current-line-number (or -1)
297    */ 
298   public int getLineNumber() {
299     if (lineCounter > -1) {
300       return lineCounter;
301     } else {
302       return -1;
303     }
304   }
305   public boolean markSupported() {
306     /* note uh: marking is not supported, cause we cannot
307      *          see into the future...
308      */
309     return false;
310   }
311   
312 }