1 52 53 package com.go.teaservlet.util; 54 55 import java.util.*; 56 import java.io.InputStream ; 57 import java.io.Reader ; 58 import java.io.IOException ; 59 import java.io.Serializable ; 60 import com.go.tea.compiler.SourceInfo; 61 import com.go.trove.io.SourceReader; 62 63 128 public class PropertyParser { 129 136 private Map mMap; 137 138 private Vector mListeners = new Vector(1); 139 private int mErrorCount = 0; 140 141 private Scanner mScanner; 142 143 146 public PropertyParser(Map map) { 147 mMap = map; 148 } 149 150 public void addErrorListener(ErrorListener listener) { 151 mListeners.addElement(listener); 152 } 153 154 public void removeErrorListener(ErrorListener listener) { 155 mListeners.removeElement(listener); 156 } 157 158 private void dispatchParseError(ErrorEvent e) { 159 mErrorCount++; 160 161 synchronized (mListeners) { 162 for (int i = 0; i < mListeners.size(); i++) { 163 ((ErrorListener)mListeners.elementAt(i)).parseError(e); 164 } 165 } 166 } 167 168 private void error(String str, SourceInfo info) { 169 dispatchParseError(new ErrorEvent(this, str, info)); 170 } 171 172 private void error(String str, Token token) { 173 error(str, token.getSourceInfo()); 174 } 175 176 180 public void parse(Reader reader) throws IOException { 181 mScanner = new Scanner(reader); 182 183 mScanner.addErrorListener(new ErrorListener() { 184 public void parseError(ErrorEvent e) { 185 dispatchParseError(e); 186 } 187 }); 188 189 try { 190 parseProperties(); 191 } 192 finally { 193 mScanner.close(); 194 } 195 } 196 197 private void parseProperties() throws IOException { 198 Token token; 199 while ((token = peek()).getId() != Token.EOF) { 200 switch (token.getId()) { 201 202 case Token.KEY: 203 case Token.LBRACE: 204 case Token.COMMENT: 205 parsePropertyList(null); 206 break; 207 208 case Token.RBRACE: 209 token = read(); 210 error("No matching left brace", token); 211 break; 212 213 default: 214 token = read(); 215 error("Unexpected token: " + token.getValue(), token); 216 break; 217 } 218 } 219 } 220 221 private void parsePropertyList(String keyPrefix) throws IOException { 222 Token token; 223 224 loop: 225 while ((token = peek()).getId() != Token.EOF) { 226 switch (token.getId()) { 227 228 case Token.KEY: 229 token = read(); 230 parseProperty(keyPrefix, token); 231 break; 232 233 case Token.COMMENT: 234 read(); 235 break; 236 237 case Token.LBRACE: 238 read(); 239 error("Nested properties must have a base name", token); 240 parseBlock(keyPrefix); 241 break; 242 243 default: 244 break loop; 245 } 246 } 247 } 248 249 private void parseProperty(String keyPrefix, Token token) 250 throws IOException { 251 252 String key = token.getValue(); 253 if (keyPrefix != null) { 254 key = keyPrefix + key; 255 } 256 257 String value = null; 258 259 if (peek().getId() == Token.VALUE) { 260 token = read(); 261 value = token.getValue(); 262 } 263 264 if (peek().getId() == Token.LBRACE) { 265 read(); 266 parseBlock(key + '.'); 267 } 268 else if (value == null) { 269 value = ""; 270 } 271 272 if (value != null) { 273 putProperty(key, value, token); 274 } 275 } 276 277 private void parseBlock(String keyPrefix) throws IOException { 279 parsePropertyList(keyPrefix); 280 281 Token token; 282 if ((token = peek()).getId() == Token.RBRACE) { 283 read(); 284 } 285 else { 286 error("Right brace expected", token); 287 } 288 } 289 290 private void putProperty(String key, String value, Token token) { 291 if (mMap.containsKey(key)) { 292 error("Property \"" + key + "\" already defined", token); 293 } 294 mMap.put(key, value); 295 } 296 297 300 public int getErrorCount() { 301 return mErrorCount; 302 } 303 304 private Token read() throws IOException { 305 return mScanner.readToken(); 306 } 307 308 private Token peek() throws IOException { 309 return mScanner.peekToken(); 310 } 311 312 private void unread(Token token) throws IOException { 313 mScanner.unreadToken(token); 314 } 315 316 322 public static interface ErrorListener extends java.util.EventListener { 323 public void parseError(ErrorEvent e); 324 } 325 326 332 public static class ErrorEvent extends java.util.EventObject { 333 private String mErrorMsg; 334 private SourceInfo mInfo; 335 336 ErrorEvent(Object source, String errorMsg, SourceInfo info) { 337 super(source); 338 mErrorMsg = errorMsg; 339 mInfo = info; 340 } 341 342 public String getErrorMessage() { 343 return mErrorMsg; 344 } 345 346 349 public String getDetailedErrorMessage() { 350 String prepend = getSourceInfoMessage(); 351 if (prepend == null || prepend.length() == 0) { 352 return mErrorMsg; 353 } 354 else { 355 return prepend + ": " + mErrorMsg; 356 } 357 } 358 359 public String getSourceInfoMessage() { 360 if (mInfo == null) { 361 return ""; 362 } 363 else { 364 return String.valueOf(mInfo.getLine()); 365 } 366 } 367 368 373 public SourceInfo getSourceInfo() { 374 return mInfo; 375 } 376 } 377 378 384 private static class Token implements java.io.Serializable { 385 public final static int UNKNOWN = 0; 386 public final static int EOF = 1; 387 388 public final static int COMMENT = 2; 389 public final static int KEY = 3; 390 public final static int VALUE = 4; 391 392 public final static int LBRACE = 5; 393 public final static int RBRACE = 6; 394 395 private final static int LAST_ID = 6; 396 397 private int mTokenId; 398 private String mValue; 399 private SourceInfo mInfo; 400 401 Token(int sourceLine, 402 int sourceStartPos, 403 int sourceEndPos, 404 int tokenId, 405 String value) { 406 407 mTokenId = tokenId; 408 mValue = value; 409 410 if (tokenId > LAST_ID) { 411 throw new IllegalArgumentException ("Token Id out of range: " + 412 tokenId); 413 } 414 415 mInfo = new SourceInfo(sourceLine, sourceStartPos, sourceEndPos); 416 417 if (sourceStartPos > sourceEndPos) { 418 throw new IllegalArgumentException 420 ("Token start position greater than " + 421 "end position at line: " + sourceLine); 422 } 423 } 424 425 public Token(SourceInfo info, int tokenId, String value) { 426 mTokenId = tokenId; 427 428 if (tokenId > LAST_ID) { 429 throw new IllegalArgumentException ("Token Id out of range: " + 430 tokenId); 431 } 432 433 mInfo = info; 434 } 435 436 public final int getId() { 437 return mTokenId; 438 } 439 440 444 public String getCode() { 445 return Code.TOKEN_CODES[mTokenId]; 446 } 447 448 public final SourceInfo getSourceInfo() { 449 return mInfo; 450 } 451 452 public String getValue() { 453 return mValue; 454 } 455 456 public String toString() { 457 StringBuffer buf = new StringBuffer (10); 458 459 String image = getCode(); 460 461 if (image != null) { 462 buf.append(image); 463 } 464 465 String str = getValue(); 466 467 if (str != null) { 468 if (image != null) { 469 buf.append(' '); 470 } 471 buf.append('"'); 472 buf.append(str); 473 buf.append('"'); 474 } 475 476 return buf.toString(); 477 } 478 479 private static class Code { 480 public static final String [] TOKEN_CODES = 481 { 482 "UNKNOWN", 483 "EOF", 484 485 "COMMENT", 486 "KEY", 487 "VALUE", 488 489 "LBRACE", 490 "RBRACE", 491 }; 492 } 493 } 494 495 501 private static class Scanner { 502 private SourceReader mSource; 503 504 505 private Stack mLookahead = new Stack(); 506 507 private boolean mScanKey = true; 508 private Token mEOFToken; 509 510 private Vector mListeners = new Vector(1); 511 private int mErrorCount = 0; 512 513 public Scanner(Reader in) { 514 mSource = new SourceReader(in, null, null); 515 } 516 517 public void addErrorListener(ErrorListener listener) { 518 mListeners.addElement(listener); 519 } 520 521 public void removeErrorListener(ErrorListener listener) { 522 mListeners.removeElement(listener); 523 } 524 525 private void dispatchParseError(ErrorEvent e) { 526 mErrorCount++; 527 528 synchronized (mListeners) { 529 for (int i = 0; i < mListeners.size(); i++) { 530 ((ErrorListener)mListeners.elementAt(i)).parseError(e); 531 } 532 } 533 } 534 535 private void error(String str, SourceInfo info) { 536 dispatchParseError(new ErrorEvent(this, str, info)); 537 } 538 539 private void error(String str) { 540 error(str, new SourceInfo(mSource.getLineNumber(), 541 mSource.getStartPosition(), 542 mSource.getEndPosition())); 543 } 544 545 548 public synchronized Token readToken() throws IOException { 549 if (mLookahead.empty()) { 550 return scanToken(); 551 } 552 else { 553 return (Token)mLookahead.pop(); 554 } 555 } 556 557 560 public synchronized Token peekToken() throws IOException { 561 if (mLookahead.empty()) { 562 return (Token)mLookahead.push(scanToken()); 563 } 564 else { 565 return (Token)mLookahead.peek(); 566 } 567 } 568 569 public synchronized void unreadToken(Token token) throws IOException { 570 mLookahead.push(token); 571 } 572 573 public void close() throws IOException { 574 mSource.close(); 575 } 576 577 public int getErrorCount() { 578 return mErrorCount; 579 } 580 581 private Token scanToken() throws IOException { 582 if (mSource.isClosed()) { 583 if (mEOFToken == null) { 584 mEOFToken = makeToken(Token.EOF, null); 585 } 586 587 return mEOFToken; 588 } 589 590 int c; 591 int peek; 592 593 int startPos; 594 595 while ( (c = mSource.read()) != -1 ) { 596 switch (c) { 597 598 case SourceReader.ENTER_CODE: 599 case SourceReader.ENTER_TEXT: 600 continue; 601 602 case '#': 603 case '!': 604 mScanKey = true; 605 return scanComment(); 606 607 case '{': 608 mScanKey = true; 609 return makeToken(Token.LBRACE, "{"); 610 case '}': 611 mScanKey = true; 612 return makeToken(Token.RBRACE, "}"); 613 614 case '0': case '1': case '2': case '3': case '4': 615 case '5': case '6': case '7': case '8': case '9': 616 case 'a': case 'b': case 'c': case 'd': case 'e': 617 case 'f': case 'g': case 'h': case 'i': case 'j': 618 case 'k': case 'l': case 'm': case 'n': case 'o': 619 case 'p': case 'q': case 'r': case 's': case 't': 620 case 'u': case 'v': case 'w': case 'x': case 'y': 621 case 'z': case '.': 622 case 'A': case 'B': case 'C': case 'D': case 'E': 623 case 'F': case 'G': case 'H': case 'I': case 'J': 624 case 'K': case 'L': case 'M': case 'N': case 'O': 625 case 'P': case 'Q': case 'R': case 'S': case 'T': 626 case 'U': case 'V': case 'W': case 'X': case 'Y': 627 case 'Z': case '_': 628 mSource.unread(); 629 return scanKeyOrValue(); 630 631 case '\n': 632 mScanKey = true; 633 case ' ': 635 case '\0': 636 case '\t': 637 continue; 638 639 default: 640 if (Character.isWhitespace((char)c)) { 641 continue; 642 } 643 else { 644 mSource.unread(); 645 return scanKeyOrValue(); 646 } 647 } 648 } 649 650 if (mEOFToken == null) { 651 mEOFToken = makeToken(Token.EOF, null); 652 } 653 654 return mEOFToken; 655 } 656 657 private Token scanKeyOrValue() throws IOException { 658 StringBuffer buf = new StringBuffer (40); 659 boolean trim = true; 660 661 int startLine = mSource.getLineNumber(); 662 int startPos = mSource.getStartPosition(); 663 int endPos = mSource.getEndPosition(); 664 665 boolean skipWhitespace = true; 666 boolean skipSeparator = true; 667 668 int c; 669 loop: 670 while ( (c = mSource.read()) != -1 ) { 671 switch (c) { 672 673 case '\n': 674 mSource.unread(); 675 break loop; 676 677 case '\\': 678 int next = mSource.read(); 679 if (next == -1 || next == '\n') { 680 skipWhitespace = true; 682 continue; 683 } 684 685 c = processEscape(c, next); 686 skipWhitespace = false; 687 break; 688 689 case '{': 690 case '}': 691 mSource.unread(); 692 break loop; 693 694 case '=': 695 case ':': 696 if (mScanKey) { 697 mSource.unread(); 698 break loop; 699 } 700 else if (skipSeparator) { 701 skipSeparator = false; 702 continue; 703 } 704 skipWhitespace = false; 705 break; 706 707 case '\'': 708 case '"': 709 if (buf.length() == 0) { 710 scanStringLiteral(c, buf); 711 endPos = mSource.getEndPosition(); 712 trim = false; 713 break loop; 714 } 715 case '0': case '1': case '2': case '3': case '4': 717 case '5': case '6': case '7': case '8': case '9': 718 case 'a': case 'b': case 'c': case 'd': case 'e': 719 case 'f': case 'g': case 'h': case 'i': case 'j': 720 case 'k': case 'l': case 'm': case 'n': case 'o': 721 case 'p': case 'q': case 'r': case 's': case 't': 722 case 'u': case 'v': case 'w': case 'x': case 'y': 723 case 'z': case '.': 724 case 'A': case 'B': case 'C': case 'D': case 'E': 725 case 'F': case 'G': case 'H': case 'I': case 'J': 726 case 'K': case 'L': case 'M': case 'N': case 'O': 727 case 'P': case 'Q': case 'R': case 'S': case 'T': 728 case 'U': case 'V': case 'W': case 'X': case 'Y': 729 case 'Z': case '_': 730 skipWhitespace = false; 731 break; 732 733 case ' ': 734 case '\0': 735 case '\t': 736 if (skipWhitespace) { 737 continue; 738 } 739 if (mScanKey) { 740 break loop; 741 } 742 break; 743 744 default: 745 if (Character.isWhitespace((char)c)) { 746 if (skipWhitespace) { 747 continue; 748 } 749 if (mScanKey) { 750 break loop; 751 } 752 } 753 else { 754 skipWhitespace = false; 755 } 756 break; 757 } 758 759 buf.append((char)c); 760 endPos = mSource.getEndPosition(); 761 skipSeparator = false; 762 } 763 764 int tokenId; 765 if (mScanKey) { 766 tokenId = Token.KEY; 767 mScanKey = false; 768 } 769 else { 770 tokenId = Token.VALUE; 771 mScanKey = true; 772 } 773 774 String value = buf.toString(); 775 776 if (trim) { 777 value = value.trim(); 778 } 779 780 return new Token(startLine, startPos, endPos, tokenId, value); 781 } 782 783 private Token scanComment() throws IOException { 784 StringBuffer buf = new StringBuffer (40); 785 786 int startLine = mSource.getLineNumber(); 787 int startPos = mSource.getStartPosition(); 788 int endPos = mSource.getEndPosition(); 789 790 int c; 791 while ( (c = mSource.peek()) != -1 ) { 792 if (c == '\n') { 793 break; 794 } 795 796 mSource.read(); 797 buf.append((char)c); 798 799 endPos = mSource.getEndPosition(); 800 } 801 802 return new Token(startLine, startPos, endPos, 803 Token.COMMENT, buf.toString()); 804 } 805 806 private void scanStringLiteral(int quote, StringBuffer buf) 807 throws IOException { 808 809 int c; 810 while ( (c = mSource.read()) != -1 ) { 811 if (c == quote) { 812 return; 813 } 814 815 if (c == '\\') { 816 int next = mSource.read(); 817 if (next == -1 || next == '\n') { 818 continue; 820 } 821 c = processEscape(c, next); 822 } 823 824 buf.append((char)c); 825 } 826 } 827 828 private int processEscape(int c, int next) { 829 switch (next) { 830 case '0': 831 return '\0'; 832 case 't': 833 return '\t'; 834 case 'n': 835 return '\n'; 836 case 'f': 837 return '\f'; 838 case 'r': 839 return '\r'; 840 841 case '\\': 842 case '\'': 843 case '\"': 844 case '=': 845 case ':': 846 case '{': 847 case '}': 848 return next; 849 850 default: 851 error("Invalid escape code: \\" + (char)next); 852 return next; 853 } 854 } 855 856 private Token makeToken(int Id, String value) { 857 return new Token(mSource.getLineNumber(), 858 mSource.getStartPosition(), 859 mSource.getEndPosition(), 860 Id, value); 861 } 862 } 863 } 864 | Popular Tags |