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 }