KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > go > teaservlet > util > PropertyParser


1 /* ====================================================================
2  * TeaServlet - Copyright (c) 1999-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.teaservlet.util;
54
55 import java.util.*;
56 import java.io.InputStream JavaDoc;
57 import java.io.Reader JavaDoc;
58 import java.io.IOException JavaDoc;
59 import java.io.Serializable JavaDoc;
60 import com.go.tea.compiler.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:--> 8 <!-- $-->, <!--$$JustDate:--> 00/12/13 <!-- $-->
126  * @deprecated Moved to com.go.trove.util package.
127  */

128 public class PropertyParser {
129     // Parsed grammer (EBNF) is:
130
//
131
// Properties ::= { PropertyList }
132
// PropertyList ::= { Property | COMMENT }
133
// Property ::= KEY [ VALUE ] [ Block ]
134
// Block ::= LBRACE PropertyList RBRACE
135

136     private Map mMap;
137
138     private Vector mListeners = new Vector(1);
139     private int mErrorCount = 0;
140
141     private Scanner mScanner;
142
143     /**
144      * @param map Map to receive properties
145      */

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 JavaDoc str, SourceInfo info) {
169         dispatchParseError(new ErrorEvent(this, str, info));
170     }
171
172     private void error(String JavaDoc str, Token token) {
173         error(str, token.getSourceInfo());
174     }
175
176     /**
177      * Parses properties from the given reader and stores them in the Map. To
178      * capture any parsing errors, call addErrorListener prior to parsing.
179      */

180     public void parse(Reader JavaDoc reader) throws IOException JavaDoc {
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 JavaDoc {
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 JavaDoc keyPrefix) throws IOException JavaDoc {
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 JavaDoc keyPrefix, Token token)
250         throws IOException JavaDoc {
251
252         String JavaDoc key = token.getValue();
253         if (keyPrefix != null) {
254             key = keyPrefix + key;
255         }
256
257         String JavaDoc 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     // When this is called, the LBRACE token has already been read.
278
private void parseBlock(String JavaDoc keyPrefix) throws IOException JavaDoc {
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 JavaDoc key, String JavaDoc value, Token token) {
291         if (mMap.containsKey(key)) {
292             error("Property \"" + key + "\" already defined", token);
293         }
294         mMap.put(key, value);
295     }
296     
297     /**
298      * Total number of errors accumulated by this PropertyParser instance.
299      */

300     public int getErrorCount() {
301         return mErrorCount;
302     }
303
304     private Token read() throws IOException JavaDoc {
305         return mScanner.readToken();
306     }
307
308     private Token peek() throws IOException JavaDoc {
309         return mScanner.peekToken();
310     }
311
312     private void unread(Token token) throws IOException JavaDoc {
313         mScanner.unreadToken(token);
314     }
315
316     /**************************************************************************
317      *
318      * @author Brian S O'Neill
319      * @version
320      * <!--$$Revision:--> 8 <!-- $-->, <!--$$JustDate:--> 00/12/13 <!-- $-->
321      */

322     public static interface ErrorListener extends java.util.EventListener JavaDoc {
323         public void parseError(ErrorEvent e);
324     }
325
326     /**************************************************************************
327      *
328      * @author Brian S O'Neill
329      * @version
330      * <!--$$Revision:--> 8 <!-- $-->, <!--$$JustDate:--> 00/12/13 <!-- $-->
331      */

332     public static class ErrorEvent extends java.util.EventObject JavaDoc {
333         private String JavaDoc mErrorMsg;
334         private SourceInfo mInfo;
335
336         ErrorEvent(Object JavaDoc source, String JavaDoc errorMsg, SourceInfo info) {
337             super(source);
338             mErrorMsg = errorMsg;
339             mInfo = info;
340         }
341         
342         public String JavaDoc getErrorMessage() {
343             return mErrorMsg;
344         }
345         
346         /**
347          * Returns the error message prepended with source file information.
348          */

349         public String JavaDoc getDetailedErrorMessage() {
350             String JavaDoc 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 JavaDoc getSourceInfoMessage() {
360             if (mInfo == null) {
361                 return "";
362             }
363             else {
364                 return String.valueOf(mInfo.getLine());
365             }
366         }
367         
368         /**
369          * This method reports on where in the source code an error was found.
370          *
371          * @return Source information on this error or null if not known.
372          */

373         public SourceInfo getSourceInfo() {
374             return mInfo;
375         }
376     }
377
378     /**************************************************************************
379      *
380      * @author Brian S O'Neill
381      * @version
382      * <!--$$Revision:--> 8 <!-- $-->, <!--$$JustDate:--> 00/12/13 <!-- $-->
383      */

384     private static class Token implements java.io.Serializable JavaDoc {
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 JavaDoc mValue;
399         private SourceInfo mInfo;
400
401         Token(int sourceLine,
402               int sourceStartPos,
403               int sourceEndPos,
404               int tokenId,
405               String JavaDoc value) {
406             
407             mTokenId = tokenId;
408             mValue = value;
409             
410             if (tokenId > LAST_ID) {
411                 throw new IllegalArgumentException JavaDoc("Token Id out of range: " +
412                                                    tokenId);
413             }
414             
415             mInfo = new SourceInfo(sourceLine, sourceStartPos, sourceEndPos);
416             
417             if (sourceStartPos > sourceEndPos) {
418                 // This is an internal error.
419
throw new IllegalArgumentException JavaDoc
420                     ("Token start position greater than " +
421                      "end position at line: " + sourceLine);
422             }
423         }
424     
425         public Token(SourceInfo info, int tokenId, String JavaDoc value) {
426             mTokenId = tokenId;
427         
428             if (tokenId > LAST_ID) {
429                 throw new IllegalArgumentException JavaDoc("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         /**
441          * Token code is non-null, and is exactly the same as the name for
442          * its Id.
443          */

444         public String JavaDoc getCode() {
445             return Code.TOKEN_CODES[mTokenId];
446         }
447
448         public final SourceInfo getSourceInfo() {
449             return mInfo;
450         }
451         
452         public String JavaDoc getValue() {
453             return mValue;
454         }
455
456         public String JavaDoc toString() {
457             StringBuffer JavaDoc buf = new StringBuffer JavaDoc(10);
458
459             String JavaDoc image = getCode();
460             
461             if (image != null) {
462                 buf.append(image);
463             }
464             
465             String JavaDoc 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 JavaDoc[] TOKEN_CODES =
481             {
482                 "UNKNOWN",
483                 "EOF",
484
485                 "COMMENT",
486                 "KEY",
487                 "VALUE",
488
489                 "LBRACE",
490                 "RBRACE",
491             };
492         }
493     }
494
495     /**************************************************************************
496      *
497      * @author Brian S O'Neill
498      * @version
499      * <!--$$Revision:--> 8 <!-- $-->, <!--$$JustDate:--> 00/12/13 <!-- $-->
500      */

501     private static class Scanner {
502         private SourceReader mSource;
503
504         /** The scanner supports any amount of lookahead. */
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 JavaDoc 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 JavaDoc str, SourceInfo info) {
536             dispatchParseError(new ErrorEvent(this, str, info));
537         }
538         
539         private void error(String JavaDoc str) {
540             error(str, new SourceInfo(mSource.getLineNumber(),
541                                       mSource.getStartPosition(),
542                                       mSource.getEndPosition()));
543         }
544
545         /**
546          * Returns EOF as the last token.
547          */

548         public synchronized Token readToken() throws IOException JavaDoc {
549             if (mLookahead.empty()) {
550                 return scanToken();
551             }
552             else {
553                 return (Token)mLookahead.pop();
554             }
555         }
556         
557         /**
558          * Returns EOF as the last token.
559          */

560         public synchronized Token peekToken() throws IOException JavaDoc {
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 JavaDoc {
570             mLookahead.push(token);
571         }
572         
573         public void close() throws IOException JavaDoc {
574             mSource.close();
575         }
576
577         public int getErrorCount() {
578             return mErrorCount;
579         }
580         
581         private Token scanToken() throws IOException JavaDoc {
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                     // fall through
634
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 JavaDoc {
658             StringBuffer JavaDoc buf = new StringBuffer JavaDoc(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                         // line continuation
681
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                     // fall through
716
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 JavaDoc 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 JavaDoc {
784             StringBuffer JavaDoc buf = new StringBuffer JavaDoc(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 JavaDoc buf)
807             throws IOException JavaDoc {
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                         // line continuation
819
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 JavaDoc value) {
857             return new Token(mSource.getLineNumber(),
858                              mSource.getStartPosition(),
859                              mSource.getEndPosition(),
860                              Id, value);
861         }
862     }
863 }
864
Popular Tags