KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > gnu > text > LineBufferedReader


1 // Copyright (c) 2004 Per M.A. Bothner.
2
// This is free software; for terms and warranty disclaimer see ./COPYING.
3

4 package gnu.text;
5 import java.io.*;
6
7 /** A LineNumberReader with some extra features:
8   *
9   * You can seek backwards to the start of the line preceding the
10   * current position (or the mark, if that has been set).
11   * You can use seek with a negative offset, or unread.
12   * You can also use peek to look at the next character without moving.
13   *
14   * The method getColumnNumber gives you the current column.
15   *
16   * Provides a method that is called at the start of a line.
17   * This is especially useful for interactive streams (e.g. prompting).
18   *
19   * It would be nice if we could inherit from LineNumberReader.
20   * That may be possible in theory, but it is difficult and
21   * expensive (because we don't get access to BufferedReader's buffer).
22   *
23   * @author Per Bothner <bothner@cygnus.com>
24   */

25
26 public class LineBufferedReader extends FilterReader
27 {
28   /** Default (initial buffer) size. */
29   final static int BUFFER_SIZE = 1024;
30
31   /** The input buffer, containing the current line etc. */
32   public char[] buffer;
33
34   /** The current read position, as an index into buffer. */
35   public int pos;
36
37   /** The length of the valid (data-containing) part of the buffer. */
38   public int limit;
39
40   /** The high-water mark for pos, at a reset or line start. */
41   int highestPos;
42
43   public char readState = '\n';
44   /** Return a character that indicates what we are currently reading.
45     * Returns '\n' if we are not inside read; '\"' if reading a string;
46     * '|' if inside a comment; '(' if inside a list; and
47     * ' ' if otherwise inside a read. */

48   public char getReadState () { return readState; }
49
50   private int flags;
51
52   // Notice the asymmetry in how "X\r\nY" is handled (assuming convertCR)..
53
// When we read forward, the positions are 0, 1, 2, 4.
54
// After seeing the '\r', we do not read ahead to look for a '\n'
55
// because if we did, and there were no '\n', terminal input would hang.
56
// The (lineNumber, lineStartPos) goes (0,0), (0,0), (0,0), (1,3).
57
// However, the methods (getLineNumber(), getColumnNumber())
58
// return the external values (0:0), (0:1), (1:0), (1:1).
59
// When we move backwards, the positions are 4, 3, 1, 0.
60
// This is because we want to stay within the same line.
61
// The (lineNumber, lineStartPos) goes (1,3), (1,3), (0,0), (0,0).
62
// For (getLineNumber(), getColumnNumber()) we get (1:1), (1:0), (0:1), (0:0)
63
// which are the same as when we are moving forwards.
64
// A nice bonus is that both skip_quick and unread_quick are trivial.
65

66   /* If true in flags, convert "\r" and "\r\n" to '\n'. */
67   private static final int CONVERT_CR = 1;
68
69   /* If true in flags, may not re-allocate buffer. */
70   private static final int USER_BUFFER = 2;
71
72   /* If true in flags, char before start of buffer was '\r'. */
73   private static final int PREV_WAS_CR = 4;
74
75   /** True if CR and CRLF should be converted to LF. */
76   public final boolean getConvertCR () { return (flags & CONVERT_CR) != 0; }
77
78   public final void setConvertCR(boolean convertCR)
79   {
80     if (convertCR)
81       flags |= CONVERT_CR;
82     else
83       flags &= ~CONVERT_CR;
84   }
85
86   /** The position that marks the start of the current or marked line.
87     * If the readAheadLimit > 0 && markPos < pos, then it is the start of the
88     * line containing the markPos.
89     * If we are at the end of a line, and have not started reading the next
90     * one (and are therefore allowed by unread back to the old line),
91     * the current line is still the old line; lineStartPos does not
92     * get set to the new pos until we read/peek the first char of the new line.
93     * If lineStartPos < 0, it means we went beyond the buffer maximum. */

94   int lineStartPos;
95
96   Object JavaDoc name;
97
98   /** The current line number (at position of lineStartPos). */
99   protected int lineNumber;
100
101   /** If mark has been called, and not invalidated, the read ahead limit.
102     * Zero if mark has not been called, or had been invalidated
103     * (due to either calling reset or excessive reading ahead). */

104   protected int readAheadLimit = 0;
105
106   /** The position of the mark (assuming readAheadLinit > 0).
107     * (Garbage if readAheadLimit <= 0). */

108   protected int markPos;
109
110   public LineBufferedReader (InputStream in)
111   {
112     super (new InputStreamReader(in));
113   }
114
115   public LineBufferedReader (Reader in)
116   {
117     super (in);
118   }
119
120   /** A hook to allow sub-classes to perform some action at start of line.
121     * Called just before the first character of the new line is read.
122     * @param revisited true if we have read here before (i.e.
123     * we did a reset of unread() to get here)
124     */

125   public void lineStart (boolean revisited) throws java.io.IOException JavaDoc
126   {
127   }
128
129   /** Called by read() when it needs its buffer filled.
130     * Read characters into buffer, starting at off, for len.
131     * Can assume that len > 0. Only called if pos>=limit.
132     * Return -1 if EOF, otherwise number of read chars.
133     * This can be usefully overridden by sub-classes. */

134   public int fill (int len) throws java.io.IOException JavaDoc
135   {
136     return in.read(buffer, pos, len);
137   }
138
139   private void clearMark ()
140   {
141     // Invalidate the mark.
142
readAheadLimit = 0;
143     // Need to maintain the lineStartPos invariant.
144
int i = lineStartPos < 0 ? 0 : lineStartPos;
145     for (;;)
146       {
147     if (++i >= pos)
148       break;
149     char ch = buffer[i-1];
150     if (ch == '\n'
151         || (ch == '\r' && (!getConvertCR() || buffer[i] != '\n')))
152       {
153         lineNumber++;
154         lineStartPos = i;
155       }
156       
157       }
158   }
159
160   /** Specify a buffer to use for the input buffer. */
161   public void setBuffer (char[] buffer)
162     throws java.io.IOException JavaDoc
163   {
164     if (buffer == null)
165       {
166     if (this.buffer != null)
167       {
168         buffer = new char[this.buffer.length];
169         System.arraycopy(this.buffer, 0, buffer, 0, this.buffer.length);
170         this.buffer = buffer;
171       }
172     flags &= ~USER_BUFFER;
173       }
174     else
175       {
176     if (limit - pos > buffer.length)
177       throw new java.io.IOException JavaDoc("setBuffer - too short");
178     flags |= USER_BUFFER;
179     reserve (buffer, 0);
180       }
181   }
182
183   /* Make sure there is enough space for space more characters in buffer. */
184
185   private void reserve (char[] buffer, int reserve)
186     throws java.io.IOException JavaDoc
187   {
188     int saveStart;
189     reserve += limit;
190     if (reserve <= buffer.length)
191       saveStart = 0;
192     else
193       {
194     saveStart = pos;
195     if (readAheadLimit > 0 && markPos < pos)
196       {
197         if (pos - markPos > readAheadLimit
198         || ((flags & USER_BUFFER) != 0
199             && reserve - markPos > buffer.length))
200           clearMark();
201         else
202           saveStart = markPos;
203       }
204
205     reserve -= buffer.length;
206     if (saveStart < lineStartPos && reserve <= saveStart)
207       ;
208     else if (reserve <= lineStartPos && saveStart > lineStartPos)
209       saveStart = lineStartPos;
210     else if ((flags & USER_BUFFER) != 0)
211       saveStart -= (saveStart - reserve) >> 2;
212     else
213       {
214         if (lineStartPos >= 0)
215         saveStart = lineStartPos;
216         buffer = new char[2 * buffer.length];
217       }
218
219     lineStartPos -= saveStart;
220     limit -= saveStart;
221     markPos -= saveStart;
222     pos -= saveStart;
223     highestPos -= saveStart;
224       }
225     if (limit > 0)
226       System.arraycopy(this.buffer, saveStart, buffer, 0, limit);
227     this.buffer = buffer;
228   }
229
230   public int read () throws java.io.IOException JavaDoc
231   {
232     char prev;
233     if (pos > 0)
234       prev = buffer[pos-1];
235     else if ((flags & PREV_WAS_CR) != 0)
236       prev = '\r';
237     else if (lineStartPos >= 0)
238       prev = '\n';
239     else
240       prev = '\0';
241     if (prev == '\r' || prev == '\n')
242       {
243     if (lineStartPos < pos && (readAheadLimit == 0 || pos <= markPos))
244       {
245         lineStartPos = pos;
246         lineNumber++;
247       }
248     boolean revisited = pos < highestPos;
249     if (prev != '\n'
250         || (pos <= 1 ? (flags & PREV_WAS_CR) == 0 : buffer[pos-2] != '\r'))
251       {
252         lineStart(revisited);
253       }
254     if (! revisited)
255       highestPos = pos + 1; // Add one for this read().
256
}
257
258     if (pos >= limit)
259       {
260     if (buffer == null)
261       buffer = new char[BUFFER_SIZE];
262     else if (limit == buffer.length)
263       reserve(buffer, 1);
264     if (pos == 0)
265       {
266         if (prev == '\r')
267           flags |= PREV_WAS_CR;
268         else
269           flags &= ~PREV_WAS_CR;
270       }
271     int readCount = fill(buffer.length - pos);
272     if (readCount <= 0)
273       return -1;
274     limit += readCount;
275       }
276
277     int ch = buffer[pos++];
278     if (ch == '\n')
279       {
280     if (prev == '\r')
281       {
282         // lineNumber is the number of lines before lineStartPos.
283
// If lineStartPos is between '\r and '\n', we will count
284
// an extra line for the '\n', which gets the count off.
285
// Hence compensate.
286
if (lineStartPos == pos - 1)
287           {
288         lineNumber--;
289         lineStartPos--;
290           }
291         if (getConvertCR())
292           return read();
293       }
294       }
295     else if (ch == '\r')
296       {
297     if (getConvertCR())
298       return '\n';
299       }
300     return ch;
301   }
302
303   public int read (char[] cbuf, int off, int len) throws java.io.IOException JavaDoc
304   {
305     // Same logic as in skip(n), when n>0.
306
int ch;
307     if (pos >= limit)
308       ch = '\0';
309     else if (pos > 0)
310       ch = buffer[pos-1];
311     else if ((flags & PREV_WAS_CR) != 0 || lineStartPos >= 0)
312       ch = '\n';
313     else
314       ch = '\0';
315     int to_do = len;
316     while (to_do > 0)
317       {
318     if (pos >= limit || ch == '\n' || ch == '\r')
319       {
320         // Return if there is no more in the input buffer, and we got
321
// at least one char. This is desirable for interactive input.
322
if (pos >= limit && to_do < len)
323           return len - to_do;
324         ch = read();
325         if (ch < 0)
326           {
327         len -= to_do;
328         return len <= 0 ? -1 : len;
329           }
330         cbuf[off++] = (char) ch;
331         to_do--;
332       }
333     else
334       {
335         int p = pos;
336         int lim = limit;
337         if (to_do < lim - p)
338           lim = p + to_do;
339         while (p < lim)
340           {
341         ch = buffer[p];
342         // For simplicity and correctness we defer handling of
343
// newlines (including previous character) to read().
344
if (ch == '\n' || ch == '\r')
345           break;
346         cbuf[off++] = (char) ch;
347         p++;
348           }
349         to_do -= p - pos;
350         pos = p;
351       }
352       }
353     return len;
354   }
355
356   /** Should return a URI or a String. */
357   public Object JavaDoc getURI ()
358   {
359     return name;
360   }
361
362   public String JavaDoc getName ()
363   {
364     return name == null ? null : name.toString();
365   }
366
367   public void setName (Object JavaDoc name)
368   {
369     this.name = name;
370   }
371
372   /** Get the current line number.
373     * The "first" line is number number 0. */

374   public int getLineNumber ()
375   {
376     int lineno = lineNumber;
377     if (readAheadLimit == 0) // Normal, fast case:
378
{
379     if (pos > 0 && pos > lineStartPos)
380       {
381         char prev = buffer[pos-1];
382         if (prev == '\n' || prev == '\r')
383           lineno++;
384       }
385       }
386     else
387       lineno += countLines(buffer, lineStartPos < 0 ? 0 : lineStartPos, pos);
388     return lineno;
389   }
390
391   public void setLineNumber (int lineNumber)
392   {
393     this.lineNumber += lineNumber - getLineNumber();
394   }
395
396   /** Return the current (zero-based) column number. */
397   public int getColumnNumber ()
398   {
399     if (pos > 0)
400       {
401     char prev = buffer[pos-1];
402     if (prev == '\n' || prev == '\r')
403       return 0;
404       }
405     if (readAheadLimit <= 0) // Normal, fast case:
406
return pos - lineStartPos;
407
408     // Somebody did a mark(). Thus lineStartPos is not necessarily the
409
// start of the current line, so we have to search.
410
int start = lineStartPos < 0 ? 0 : lineStartPos;
411     for (int i = start; i < pos; )
412       {
413     char ch = buffer[i++];
414     if (ch == '\n' || ch == '\r')
415       start = i;
416       }
417     int col = pos - start;
418     if (lineStartPos < 0)
419       col -= lineStartPos;
420     return col;
421   }
422
423   public boolean markSupported ()
424   {
425     return true;
426   }
427
428   public synchronized void mark (int readAheadLimit)
429   {
430     if (this.readAheadLimit > 0)
431       clearMark();
432     this.readAheadLimit = readAheadLimit;
433     markPos = pos;
434   }
435
436   public void reset () throws IOException
437   {
438     if (readAheadLimit <= 0)
439       throw new IOException ("mark invalid");
440     if (pos > highestPos)
441       highestPos = pos;
442     pos = markPos;
443     readAheadLimit = 0;
444   }
445
446   /** Read a line.
447    * If mode is 'I' ("ignore") ignore delimiters.
448    * If mode is 'P' ("peek") leave delimiter in input stream.
449    * If mode is 'A' ("append") append delimiter to result.
450    */

451
452   public void readLine(StringBuffer JavaDoc sbuf, char mode)
453     throws IOException
454   {
455     for (;;)
456       {
457     int ch = read();
458     if (ch < 0)
459       return;
460         int start = --pos;
461         while (pos < limit)
462           {
463             ch = buffer[pos++];
464         if (ch == '\r' || ch == '\n')
465               {
466                 sbuf.append(buffer, start, pos - 1 - start);
467                 if (mode == 'P')
468                   {
469                     pos--;
470                     return;
471                   }
472                 if (getConvertCR () || ch == '\n')
473                   {
474                     if (mode != 'I')
475                       sbuf.append('\n');
476                   }
477                 else
478                   {
479                     if (mode != 'I')
480                       sbuf.append('\r');
481                     ch = read();
482                     if (ch == '\n')
483                       {
484                         if (mode != 'I')
485                           sbuf.append('\n');
486                       }
487                     else if (ch >= 0)
488                       unread_quick();
489                   }
490                 return;
491               }
492           }
493     sbuf.append(buffer, start, pos - start);
494       }
495   }
496
497   public String JavaDoc readLine() throws IOException
498   {
499     int ch = read();
500     if (ch < 0)
501       return null;
502     if (ch == '\r' || ch == 'n')
503       return "";
504     int start = pos - 1;
505     while (pos < limit)
506       {
507     ch = buffer[pos++];
508     if (ch == '\r' || ch == '\n')
509           {
510             if (ch != '\n' && ! getConvertCR())
511               {
512                 if (pos >= limit)
513                   {
514                     pos--;
515                     break;
516                   }
517                 if (buffer[pos] == '\n')
518                   pos++;
519               }
520             return new String JavaDoc(buffer, start, pos - start);
521           }
522       }
523     StringBuffer JavaDoc sbuf = new StringBuffer JavaDoc(100);
524     sbuf.append(buffer, start, pos);
525     readLine(sbuf, 'I');
526     return sbuf.toString();
527   }
528
529   /** Skip forwards or backwards a number of characters. */
530   public int skip(int n) throws IOException
531   {
532     if (n < 0)
533       {
534     int to_do = -n;
535     for (; to_do > 0 && pos > 0; to_do--)
536       unread();
537     return n + to_do;
538       }
539     else
540       {
541     // Same logic as in read(char[],int,int).
542
int to_do = n;
543     int ch;
544     if (pos >= limit)
545       ch = '\0';
546     else if (pos > 0)
547       ch = buffer[pos-1];
548     else if ((flags & PREV_WAS_CR) != 0 || lineStartPos >= 0)
549       ch = '\n';
550     else
551       ch = '\0';
552     while (to_do > 0)
553       {
554         if (ch == '\n' || ch == '\r' || pos >= limit)
555           {
556         ch = read();
557         if (ch < 0)
558           return n - to_do;
559         to_do--;
560           }
561         else
562           {
563         int p = pos;
564         int lim = limit;
565         if (to_do < lim - p)
566           lim = p + to_do;
567         while (p < lim)
568           {
569             ch = buffer[p];
570             // For simplicity and correctness we defer handling of
571
// newlines (including previous character) to read().
572
if (ch == '\n' || ch == '\r')
573               break;
574             p++;
575           }
576         to_do -= p - pos;
577         pos = p;
578           }
579       }
580     return n;
581       }
582   }
583
584   public boolean ready () throws java.io.IOException JavaDoc
585   {
586     return pos < limit || in.ready();
587   }
588
589   /** Same as skip(), but assumes previous command was a non-EOF peek(). */
590   public final void skip_quick () throws java.io.IOException JavaDoc
591   {
592     pos++;
593   }
594
595   public void skip () throws java.io.IOException JavaDoc
596   {
597     read();
598   }
599
600   static int countLines (char[] buffer, int start, int limit)
601   {
602     int count = 0;
603     char prev = '\0';
604     for (int i = start; i < limit; i++)
605       {
606     char ch = buffer[i];
607     if ((ch == '\n' && prev != '\r') || ch == '\r')
608       count++;
609     prev = ch;
610       }
611     return count;
612   }
613
614   /** Skips the rest of the current line, including the line terminator. */
615   public void skipRestOfLine ()
616        throws java.io.IOException JavaDoc
617   {
618     for (;;)
619       {
620         int c = read();
621         if (c < 0)
622           return;
623         if (c == '\r')
624           {
625             c = read();
626             if (c >= 0 && c != '\n')
627               unread();
628             break;
629           }
630         else if (c == '\n')
631           break;
632       }
633   }
634
635   /* Move one character backwards. */
636   public void unread ()
637        throws java.io.IOException JavaDoc
638   {
639     if (pos == 0)
640       throw new java.io.IOException JavaDoc("unread too much");
641     pos--;
642     char ch = buffer[pos];
643     if (ch == '\n' || ch == '\r')
644       {
645     if (pos > 0 && ch == '\n' && getConvertCR() && buffer[pos-1] == '\r')
646       pos--;
647     if (pos < lineStartPos)
648       {
649         lineNumber--;
650         int i;
651         for (i = pos; i > 0; )
652           {
653         ch = buffer[--i];
654         if (ch == '\r' || ch == '\n')
655           {
656             i++;
657             break;
658           }
659           }
660         lineStartPos = i;
661       }
662       }
663   }
664
665   /** Same as unread, but only allowed after non-EOF-returning read().
666    * Also allowed after an intervening peek(), but only if the read()
667    * did not return '\r' or '\n'. */

668   public void unread_quick ()
669   {
670     pos--;
671   }
672
673   public int peek ()
674        throws java.io.IOException JavaDoc
675   {
676     if (pos < limit && pos > 0)
677       {
678     char ch = buffer[pos - 1];
679     if (ch != '\n' && ch != '\r')
680       {
681         ch = buffer[pos];
682         if (ch == '\r' && getConvertCR())
683           ch = '\n';
684         return ch;
685       }
686       }
687     int c = read ();
688     if (c >= 0)
689       unread_quick();
690     return c;
691   }
692
693 }
694
Popular Tags