/*
 * $Header$
 * $Revision: 1334 $
 * $Date: 2008-11-29 09:58:48 -0800 (Sat, 29 Nov 2008) $
 *
 * ====================================================================
 *
 * Copyright 2000-2002 bob mcwhirter & James Strachan.
 * All rights reserved.
 *
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 
 *   * Neither the name of the Jaxen Project nor the names of its
 *     contributors may be used to endorse or promote products derived 
 *     from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ====================================================================
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Jaxen Project and was originally
 * created by bob mcwhirter <bob@werken.com> and
 * James Strachan <jstrachan@apache.org>.  For more information on the
 * Jaxen Project, please see <http://www.jaxen.org/>.
 *
 * $Id: XPathReader.java 1334 2008-11-29 17:58:48Z elharo $
 */


package org.jaxen.saxpath.base;

import java.util.ArrayList;

import org.jaxen.saxpath.Axis;
import org.jaxen.saxpath.Operator;
import org.jaxen.saxpath.SAXPathException;
import org.jaxen.saxpath.XPathHandler;
import org.jaxen.saxpath.XPathSyntaxException;
import org.jaxen.saxpath.helpers.DefaultXPathHandler;

/** Implementation of SAXPath's <code>XPathReader</code> which
 *  generates callbacks to an <code>XPathHandler</code>.
 *
 *  @author bob mcwhirter (bob@werken.com)
 */
public class XPathReader implements org.jaxen.saxpath.XPathReader
{
    private ArrayList  tokens;
    private XPathLexer lexer;

    private XPathHandler handler;
    
    private static XPathHandler defaultHandler = new DefaultXPathHandler();

    /**
     * Create a new <code>XPathReader</code> with a do-nothing
     * <code>XPathHandler</code>.
     */
    public XPathReader()
    {
        setXPathHandler( defaultHandler );
    }

    public void setXPathHandler(XPathHandler handler)
    {
        this.handler = handler;
    }

    public XPathHandler getXPathHandler()
    {
        return this.handler;
    }

    public void parse(String xpath) throws SAXPathException
    {
        setUpParse( xpath );

        getXPathHandler().startXPath();

        expr();

        getXPathHandler().endXPath();

        if ( LA(1) != TokenTypes.EOF )
        {
            XPathSyntaxException ex = createSyntaxException( "Unexpected '" + LT(1).getTokenText() + "'" );
            throw ex;
        }

        lexer  = null;
        tokens = null;
    }

    void setUpParse(String xpath)
    {
        this.tokens = new ArrayList();
        this.lexer = new XPathLexer( xpath );
    }

    private void pathExpr() throws SAXPathException
    {
        getXPathHandler().startPathExpr();

        switch ( LA(1) )
        {
            case TokenTypes.DOUBLE:
            case TokenTypes.LITERAL:
            {
                filterExpr();

                if ( LA(1) == TokenTypes.SLASH || LA(1) == TokenTypes.DOUBLE_SLASH )
                {
                    XPathSyntaxException ex = createSyntaxException("Node-set expected");
                    throw ex;
                }

                break;
            }                
            case TokenTypes.LEFT_PAREN:
            case TokenTypes.DOLLAR:
            {
                filterExpr();
                    
                if ( LA(1) == TokenTypes.SLASH || LA(1) == TokenTypes.DOUBLE_SLASH)
                {
                    locationPath( false );
                }
                break;
            }
            case TokenTypes.IDENTIFIER:
            {

                if ( ( LA(2) == TokenTypes.LEFT_PAREN
                     &&
                       ! isNodeTypeName( LT(1) ) )
                     ||
                    ( LA(2) == TokenTypes.COLON
                      &&
                      LA(4) == TokenTypes.LEFT_PAREN) ) 
                {
                    filterExpr();
                    
                    if ( LA(1) == TokenTypes.SLASH || LA(1) == TokenTypes.DOUBLE_SLASH)
                    {
                        locationPath( false );
                    }
                }
                else
                {
                    locationPath( false );
                }
                break;
            }
            case TokenTypes.DOT:
            case TokenTypes.DOT_DOT:
            case TokenTypes.STAR:
            case TokenTypes.AT:
            {
                locationPath( false );
                break;
            }
            case TokenTypes.SLASH:
            case TokenTypes.DOUBLE_SLASH:
            {
                locationPath( true );
                break;
            }
            default:
            {
                XPathSyntaxException ex = createSyntaxException( "Unexpected '" + LT(1).getTokenText() + "'" );
                throw ex;
            }
        }

        getXPathHandler().endPathExpr();
    }

    private void literal() throws SAXPathException
    {
        Token token = match( TokenTypes.LITERAL );

        getXPathHandler().literal( token.getTokenText() );
    }

    private void functionCall() throws SAXPathException
    {
        String prefix       = null;
        String functionName = null;

        if ( LA(2) == TokenTypes.COLON )
        {
            prefix = match( TokenTypes.IDENTIFIER ).getTokenText();
            match( TokenTypes.COLON );
        }
        else
        {
            prefix = "";
        }

        functionName = match( TokenTypes.IDENTIFIER ).getTokenText();

        getXPathHandler().startFunction( prefix,
                                         functionName );

        match ( TokenTypes.LEFT_PAREN );

        arguments();

        match ( TokenTypes.RIGHT_PAREN );

        getXPathHandler().endFunction();
    }

    private void arguments() throws SAXPathException
    {
        while ( LA(1) != TokenTypes.RIGHT_PAREN )
        {
            expr();

            if ( LA(1) == TokenTypes.COMMA )
            {
                match( TokenTypes.COMMA );
            }
            else
            {
                break;
            }
        }
    }

    private void filterExpr() throws SAXPathException
    {

        getXPathHandler().startFilterExpr();

        switch ( LA(1) )
        {
            case TokenTypes.DOUBLE:
            {
                Token token = match( TokenTypes.DOUBLE );
                
                getXPathHandler().number( Double.parseDouble( token.getTokenText() ) );
                break;
            }
            case TokenTypes.LITERAL:
            {
                literal();
                break;
            }
            case TokenTypes.LEFT_PAREN:
            {
                match( TokenTypes.LEFT_PAREN );
                expr();
                match( TokenTypes.RIGHT_PAREN );
                break;
            }
            case TokenTypes.IDENTIFIER:
            {
                functionCall();
                break;
            }
            case TokenTypes.DOLLAR:
            {
                variableReference();
                break;
            }
        }

        predicates();

        getXPathHandler().endFilterExpr();
    }

    private void variableReference() throws SAXPathException
    {
        match( TokenTypes.DOLLAR );

        String prefix       = null;
        String variableName = null;

        if ( LA(2) == TokenTypes.COLON )
        {
            prefix = match( TokenTypes.IDENTIFIER ).getTokenText();
            match( TokenTypes.COLON );
        }
        else
        {
            prefix = "";
        }

        variableName = match( TokenTypes.IDENTIFIER ).getTokenText();

        getXPathHandler().variableReference( prefix,
                                             variableName );
    }

    void locationPath(boolean isAbsolute) throws SAXPathException
    {
        switch ( LA(1) )
        {
            case TokenTypes.SLASH:
            case TokenTypes.DOUBLE_SLASH:
            {
                if ( isAbsolute )
                {
                    absoluteLocationPath();
                }
                else
                {
                    relativeLocationPath();
                }
                break;
            }
            case TokenTypes.AT:
            case TokenTypes.IDENTIFIER:
            case TokenTypes.DOT:
            case TokenTypes.DOT_DOT:
            case TokenTypes.STAR:
            {
                relativeLocationPath();
                break;
            }
            default:
            {
                XPathSyntaxException ex = createSyntaxException( "Unexpected '" + LT(1).getTokenText() + "'" );
                throw ex;
            }
        }
    }

    private void absoluteLocationPath() throws SAXPathException
    {
        getXPathHandler().startAbsoluteLocationPath();

        switch ( LA(1) )
        {
            case TokenTypes.SLASH:
            {
                match( TokenTypes.SLASH );

                switch ( LA(1) )
                {

                    case TokenTypes.DOT:
                    case TokenTypes.DOT_DOT:
                    case TokenTypes.AT:
                    case TokenTypes.IDENTIFIER:
                    case TokenTypes.STAR:
                    {
                        steps();
                        break;
                    }
                }
                break;
            }
            case TokenTypes.DOUBLE_SLASH:
            {
                getXPathHandler().startAllNodeStep( Axis.DESCENDANT_OR_SELF );
                getXPathHandler().endAllNodeStep();

                match( TokenTypes.DOUBLE_SLASH );
                switch ( LA(1) )
                {
                    case TokenTypes.DOT:
                    case TokenTypes.DOT_DOT:
                    case TokenTypes.AT:
                    case TokenTypes.IDENTIFIER:
                    case TokenTypes.STAR:
                    {
                        steps();
                        break;
                    }
                    default:
                        XPathSyntaxException ex = this.createSyntaxException("Location path cannot end with //");
                        throw ex;
                }
                break;
            }
        }
        
        getXPathHandler().endAbsoluteLocationPath();
    }

    private void relativeLocationPath() throws SAXPathException
    {
        getXPathHandler().startRelativeLocationPath();

        switch ( LA(1) )
        {
            case TokenTypes.SLASH:
            {
                match( TokenTypes.SLASH );
                break;
            }
            case TokenTypes.DOUBLE_SLASH:
            {
                getXPathHandler().startAllNodeStep( Axis.DESCENDANT_OR_SELF );
                getXPathHandler().endAllNodeStep();

                match( TokenTypes.DOUBLE_SLASH );

                break;
            }
        }

        steps();

        getXPathHandler().endRelativeLocationPath();
    }

    private void steps() throws SAXPathException
    {
        switch ( LA(1) )
        {

            case TokenTypes.DOT:
            case TokenTypes.DOT_DOT:
            case TokenTypes.AT:
            case TokenTypes.IDENTIFIER:
            case TokenTypes.STAR:
            {
                step();
                break;
            }
            case TokenTypes.EOF:
            {
                return;
            }
            default:
            {
                XPathSyntaxException ex = createSyntaxException( "Expected one of '.', '..', '@', '*', <QName>" );
                throw ex;
            }
        }

        do
        {
            if ( ( LA(1) == TokenTypes.SLASH)
                 ||
                 ( LA(1) == TokenTypes.DOUBLE_SLASH ) )
            {
                switch ( LA(1) )
                {
                    case TokenTypes.SLASH:
                    {
                        match( TokenTypes.SLASH );
                        break;
                    }
                    case TokenTypes.DOUBLE_SLASH:
                    {
                        getXPathHandler().startAllNodeStep( Axis.DESCENDANT_OR_SELF );
                        getXPathHandler().endAllNodeStep();

                        match( TokenTypes.DOUBLE_SLASH );
                        break;
                    }
                }
            }
            else
            {
                return;
            }
            
            switch ( LA(1) )
            {
                case TokenTypes.DOT:
                case TokenTypes.DOT_DOT:
                case TokenTypes.AT:
                case TokenTypes.IDENTIFIER:
                case TokenTypes.STAR:
                {
                    step();
                    break;
                }
                default:
                {
                    XPathSyntaxException ex = createSyntaxException( "Expected one of '.', '..', '@', '*', <QName>" );
                    throw ex;
                }
            }

        } while ( true );
    }

    void step() throws SAXPathException
    {
        int axis = 0;

        switch ( LA(1) )
        {
            case TokenTypes.DOT:
            case TokenTypes.DOT_DOT:
            {
                abbrStep();
                return;
            }
            case TokenTypes.AT:
            {
                axis = axisSpecifier();
                break;
            }
            case TokenTypes.IDENTIFIER:
            {
                if ( LA(2) == TokenTypes.DOUBLE_COLON )
                {
                    axis = axisSpecifier();
                }
                else
                {
                    axis = Axis.CHILD;
                }
                break;
            }
            case TokenTypes.STAR:
            {
                axis = Axis.CHILD;
                break;
            }
        }

        nodeTest( axis );
    }

    private int axisSpecifier() throws SAXPathException
    {
        int axis = 0;

        switch ( LA(1) )
        {
            case TokenTypes.AT:
            {
                match( TokenTypes.AT );
                axis = Axis.ATTRIBUTE;
                break;
            }
            case TokenTypes.IDENTIFIER:
            {
                Token token = LT( 1 );

                axis = Axis.lookup( token.getTokenText() );

                if ( axis == Axis.INVALID_AXIS )
                {
                    throwInvalidAxis( token.getTokenText() );
                }

                match( TokenTypes.IDENTIFIER );
                match( TokenTypes.DOUBLE_COLON );

                break;
            }
        }

        return axis;
    }

    private void nodeTest(int axis) throws SAXPathException
    {
        switch ( LA(1) )
        {
            case TokenTypes.IDENTIFIER:
            {
                switch ( LA(2) )
                {
                    case TokenTypes.LEFT_PAREN:
                    {
                        nodeTypeTest( axis );
                        break;
                    }
                    default:
                    {
                        nameTest( axis );
                        break;
                    }
                }
                break;
            }
            case TokenTypes.STAR:
            {
                nameTest( axis );
                break;
            }
            default:
                XPathSyntaxException ex = createSyntaxException("Expected <QName> or *");
                throw ex;
        }
    }

    private void nodeTypeTest(int axis) throws SAXPathException
    {
        Token  nodeTypeToken = match( TokenTypes.IDENTIFIER );
        String nodeType      = nodeTypeToken.getTokenText();

        match( TokenTypes.LEFT_PAREN );

        if ( "processing-instruction".equals( nodeType ) )
        {
            String piName = "";

            if ( LA(1) == TokenTypes.LITERAL )
            {
                piName = match( TokenTypes.LITERAL ).getTokenText();
            }

            match( TokenTypes.RIGHT_PAREN );

            getXPathHandler().startProcessingInstructionNodeStep( axis,
                                                                  piName );

            predicates();

            getXPathHandler().endProcessingInstructionNodeStep();
        }
        else if ( "node".equals( nodeType ) )
        {
            match( TokenTypes.RIGHT_PAREN );

            getXPathHandler().startAllNodeStep( axis );

            predicates();

            getXPathHandler().endAllNodeStep();
        }
        else if ( "text".equals( nodeType ) )
        {
            match( TokenTypes.RIGHT_PAREN );

            getXPathHandler().startTextNodeStep( axis );

            predicates();

            getXPathHandler().endTextNodeStep();
        }
        else if ( "comment".equals( nodeType ) )
        {
            match( TokenTypes.RIGHT_PAREN );

            getXPathHandler().startCommentNodeStep( axis );

            predicates();

            getXPathHandler().endCommentNodeStep();
        }
        else
        {
            XPathSyntaxException ex = createSyntaxException( "Expected node-type" );
            throw ex;
        }
    }

    private void nameTest(int axis) throws SAXPathException
    {
        String prefix    = null;
        String localName = null;

        switch ( LA(2) )
        {
            case TokenTypes.COLON:
            {
                switch ( LA(1) )
                {
                    case TokenTypes.IDENTIFIER:
                    {
                        prefix = match( TokenTypes.IDENTIFIER ).getTokenText();
                        match( TokenTypes.COLON );
                        break;
                    }
                }
                break;
            }
        }
        
        switch ( LA(1) )
        {
            case TokenTypes.IDENTIFIER:
            {
                localName = match( TokenTypes.IDENTIFIER ).getTokenText();
                break;
            }
            case TokenTypes.STAR:
            {
                match( TokenTypes.STAR );
                localName = "*";
                break;
            }
        }

        if ( prefix == null )
        {
            prefix = "";
        }
        
        getXPathHandler().startNameStep( axis,
                                         prefix,
                                         localName );

        predicates();

        getXPathHandler().endNameStep();
    }

    private void abbrStep() throws SAXPathException
    {
        switch ( LA(1) )
        {
            case TokenTypes.DOT:
            {
                match( TokenTypes.DOT );
                getXPathHandler().startAllNodeStep( Axis.SELF );
                predicates();
                getXPathHandler().endAllNodeStep();
                break;
            }
            case TokenTypes.DOT_DOT:
            {
                match( TokenTypes.DOT_DOT );
                getXPathHandler().startAllNodeStep( Axis.PARENT );
                predicates();
                getXPathHandler().endAllNodeStep();
                break;
            }
        }
    }

    private void predicates() throws SAXPathException
    {
        while (true )
        {
            if ( LA(1) == TokenTypes.LEFT_BRACKET )
            {
                predicate();
            }
            else
            {
                break;
            }
        }
    }
    
    void predicate() throws SAXPathException
    {
        getXPathHandler().startPredicate();
        
        match( TokenTypes.LEFT_BRACKET );
        
        predicateExpr();

        match( TokenTypes.RIGHT_BRACKET );

        getXPathHandler().endPredicate();
    }

    private void predicateExpr() throws SAXPathException
    {
        expr();
    }

    private void expr() throws SAXPathException
    {
        orExpr();
    }

    private void orExpr() throws SAXPathException
    {
        getXPathHandler().startOrExpr();
        
        andExpr();

        boolean create = false;

        switch ( LA(1) )
        {
            case TokenTypes.OR:
            {
                create = true;
                match( TokenTypes.OR );
                orExpr();
                break;
            }
        }

        getXPathHandler().endOrExpr( create );
    }

    private void andExpr() throws SAXPathException
    {
        getXPathHandler().startAndExpr();

        equalityExpr();

        boolean create = false;

        switch ( LA(1) )
        {
            case TokenTypes.AND:
            {
                create = true;
                match( TokenTypes.AND );
                andExpr();
                break;
            }
        }

        getXPathHandler().endAndExpr( create );
    }

    private void equalityExpr() throws SAXPathException
    {
        relationalExpr();

        int la = LA(1);
        while (la == TokenTypes.EQUALS || la == TokenTypes.NOT_EQUALS)
        {
            switch ( la )
            {
                case TokenTypes.EQUALS:
                {
                    match( TokenTypes.EQUALS );
                    getXPathHandler().startEqualityExpr();
                    relationalExpr();
                    getXPathHandler().endEqualityExpr( Operator.EQUALS );
                    break;
                }
                case TokenTypes.NOT_EQUALS:
                {
                    match( TokenTypes.NOT_EQUALS );
                    getXPathHandler().startEqualityExpr();
                    relationalExpr();
                    getXPathHandler().endEqualityExpr( Operator.NOT_EQUALS );
                    break;
                }
            }
            la = LA(1);
        }
    }
    
    private void relationalExpr() throws SAXPathException
    {

        additiveExpr();

        int la = LA(1);
        // Very important: TokenTypes.LESS_THAN != Operator.LESS_THAN
        //                 TokenTypes.GREATER_THAN != Operator.GREATER_THAN
        //                 TokenTypes.GREATER_THAN_EQUALS != Operator.GREATER_THAN_EQUALS
        //                 TokenTypes.LESS_THAN_EQUALS != Operator.LESS_THAN_EQUALS
        while (la == TokenTypes.LESS_THAN_SIGN 
            || la == TokenTypes.GREATER_THAN_SIGN 
            || la == TokenTypes.LESS_THAN_OR_EQUALS_SIGN 
            || la == TokenTypes.GREATER_THAN_OR_EQUALS_SIGN ) {
            switch ( la )
            {
                case TokenTypes.LESS_THAN_SIGN:
                {
                    match( TokenTypes.LESS_THAN_SIGN );
                    getXPathHandler().startRelationalExpr();
                    additiveExpr();
                    getXPathHandler().endRelationalExpr( Operator.LESS_THAN );
                    break;
                }
                case TokenTypes.GREATER_THAN_SIGN:
                {
                    match( TokenTypes.GREATER_THAN_SIGN );
                    getXPathHandler().startRelationalExpr();
                    additiveExpr();
                    getXPathHandler().endRelationalExpr( Operator.GREATER_THAN );
                    break;
                }
                case TokenTypes.GREATER_THAN_OR_EQUALS_SIGN:
                {
                    match( TokenTypes.GREATER_THAN_OR_EQUALS_SIGN );
                    getXPathHandler().startRelationalExpr();
                    additiveExpr();
                    getXPathHandler().endRelationalExpr( Operator.GREATER_THAN_EQUALS );
                    break;
                }
                case TokenTypes.LESS_THAN_OR_EQUALS_SIGN:
                {
                    match( TokenTypes.LESS_THAN_OR_EQUALS_SIGN );
                    getXPathHandler().startRelationalExpr();
                    additiveExpr();
                    getXPathHandler().endRelationalExpr( Operator.LESS_THAN_EQUALS );
                    break;
                }
            }
            la = LA(1);
        }
    } 

    
    private void additiveExpr() throws SAXPathException
    {
        multiplicativeExpr();

        int la = LA(1);
        while (la == TokenTypes.PLUS || la == TokenTypes.MINUS)
        {
            switch ( la )
            {
                case TokenTypes.PLUS:
                {
                    match( TokenTypes.PLUS );
                    getXPathHandler().startAdditiveExpr();
                    multiplicativeExpr();
                    getXPathHandler().endAdditiveExpr( Operator.ADD );
                    break;
                }
                case TokenTypes.MINUS:
                {
                    match( TokenTypes.MINUS );
                    getXPathHandler().startAdditiveExpr();
                    multiplicativeExpr();
                    getXPathHandler().endAdditiveExpr( Operator.SUBTRACT );
                    break;
                }
            }
            la = LA(1);
        }
    }

    private void multiplicativeExpr() throws SAXPathException
    {
        unaryExpr();
       
        int la = LA(1);
        while (la == TokenTypes.STAR_OPERATOR || la == TokenTypes.DIV || la == TokenTypes.MOD)
        {
            switch ( la )
            {
                case TokenTypes.STAR:
                case TokenTypes.STAR_OPERATOR:
                {
                    match( TokenTypes.STAR_OPERATOR );
                    getXPathHandler().startMultiplicativeExpr();
                    unaryExpr();
                    getXPathHandler().endMultiplicativeExpr( Operator.MULTIPLY );
                    break;
                }
                case TokenTypes.DIV:
                {
                    match( TokenTypes.DIV );
                    getXPathHandler().startMultiplicativeExpr();
                    unaryExpr();
                    getXPathHandler().endMultiplicativeExpr( Operator.DIV );
                    break;
                }
                case TokenTypes.MOD:
                {
                    match( TokenTypes.MOD );
                    getXPathHandler().startMultiplicativeExpr();
                    unaryExpr();
                    getXPathHandler().endMultiplicativeExpr( Operator.MOD );
                    break;
                }
            }
            la = LA(1);
        }

    }

    private void unaryExpr() throws SAXPathException
    {
        switch ( LA(1) )
        {
            case TokenTypes.MINUS:
            {
                getXPathHandler().startUnaryExpr();
                match( TokenTypes.MINUS );
                unaryExpr();
                getXPathHandler().endUnaryExpr( Operator.NEGATIVE );
                break;
            }
            default:
            {
                unionExpr();
                break;
            }
        }

        
    }

    private void unionExpr() throws SAXPathException
    {
        getXPathHandler().startUnionExpr();

        pathExpr();

        boolean create = false;

        switch ( LA(1) )
        {
            case TokenTypes.PIPE:
            {
                match( TokenTypes.PIPE );
                create = true;
                expr();
                break;
            }
        }

        getXPathHandler().endUnionExpr( create );
    }

    private Token match(int tokenType) throws XPathSyntaxException
    {
        LT(1);

        Token token = (Token) tokens.get( 0 );

        if ( token.getTokenType() == tokenType )
        {
            tokens.remove(0);
            return token;
        }

        
        XPathSyntaxException ex = createSyntaxException( "Expected: " + TokenTypes.getTokenText( tokenType ) );
        throw ex;
    }

    private int LA(int position)
    {
        return LT(position).getTokenType();
    }

    
    // XXX This method's a HotSpot; could we improve it?
    private Token LT(int position)
    {
        if ( tokens.size() <= ( position - 1 ) )
        {
            for ( int i = 0 ; i < position ; ++i )
            {
                tokens.add( lexer.nextToken() );
            }
        }

        return (Token) tokens.get( position - 1 );
    }

    private boolean isNodeTypeName(Token name)
    {
        String text = name.getTokenText();

        if ( "node".equals( text )
             ||
             "comment".equals( text )
             ||
             "text".equals( text )
             ||
             "processing-instruction".equals( text ) )
        {
            return true;
        }

        return false;
    }

    private XPathSyntaxException createSyntaxException(String message)
    {
        String xpath    = this.lexer.getXPath();
        int    position = LT(1).getTokenBegin();

        return new XPathSyntaxException( xpath,
                                         position,
                                         message );
    }

    private void throwInvalidAxis(String invalidAxis) throws SAXPathException
    {
        String xpath    = this.lexer.getXPath();
        int    position = LT(1).getTokenBegin();

        String message  = "Expected valid axis name instead of [" + invalidAxis + "]";

        throw new XPathSyntaxException( xpath,
                                        position,
                                        message );
    }
}
