001    // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 
002    // The Molly framework is freely distributable under the terms of an
003    // MIT-style license. For details, see the molly pages web site at:
004    // http://www.mollypages.org/. Use, modify, have fun !
005    
006    package fc.util.pagetemplate;
007    
008    import java.io.*;
009    import java.util.*;
010    
011    import fc.io.*;
012    import fc.util.*;
013    
014    /**
015    A Reader suitable for lexing. Supports all of: <code>peek</code>,
016    <code>read</code> and <code>unread</code>. (no JDK 1.5 reader class has
017    all of those). Mark/reset is not supported because it's too complex to
018    implement given the current <i>fixed-buffer</i> implementation of this
019    class. (on the flip-side this implementation does allow to read
020    <i>very</i> large files without risk of running out of JDK memory).
021    <p>
022    Note 1: If this class is invoked from the command line, setting the dbg
023    flag in the code to <code>true</code> is useful.
024    
025    @author hursh jain
026    */
027    public final class TemplateReader extends Reader
028    {
029    static final boolean dbg = false;
030    
031    //our own buf/pos because most/all reader subclasses dont have mark/reset/unread  
032    char[]      buf       = null;
033    int       pos       = 0;
034    int       count       = 0;
035    int       markpos     = 0;
036    //line, col and other tracking 
037    int       line      = 1;
038    int       col       = 0;
039    int       lastcol     = 1;  //for unread past a newline
040    boolean     pushBackNL    = false;
041    boolean     lastWasCR   = false;
042    String      encoding;
043    static String DEFAULT_ENCODING = TemplatePage.DEFAULT_ENCODING;
044    
045    /** 
046    Creates a new TemplateReader wrapping the specified reader
047    */
048    public TemplateReader(Reader r) throws IOException
049      {
050      Argcheck.notnull(r, "specified reader was null");
051      buf = IOUtil.readerToCharArray(r);
052      this.encoding = DEFAULT_ENCODING;
053      }
054    
055    /**
056    Creates a reader with the specified non-null encoding.
057    */
058    public TemplateReader(File file, String encoding)  throws IOException
059      {
060      Argcheck.notnull(file, "specified file was null");
061      Argcheck.notnull(encoding, "specified encoding was null");
062      this.encoding = encoding;
063      buf = IOUtil.fileToCharArray(file, encoding);
064      }
065    
066    /**
067    Creates a reader using the UTF-8 encoding.
068    */
069    public TemplateReader(File file)  throws IOException
070      {
071      this(file, DEFAULT_ENCODING);
072      }
073    
074    public void close() throws IOException
075      {
076      //no underlying stream since everything read into buffer. not much to do.
077      }
078      
079    public int read() throws IOException
080      {
081      if (pos == buf.length) {
082        return -1;
083        }
084        
085      char c = buf[pos++];  
086      
087      if (dbg) System.out.println(">>>>>>>> DEBUG: read() from BUF, c=" + StringUtil.viewableAscii(c));
088      adjustReadLineNum(c);
089    
090      return c;
091      }
092    
093    public int read(char[] buf, int start, int len) throws IOException
094      {
095      throw new IOException("not implemented, use the read() method instead");
096      }
097    
098    /**
099    Unreads the current character (which could be EOF) so that the next read will 
100    return the current character (or EOF) again.
101    */
102    public void unread() throws IOException
103      {
104      char c = 0;
105      
106      if (pos == 0) 
107        {
108        throw new IOException("I am at the beginning of the stream. Cannot unread anything because nothing has been read so far");
109        }
110      else{ 
111        c = buf[--pos];
112        if (dbg) System.out.println(">>>>>>>> DEBUG: unread() from BUF, c=" + StringUtil.viewableAscii(c));
113        }
114      
115      adjustUnreadLineNum(c);
116      }
117    
118    /**
119    Unreads the specified number of characters
120    */
121    public void unread(int count)  throws IOException
122      {
123      for (int n = 0; n < count; n++) {
124        unread();
125        }
126      }
127    
128    /**
129    Useful for inserting included files into the stream and then parsing that content in-line
130    with the rest of the file.
131    */
132    public void insertIntoStream(File file) throws IOException
133      {
134      char[] insert = IOUtil.fileToCharArray(file, encoding);
135    
136      char[] result = new char[buf.length + insert.length];
137      System.arraycopy(buf, 0, result, 0, pos);
138      System.arraycopy(insert, 0, result, pos, insert.length);
139      System.arraycopy(buf, pos, result, pos+insert.length, buf.length-pos);
140      
141      buf = result;
142      }
143    
144    /**
145    Useful for inserting included files into the stream and then parsing that content in-line
146    with the rest of the file.
147    */
148    public void insertIntoStream(Reader r) throws IOException
149      {
150      char[] insert = IOUtil.readerToCharArray(r);
151    
152      char[] result = new char[buf.length + insert.length];
153      System.arraycopy(buf, 0, result, 0, pos);
154      System.arraycopy(insert, 0, result, pos, insert.length);
155      System.arraycopy(buf, pos, result, pos+insert.length, buf.length-pos);
156      
157      buf = result;
158      }
159    
160    
161    void adjustReadLineNum(char c)
162      {
163      // we can read: \r, \r\n , \n all of which increase line count by exactly 1
164      switch (c) 
165        {
166        case '\n': 
167          if (! lastWasCR) {
168            line++;
169            lastcol=col;
170            col=1;
171            }
172          else {
173            lastWasCR = false;
174            }
175          break;
176        
177        case '\r': 
178          line++;
179          lastcol=col;
180          col=1;
181          lastWasCR = true;
182          break;
183        
184        case '\t':
185          col = col + 4;
186          break;
187        
188        default:
189          col++;
190        }
191      }
192    
193    
194    void adjustUnreadLineNum(char c)
195      {
196      // we can unread: \r, \r\n , \n all of which reduce line count by exactly 1
197      switch (c) {
198        case '\n': 
199          pushBackNL = true;
200          line--;
201          col=lastcol;
202          break;
203        case '\r': 
204          if (! pushBackNL) { 
205            line--;
206            col=lastcol;
207            }
208          else{
209            pushBackNL = false;
210            }
211          break;
212        case '\t':
213          col = col - 4;
214          break;
215        default:
216          col--;
217        }
218      }
219    
220    public int peek() throws IOException
221      {
222      return buf[pos];
223      }
224    
225    
226    /**
227    Skips all whitespace characters such that the next {@link read} will
228    return the <b>next</b> non-whitespace character (or EOF if there are no
229    more characters).
230    */
231    public void skipWhitespace() throws IOException
232      {
233      int c = -1;
234      while (true)
235        {
236        c = read();
237        
238        if (c == -1) {
239          break;
240          }
241        
242        if (! Character.isWhitespace(c)) {
243          unread();
244          break;
245          }
246        }
247      }
248      
249    
250    
251    /**
252    Tries to read/consumes the specified char and returns true
253    if successful. If the specified char is not found, does not
254    consume anything and returns false.
255    */
256    public boolean match(int target) throws IOException
257      {
258      int c = read();
259      
260      if (c == target)
261        return true;
262      else
263        unread();
264      
265      return false;
266      }
267    
268    /**
269    Tries to read/consumes the specified non-null string and returns true
270    if successful. If the specified string is not found, does not
271    consume anything and returns false.
272    */
273    public boolean match(String target) throws IOException
274      {
275      if (target == null)
276        throw new IllegalArgumentException("Specified target string was null");
277      
278      int c = -1;
279      for (int i = 0; i < target.length(); i++)
280        {
281        c = read();
282        
283        if ( c == -1 || c != target.charAt(i)) {
284          unread(i+1);
285          return false;
286          }
287        }
288      
289      return true;
290      }
291    
292    public boolean matchIgnoreCase(String target) throws IOException
293      {
294      if (target == null)
295        throw new IllegalArgumentException("Specified target string was null");
296      
297      int c = -1;
298      for (int i = 0; i < target.length(); i++)
299        {
300        c = read();
301        
302        if ( c == -1 || c != Character.toLowerCase(target.charAt(i))) {
303          unread(i+1);
304          return false;
305          }
306    
307        }
308      
309      return true;
310      }
311    
312    public boolean markSupported()
313      {
314      return false;
315      }
316    
317    public  int getLine() { 
318      return line; 
319      }
320      
321    public  int getCol() { 
322      return col; 
323      }
324    
325    char[] getBuf() { return buf; }
326    int getPos() { return pos; }
327    
328    //other utility methods
329    
330    public static void main (String args[]) throws IOException
331      {
332      //CHANGE CHAR BUFFER TO A SMALL VALUE FOR TESTING */
333      StringReader sr = null;
334      TemplateReader lex = null;
335      int c = -1;
336      
337      System.out.println("Reading an empty string....."); 
338      sr = new StringReader("");  
339      lex = new TemplateReader(sr);
340      while ( (c = lex.read()) != -1) {
341        testprint(lex, c);
342        }
343      
344      System.out.println("----------------- TEST 2 --------------");
345      sr = new StringReader("abc");   
346      lex = new TemplateReader(sr);
347      while ( (c = lex.read()) != -1) {
348        testprint(lex, c);
349        //System.out.print(c + " ");
350        }
351    
352      System.out.println("----------------- TEST 3 --------------");
353      sr = new StringReader("abcde");   
354      lex = new TemplateReader(sr);
355      try {
356        c = lex.read();
357        testprint(lex, c);
358        lex.unread();
359        testprint(lex, -10);
360        lex.unread();
361        testprint(lex, -10);
362        c = lex.read();
363        testprint(lex, c);
364        }
365      catch (Exception e) {
366        e.printStackTrace();
367        }
368    
369      System.out.println("----------------- TEST 4 --------------");
370      sr = new StringReader("abcd\ne");   
371      lex = new TemplateReader(sr);
372      try {
373        c = lex.read();
374        testprint(lex, c);
375        lex.unread();
376        testprint(lex, -10);
377    
378        for (int i = 0; i < 5; i++) {
379          c = lex.read();
380          testprint(lex, c);
381          }
382    
383        for (int i = 0; i < 5; i++) {
384          lex.unread();
385          testprint(lex, -10);
386          }
387        
388        for (int i = 0; i < 5; i++) {
389          c = lex.read();
390          testprint(lex, c);
391          }
392        
393        c = lex.read();
394        testprint(lex, c);
395        }
396      catch (Exception e) {
397        e.printStackTrace();
398        }
399    
400      System.out.println("----------------- TEST 5 --------------");
401      sr = new StringReader("abcd\r\ne");   
402      lex = new TemplateReader(sr);
403      try {
404        c = lex.read();
405        testprint(lex, c, lex.peek());
406        lex.unread();
407        testprint(lex, -10, lex.peek());
408    
409        for (int i = 0; i < 5; i++) {
410          c = lex.read();
411          testprint(lex, c, lex.peek());
412          }
413    
414        for (int i = 0; i < 5; i++) {
415          lex.unread();
416          testprint(lex, -10, lex.peek());
417          }
418        
419        for (int i = 0; i < 5; i++) {
420          c = lex.read();
421          testprint(lex, c, lex.peek());
422          }
423        
424        c = lex.read();
425        testprint(lex, c, lex.peek());
426        }
427      catch (Exception e) {
428        e.printStackTrace();
429        }
430    
431      System.out.println("--------- TEST 6 ---(insert into stream middle)-------");
432      sr = new StringReader("abc"); 
433      lex = new TemplateReader(sr);
434      
435      try {
436        c = lex.read();
437        testprint(lex, c);
438      
439        StringReader insert = new StringReader("123");
440        System.out.println("inserting \"123\" into the stream\n");
441        lex.insertIntoStream(insert);
442    
443        while ( (c = lex.read()) != -1) {
444          testprint(lex, c);
445          }
446        }
447      catch (Exception e) {
448        e.printStackTrace();
449        }
450    
451    
452      System.out.println("--------- TEST 7 ---(insert into stream begin)-------");
453      sr = new StringReader("abc"); 
454      lex = new TemplateReader(sr);
455      
456      try {
457        StringReader insert = new StringReader("123");
458        System.out.println("inserting \"123\" into the beginning of stream\n");
459        lex.insertIntoStream(insert);
460    
461        while ( (c = lex.read()) != -1) {
462          testprint(lex, c);
463          }
464        }
465      catch (Exception e) {
466        e.printStackTrace();
467        }
468    
469      System.out.println("--------- TEST 8 ---(insert into stream end)-------");
470      sr = new StringReader("abc"); 
471      lex = new TemplateReader(sr);
472      
473      try {
474        while ( (c = lex.read()) != -1) {
475          testprint(lex, c);
476          }
477        StringReader insert = new StringReader("123");
478        System.out.println("inserting \"123\" into the end of the stream\n");
479        lex.insertIntoStream(insert);
480    
481        while ( (c = lex.read()) != -1) {
482          testprint(lex, c);
483          }
484        }
485      catch (Exception e) {
486        e.printStackTrace();
487        }
488    
489      }
490    
491    private static void testprint(TemplateReader lex, int c, int peek)
492      {
493      if (c == -1) {
494        System.out.println("====> recieved EOF (-1) from read().......");
495        }
496    
497      System.out.format(
498        "buf=%s, pos=%d, buflen=%d\nline=%d, col=%d, char=[%s]",
499        StringUtil.arrayToString(lex.getBuf()), lex.getPos(), lex.getBuf().length,
500        lex.getLine(), lex.getCol(), 
501        (c == -10) ? "N/A" : StringUtil.viewableAscii((char)c));  
502    
503      if (peek != -2)  
504        System.out.format(", peek=[%s]", StringUtil.viewableAscii((char)peek));
505    
506      System.out.print("\n\n");
507      }
508    
509    private static void testprint(TemplateReader lex, int c)
510      {
511      testprint(lex, c, -2);
512      }
513    
514    }