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}