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 }