1/*
2 * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package javax.swing.text.html;
26
27import java.io.*;
28
29/**
30 * A CSS parser. This works by way of a delegate that implements the
31 * CSSParserCallback interface. The delegate is notified of the following
32 * events:
33 * <ul>
34 *   <li>Import statement: <code>handleImport</code>
35 *   <li>Selectors <code>handleSelector</code>. This is invoked for each
36 *       string. For example if the Reader contained p, bar , a {}, the delegate
37 *       would be notified 4 times, for 'p,' 'bar' ',' and 'a'.
38 *   <li>When a rule starts, <code>startRule</code>
39 *   <li>Properties in the rule via the <code>handleProperty</code>. This
40 *       is invoked one per property/value key, eg font size: foo;, would
41 *       cause the delegate to be notified once with a value of 'font size'.
42 *   <li>Values in the rule via the <code>handleValue</code>, this is notified
43 *       for the total value.
44 *   <li>When a rule ends, <code>endRule</code>
45 * </ul>
46 * This will parse much more than CSS 1, and loosely implements the
47 * recommendation for <i>Forward-compatible parsing</i> in section
48 * 7.1 of the CSS spec found at:
49 * <a href=http://www.w3.org/TR/REC-CSS1>http://www.w3.org/TR/REC-CSS1</a>.
50 * If an error results in parsing, a RuntimeException will be thrown.
51 * <p>
52 * This will preserve case. If the callback wishes to treat certain poritions
53 * case insensitively (such as selectors), it should use toLowerCase, or
54 * something similar.
55 *
56 * @author Scott Violet
57 */
58class CSSParser {
59    // Parsing something like the following:
60    // (@rule | ruleset | block)*
61    //
62    // @rule       (block | identifier)*; (block with {} ends @rule)
63    // block       matching [] () {} (that is, [()] is a block, [(){}{[]}]
64    //                                is a block, ()[] is two blocks)
65    // identifier  "*" | '*' | anything but a [](){} and whitespace
66    //
67    // ruleset     selector decblock
68    // selector    (identifier | (block, except block '{}') )*
69    // declblock   declaration* block*
70    // declaration (identifier* stopping when identifier ends with :)
71    //             (identifier* stopping when identifier ends with ;)
72    //
73    // comments /* */ can appear any where, and are stripped.
74
75
76    // identifier - letters, digits, dashes and escaped characters
77    // block starts with { ends with matching }, () [] and {} always occur
78    //   in matching pairs, '' and "" also occur in pairs, except " may be
79
80
81    // Indicates the type of token being parsed.
82    private static final int   IDENTIFIER = 1;
83    private static final int   BRACKET_OPEN = 2;
84    private static final int   BRACKET_CLOSE = 3;
85    private static final int   BRACE_OPEN = 4;
86    private static final int   BRACE_CLOSE = 5;
87    private static final int   PAREN_OPEN = 6;
88    private static final int   PAREN_CLOSE = 7;
89    private static final int   END = -1;
90
91    private static final char[] charMapping = { 0, 0, '[', ']', '{', '}', '(',
92                                               ')', 0};
93
94
95    /** Set to true if one character has been read ahead. */
96    private boolean        didPushChar;
97    /** The read ahead character. */
98    private int            pushedChar;
99    /** Temporary place to hold identifiers. */
100    private StringBuffer   unitBuffer;
101    /** Used to indicate blocks. */
102    private int[]          unitStack;
103    /** Number of valid blocks. */
104    private int            stackCount;
105    /** Holds the incoming CSS rules. */
106    private Reader         reader;
107    /** Set to true when the first non @ rule is encountered. */
108    private boolean        encounteredRuleSet;
109    /** Notified of state. */
110    private CSSParserCallback callback;
111    /** nextToken() inserts the string here. */
112    private char[]         tokenBuffer;
113    /** Current number of chars in tokenBufferLength. */
114    private int            tokenBufferLength;
115    /** Set to true if any whitespace is read. */
116    private boolean        readWS;
117
118
119    // The delegate interface.
120    static interface CSSParserCallback {
121        /** Called when an @import is encountered. */
122        void handleImport(String importString);
123        // There is currently no way to distinguish between '"foo,"' and
124        // 'foo,'. But this generally isn't valid CSS. If it becomes
125        // a problem, handleSelector will have to be told if the string is
126        // quoted.
127        void handleSelector(String selector);
128        void startRule();
129        // Property names are mapped to lower case before being passed to
130        // the delegate.
131        void handleProperty(String property);
132        void handleValue(String value);
133        void endRule();
134    }
135
136    CSSParser() {
137        unitStack = new int[2];
138        tokenBuffer = new char[80];
139        unitBuffer = new StringBuffer();
140    }
141
142    void parse(Reader reader, CSSParserCallback callback,
143               boolean inRule) throws IOException {
144        this.callback = callback;
145        stackCount = tokenBufferLength = 0;
146        this.reader = reader;
147        encounteredRuleSet = false;
148        try {
149            if (inRule) {
150                parseDeclarationBlock();
151            }
152            else {
153                while (getNextStatement());
154            }
155        } finally {
156            callback = null;
157            reader = null;
158        }
159    }
160
161    /**
162     * Gets the next statement, returning false if the end is reached. A
163     * statement is either an @rule, or a ruleset.
164     */
165    private boolean getNextStatement() throws IOException {
166        unitBuffer.setLength(0);
167
168        int token = nextToken((char)0);
169
170        switch (token) {
171        case IDENTIFIER:
172            if (tokenBufferLength > 0) {
173                if (tokenBuffer[0] == '@') {
174                    parseAtRule();
175                }
176                else {
177                    encounteredRuleSet = true;
178                    parseRuleSet();
179                }
180            }
181            return true;
182        case BRACKET_OPEN:
183        case BRACE_OPEN:
184        case PAREN_OPEN:
185            parseTillClosed(token);
186            return true;
187
188        case BRACKET_CLOSE:
189        case BRACE_CLOSE:
190        case PAREN_CLOSE:
191            // Shouldn't happen...
192            throw new RuntimeException("Unexpected top level block close");
193
194        case END:
195            return false;
196        }
197        return true;
198    }
199
200    /**
201     * Parses an @ rule, stopping at a matching brace pair, or ;.
202     */
203    private void parseAtRule() throws IOException {
204        // PENDING: make this more effecient.
205        boolean        done = false;
206        boolean isImport = (tokenBufferLength == 7 &&
207                            tokenBuffer[0] == '@' && tokenBuffer[1] == 'i' &&
208                            tokenBuffer[2] == 'm' && tokenBuffer[3] == 'p' &&
209                            tokenBuffer[4] == 'o' && tokenBuffer[5] == 'r' &&
210                            tokenBuffer[6] == 't');
211
212        unitBuffer.setLength(0);
213        while (!done) {
214            int       nextToken = nextToken(';');
215
216            switch (nextToken) {
217            case IDENTIFIER:
218                if (tokenBufferLength > 0 &&
219                    tokenBuffer[tokenBufferLength - 1] == ';') {
220                    --tokenBufferLength;
221                    done = true;
222                }
223                if (tokenBufferLength > 0) {
224                    if (unitBuffer.length() > 0 && readWS) {
225                        unitBuffer.append(' ');
226                    }
227                    unitBuffer.append(tokenBuffer, 0, tokenBufferLength);
228                }
229                break;
230
231            case BRACE_OPEN:
232                if (unitBuffer.length() > 0 && readWS) {
233                    unitBuffer.append(' ');
234                }
235                unitBuffer.append(charMapping[nextToken]);
236                parseTillClosed(nextToken);
237                done = true;
238                // Skip a tailing ';', not really to spec.
239                {
240                    int nextChar = readWS();
241                    if (nextChar != -1 && nextChar != ';') {
242                        pushChar(nextChar);
243                    }
244                }
245                break;
246
247            case BRACKET_OPEN: case PAREN_OPEN:
248                unitBuffer.append(charMapping[nextToken]);
249                parseTillClosed(nextToken);
250                break;
251
252            case BRACKET_CLOSE: case BRACE_CLOSE: case PAREN_CLOSE:
253                throw new RuntimeException("Unexpected close in @ rule");
254
255            case END:
256                done = true;
257                break;
258            }
259        }
260        if (isImport && !encounteredRuleSet) {
261            callback.handleImport(unitBuffer.toString());
262        }
263    }
264
265    /**
266     * Parses the next rule set, which is a selector followed by a
267     * declaration block.
268     */
269    private void parseRuleSet() throws IOException {
270        if (parseSelectors()) {
271            callback.startRule();
272            parseDeclarationBlock();
273            callback.endRule();
274        }
275    }
276
277    /**
278     * Parses a set of selectors, returning false if the end of the stream
279     * is reached.
280     */
281    private boolean parseSelectors() throws IOException {
282        // Parse the selectors
283        int       nextToken;
284
285        if (tokenBufferLength > 0) {
286            callback.handleSelector(new String(tokenBuffer, 0,
287                                               tokenBufferLength));
288        }
289
290        unitBuffer.setLength(0);
291        for (;;) {
292            while ((nextToken = nextToken((char)0)) == IDENTIFIER) {
293                if (tokenBufferLength > 0) {
294                    callback.handleSelector(new String(tokenBuffer, 0,
295                                                       tokenBufferLength));
296                }
297            }
298            switch (nextToken) {
299            case BRACE_OPEN:
300                return true;
301
302            case BRACKET_OPEN: case PAREN_OPEN:
303                parseTillClosed(nextToken);
304                // Not too sure about this, how we handle this isn't very
305                // well spec'd.
306                unitBuffer.setLength(0);
307                break;
308
309            case BRACKET_CLOSE: case BRACE_CLOSE: case PAREN_CLOSE:
310                throw new RuntimeException("Unexpected block close in selector");
311
312            case END:
313                // Prematurely hit end.
314                return false;
315            }
316        }
317    }
318
319    /**
320     * Parses a declaration block. Which a number of declarations followed
321     * by a })].
322     */
323    private void parseDeclarationBlock() throws IOException {
324        for (;;) {
325            int token = parseDeclaration();
326            switch (token) {
327            case END: case BRACE_CLOSE:
328                return;
329
330            case BRACKET_CLOSE: case PAREN_CLOSE:
331                // Bail
332                throw new RuntimeException("Unexpected close in declaration block");
333            case IDENTIFIER:
334                break;
335            }
336        }
337    }
338
339    /**
340     * Parses a single declaration, which is an identifier a : and another
341     * identifier. This returns the last token seen.
342     */
343    // identifier+: identifier* ;|}
344    private int parseDeclaration() throws IOException {
345        int    token;
346
347        if ((token = parseIdentifiers(':', false)) != IDENTIFIER) {
348            return token;
349        }
350        // Make the property name to lowercase
351        for (int counter = unitBuffer.length() - 1; counter >= 0; counter--) {
352            unitBuffer.setCharAt(counter, Character.toLowerCase
353                                 (unitBuffer.charAt(counter)));
354        }
355        callback.handleProperty(unitBuffer.toString());
356
357        token = parseIdentifiers(';', true);
358        callback.handleValue(unitBuffer.toString());
359        return token;
360    }
361
362    /**
363     * Parses identifiers until <code>extraChar</code> is encountered,
364     * returning the ending token, which will be IDENTIFIER if extraChar
365     * is found.
366     */
367    private int parseIdentifiers(char extraChar,
368                                 boolean wantsBlocks) throws IOException {
369        int   nextToken;
370        int   ubl;
371
372        unitBuffer.setLength(0);
373        for (;;) {
374            nextToken = nextToken(extraChar);
375
376            switch (nextToken) {
377            case IDENTIFIER:
378                if (tokenBufferLength > 0) {
379                    if (tokenBuffer[tokenBufferLength - 1] == extraChar) {
380                        if (--tokenBufferLength > 0) {
381                            if (readWS && unitBuffer.length() > 0) {
382                                unitBuffer.append(' ');
383                            }
384                            unitBuffer.append(tokenBuffer, 0,
385                                              tokenBufferLength);
386                        }
387                        return IDENTIFIER;
388                    }
389                    if (readWS && unitBuffer.length() > 0) {
390                        unitBuffer.append(' ');
391                    }
392                    unitBuffer.append(tokenBuffer, 0, tokenBufferLength);
393                }
394                break;
395
396            case BRACKET_OPEN:
397            case BRACE_OPEN:
398            case PAREN_OPEN:
399                ubl = unitBuffer.length();
400                if (wantsBlocks) {
401                    unitBuffer.append(charMapping[nextToken]);
402                }
403                parseTillClosed(nextToken);
404                if (!wantsBlocks) {
405                    unitBuffer.setLength(ubl);
406                }
407                break;
408
409            case BRACE_CLOSE:
410                // No need to throw for these two, we return token and
411                // caller can do whatever.
412            case BRACKET_CLOSE:
413            case PAREN_CLOSE:
414            case END:
415                // Hit the end
416                return nextToken;
417            }
418        }
419    }
420
421    /**
422     * Parses till a matching block close is encountered. This is only
423     * appropriate to be called at the top level (no nesting).
424     */
425    private void parseTillClosed(int openToken) throws IOException {
426        int       nextToken;
427        boolean   done = false;
428
429        startBlock(openToken);
430        while (!done) {
431            nextToken = nextToken((char)0);
432            switch (nextToken) {
433            case IDENTIFIER:
434                if (unitBuffer.length() > 0 && readWS) {
435                    unitBuffer.append(' ');
436                }
437                if (tokenBufferLength > 0) {
438                    unitBuffer.append(tokenBuffer, 0, tokenBufferLength);
439                }
440                break;
441
442            case BRACKET_OPEN: case BRACE_OPEN: case PAREN_OPEN:
443                if (unitBuffer.length() > 0 && readWS) {
444                    unitBuffer.append(' ');
445                }
446                unitBuffer.append(charMapping[nextToken]);
447                startBlock(nextToken);
448                break;
449
450            case BRACKET_CLOSE: case BRACE_CLOSE: case PAREN_CLOSE:
451                if (unitBuffer.length() > 0 && readWS) {
452                    unitBuffer.append(' ');
453                }
454                unitBuffer.append(charMapping[nextToken]);
455                endBlock(nextToken);
456                if (!inBlock()) {
457                    done = true;
458                }
459                break;
460
461            case END:
462                // Prematurely hit end.
463                throw new RuntimeException("Unclosed block");
464            }
465        }
466    }
467
468    /**
469     * Fetches the next token.
470     */
471    private int nextToken(char idChar) throws IOException {
472        readWS = false;
473
474        int     nextChar = readWS();
475
476        switch (nextChar) {
477        case '\'':
478            readTill('\'');
479            if (tokenBufferLength > 0) {
480                tokenBufferLength--;
481            }
482            return IDENTIFIER;
483        case '"':
484            readTill('"');
485            if (tokenBufferLength > 0) {
486                tokenBufferLength--;
487            }
488            return IDENTIFIER;
489        case '[':
490            return BRACKET_OPEN;
491        case ']':
492            return BRACKET_CLOSE;
493        case '{':
494            return BRACE_OPEN;
495        case '}':
496            return BRACE_CLOSE;
497        case '(':
498            return PAREN_OPEN;
499        case ')':
500            return PAREN_CLOSE;
501        case -1:
502            return END;
503        default:
504            pushChar(nextChar);
505            getIdentifier(idChar);
506            return IDENTIFIER;
507        }
508    }
509
510    /**
511     * Gets an identifier, returning true if the length of the string is greater than 0,
512     * stopping when <code>stopChar</code>, whitespace, or one of {}()[] is
513     * hit.
514     */
515    // NOTE: this could be combined with readTill, as they contain somewhat
516    // similar functionality.
517    private boolean getIdentifier(char stopChar) throws IOException {
518        boolean lastWasEscape = false;
519        boolean done = false;
520        int escapeCount = 0;
521        int escapeChar = 0;
522        int nextChar;
523        int intStopChar = (int)stopChar;
524        // 1 for '\', 2 for valid escape char [0-9a-fA-F], 3 for
525        // stop character (white space, ()[]{}) 0 otherwise
526        short type;
527        int escapeOffset = 0;
528
529        tokenBufferLength = 0;
530        while (!done) {
531            nextChar = readChar();
532            switch (nextChar) {
533            case '\\':
534                type = 1;
535                break;
536
537            case '0': case '1': case '2': case '3': case '4': case '5':
538            case '6': case '7': case '8': case '9':
539                type = 2;
540                escapeOffset = nextChar - '0';
541                break;
542
543            case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
544                type = 2;
545                escapeOffset = nextChar - 'a' + 10;
546                break;
547
548            case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
549                type = 2;
550                escapeOffset = nextChar - 'A' + 10;
551                break;
552
553            case '\'': case '"': case '[': case ']': case '{': case '}':
554            case '(': case ')':
555            case ' ': case '\n': case '\t': case '\r':
556                type = 3;
557                break;
558
559            case '/':
560                type = 4;
561                break;
562
563            case -1:
564                // Reached the end
565                done = true;
566                type = 0;
567                break;
568
569            default:
570                type = 0;
571                break;
572            }
573            if (lastWasEscape) {
574                if (type == 2) {
575                    // Continue with escape.
576                    escapeChar = escapeChar * 16 + escapeOffset;
577                    if (++escapeCount == 4) {
578                        lastWasEscape = false;
579                        append((char)escapeChar);
580                    }
581                }
582                else {
583                    // no longer escaped
584                    lastWasEscape = false;
585                    if (escapeCount > 0) {
586                        append((char)escapeChar);
587                        // Make this simpler, reprocess the character.
588                        pushChar(nextChar);
589                    }
590                    else if (!done) {
591                        append((char)nextChar);
592                    }
593                }
594            }
595            else if (!done) {
596                if (type == 1) {
597                    lastWasEscape = true;
598                    escapeChar = escapeCount = 0;
599                }
600                else if (type == 3) {
601                    done = true;
602                    pushChar(nextChar);
603                }
604                else if (type == 4) {
605                    // Potential comment
606                    nextChar = readChar();
607                    if (nextChar == '*') {
608                        done = true;
609                        readComment();
610                        readWS = true;
611                    }
612                    else {
613                        append('/');
614                        if (nextChar == -1) {
615                            done = true;
616                        }
617                        else {
618                            pushChar(nextChar);
619                        }
620                    }
621                }
622                else {
623                    append((char)nextChar);
624                    if (nextChar == intStopChar) {
625                        done = true;
626                    }
627                }
628            }
629        }
630        return (tokenBufferLength > 0);
631    }
632
633    /**
634     * Reads till a <code>stopChar</code> is encountered, escaping characters
635     * as necessary.
636     */
637    private void readTill(char stopChar) throws IOException {
638        boolean lastWasEscape = false;
639        int escapeCount = 0;
640        int escapeChar = 0;
641        int nextChar;
642        boolean done = false;
643        int intStopChar = (int)stopChar;
644        // 1 for '\', 2 for valid escape char [0-9a-fA-F], 0 otherwise
645        short type;
646        int escapeOffset = 0;
647
648        tokenBufferLength = 0;
649        while (!done) {
650            nextChar = readChar();
651            switch (nextChar) {
652            case '\\':
653                type = 1;
654                break;
655
656            case '0': case '1': case '2': case '3': case '4':case '5':
657            case '6': case '7': case '8': case '9':
658                type = 2;
659                escapeOffset = nextChar - '0';
660                break;
661
662            case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
663                type = 2;
664                escapeOffset = nextChar - 'a' + 10;
665                break;
666
667            case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
668                type = 2;
669                escapeOffset = nextChar - 'A' + 10;
670                break;
671
672            case -1:
673                // Prematurely reached the end!
674                throw new RuntimeException("Unclosed " + stopChar);
675
676            default:
677                type = 0;
678                break;
679            }
680            if (lastWasEscape) {
681                if (type == 2) {
682                    // Continue with escape.
683                    escapeChar = escapeChar * 16 + escapeOffset;
684                    if (++escapeCount == 4) {
685                        lastWasEscape = false;
686                        append((char)escapeChar);
687                    }
688                }
689                else {
690                    // no longer escaped
691                    if (escapeCount > 0) {
692                        append((char)escapeChar);
693                        if (type == 1) {
694                            lastWasEscape = true;
695                            escapeChar = escapeCount = 0;
696                        }
697                        else {
698                            if (nextChar == intStopChar) {
699                                done = true;
700                            }
701                            append((char)nextChar);
702                            lastWasEscape = false;
703                        }
704                    }
705                    else {
706                        append((char)nextChar);
707                        lastWasEscape = false;
708                    }
709                }
710            }
711            else if (type == 1) {
712                lastWasEscape = true;
713                escapeChar = escapeCount = 0;
714            }
715            else {
716                if (nextChar == intStopChar) {
717                    done = true;
718                }
719                append((char)nextChar);
720            }
721        }
722    }
723
724    private void append(char character) {
725        if (tokenBufferLength == tokenBuffer.length) {
726            char[] newBuffer = new char[tokenBuffer.length * 2];
727            System.arraycopy(tokenBuffer, 0, newBuffer, 0, tokenBuffer.length);
728            tokenBuffer = newBuffer;
729        }
730        tokenBuffer[tokenBufferLength++] = character;
731    }
732
733    /**
734     * Parses a comment block.
735     */
736    private void readComment() throws IOException {
737        int nextChar;
738
739        for(;;) {
740            nextChar = readChar();
741            switch (nextChar) {
742            case -1:
743                throw new RuntimeException("Unclosed comment");
744            case '*':
745                nextChar = readChar();
746                if (nextChar == '/') {
747                    return;
748                }
749                else if (nextChar == -1) {
750                    throw new RuntimeException("Unclosed comment");
751                }
752                else {
753                    pushChar(nextChar);
754                }
755                break;
756            default:
757                break;
758            }
759        }
760    }
761
762    /**
763     * Called when a block start is encountered ({[.
764     */
765    private void startBlock(int startToken) {
766        if (stackCount == unitStack.length) {
767            int[]     newUS = new int[stackCount * 2];
768
769            System.arraycopy(unitStack, 0, newUS, 0, stackCount);
770            unitStack = newUS;
771        }
772        unitStack[stackCount++] = startToken;
773    }
774
775    /**
776     * Called when an end block is encountered )]}
777     */
778    private void endBlock(int endToken) {
779        int    startToken;
780
781        switch (endToken) {
782        case BRACKET_CLOSE:
783            startToken = BRACKET_OPEN;
784            break;
785        case BRACE_CLOSE:
786            startToken = BRACE_OPEN;
787            break;
788        case PAREN_CLOSE:
789            startToken = PAREN_OPEN;
790            break;
791        default:
792            // Will never happen.
793            startToken = -1;
794            break;
795        }
796        if (stackCount > 0 && unitStack[stackCount - 1] == startToken) {
797            stackCount--;
798        }
799        else {
800            // Invalid state, should do something.
801            throw new RuntimeException("Unmatched block");
802        }
803    }
804
805    /**
806     * @return true if currently in a block.
807     */
808    private boolean inBlock() {
809        return (stackCount > 0);
810    }
811
812    /**
813     * Skips any white space, returning the character after the white space.
814     */
815    private int readWS() throws IOException {
816        int nextChar;
817        while ((nextChar = readChar()) != -1 &&
818               Character.isWhitespace((char)nextChar)) {
819            readWS = true;
820        }
821        return nextChar;
822    }
823
824    /**
825     * Reads a character from the stream.
826     */
827    private int readChar() throws IOException {
828        if (didPushChar) {
829            didPushChar = false;
830            return pushedChar;
831        }
832        return reader.read();
833        // Uncomment the following to do case insensitive parsing.
834        /*
835        if (retValue != -1) {
836            return (int)Character.toLowerCase((char)retValue);
837        }
838        return retValue;
839        */
840    }
841
842    /**
843     * Supports one character look ahead, this will throw if called twice
844     * in a row.
845     */
846    private void pushChar(int tempChar) {
847        if (didPushChar) {
848            // Should never happen.
849            throw new RuntimeException("Can not handle look ahead of more than one character");
850        }
851        didPushChar = true;
852        pushedChar = tempChar;
853    }
854}
855