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
006package fc.util.pagetemplate;
007
008import java.io.*;
009import java.util.*;
010
011import fc.io.*;
012import fc.util.*;
013
014/**
015A 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
017all of those). Mark/reset is not supported because it's too complex to
018implement given the current <i>fixed-buffer</i> implementation of this
019class. (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>
022Note 1: If this class is invoked from the command line, setting the dbg
023flag in the code to <code>true</code> is useful.
024
025@author hursh jain
026*/
027public final class TemplateReader extends Reader
028{
029static final boolean dbg = false;
030
031//our own buf/pos because most/all reader subclasses dont have mark/reset/unread  
032char[]      buf       = null;
033int       pos       = 0;
034int       count       = 0;
035int       markpos     = 0;
036//line, col and other tracking 
037int       line      = 1;
038int       col       = 0;
039int       lastcol     = 1;  //for unread past a newline
040boolean     pushBackNL    = false;
041boolean     lastWasCR   = false;
042String      encoding;
043static String DEFAULT_ENCODING = TemplatePage.DEFAULT_ENCODING;
044
045/** 
046Creates a new TemplateReader wrapping the specified reader
047*/
048public 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/**
056Creates a reader with the specified non-null encoding.
057*/
058public 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/**
067Creates a reader using the UTF-8 encoding.
068*/
069public TemplateReader(File file)  throws IOException
070  {
071  this(file, DEFAULT_ENCODING);
072  }
073
074public void close() throws IOException
075  {
076  //no underlying stream since everything read into buffer. not much to do.
077  }
078  
079public 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
093public 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/**
099Unreads the current character (which could be EOF) so that the next read will 
100return the current character (or EOF) again.
101*/
102public 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/**
119Unreads the specified number of characters
120*/
121public void unread(int count)  throws IOException
122  {
123  for (int n = 0; n < count; n++) {
124    unread();
125    }
126  }
127
128/**
129Useful for inserting included files into the stream and then parsing that content in-line
130with the rest of the file.
131*/
132public 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/**
145Useful for inserting included files into the stream and then parsing that content in-line
146with the rest of the file.
147*/
148public 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
161void 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
194void 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
220public int peek() throws IOException
221  {
222  return buf[pos];
223  }
224
225
226/**
227Skips all whitespace characters such that the next {@link read} will
228return the <b>next</b> non-whitespace character (or EOF if there are no
229more characters).
230*/
231public 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/**
252Tries to read/consumes the specified char and returns true
253if successful. If the specified char is not found, does not
254consume anything and returns false.
255*/
256public 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/**
269Tries to read/consumes the specified non-null string and returns true
270if successful. If the specified string is not found, does not
271consume anything and returns false.
272*/
273public 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
292public 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
312public boolean markSupported()
313  {
314  return false;
315  }
316
317public  int getLine() { 
318  return line; 
319  }
320  
321public  int getCol() { 
322  return col; 
323  }
324
325char[] getBuf() { return buf; }
326int getPos() { return pos; }
327
328//other utility methods
329
330public 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
491private 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
509private static void testprint(TemplateReader lex, int c)
510  {
511  testprint(lex, c, -2);
512  }
513
514}