Mercurial > hg > blitz_condensed
comparison src/com/go/trove/util/PropertyParser.java @ 0:3dc0c5604566
Initial checkin of blitz 2.0 fcs - no installer yet.
author | Dan Creswell <dan.creswell@gmail.com> |
---|---|
date | Sat, 21 Mar 2009 11:00:06 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:3dc0c5604566 |
---|---|
1 /* ==================================================================== | |
2 * Trove - Copyright (c) 1997-2000 Walt Disney Internet Group | |
3 * ==================================================================== | |
4 * The Tea Software License, Version 1.1 | |
5 * | |
6 * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved. | |
7 * | |
8 * Redistribution and use in source and binary forms, with or without | |
9 * modification, are permitted provided that the following conditions | |
10 * are met: | |
11 * | |
12 * 1. Redistributions of source code must retain the above copyright | |
13 * notice, this list of conditions and the following disclaimer. | |
14 * | |
15 * 2. Redistributions in binary form must reproduce the above copyright | |
16 * notice, this list of conditions and the following disclaimer in | |
17 * the documentation and/or other materials provided with the | |
18 * distribution. | |
19 * | |
20 * 3. The end-user documentation included with the redistribution, | |
21 * if any, must include the following acknowledgment: | |
22 * "This product includes software developed by the | |
23 * Walt Disney Internet Group (http://opensource.go.com/)." | |
24 * Alternately, this acknowledgment may appear in the software itself, | |
25 * if and wherever such third-party acknowledgments normally appear. | |
26 * | |
27 * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must | |
28 * not be used to endorse or promote products derived from this | |
29 * software without prior written permission. For written | |
30 * permission, please contact opensource@dig.com. | |
31 * | |
32 * 5. Products derived from this software may not be called "Tea", | |
33 * "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet", | |
34 * "Kettle", "Trove" or "BeanDoc" appear in their name, without prior | |
35 * written permission of the Walt Disney Internet Group. | |
36 * | |
37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |
38 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
39 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
40 * DISCLAIMED. IN NO EVENT SHALL THE WALT DISNEY INTERNET GROUP OR ITS | |
41 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
42 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
43 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
44 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
45 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
47 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
48 * ==================================================================== | |
49 * | |
50 * For more information about Tea, please see http://opensource.go.com/. | |
51 */ | |
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 /****************************************************************************** | |
64 * Parses a properties file similar to how {@link java.util.Properties} does, | |
65 * except: | |
66 * | |
67 * <ul> | |
68 * <li>Values have trailing whitespace trimmed. | |
69 * <li>Quotation marks ( " or ' ) can be used to define keys and values that | |
70 * have embedded spaces. | |
71 * <li>Quotation marks can also be used to define multi-line keys and values | |
72 * without having to use continuation characters. | |
73 * <li>Properties may be nested using braces '{' and '}'. | |
74 * </ul> | |
75 * | |
76 * Just like Properties, comment lines start with optional whitespace followed | |
77 * by a '#' or '!'. Keys and values may have an optional '=' or ':' as a | |
78 * separator, unicode escapes are supported as well as other common escapes. | |
79 * A line may end in a backslash so that it continues to the next line. | |
80 * Escapes for brace characters '{' and '}' are also supported. | |
81 * | |
82 * Example: | |
83 * | |
84 * <pre> | |
85 * # Properties file | |
86 * | |
87 * foo = bar | |
88 * foo.sub = blink | |
89 * empty | |
90 * | |
91 * block { | |
92 * inner { | |
93 * foo = bar | |
94 * item | |
95 * } | |
96 * next.item = "true" | |
97 * } | |
98 * | |
99 * section = test { | |
100 * level = 4 | |
101 * message = "Message: " | |
102 * } | |
103 * </pre> | |
104 * | |
105 * is equivalent to | |
106 * | |
107 * <pre> | |
108 * # Properties file | |
109 * | |
110 * foo = bar | |
111 * foo.sub = blink | |
112 * empty | |
113 * | |
114 * block.inner.foo = bar | |
115 * block.inner.item | |
116 * block.next.item = true | |
117 * | |
118 * section = test | |
119 * section.level = 4 | |
120 * section.message = Message: | |
121 * </pre> | |
122 * | |
123 * @author Brian S O'Neill | |
124 * @version | |
125 * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 12/11/00 <!-- $--> | |
126 */ | |
127 public class PropertyParser { | |
128 // Parsed grammer (EBNF) is: | |
129 // | |
130 // Properties ::= { PropertyList } | |
131 // PropertyList ::= { Property | COMMENT } | |
132 // Property ::= KEY [ VALUE ] [ Block ] | |
133 // Block ::= LBRACE PropertyList RBRACE | |
134 | |
135 private Map mMap; | |
136 | |
137 private Vector mListeners = new Vector(1); | |
138 private int mErrorCount = 0; | |
139 | |
140 private Scanner mScanner; | |
141 | |
142 /** | |
143 * @param map Map to receive properties | |
144 */ | |
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 /** | |
176 * Parses properties from the given reader and stores them in the Map. To | |
177 * capture any parsing errors, call addErrorListener prior to parsing. | |
178 */ | |
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 // When this is called, the LBRACE token has already been read. | |
277 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 /** | |
297 * Total number of errors accumulated by this PropertyParser instance. | |
298 */ | |
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 /************************************************************************** | |
316 * | |
317 * @author Brian S O'Neill | |
318 * @version | |
319 * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 12/11/00 <!-- $--> | |
320 */ | |
321 public static interface ErrorListener extends java.util.EventListener { | |
322 public void parseError(ErrorEvent e); | |
323 } | |
324 | |
325 /************************************************************************** | |
326 * | |
327 * @author Brian S O'Neill | |
328 * @version | |
329 * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 12/11/00 <!-- $--> | |
330 */ | |
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 /** | |
346 * Returns the error message prepended with source file information. | |
347 */ | |
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 /** | |
368 * This method reports on where in the source code an error was found. | |
369 * | |
370 * @return Source information on this error or null if not known. | |
371 */ | |
372 public SourceInfo getSourceInfo() { | |
373 return mInfo; | |
374 } | |
375 } | |
376 | |
377 /************************************************************************** | |
378 * | |
379 * @author Brian S O'Neill | |
380 * @version | |
381 * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 12/11/00 <!-- $--> | |
382 */ | |
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 // This is an internal error. | |
418 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 /** | |
440 * Token code is non-null, and is exactly the same as the name for | |
441 * its Id. | |
442 */ | |
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 /************************************************************************** | |
495 * | |
496 * @author Brian S O'Neill | |
497 * @version | |
498 * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 12/11/00 <!-- $--> | |
499 */ | |
500 private static class Scanner { | |
501 private SourceReader mSource; | |
502 | |
503 /** The scanner supports any amount of lookahead. */ | |
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 /** | |
545 * Returns EOF as the last token. | |
546 */ | |
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 /** | |
557 * Returns EOF as the last token. | |
558 */ | |
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 // fall through | |
633 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 // line continuation | |
680 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 // fall through | |
715 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 // line continuation | |
818 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 } |