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 }