persistence@glassfish.java.net

Add a property to the cmp EJB QL compiler to disable ORDER BY validation

From: Michael Bouschen <Michael.Bouschen_at_Sun.COM>
Date: Wed, 20 Sep 2006 21:55:20 +0200

Hi Marina, hi Mitesh,

I filed a cmp enhancement for the issue of disabling the ORDER BY
validation in cmp's EJB QL compiler:
  https://glassfish.dev.java.net/issues/show_bug.cgi?id=1175

Here is the corresponding discussion on the users alias:
  
https://glassfish.dev.java.net/servlets/BrowseList?list=users&by=thread&from=521685

The attached version of Semantic.g (you find it under
cmp/support/ejb/src/.../ejb/ejbqlc) adds a property
  com.sun.jdo.spi.persistence.support.ejb.ejbqlc.DISABLE_ORDERBY_VALIDATION
If set to true method checkSelectOrderbyClause immediately returns and
does not validate the ORDER BY clause against the SELECT clause. The
property is false by default, so the user needs to explicitly set it to
true in order to get the new behavior. Please have a look.

Thanks!

Regards Michael


/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */



/*
 * Semantic.g
 *
 * Created on November 19, 2001
 */

header
{
    package com.sun.jdo.spi.persistence.support.ejb.ejbqlc;
    
    import java.util.ResourceBundle;
    import java.lang.reflect.Method;
    import com.sun.jdo.spi.persistence.utility.I18NHelper;
    import com.sun.jdo.spi.persistence.support.ejb.ejbc.MethodHelper;
}

/**
 * This class defines the semantic analysis of the EJBQL compiler.
 * Input of this pass is the AST as produced by the parser,
 * that consists of EJBQLAST nodes.
 * The result is a typed EJBQLAST tree.
 *
 * @author Michael Bouschen
 * @author Shing Wai Chan
 */
class Semantic extends TreeParser;

options
{
    importVocab = EJBQL;
    buildAST = true;
    defaultErrorHandler = false;
    ASTLabelType = "EJBQLAST"; //NOI18N
}

{
    /** Name of the property to disable order by validation. */
    public static final String DISABLE_ORDERBY_VALIDATION_PROPERTY =
        "com.sun.jdo.spi.persistence.support.ejb.ejbqlc.DISABLE_ORDERBY_VALIDATION"; // NOI18N

    /**
     * Property to disable order by validation.
     * Note, the default is false, meaning the compiler checks that select
     * clause and orderby clause are compatible.
     */
    private static final boolean DISABLE_ORDERBY_VALIDATION =
        Boolean.getBoolean(DISABLE_ORDERBY_VALIDATION_PROPERTY);

    /** Symbol table handling names of variables and parameters. */
    protected SymbolTable symtab;

    /** Type info access helper. */
    protected TypeSupport typeSupport;
    
    /** Parameter info helper. */
    protected ParameterSupport paramSupport;
    
    /** The Method instance of the finder/selector method. */
    protected Method method;

    /** result-type-mapping element from the DD. */
    protected int resultTypeMapping;

    /** Flag indicating finder or selector. */
    protected boolean finderNotSelector;

    /** Flag indicating have aggregate function or not. */
    protected boolean isAggregate = false;

    /** The ejb-name. */
    protected String ejbName;

    /** I18N support. */
    protected final static ResourceBundle msgs = I18NHelper.loadBundle(
        Semantic.class);
    
    /**
     * Initializes the semantic analysis.
     * @param typeSupport type info access helper.
     * @param paramSupport parameter info helper.
     * @param method method instance of the finder/selector method.
     * @param resultTypeMapping result-type-mapping element from the DD
     * @param finderNotSelector <code>true</code> for finder;
     * <code>false</code> for selector
     * @param ejbName the ejb name of the finder/selector method.
     */
    public void init(TypeSupport typeSupport, ParameterSupport paramSupport,
                     Method method, int resultTypeMapping,
                     boolean finderNotSelector, String ejbName)
    {
        this.symtab = new SymbolTable();
        this.typeSupport = typeSupport;
        this.paramSupport = paramSupport;
        this.method = method;
        this.resultTypeMapping = resultTypeMapping;
        this.finderNotSelector = finderNotSelector;
        this.ejbName = ejbName;
    }

    /** */
    public void reportError(RecognitionException ex) {
        ErrorMsg.fatal(I18NHelper.getMessage(msgs, "ERR_SemanticError"), ex); //NOI18N
    }

    /** */
    public void reportError(String s) {
        ErrorMsg.fatal(I18NHelper.getMessage(msgs, "ERR_SemanticError") + s); //NOI18N
    }
    
    //========= Internal helper methods ==========

    /**
     * Checks the return type and the type of the select clause expression
     * of a finder method.
     * <p>
     * The return type of a finder must be one of the following:
     * <ul>
     * <li>java.util.Collection (multi-object finder)
     * <li>java.util.Enumeration (EJB 1.1 multi-object finder)
     * <li>the entity bean's remote interface (single-object finder)
     * <li>the entity bean's local interface (single-object finder)
     * </ul>
     * The type of the select clause expression of a finder must be
     * the entity bean's local or remote interface.
     * @param returnType the return type of the finder/selector method object
     * @param selectClauseTypeInfo the type info of the select clause
     * expression.
     */
    private void checkFinderReturnType(
        Class returnType, Object selectClauseTypeInfo)
    {
        String selectClauseTypeName = typeSupport.getTypeName(selectClauseTypeInfo);
        Object returnTypeInfo = typeSupport.getTypeInfo(returnType);
        // The return type of a finder must be Collection or Enumeration or
        // the entity bean's remote or local interface
        if ((returnType != java.util.Collection.class) &&
            (returnType != java.util.Enumeration.class) &&
            (!typeSupport.isRemoteInterfaceOfEjb(returnTypeInfo, ejbName)) &&
            (!typeSupport.isLocalInterfaceOfEjb(returnTypeInfo, ejbName))) {
            ErrorMsg.error(I18NHelper.getMessage(msgs,
                "EXC_InvalidFinderReturnType", returnType.getName())); //NOI18N
                    
        }
        
        // The type of the select clause expression must be the ejb name
        // of this bean.
        if (!selectClauseTypeName.equals(this.ejbName)) {
            ErrorMsg.error(I18NHelper.getMessage(msgs,
                "EXC_InvalidFinderSelectClauseType", selectClauseTypeName)); //NOI18N
        }
    }

    /**
     * Implements type compatibility for selector. The method returns
     * <code>true</code> if returnTypeInfo is compatible with
     * selectClauseTypeInfo.
     */
    private boolean isCompatibleSelectorSelectorReturnType(
            Object returnTypeInfo, Object selectClauseTypeInfo)
    {
        if (isAggregate) {
            return getCommonOperandType(selectClauseTypeInfo, returnTypeInfo) != TypeSupport.errorType;
        } else {
            return typeSupport.isCompatibleWith(selectClauseTypeInfo, returnTypeInfo);
        }
    }
    

    /**
     * Checks the return type and the type of the select clause expression
     * of a selector method.
     * <p>
     * The return type of a selector must be one of the following:
     * <ul>
     * <li>java.util.Collection (multi-object selector)
     * <li>java.util.Set (multi-object selector)
     * <li>assignable from the type of the select clause expression
     * (single-object selector)
     * </ul>
     * @param returnType the return type of the finder/selector method object
     * @param selectClauseTypeInfo the type info of the select clause
     * expression.
     */
    private void checkSelectorReturnType(
        Class returnType, Object selectClauseTypeInfo)
    {
        String selectClauseTypeName = typeSupport.getTypeName(selectClauseTypeInfo);
        Object returnTypeInfo = typeSupport.getTypeInfo(returnType);
        // The return type of a selector must be Collection or Set or
        // assingable from the type of the select clause expression
        if ((returnType != java.util.Collection.class) &&
            (returnType != java.util.Set.class) &&
            !isCompatibleSelectorSelectorReturnType(returnTypeInfo,
                selectClauseTypeInfo)) {
            ErrorMsg.error(I18NHelper.getMessage(msgs,
                "EXC_InvalidSelectorReturnType", //NOI18N
                typeSupport.getTypeName(returnTypeInfo), selectClauseTypeName));
        }
    }

    /**
     * Checks the result-type-mapping element setting in the case of a finder
     * method. Finder must not specify result-type-mapping.
     */
    private void checkFinderResultTypeMapping()
    {
        if (resultTypeMapping != MethodHelper.NO_RETURN) {
            ErrorMsg.error(I18NHelper.getMessage(msgs,
                "EXC_InvalidResultTypeMappingForFinder")); //NOI18N
        }
    }

    /**
     * Checks the setting of the result-type-mapping element for a
     * selector. Only selectors returning a entity object may
     * specify this.
     * <p>
     * The method checks the following error cases:
     * <ul>
     * <li>result-type-mapping is specified as Remote,
     * but bean does not have remote interface
     * <li>result-type-mapping is specified as Local,
     * but bean does not have local interface
     * <li>single-object selector returns remote interface,
     * but result-type-mapping is not specified as Remote
     * <li>single-object selector returns local interface,
     * but result-type-mapping is specified as Remote
     * <li>result-type-mapping is specified for a selector returning
     * non-entity objects.
     * </ul>
     * @param returnType the return type of the finder/selector method object
     * @param selectClauseTypeInfo the type info of the select clause.
     */
    private void checkSelectorResultTypeMapping(
        Class returnType, Object selectClauseTypeInfo)
    {
        Object returnTypeInfo = typeSupport.getTypeInfo(returnType);

        // case: multi-object selector returning entity objects
        if (typeSupport.isCollectionType(returnTypeInfo) &&
            typeSupport.isEjbName(selectClauseTypeInfo)) {
            if (resultTypeMapping == MethodHelper.REMOTE_RETURN) {
                // result-type-mapping is Remote =>
                // bean must have remote interface
                if (!typeSupport.hasRemoteInterface(selectClauseTypeInfo)) {
                    ErrorMsg.error(I18NHelper.getMessage(msgs,
                        "EXC_InvalidRemoteResultTypeMappingForMultiSelector", //NOI18N
                        selectClauseTypeInfo));
                }
            }
            else {
                // result-type-mapping is Local or not specified =>
                // bean must have local interface
                if (!typeSupport.hasLocalInterface(selectClauseTypeInfo)) {
                    ErrorMsg.error(I18NHelper.getMessage(msgs,
                        "EXC_InvalidLocalResultTypeMappingForMultiSelector", //NOI18N
                        selectClauseTypeInfo));
                }
            }
        }
        // case: single-object selector returning remote interface
        else if (typeSupport.isRemoteInterface(returnTypeInfo)) {
            // result-type-mapping must be Remote
            if (resultTypeMapping != MethodHelper.REMOTE_RETURN) {
                ErrorMsg.error(I18NHelper.getMessage(msgs,
                    "EXC_InvalidLocalResultTypeMappingForSingleSelector")); //NOI18N
            }
        }
        // case: single-object selector returning local interface
        else if (typeSupport.isLocalInterface(returnTypeInfo)) {
            // result-type-mapping must be Local or not specified
            if (resultTypeMapping == MethodHelper.REMOTE_RETURN) {
                ErrorMsg.error(I18NHelper.getMessage(msgs,
                    "EXC_InvalidRemoteResultTypeMappingForSingleSelector")); //NOI18N
            }
        }
        // cases: single-object and multi-object selector
        // returning non-enity object(s)
        else if (resultTypeMapping != MethodHelper.NO_RETURN) {
            // result-type-mapping must not be specified
            ErrorMsg.error(I18NHelper.getMessage(msgs,
                "EXC_InvalidResultTypeMappingForSelector", //NOI18N
                selectClauseTypeInfo));
        }
    }

    /**
     * Checks that select clause and orderby clause are compatible.
     * <p>
     * The method checks the following error cases:
     * <ul>
     * <li>if the select clause is an identification variable or
     * a single valued cmr path expression, then the orderby item
     * must be a cmp field of the entity bean abstract schema
     * type value returned by the SELECT clause
     * <li>if the select clause is a cmp field, then
     * orderby item must be empty or the same cmp field.
     * </ul>
     * @param select the select clause of the query
     * @param orderby the orderby clause of the query
     */
    private void checkSelectOrderbyClause(EJBQLAST select, EJBQLAST orderby)
    {
        // nothing to check if no orderby clause or
        // if orderby validation is disabled
        if ((orderby == null) || DISABLE_ORDERBY_VALIDATION) {
            return;
        }

        AST selectReturnAST = select.getFirstChild();
        // skip DISTINCT node, so selectReturnAST should be one of the following:
        // Object(x), cmr-field, cmp-field
        // it is illegal to be an aggregate function node
        if (selectReturnAST.getType() == DISTINCT) {
            selectReturnAST = selectReturnAST.getNextSibling();
        }

        if (selectReturnAST.getType() == CMP_FIELD_ACCESS) {
            StringBuffer buf = new StringBuffer();
            genPathExpression(selectReturnAST, buf);
            String selectReturnPathExpr = buf.toString();
            for (AST sibling = orderby.getFirstChild();
                    sibling != null;
                    sibling = sibling.getNextSibling().getNextSibling()) {

                // share buf
                buf.setLength(0);
                genPathExpression(sibling, buf);
                String siblingPathExpr = buf.toString();
                if (!selectReturnPathExpr.equals(siblingPathExpr)) {
                    ErrorMsg.error(I18NHelper.getMessage(msgs,
                    "EXC_InvalidOrderbyItemForCMPSelect", //NOI18N
                    siblingPathExpr));
                }
            }
        } else {
            AST abstractSchemaAST = null;
            if (selectReturnAST.getType() == SINGLE_CMR_FIELD_ACCESS) {
                abstractSchemaAST = selectReturnAST;
            } else if (selectReturnAST.getType() == OBJECT) {
                abstractSchemaAST = selectReturnAST.getFirstChild();
            } else { // it must be an aggregate function node
                ErrorMsg.error(I18NHelper.getMessage(msgs,
                "EXC_InvalidAggregateOrderby" //NOI18N
                ));
            }

            StringBuffer buf = new StringBuffer();
            genPathExpression(abstractSchemaAST, buf);
            String abstractSchemaExpr = buf.toString();
            for (AST sibling = orderby.getFirstChild();
                    sibling != null;
                    sibling = sibling.getNextSibling().getNextSibling()) {

                // share buf
                buf.setLength(0);
                genPathExpression(sibling.getFirstChild(), buf);
                String siblingRootExpr = buf.toString();
                if (!abstractSchemaExpr.equals(siblingRootExpr)) {
                    buf.setLength(0);
                    genPathExpression(sibling, buf);
                    ErrorMsg.error(I18NHelper.getMessage(msgs,
                    "EXC_InvalidOrderbyItem", //NOI18N
                    buf.toString()));
                }
            }
        }
    }

    /**
     * Form a string representation of a dot expression and append to given
     * StringBuffer.
     * @param ast the AST node representing the root the of the expression
     * @param buf the StringBuffer that will have result of path expression
     * append
     */
    //SW: We can write this method without recursion. Michael suggests to use
    //recursion for readability.
    private void genPathExpression(AST ast, StringBuffer buf) {
        if (ast == null) {
            return;
        }
        switch (ast.getType()) {
            case CMP_FIELD_ACCESS:
            case COLLECTION_CMR_FIELD_ACCESS:
            case SINGLE_CMR_FIELD_ACCESS:
                AST left = ast.getFirstChild();
                AST right = left.getNextSibling();
                genPathExpression(left, buf);
                buf.append('.');
                genPathExpression(right, buf);
                break;
            default:
                buf.append(ast.getText());
                break;
        }
    }

    /**
     * Analyses a logical operation AND, OR
     * @param op the logical operator
     * @param leftAST left operand
     * @param rightAST right operand
     * @return the type info of the operator
     */
    private Object analyseConditionalExpr(EJBQLAST op, EJBQLAST leftAST, EJBQLAST rightAST)
    {
        Object left = leftAST.getTypeInfo();
        Object right = rightAST.getTypeInfo();
        
        // handle error type
        if (typeSupport.isErrorType(left) || typeSupport.isErrorType(right))
            return typeSupport.errorType;
        
        if (typeSupport.isBooleanType(left) && typeSupport.isBooleanType(right)) {
            Object common = typeSupport.booleanType;
            return common;
        }

        // if this code is reached a bitwise operator was used with invalid arguments
        ErrorMsg.error(op.getLine(), op.getColumn(),
            I18NHelper.getMessage(msgs, "EXC_InvalidArguments", op.getText())); //NOI18N
        return typeSupport.errorType;
    }
    
    /**
     * Analyses a equality operation (==, <>)
     * @param op the relational operator
     * @param leftAST left operand
     * @param rightAST right operand
     * @return the type info of the operator
     */
    private Object analyseEqualityExpr(EJBQLAST op, EJBQLAST leftAST, EJBQLAST rightAST)
    {
        Object left = leftAST.getTypeInfo();
        Object right = rightAST.getTypeInfo();
        
        // handle error type
        if (typeSupport.isErrorType(left) || typeSupport.isErrorType(right)) {
            return typeSupport.errorType;
        }

        // check left hand side for literals and input params
        if (isLiteral(leftAST)) {
            ErrorMsg.error(leftAST.getLine(), leftAST.getColumn(),
                I18NHelper.getMessage(msgs, "EXC_InvalidLHSLiteral", //NOI18N
                    leftAST.getText(), op.getText()));
            return typeSupport.errorType;
        }
        else if (isInputParameter(leftAST)) {
            ErrorMsg.error(leftAST.getLine(), leftAST.getColumn(),
                I18NHelper.getMessage(msgs, "EXC_InvalidLHSParameter", //NOI18N
                    leftAST.getText(), op.getText()));
            return typeSupport.errorType;
        }
        
        // check operand types
        if (typeSupport.isNumberType(left) && typeSupport.isNumberType(right)) {
            return typeSupport.booleanType;
        }
        else if (typeSupport.isStringType(left) && typeSupport.isStringType(right)) {
            return typeSupport.booleanType;
        }
        else if (typeSupport.isDateTimeType(left) && typeSupport.isDateTimeType(right)) {
            return typeSupport.booleanType;
        }
        else if (isEntityBeanValue(leftAST) && isEntityBeanValue(rightAST) &&
                 (typeSupport.isCompatibleWith(left, right) ||
                  typeSupport.isCompatibleWith(right, left))) {
            String leftEjbName = (String)leftAST.getTypeInfo();
            // the input parameter must be on right hand side of an equality
            // expression ('?1' = e.department is not supported)
            return analyseParameterEjbName(rightAST, leftEjbName);
        }
        else if (typeSupport.isBooleanType(left) && typeSupport.isBooleanType(right)) {
            return typeSupport.booleanType;
        }

        // if this code is reached a conditional operator was used with invalid arguments
        ErrorMsg.error(op.getLine(), op.getColumn(),
            I18NHelper.getMessage(msgs, "EXC_InvalidArguments", op.getText())); //NOI18N
        return typeSupport.errorType;
    }
    
    /**
     * Analyses a relational operation (<, <=, >, >=)
     * @param op the relational operator
     * @param leftAST left operand
     * @param rightAST right operand
     * @return the type info of the operator
     */
    private Object analyseRelationalExpr(EJBQLAST op, EJBQLAST leftAST, EJBQLAST rightAST)
    {
        Object left = leftAST.getTypeInfo();
        Object right = rightAST.getTypeInfo();

        // handle error type
        if (typeSupport.isErrorType(left) || typeSupport.isErrorType(right)) {
            return typeSupport.errorType;
        }

        // check left hand side for literals and input params
        if (isLiteral(leftAST)) {
            ErrorMsg.error(leftAST.getLine(), leftAST.getColumn(),
                I18NHelper.getMessage(msgs, "EXC_InvalidLHSLiteral", //NOI18N
                    leftAST.getText(), op.getText()));
            return typeSupport.errorType;
        }
        else if (isInputParameter(leftAST)) {
            ErrorMsg.error(leftAST.getLine(), leftAST.getColumn(),
                I18NHelper.getMessage(msgs, "EXC_InvalidLHSParameter", //NOI18N
                    leftAST.getText(), op.getText()));
            return typeSupport.errorType;
        }
        
        // check operand types
        if ((typeSupport.isNumberType(left) && typeSupport.isNumberType(right)) ||
            (typeSupport.isDateTimeType(left) && typeSupport.isDateTimeType(right)) ||
            (typeSupport.isStringType(left) && typeSupport.isStringType(right))) {
            return typeSupport.booleanType;
        }

        // if this code is reached a conditional operator was used with invalid arguments
        ErrorMsg.error(op.getLine(), op.getColumn(),
            I18NHelper.getMessage(msgs, "EXC_InvalidArguments", op.getText())); //NOI18N
        return typeSupport.errorType;
    }
    
    /**
     * Analyses a binary arithmetic expression +, -, *, /.
     * @param op the operator
     * @param leftAST left operand
     * @param rightAST right operand
     * @return the type info of the operator
     */
    private Object analyseBinaryArithmeticExpr(EJBQLAST op, EJBQLAST leftAST, EJBQLAST rightAST)
    {
        Object left = leftAST.getTypeInfo();
        Object right = rightAST.getTypeInfo();

        // handle error type
        if (typeSupport.isErrorType(left) || typeSupport.isErrorType(right)) {
            return typeSupport.errorType;
        }

        if (typeSupport.isNumberType(left) && typeSupport.isNumberType(right)) {
            Object common = getCommonOperandType(left, right);
            if (!typeSupport.isErrorType(common))
                return common;
        }

        // if this code is reached a conditional operator was used with invalid arguments
        ErrorMsg.error(op.getLine(), op.getColumn(),
            I18NHelper.getMessage(msgs, "EXC_InvalidArguments", op.getText())); //NOI18N
        return typeSupport.errorType;
    }

    /**
     * Returns the common type info for the specified operand types.
     * This includes binary numeric promotion as specified in Java.
     * @param left type info of left operand
     * @param right type info of right operand
     * @return the type info of the operator
     */
    private Object getCommonOperandType(Object left, Object right)
    {
        if (typeSupport.isNumberType(left) && typeSupport.isNumberType(right)) {
            boolean wrapper = false;

            // handle java.math.BigDecimal:
            if (typeSupport.bigDecimalType.equals(left)) {
                return left;
            }
            if (typeSupport.bigDecimalType.equals(right)) {
                return right;
            }
            
            // handle java.math.BigInteger
            if (typeSupport.bigIntegerType.equals(left)) {
                // if right is floating point return BigDecimal,
                // otherwise return BigInteger
                return typeSupport.isFloatingPointType(right) ?
                       typeSupport.bigDecimalType : left;
            }
            if (typeSupport.bigIntegerType.equals(right)) {
                // if left is floating point return BigDecimal,
                // otherwise return BigInteger
                return typeSupport.isFloatingPointType(left) ?
                       typeSupport.bigDecimalType : right;
            }

            if (typeSupport.isNumericWrapperType(left)) {
                left = typeSupport.getPrimitiveType(left);
                wrapper = true;
            }
            if (typeSupport.isNumericWrapperType(right)) {
                right = typeSupport.getPrimitiveType(right);
                wrapper = true;
            }
            
            // handle numeric types with arbitrary arithmetic operator
            if (typeSupport.isNumericType(left) && typeSupport.isNumericType(right)) {
                Object promotedType = typeSupport.binaryNumericPromotion(left, right);
                if (wrapper)
                    promotedType = typeSupport.getWrapperType(promotedType);
                return promotedType;
            }
        }
        else if (typeSupport.isBooleanType(left) && typeSupport.isBooleanType(right)) {
            // check for boolean wrapper class: if one of the operands has the
            // type Boolean return Boolean, otherwise return boolean.
            if (left.equals(typeSupport.booleanClassType) ||
                right.equals(typeSupport.booleanClassType))
                return typeSupport.booleanClassType;
            else
                return typeSupport.booleanType;
        }
        else if (typeSupport.isCompatibleWith(left, right)) {
            return right;
        }
        else if (typeSupport.isCompatibleWith(right, left)) {
            return left;
        }

        // not compatible types => return errorType
        return typeSupport.errorType;
    }

    /**
     * Analyses a unary expression (+ and -).
     * @param op the operator
     * @param argASTleftAST left operand
     * @param rightAST right operand
     * @return the type info of the operator
     */
    private Object analyseUnaryArithmeticExpr(EJBQLAST op, EJBQLAST argAST)
    {
        Object arg = argAST.getTypeInfo();

        // handle error type
        if (typeSupport.isErrorType(arg))
            return arg;
        
        if (typeSupport.isNumberType(arg)) {
            boolean wrapper = false;
            if (typeSupport.isNumericWrapperType(arg)) {
                arg = typeSupport.getPrimitiveType(arg);
                wrapper = true;
            }

            Object promotedType = typeSupport.unaryNumericPromotion(arg);
            if (wrapper)
                promotedType = typeSupport.getWrapperType(promotedType);
            return promotedType;
        }
        
        // if this code is reached a conditional operator was used with invalid arguments
        ErrorMsg.error(op.getLine(), op.getColumn(),
            I18NHelper.getMessage(msgs, "EXC_InvalidArguments", op.getText())); //NOI18N
        return typeSupport.errorType;
    }
    
    /**
     * Analyses a expression node that is expected to access a collection
     * valued CMR field. It returns the element type of the collection valued
     * CMR field.
     * @param fieldAccess the field access node
     * @return the type info of the operator
     */
    private Object analyseCollectionValuedCMRField(EJBQLAST fieldAccess)
    {
        if (fieldAccess.getType() != COLLECTION_CMR_FIELD_ACCESS) {
            ErrorMsg.fatal(I18NHelper.getMessage(msgs, "ERR_InvalidPathExpr")); //NOI18N
            return typeSupport.errorType;
        }

        EJBQLAST classExpr = (EJBQLAST)fieldAccess.getFirstChild();
        EJBQLAST field = (EJBQLAST)classExpr.getNextSibling();
        Object fieldInfo =
            typeSupport.getFieldInfo(classExpr.getTypeInfo(), field.getText());
        return typeSupport.getElementType(fieldInfo);
    }

    /**
     * Analyses a MEMBER OF operation.
     * @param op the MEMBER OF operator
     * @param value node representing the value to be tested
     * @param col the collection
     * @return the type info of the operator
     */
    private Object analyseMemberExpr(EJBQLAST op, EJBQLAST value, EJBQLAST col)
    {
        Object valueTypeInfo = value.getTypeInfo();
        Object elementTypeInfo = analyseCollectionValuedCMRField(col);

        // handle error type
        if (typeSupport.isErrorType(valueTypeInfo) ||
            typeSupport.isErrorType(elementTypeInfo)) {
            return typeSupport.errorType;
        }

        // check compatibility
        if (typeSupport.isCompatibleWith(valueTypeInfo, elementTypeInfo) ||
            typeSupport.isCompatibleWith(elementTypeInfo, valueTypeInfo)) {

            return analyseParameterEjbName(value, (String)elementTypeInfo);
        }
        
        // if this code is reached there is a compatibility problem
        // with the value and the collection expr
        ErrorMsg.error(op.getLine(), op.getColumn(),
            I18NHelper.getMessage(msgs, "EXC_CollectionElementTypeMismatch", //NOI18N
                typeSupport.getTypeName(elementTypeInfo),
                typeSupport.getTypeName(valueTypeInfo)));
        return typeSupport.errorType;
    }

    /**
     * Analyses the type of the element to be compatible with the type of the
     * value expression in the sense that element type can be cast into value
     * type without losing precision.
     * For instance, element type can be a double and value type can be an
     * integer.
     * @param elementAST given element
     * @param valueTypeInfo the type to be check for compatibility
     * @return the type info of the elementAST or typeSupport.errorType
     */
    private Object analyseInCollectionElement(EJBQLAST elementAST,
            Object valueTypeInfo)
    {
        Object elementTypeInfo = elementAST.getTypeInfo();

        // handle error type
        if (typeSupport.isErrorType(valueTypeInfo) ||
            typeSupport.isErrorType(elementTypeInfo)) {
            return typeSupport.errorType;
        }

        Object common = getCommonOperandType(elementTypeInfo, valueTypeInfo);
        if (!typeSupport.isErrorType(common) &&
                elementTypeInfo.equals(common)) {
            return common;
        }

        // if this code is reached there is a compatibility problem
        // with the value and the collection expr
        ErrorMsg.error(elementAST.getLine(), elementAST.getColumn(),
            I18NHelper.getMessage(msgs, "EXC_CollectionElementTypeMismatch", //NOI18N
            typeSupport.getTypeName(valueTypeInfo),
            typeSupport.getTypeName(elementTypeInfo)));
        return typeSupport.errorType;
    }

    /**
     * Analyses whether paramAST can be associated to a ejbName.
     * @param paramAST AST node corresponds to a PARAMETER
     * @param ejbName name to be check with paramAST
     * @return the type info of typeSupport.booleanType or typeSupport.errorType
     */
    private Object analyseParameterEjbName(EJBQLAST paramAST, String ejbName)
    {
        if (isInputParameter(paramAST)) {
            String paramName = paramAST.getText();
            String paramEjbName = paramSupport.getParameterEjbName(paramName);
            if (paramEjbName != null && !paramEjbName.equals(ejbName)) {
                ErrorMsg.error(paramAST.getLine(), paramAST.getColumn(),
                    I18NHelper.getMessage(msgs,
                    "EXC_MultipleEJBNameParameter", // NOI18N
                    paramName, ejbName, paramEjbName));
                return typeSupport.errorType;
            } else {
                paramSupport.setParameterEjbName(paramName, ejbName);
            }
        }
        return typeSupport.booleanType;
    }
    
    /**
     * Returns <code>true</code> if ast denotes a entity bena value.
     */
    private boolean isEntityBeanValue(EJBQLAST ast)
    {
        switch(ast.getType()) {
        case SINGLE_CMR_FIELD_ACCESS:
        case IDENTIFICATION_VAR:
            return true;
        case INPUT_PARAMETER:
            Object typeInfo = ast.getTypeInfo();
            return typeSupport.isEjbOrInterfaceName(typeInfo);
        }
        return false;
    }

    /**
     * Returns <code>true</code> if ast denotes a literal.
     */
    private boolean isLiteral(EJBQLAST ast)
    {
        int tokenType = ast.getType();
        return ((tokenType == INT_LITERAL) ||
                (tokenType == LONG_LITERAL) ||
                (tokenType == STRING_LITERAL) ||
                (tokenType == FLOAT_LITERAL) ||
                (tokenType == DOUBLE_LITERAL) ||
                (tokenType == TRUE) ||
                (tokenType == FALSE));
    }

    /**
     * Returns <code>true</code> if ast denotes a input parameter access.
     */
    private boolean isInputParameter(EJBQLAST ast)
    {
        return ast.getType() == INPUT_PARAMETER;
    }
    
    /**
     * The method checks the specified node being an expression of type String.
     * @param expr the expression to be checked
     * @return <code>true</code> if the specified expression has the type String.
     */
    private boolean isStringExpr(EJBQLAST expr)
    {
        Object exprType = expr.getTypeInfo();
        
        // handle error type
        if (typeSupport.isErrorType(exprType))
            return true;
        
        // expr must have the type String
        if (!typeSupport.isStringType(exprType)) {
            ErrorMsg.error(expr.getLine(), expr.getColumn(),
                I18NHelper.getMessage(msgs, "EXC_StringExprExpected", //NOI18N
                    typeSupport.getTypeName(exprType)));
            return false;
        }
        
        // everything is ok => return true;
        return true;
    }

    /**
     * The method checks the specified node being an expression of
     * type int or java.lang.Integer.
     * @param expr the expression to be checked
     * @return <code>true</code> if the specified expression has the type
     * int or java.lang.Integer.
     */
    private boolean isIntExpr(EJBQLAST expr)
    {
        Object exprType = expr.getTypeInfo();
        
        // handle error type
        if (typeSupport.isErrorType(exprType))
            return true;
        
        // expr must have the type int or Integer
        if (!typeSupport.isIntType(exprType)) {
            ErrorMsg.error(expr.getLine(), expr.getColumn(),
                I18NHelper.getMessage(msgs, "EXC_IntExprExpected", //NOI18N
                    typeSupport.getTypeName(exprType)));
            return false;
        }
        
        // everything is ok => return true;
        return true;
    }

    /**
     * The method checks the specified node being an expression of
     * type double or java.lang.Double.
     * @param expr the expression to be checked
     * @return <code>true</code> if the specified expression has the type
     * double or java.lang.Double.
     */
    private boolean isDoubleExpr(EJBQLAST expr)
    {
        Object exprType = expr.getTypeInfo();
        
        // handle error type
        if (typeSupport.isErrorType(exprType))
            return true;
        
        // expr must have the type double or Double
        if (!typeSupport.isDoubleType(exprType)) {
            ErrorMsg.error(expr.getLine(), expr.getColumn(),
                I18NHelper.getMessage(msgs, "EXC_DoubleExprExpected", //NOI18N
                    typeSupport.getTypeName(exprType)));
            return false;
        }
        
        // everything is ok => return true;
        return true;
    }

    /**
     * The method checks the specified node being an expression of a number type
     * (a numeric type or a number wrapper class).
     * @param expr the expression to be checked
     * @return <code>true</code> if the specified expression has a number type.
     */
    private boolean isNumberExpr(EJBQLAST expr)
    {
        Object exprType = expr.getTypeInfo();
        
        // handle error type
        if (typeSupport.isErrorType(exprType))
            return true;
        
        // expr must have a number type
        if (!typeSupport.isNumberType(exprType)) {
            ErrorMsg.error(expr.getLine(), expr.getColumn(),
                I18NHelper.getMessage(msgs, "EXC_NumberExprExpected", //NOI18N
                    typeSupport.getTypeName(exprType)));
            return false;
        }
        
        // everything is ok => return true;
        return true;
    }

    /**
     * The method checks the specified node being an expression of a number type
     * (a numeric type or a number wrapper class).
     * @param expr the expression to be checked
     * @return <code>true</code> if the specified expression has a number or
     * String type
     */
    private boolean isNumberOrStringExpr(EJBQLAST expr)
    {
        Object exprType = expr.getTypeInfo();
        
        // handle error type
        if (typeSupport.isErrorType(exprType))
            return true;
        
        // expr must have a number type
        if (!typeSupport.isNumberType(exprType) &&
                !typeSupport.isStringType(exprType)) {
            ErrorMsg.error(expr.getLine(), expr.getColumn(),
                I18NHelper.getMessage(msgs,
                    "EXC_NumberOrStringExprExpected", //NOI18N
                    typeSupport.getTypeName(exprType)));
            return false;
        }
        
        // everything is ok => return true;
        return true;
    }

    /**
     * The method checks whether the specified node denotes a valid abstract
     * schema type.
     * @param ident the node to be checked
     * @return the type info for the abstract bean class of the specified
     * abstract schema type.
     */
    private Object checkAbstractSchemaType(EJBQLAST ident)
    {
        String name = ident.getText();
        Object typeInfo =
            typeSupport.getTypeInfoForAbstractSchema(name);
        if (typeInfo == null) {
            ErrorMsg.error(ident.getLine(), ident.getColumn(),
                I18NHelper.getMessage(msgs,
                    "EXC_AbstractSchemNameExpected", name)); //NOI18N
            typeInfo = typeSupport.errorType;
        }
        return typeInfo;
    }

    /**
     * Returns true if the specified text is a string literal consisting of a
     * single char. Escaped chars are counted as a single char such as \ uxxxx.
     */
    private boolean isSingleCharacterStringLiteral(String text)
    {
        int i = 0;
        int length = text.length();
        if (length == 0) {
            // empty string
            return false;
        }
        if (text.charAt(i) == '\\')
        {
            i++;
            if (i == length) {
                // string literal was '\'
                return true;
            }
            // escaped char => check the next char
            if (text.charAt(i) == 'u') {
                // unicode
                i +=5;
            }
            else if (('0' <= text.charAt(i)) && (text.charAt(i) <= '3')) {
                i++;
                if ((i < length) && isOctalDigit(text.charAt(i))) {
                    i++;
                    if ((i < length) && isOctalDigit(text.charAt(i))) {
                        i++;
                    }
                }
            }
            else if (isOctalDigit(text.charAt(i))) {
                i++;
                if ((i < length) && isOctalDigit(text.charAt(i))) {
                    i++;
                }
            }
            else {
                i++;
            }
        }
        else if (text.charAt(i) == '\''){
            // check special EJBQL single quote char
            i++;
            if ((i < length) && (text.charAt(i) == '\'')) {
                i++;
            }
        }
        else {
            i++;
        }
        // reached end of text?
        return (i == length);
    }

    /** Returns true if the specified char is an octal digit */
    private boolean isOctalDigit(char c)
    {
        return ('0' <= c && c <= '7');
    }

}

// rules

query
    : #(QUERY fromClause s:selectClause whereClause o:orderbyClause)
        {
            checkSelectOrderbyClause(#s, #o);
        }
    ;

// ----------------------------------
// rules: from clause
// ----------------------------------

fromClause
    : #( FROM ( identificationVarDecl )+ )
    ;

identificationVarDecl
    : collectionMemberDecl
    | rangeVarDecl
    ;

collectionMemberDecl
    : #(IN p:collectionValuedPathExpression var:IDENT)
        {
            Object typeInfo = analyseCollectionValuedCMRField(#p);
            String name = #var.getText();
            Object identVar = new IdentificationVariable(name, typeInfo);
            if (symtab.declare(name, identVar) != null) {
                ErrorMsg.error(#var.getLine(), #var.getColumn(),
                    I18NHelper.getMessage(msgs, "EXC_MultipleDeclaration", name)); //NOI18N
            }
            #var.setType(IDENTIFICATION_VAR_DECL);
            #var.setTypeInfo(typeInfo);
        }
    ;

rangeVarDecl
    : #(RANGE abstractSchemaName:ABSTRACT_SCHEMA_NAME var:IDENT)
        {
            // check abstract schema name
            Object typeInfo =
                checkAbstractSchemaType(#abstractSchemaName);
            #abstractSchemaName.setTypeInfo(typeInfo);

            // check identification variable
            String name = #var.getText();
            Object identVar = new IdentificationVariable(name, typeInfo);
            if (symtab.declare(name, identVar) != null) {
                ErrorMsg.error(#var.getLine(), #var.getColumn(),
                    I18NHelper.getMessage(msgs, "EXC_MultipleDeclaration", name)); //NOI18N
            }
            #var.setType(IDENTIFICATION_VAR_DECL);
            #var.setTypeInfo(typeInfo);
        }
    ;

// ----------------------------------
// rules: select clause
// ----------------------------------

selectClause
    : #( SELECT distinct p:projection )
        {
            Object selectClauseTypeInfo = #p.getTypeInfo();
            Class returnType = method.getReturnType();
            if (finderNotSelector) {
                checkFinderReturnType(returnType, selectClauseTypeInfo);
                checkFinderResultTypeMapping();
            }
            else {
                checkSelectorReturnType(returnType, selectClauseTypeInfo);
                checkSelectorResultTypeMapping(returnType,
                                               selectClauseTypeInfo);
            }
        }
    ;

distinct
    : DISTINCT
    | // empty rule
        {
            // Insert DISTINCT keyword, in the case of a multi-object selector
            // having java.util.Set as return type
            if (!finderNotSelector &&
                (method.getReturnType() == java.util.Set.class)) {
                #distinct = #[DISTINCT,"distinct"];
            }
        }
    ;

projection
    : singleValuedPathExpression
    | #( o:OBJECT var:IDENT )
        {
            String name = #var.getText();
            Object decl = symtab.getDeclaration(name);
            Object typeInfo = null;
            if ((decl != null) &&
                (decl instanceof IdentificationVariable)) {
                #var.setType(IDENTIFICATION_VAR);
                typeInfo = ((IdentificationVariable)decl).getTypeInfo();
            }
            else {
                ErrorMsg.error(#var.getLine(), #var.getColumn(),
                    I18NHelper.getMessage(msgs,
                        "EXC_IdentificationVariableExcepted", name)); //NOI18N
            }
            #var.setTypeInfo(typeInfo);
            #o.setTypeInfo(typeInfo);
        }
    | #( sum:SUM ( DISTINCT )? sumExpr:cmpPathExpression )
        {
            // check numeric type
            Object typeInfo = #sumExpr.getTypeInfo();
            if (!typeSupport.isNumberType(typeInfo) ||
                    typeSupport.isCharType(typeInfo)) {
                ErrorMsg.error(#sumExpr.getLine(), #sumExpr.getColumn(),
                    I18NHelper.getMessage(msgs,
                        "EXC_NumberExprExpected", //NO18N
                        typeSupport.getTypeName(typeInfo)));
            }
            #sum.setTypeInfo(typeSupport.getSumReturnType(typeInfo));
            isAggregate = true;
        }
    | #( avg:AVG ( DISTINCT )? avgExpr:cmpPathExpression )
        {
            // check numeric type
            Object typeInfo = #avgExpr.getTypeInfo();
            if (!typeSupport.isNumberType(typeInfo) ||
                    typeSupport.isCharType(typeInfo)) {
                ErrorMsg.error(#avgExpr.getLine(), #avgExpr.getColumn(),
                    I18NHelper.getMessage(msgs,
                        "EXC_NumberExprExpected", //NO18N
                        typeSupport.getTypeName(typeInfo)));
            }
            #avg.setTypeInfo(typeSupport.getAvgReturnType(typeInfo));
            isAggregate = true;
        }
    | #( min:MIN ( DISTINCT )? minExpr:cmpPathExpression )
        {
            // check orderable type
            Object typeInfo = #minExpr.getTypeInfo();
            if (!typeSupport.isOrderableType(typeInfo)) {
                ErrorMsg.error(#minExpr.getLine(), #minExpr.getColumn(),
                    I18NHelper.getMessage(msgs,
                        "EXC_OrderableExpected", //NO18N
                        typeSupport.getTypeName(typeInfo)));
            }
            #min.setTypeInfo(typeSupport.getMinMaxReturnType(typeInfo));
            isAggregate = true;
        }
    | #( max:MAX ( DISTINCT )? maxExpr:cmpPathExpression )
        {
            // check orderable type
            Object typeInfo = #maxExpr.getTypeInfo();
            if (!typeSupport.isOrderableType(typeInfo)) {
                ErrorMsg.error(#maxExpr.getLine(), #maxExpr.getColumn(),
                    I18NHelper.getMessage(msgs,
                        "EXC_OrderableExpected", //NO18N
                        typeSupport.getTypeName(typeInfo)));
            }
            #max.setTypeInfo(typeSupport.getMinMaxReturnType(typeInfo));
            isAggregate = true;
        }
    | #( c:COUNT ( DISTINCT )? countExpr )
        {
            #c.setTypeInfo(typeSupport.longClassType);
            isAggregate = true;
        }
    ;

countExpr
    : v:IDENT
        {
            String name = #v.getText();
            Object decl = symtab.getDeclaration(name);
            Object typeInfo = null;
            if ((decl != null) &&
                (decl instanceof IdentificationVariable)) {
                #v.setType(IDENTIFICATION_VAR);
                typeInfo = ((IdentificationVariable)decl).getTypeInfo();
            }
            else {
                ErrorMsg.error(#v.getLine(), #v.getColumn(),
                    I18NHelper.getMessage(msgs,
                    "EXC_IdentificationVariableExcepted", name)); //NOI18N
            }
            #v.setTypeInfo(typeInfo);
        }
    | singleValuedPathExpression
    ;

// ----------------------------------
// rules: where clause
// ----------------------------------

whereClause
    : #( WHERE e:expression )
        {
            Object typeInfo = #e.getTypeInfo();
            if (!typeSupport.isBooleanType(typeInfo)) {
                ErrorMsg.error(#e.getLine(), #e.getColumn(),
                    I18NHelper.getMessage(msgs, "EXC_BooleanWhereClauseExpected", //NOI18N
                        typeSupport.getTypeName(typeInfo)));
            }
        }
    ;

// ----------------------------------
// rules: order by clause
// ----------------------------------

orderbyClause
    : #( ORDER ( orderbyItem )+ )
    | // empty rule
    ;

orderbyItem
    : expr:cmpPathExpression ( ASC | DESC )
        {
            // check orderable type
            Object typeInfo = #expr.getTypeInfo();
            if (!typeSupport.isOrderableType(typeInfo)) {
                ErrorMsg.error(#expr.getLine(), #expr.getColumn(),
                    I18NHelper.getMessage(msgs,
                        "EXC_OrderableOrderbyClauseExpected", //NO18N
                        typeSupport.getTypeName(typeInfo)));
            }
        }
    ;

// ----------------------------------
// rules: expression
// ----------------------------------

expression
    : conditionalExpr
    | relationalExpr
    | binaryArithmeticExpr
    | unaryExpr
    | betweenExpr
    | likeExpr
    | inExpr
    | nullComparisonExpr
    | emptyCollectionComparisonExpr
    | collectionMemberExpr
    | function
    | primary
    ;

conditionalExpr
    : #( op1:AND left1:expression right1:expression )
        {
            #op1.setTypeInfo(analyseConditionalExpr(#op1, #left1, #right1));
        }
    | #( op2:OR left2:expression right2:expression )
        {
            #op2.setTypeInfo(analyseConditionalExpr(#op2, #left2, #right2));
        }
    ;

relationalExpr
    : #( op1:EQUAL left1:expression right1:expression )
        {
            #op1.setTypeInfo(analyseEqualityExpr(#op1, #left1, #right1));
        }
    | #( op2:NOT_EQUAL left2:expression right2:expression )
        {
            #op2.setTypeInfo(analyseEqualityExpr(#op2, #left2, #right2));
        }
    | #( op3:LT left3:expression right3:expression )
        {
            #op3.setTypeInfo(analyseRelationalExpr(#op3, #left3, #right3));
        }
    | #( op4:LE left4:expression right4:expression )
        {
            #op4.setTypeInfo(analyseRelationalExpr(#op4, #left4, #right4));
        }
    | #( op5:GT left5:expression right5:expression )
        {
            #op5.setTypeInfo(analyseRelationalExpr(#op5, #left5, #right5));
        }
    | #( op6:GE left6:expression right6:expression )
        {
            #op6.setTypeInfo(analyseRelationalExpr(#op6, #left6, #right6));
        }
    ;

binaryArithmeticExpr
    : #( op1:PLUS left1:expression right1:expression )
        {
            #op1.setTypeInfo(analyseBinaryArithmeticExpr(#op1, #left1, #right1));
        }
    | #( op2:MINUS left2:expression right2:expression )
        {
            #op2.setTypeInfo(analyseBinaryArithmeticExpr(#op2, #left2, #right2));
        }
    | #( op3:STAR left3:expression right3:expression )
        {
            #op3.setTypeInfo(analyseBinaryArithmeticExpr(#op3, #left3, #right3));
        }
    | #( op4:DIV left4:expression right4:expression )
        {
            #op4.setTypeInfo(analyseBinaryArithmeticExpr(#op4, #left4, #right4));
        }
    ;

unaryExpr
    : #( op1:UNARY_PLUS arg1:expression )
        {
            #op1.setTypeInfo(analyseUnaryArithmeticExpr(#op1, #arg1));
        }
    | #( op2:UNARY_MINUS arg2:expression )
        {
            #op2.setTypeInfo(analyseUnaryArithmeticExpr(#op2, #arg2));
        }
    | #( op3:NOT arg3:expression )
        {
            Object typeInfo = typeSupport.errorType;
            Object arg = #arg3.getTypeInfo();
            if (typeSupport.isErrorType(arg))
                typeInfo = typeSupport.errorType;
            else if (typeSupport.isBooleanType(arg))
                typeInfo = arg;
            else {
                ErrorMsg.error(#op3.getLine(), #op3.getColumn(),
                    I18NHelper.getMessage(msgs, "EXC_InvalidArguments", //NOI18N
                        #op3.getText()));
            }
            #op3.setTypeInfo(typeInfo);
        }
    ;

betweenExpr
    : #( op1:BETWEEN expr1:expression lower1:expression upper1:expression )
        {
            #op1.setTypeInfo((isNumberExpr(#expr1) && isNumberExpr(#lower1) && isNumberExpr(#upper1)) ?
                typeSupport.booleanType : typeSupport.errorType);
        }
    | #( op2:NOT_BETWEEN expr2:expression lower2:expression upper2:expression )
        {
            #op2.setTypeInfo((isNumberExpr(#expr2) && isNumberExpr(#lower2) && isNumberExpr(#upper2)) ?
                typeSupport.booleanType : typeSupport.errorType);
        }
    ;

likeExpr
    : #( op1:LIKE expr1:cmpPathExpression pattern escape )
        {
            #op1.setTypeInfo(isStringExpr(#expr1) ?
                typeSupport.booleanType : typeSupport.errorType);
        }
    | #( op2:NOT_LIKE expr2:cmpPathExpression pattern escape )
        {
            #op2.setTypeInfo(isStringExpr(#expr2) ?
                typeSupport.booleanType : typeSupport.errorType);
        }
    ;

pattern
    : STRING_LITERAL
    | p:inputParameter
        {
            if (!typeSupport.isStringType(#p.getTypeInfo())) {
                ErrorMsg.error(#p.getLine(), #p.getColumn(),
                    I18NHelper.getMessage(msgs, "EXC_InvalidPatternDefinition",
                        #p.getText())); //NOI18N
            }
        }
    ;

escape
    : #( ESCAPE escapeCharacter )
    | // empty rule
    ;

escapeCharacter
    : s:STRING_LITERAL
        {
            String literal = #s.getText();
            // String must be single charater string literal =>
            // either '<char>' or ''''
            if (!isSingleCharacterStringLiteral(#s.getText())) {
                ErrorMsg.error(#s.getLine(), #s.getColumn(),
                    I18NHelper.getMessage(msgs,
                        "EXC_InvalidEscapeDefinition", #s.getText())); //NOI18N
            }
        }
    | p:inputParameter
        {
            Object paramType = #p.getTypeInfo();
            if (!typeSupport.isCharType(paramType)) {
                ErrorMsg.error(#p.getLine(), #p.getColumn(),
                    I18NHelper.getMessage(msgs,
                        "EXC_InvalidEscapeParameterDefinition", #p.getText())); //NOI18N
            }
        }
    ;

inExpr
    : #( op1:IN expr1:cmpPathExpression inCollection[#expr1.getTypeInfo()] )
        {
            #op1.setTypeInfo(isNumberOrStringExpr(#expr1) ?
                typeSupport.booleanType : typeSupport.errorType);
        }
    | #( op2:NOT_IN expr2:cmpPathExpression inCollection[#expr2.getTypeInfo()] )
        {
            #op2.setTypeInfo(isNumberOrStringExpr(#expr2) ?
                typeSupport.booleanType : typeSupport.errorType);
        }
    ;

nullComparisonExpr
    : #( op1:NULL expr1: ( singleValuedPathExpression | inputParameter ) )
        {
            #op1.setTypeInfo(typeSupport.booleanType);
        }
    | #( op2:NOT_NULL expr2: ( singleValuedPathExpression | inputParameter ) )
        {
            #op2.setTypeInfo(typeSupport.booleanType);
        }
    ;

emptyCollectionComparisonExpr
{
    Object elementTypeInfo = null;
}
    : #( op1:EMPTY col1:collectionValuedPathExpression )
        {
            elementTypeInfo = analyseCollectionValuedCMRField(#col1);
            #op1.setTypeInfo(typeSupport.isErrorType(elementTypeInfo) ?
                typeSupport.errorType : typeSupport.booleanType );
        }
    | #( op2:NOT_EMPTY col2:collectionValuedPathExpression )
        {
            elementTypeInfo = analyseCollectionValuedCMRField(#col2);
            #op2.setTypeInfo(typeSupport.isErrorType(elementTypeInfo) ?
                typeSupport.errorType : typeSupport.booleanType );
        }
    ;

collectionMemberExpr
    : #( op1:MEMBER value1:member col1:collectionValuedPathExpression )
        {
            #op1.setTypeInfo(analyseMemberExpr(#op1, #value1, #col1));
        }
    | #( op2:NOT_MEMBER value2:member col2:collectionValuedPathExpression )
        {
            #op2.setTypeInfo(analyseMemberExpr(#op2, #value2, #col2));
        }
    ;

member
    : identificationVariable
    | inputParameter
    | singleValuedCmrPathExpression
    ;

function
    : concat
    | substring
    | length
    | locate
    | abs
    | sqrt
    | mod
    ;

concat
    : #( op:CONCAT arg1:expression arg2:expression )
        {
            #op.setTypeInfo((isStringExpr(#arg1) && isStringExpr(#arg2)) ?
                typeSupport.stringType : typeSupport.errorType);
        }
    ;

substring
    : #( op:SUBSTRING arg1:expression arg2:expression arg3:expression )
        {
            #op.setTypeInfo((isStringExpr(#arg1) && isIntExpr(#arg2) && isIntExpr(#arg3)) ?
                typeSupport.stringType : typeSupport.errorType);
        }
    ;

length
    : #( op:LENGTH arg:expression )
        {
            #op.setTypeInfo(isStringExpr(#arg) ?
                typeSupport.intType : typeSupport.errorType);
        }
    ;

locate
    : #( op:LOCATE arg1:expression arg2:expression ( arg3:expression )? )
        {
            #op.setTypeInfo((isStringExpr(#arg1) && isStringExpr(#arg2) &&
                             ((#arg3 == null) || isIntExpr(#arg3))) ?
                typeSupport.intType : typeSupport.errorType);
        }
    ;

abs
    : #( op:ABS expr:expression )
        {
            #op.setTypeInfo(isNumberExpr(#expr) ?
                #expr.getTypeInfo() : typeSupport.errorType);
        }
    ;

sqrt
    : #( op:SQRT expr:expression )
        {
            #op.setTypeInfo(isDoubleExpr(#expr) ?
                #expr.getTypeInfo() : typeSupport.errorType);
        }
    ;

mod
    : #( op:MOD arg1:expression arg2:expression )
        {
            #op.setTypeInfo((isIntExpr(#arg1) && isIntExpr(#arg2)) ?
                typeSupport.intType : typeSupport.errorType);
        }
    ;

primary
    : literal
    | singleValuedPathExpression
    | identificationVariable
    | inputParameter
    ;

literal
    : b1:TRUE { #b1.setTypeInfo(typeSupport.booleanType); }
    | b2:FALSE { #b2.setTypeInfo(typeSupport.booleanType); }
    | s:STRING_LITERAL { #s.setTypeInfo(typeSupport.stringType); }
    | i:INT_LITERAL { #i.setTypeInfo(typeSupport.intType); }
    | l:LONG_LITERAL { #l.setTypeInfo(typeSupport.longType); }
    | f:FLOAT_LITERAL { #f.setTypeInfo(typeSupport.floatType); }
    | d:DOUBLE_LITERAL { #d.setTypeInfo(typeSupport.doubleType); }
    ;

pathExpression
    : #( dot:DOT o:objectDenoter i:IDENT )
        {
            String fieldName = #i.getText();
            Object typeInfo = #o.getTypeInfo();
            Object fieldTypeInfo =
                typeSupport.getFieldType(typeInfo, fieldName);
            if (fieldTypeInfo == null) {
                // field is not known
                ErrorMsg.error(#i.getLine(), #i.getColumn(),
                    I18NHelper.getMessage(msgs, "EXC_UnknownField", fieldName, //NOI18N
                        typeSupport.getAbstractSchemaForTypeInfo(typeInfo)));
                fieldTypeInfo = typeSupport.errorType;
            }
            else {
                Object fieldInfo = typeSupport.getFieldInfo(typeInfo, fieldName);
                if (fieldInfo == null) {
                    ErrorMsg.fatal(I18NHelper.getMessage(msgs,
                            "ERR_MissingFieldInfo", //NOI18N
                            fieldName, typeSupport.getTypeName(typeInfo)));
                }
                if (!typeSupport.isRelationship(fieldInfo)) {
                    // field is not a relationship => cmp field
                    #i.setType(CMP_FIELD);
                    #dot.setType(CMP_FIELD_ACCESS);
                }
                else if (typeSupport.isCollectionType(fieldTypeInfo)) {
                    // field is a relationship of a collection type =>
                    // collection valued cmr field
                    #i.setType(COLLECTION_CMR_FIELD);
                    #dot.setType(COLLECTION_CMR_FIELD_ACCESS);
                }
                else {
                    // field is a relationship of a non collection type =>
                    // single valued cmr field
                    #i.setType(SINGLE_CMR_FIELD);
                    #dot.setType(SINGLE_CMR_FIELD_ACCESS);
                }
            }
            #dot.setTypeInfo(fieldTypeInfo);
            #i.setTypeInfo(fieldTypeInfo);
        }

    ;

objectDenoter
    : identificationVariable
    | singleValuedCmrPathExpression
    ;

identificationVariable
    : i:IDENT
        {
            String name = #i.getText();
            Object decl = symtab.getDeclaration(name);
            // check for identification variables
            if ((decl != null) && (decl instanceof IdentificationVariable)) {
                #i.setType(IDENTIFICATION_VAR);
                #i.setTypeInfo(((IdentificationVariable)decl).getTypeInfo());
            }
            else {
                #i.setTypeInfo(typeSupport.errorType);
                ErrorMsg.error(#i.getLine(), #i.getColumn(),
                    I18NHelper.getMessage(msgs, "EXC_UndefinedIdentifier", name)); //NOI18N
                        
            }
        }
    ;

singleValuedPathExpression
    : p:pathExpression
        {
            int fieldTokenType = #p.getType();
            if ((fieldTokenType != SINGLE_CMR_FIELD_ACCESS) &&
                (fieldTokenType != CMP_FIELD_ACCESS)) {
                EJBQLAST classExpr = (EJBQLAST)#p.getFirstChild();
                EJBQLAST field = (EJBQLAST)classExpr.getNextSibling();
                ErrorMsg.error(field.getLine(), field.getColumn(),
                    I18NHelper.getMessage(msgs, "EXC_SingleValuedCMROrCMPFieldExpected", //NOI18N
                        field.getText(), typeSupport.getTypeName(field.getTypeInfo())));
                #p.setType(SINGLE_CMR_FIELD_ACCESS);
            }
        }
    ;

cmpPathExpression
    : p:pathExpression
        {
            int fieldTokenType = #p.getType();
            if ((fieldTokenType != CMP_FIELD_ACCESS)) {
                EJBQLAST classExpr = (EJBQLAST)#p.getFirstChild();
                EJBQLAST field = (EJBQLAST)classExpr.getNextSibling();
                ErrorMsg.error(field.getLine(), field.getColumn(),
                    I18NHelper.getMessage(msgs, "EXC_CMPFieldExpected", //NOI18N
                        field.getText(), typeSupport.getTypeName(field.getTypeInfo())));
                #p.setType(CMP_FIELD_ACCESS);
            }
        }
    ;

singleValuedCmrPathExpression
    : p:pathExpression
        {
            int fieldTokenType = #p.getType();
            if (fieldTokenType != SINGLE_CMR_FIELD_ACCESS) {
                EJBQLAST classExpr = (EJBQLAST)#p.getFirstChild();
                EJBQLAST field = (EJBQLAST)classExpr.getNextSibling();
                ErrorMsg.error(field.getLine(), field.getColumn(),
                    I18NHelper.getMessage(msgs, "EXC_SingleValuedCMRFieldExpected", //NOI18N
                        field.getText(), typeSupport.getTypeName(field.getTypeInfo())));
                #p.setType(COLLECTION_CMR_FIELD_ACCESS);
            }
        }
    ;

collectionValuedPathExpression
    : p:pathExpression
        {
            int fieldTokenType = #p.getType();
            if (fieldTokenType != COLLECTION_CMR_FIELD_ACCESS) {
                EJBQLAST classExpr = (EJBQLAST)#p.getFirstChild();
                EJBQLAST field = (EJBQLAST)classExpr.getNextSibling();
                ErrorMsg.error(field.getLine(), field.getColumn(),
                    I18NHelper.getMessage(msgs, "EXC_CollectionValuedCMRFieldExpected", //NOI18N
                        field.getText(), typeSupport.getTypeName(field.getTypeInfo())));
                #p.setType(COLLECTION_CMR_FIELD_ACCESS);
            }
        }
    ;

inCollection [Object valueExprTypeInfo]
    : ( inCollectionElement[valueExprTypeInfo] )+
    ;

inCollectionElement [Object valueExprTypeInfo]
    : l:literal
        {
            l.setTypeInfo(analyseInCollectionElement(#l, valueExprTypeInfo));
        }
    | i:inputParameter
        {
            i.setTypeInfo(analyseInCollectionElement(#i, valueExprTypeInfo));
        }
    ;

inputParameter
    : param:INPUT_PARAMETER
        {
            Object typeInfo = typeSupport.getTypeInfo(
                paramSupport.getParameterType(#param.getText()));
            #param.setTypeInfo(typeInfo);
        }
    ;