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