/*
 * Copyright (c) 2006, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation
//
package org.eclipse.persistence.jpa.jpql.tools;

import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.AbsExpression_InvalidNumericExpression;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.AvgFunction_InvalidNumericExpression;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.BetweenExpression_WrongType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.CollectionMemberExpression_Embeddable;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ComparisonExpression_WrongComparisonType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ConcatExpression_Expression_WrongType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ConstructorExpression_UndefinedConstructor;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ConstructorExpression_UnknownType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.CountFunction_DistinctEmbeddable;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.EncapsulatedIdentificationVariableExpression_NotMapValued;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.IdentificationVariable_EntityName;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.LengthExpression_WrongType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.LocateExpression_FirstExpression_WrongType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.LocateExpression_SecondExpression_WrongType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.LocateExpression_ThirdExpression_WrongType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.LowerExpression_WrongType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ModExpression_FirstExpression_WrongType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ModExpression_SecondExpression_WrongType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.NotExpression_WrongType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.NullComparisonExpression_InvalidType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.SqrtExpression_WrongType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.SubstringExpression_FirstExpression_WrongType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.SubstringExpression_SecondExpression_WrongType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.SubstringExpression_ThirdExpression_WrongType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.SumFunction_WrongType;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.UpdateItem_NotAssignable;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.UpdateItem_NotResolvable;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.UpdateItem_NullNotAssignableToPrimitive;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.UpperExpression_WrongType;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.persistence.jpa.jpql.AbstractSemanticValidator;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.ITypeHelper;
import org.eclipse.persistence.jpa.jpql.JPAVersion;
import org.eclipse.persistence.jpa.jpql.LiteralType;
import org.eclipse.persistence.jpa.jpql.LiteralVisitor;
import org.eclipse.persistence.jpa.jpql.SemanticValidatorHelper;
import org.eclipse.persistence.jpa.jpql.parser.AbsExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSchemaName;
import org.eclipse.persistence.jpa.jpql.parser.AdditionExpression;
import org.eclipse.persistence.jpa.jpql.parser.AllOrAnyExpression;
import org.eclipse.persistence.jpa.jpql.parser.AndExpression;
import org.eclipse.persistence.jpa.jpql.parser.ArithmeticExpression;
import org.eclipse.persistence.jpa.jpql.parser.ArithmeticFactor;
import org.eclipse.persistence.jpa.jpql.parser.AvgFunction;
import org.eclipse.persistence.jpa.jpql.parser.BetweenExpression;
import org.eclipse.persistence.jpa.jpql.parser.BooleanPrimaryBNF;
import org.eclipse.persistence.jpa.jpql.parser.CaseExpression;
import org.eclipse.persistence.jpa.jpql.parser.CoalesceExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionMemberExpression;
import org.eclipse.persistence.jpa.jpql.parser.ComparisonExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConcatExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConstructorExpression;
import org.eclipse.persistence.jpa.jpql.parser.CountFunction;
import org.eclipse.persistence.jpa.jpql.parser.DivisionExpression;
import org.eclipse.persistence.jpa.jpql.parser.EmptyCollectionComparisonExpression;
import org.eclipse.persistence.jpa.jpql.parser.EncapsulatedIdentificationVariableExpression;
import org.eclipse.persistence.jpa.jpql.parser.EntryExpression;
import org.eclipse.persistence.jpa.jpql.parser.ExistsExpression;
import org.eclipse.persistence.jpa.jpql.parser.Expression;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable;
import org.eclipse.persistence.jpa.jpql.parser.IndexExpression;
import org.eclipse.persistence.jpa.jpql.parser.InputParameter;
import org.eclipse.persistence.jpa.jpql.parser.JPQLQueryBNF;
import org.eclipse.persistence.jpa.jpql.parser.KeyExpression;
import org.eclipse.persistence.jpa.jpql.parser.KeywordExpression;
import org.eclipse.persistence.jpa.jpql.parser.LengthExpression;
import org.eclipse.persistence.jpa.jpql.parser.LikeExpression;
import org.eclipse.persistence.jpa.jpql.parser.LiteralBNF;
import org.eclipse.persistence.jpa.jpql.parser.LocateExpression;
import org.eclipse.persistence.jpa.jpql.parser.LowerExpression;
import org.eclipse.persistence.jpa.jpql.parser.MaxFunction;
import org.eclipse.persistence.jpa.jpql.parser.MinFunction;
import org.eclipse.persistence.jpa.jpql.parser.ModExpression;
import org.eclipse.persistence.jpa.jpql.parser.MultiplicationExpression;
import org.eclipse.persistence.jpa.jpql.parser.NotExpression;
import org.eclipse.persistence.jpa.jpql.parser.NullComparisonExpression;
import org.eclipse.persistence.jpa.jpql.parser.NullExpression;
import org.eclipse.persistence.jpa.jpql.parser.NullIfExpression;
import org.eclipse.persistence.jpa.jpql.parser.NumericLiteral;
import org.eclipse.persistence.jpa.jpql.parser.OrExpression;
import org.eclipse.persistence.jpa.jpql.parser.OrderByClause;
import org.eclipse.persistence.jpa.jpql.parser.OrderByItem;
import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.SimpleArithmeticExpressionBNF;
import org.eclipse.persistence.jpa.jpql.parser.SizeExpression;
import org.eclipse.persistence.jpa.jpql.parser.SqrtExpression;
import org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression;
import org.eclipse.persistence.jpa.jpql.parser.StringLiteral;
import org.eclipse.persistence.jpa.jpql.parser.StringPrimaryBNF;
import org.eclipse.persistence.jpa.jpql.parser.SubExpression;
import org.eclipse.persistence.jpa.jpql.parser.SubstringExpression;
import org.eclipse.persistence.jpa.jpql.parser.SubtractionExpression;
import org.eclipse.persistence.jpa.jpql.parser.SumFunction;
import org.eclipse.persistence.jpa.jpql.parser.TrimExpression;
import org.eclipse.persistence.jpa.jpql.parser.UpdateClause;
import org.eclipse.persistence.jpa.jpql.parser.UpdateItem;
import org.eclipse.persistence.jpa.jpql.parser.UpperExpression;
import org.eclipse.persistence.jpa.jpql.parser.ValueExpression;

/**
 * This validator is responsible to gather the problems found in a JPQL query by validating the
 * content to make sure it is semantically valid. The grammar is not validated by this visitor.
 * <p>
 * For instance, the function <b>AVG</b> accepts a state field path. The property it represents has
 * to be of numeric type. <b>AVG(e.name)</b> is parsable but is not semantically valid because the
 * type of name is a string (the property signature is: "<code>private String name</code>").
 * <p>
 * Provisional API: This interface is part of an interim API that is still under development and
 * expected to change significantly before reaching stability. It is available at this early stage
 * to solicit feedback from pioneering adopters on the understanding that any code that uses this
 * API will almost certainly be broken (repeatedly) as the API evolves.
 *
 * @see DefaultGrammarValidator
 *
 * @version 2.5
 * @since 2.3
 * @author Pascal Filion
 */
@SuppressWarnings("nls")
public class DefaultSemanticValidator extends AbstractSemanticValidator {

    /**
     * This validator determines whether the {@link Expression} visited represents
     * {@link Expression#NULL}.
     */
    protected NullValueVisitor nullValueVisitor;

    /**
     * This finder is responsible to retrieve the abstract schema name from the <b>UPDATE</b> range
     * declaration expression.
     */
    protected UpdateClauseAbstractSchemaNameFinder updateClauseAbstractSchemaNameFinder;

    /**
     * The {@link TypeValidator TypeVlidators} mapped to their Java class. Those validators validate
     * any {@link Expression} by making sure its type matches the desired type.
     */
    protected Map<Class<? extends TypeValidator>, TypeValidator> validators;

    /**
     * Creates a new <code>DefaultSemanticValidator</code>.
     *
     * @param queryContext The context used to query information about the JPQL query
     * @exception NullPointerException The given {@link JPQLQueryContext} cannot be <code>null</code>
     */
    public DefaultSemanticValidator(JPQLQueryContext queryContext) {
        super(new GenericSemanticValidatorHelper(queryContext));
    }

    /**
     * Creates a new <code>DefaultSemanticValidator</code>.
     *
     * @param helper The given helper allows the validator to access the JPA artifacts without using
     * Hermes SPI directly
     * @exception NullPointerException The given {@link SemanticValidatorHelper} cannot be <code>null</code>
     * @since 2.4
     */
    public DefaultSemanticValidator(SemanticValidatorHelper helper) {
        super(helper);
    }

    protected boolean areTypesEquivalent(Object[] typeDeclarations1, Object[] typeDeclarations2) {

        // Empty array
        if ((typeDeclarations1.length == 0) && (typeDeclarations2.length == 0)) {
            return true;
        }

        // Different array length, always not equivalent
        if (typeDeclarations1.length != typeDeclarations2.length) {
            return false;
        }

        // Compare each element of the array together
        for (int index = typeDeclarations1.length; --index >= 0; ) {
            if (!helper.isTypeDeclarationAssignableTo(typeDeclarations1[index], typeDeclarations2[index])) {
                return false;
            }
        }

        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected LiteralVisitor buildLiteralVisitor() {
        return new DefaultLiteralVisitor();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected OwningClauseVisitor buildOwningClauseVisitor() {
        return new OwningClauseVisitor();
    }

    protected ResultVariableInOrderByVisitor buildResultVariableInOrderByVisitor() {
        return new ResultVariableInOrderByVisitor();
    }

    protected AbstractSchemaName findAbstractSchemaName(UpdateItem expression) {
        UpdateClauseAbstractSchemaNameFinder visitor = getUpdateClauseAbstractSchemaNameFinder();
        try {
            expression.accept(visitor);
            return visitor.expression;
        }
        finally {
            visitor.expression = null;
        }
    }

    protected NullValueVisitor getNullValueVisitor() {
        if (nullValueVisitor == null) {
            nullValueVisitor = new NullValueVisitor();
        }
        return nullValueVisitor;
    }

    protected Object getType(Expression expression) {
        return helper.getType(expression);
    }

    protected ITypeHelper getTypeHelper() {
        return helper.getTypeHelper();
    }

    protected UpdateClauseAbstractSchemaNameFinder getUpdateClauseAbstractSchemaNameFinder() {
        if (updateClauseAbstractSchemaNameFinder == null) {
            updateClauseAbstractSchemaNameFinder = new UpdateClauseAbstractSchemaNameFinder();
        }
        return updateClauseAbstractSchemaNameFinder;
    }

    protected TypeValidator getValidator(Class<? extends TypeValidator> validatorClass) {
        TypeValidator validator = validators.get(validatorClass);
        if (validator == null) {
            try {
                Constructor<? extends TypeValidator> constructor = validatorClass.getDeclaredConstructor(DefaultSemanticValidator.class);
                constructor.setAccessible(true);
                validator = constructor.newInstance(this);
                validators.put(validatorClass, validator);
            }
            catch (ReflectiveOperationException e) { /* Never happens */ }
        }
        return validator;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void initialize() {
        super.initialize();
        validators = new HashMap<Class<? extends TypeValidator>, TypeValidator>();
    }

    /**
     * Determines whether the given {@link Expression} is of the correct type based on these rules:
     * <ul>
     * <li>The {@link Expression} returns a boolean value;</li>
     * </ul>
     *
     * @param expression The {@link Expression} to validate
     * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code>
     * otherwise
     */
    protected boolean isBooleanType(Expression expression) {
        return isValid(expression, BooleanTypeValidator.class);
    }

    protected boolean isComparisonEquivalentType(Expression expression1, Expression expression2) {

        Object type1 = helper.getType(expression1);
        Object type2 = helper.getType(expression2);

        // 1. The types are the same
        // 2. The type cannot be determined, pretend they are equivalent,
        //    another rule will validate it
        // 3. One is assignable to the other one
        ITypeHelper typeHelper = getTypeHelper();

        return (type1 == type2) ||
               !helper.isTypeResolvable(type1) ||
               !helper.isTypeResolvable(type2) ||
                typeHelper.isNumericType(type1) && typeHelper.isNumericType(type2) ||
                typeHelper.isDateType(type1)    && typeHelper.isDateType(type2)    ||
                helper.isAssignableTo(type1, type2) ||
                helper.isAssignableTo(type2, type1);
    }

    protected boolean isEquivalentBetweenType(Expression expression1, Expression expression2) {

        Object type1 = helper.getType(expression1);
        Object type2 = helper.getType(expression2);

        // The type cannot be determined, pretend they are equivalent,
        // another rule will validate it
        if (!helper.isTypeResolvable(type1) ||
            !helper.isTypeResolvable(type2)) {

            return true;
        }

        ITypeHelper typeHelper = getTypeHelper();

        if (type1 == type2) {
            return typeHelper.isNumericType(type1) ||
            typeHelper.isStringType(type1)  ||
            typeHelper.isDateType(type1);
        }
        else {
            return typeHelper.isNumericType(type1) && typeHelper.isNumericType(type2) ||
                   typeHelper.isStringType(type1)  && typeHelper.isStringType(type2)  ||
                   typeHelper.isDateType(type1)    && typeHelper.isDateType(type2);
        }
    }

    /**
     * Determines whether the given {@link Expression} is of the correct type based on these rules:
     * <ul>
     * <li>The {@link Expression}'s type is an integral type (long or integer).</li>
     * </ul>
     *
     * @param expression The {@link Expression} to validate
     * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code>
     * otherwise
     */
    protected boolean isIntegralType(Expression expression) {

        if (isNumericType(expression)) {

            ITypeHelper typeHelper = getTypeHelper();
            Object type = helper.getType(expression);

            return type == typeHelper.unknownType() ||
                   typeHelper.isIntegralType(type);
        }

        return false;
    }

    protected boolean isNullValue(Expression expression) {
        NullValueVisitor visitor = getNullValueVisitor();
        try {
            expression.accept(visitor);
            return visitor.valid;
        }
        finally {
            visitor.valid = false;
        }
    }

    /**
     * Determines whether the given {@link Expression} is of the correct type based on these rules:
     * <ul>
     * <li>The {@link Expression} returns a numeric value;</li>
     * </ul>
     *
     * @param expression The {@link Expression} to validate
     * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code>
     * otherwise
     */
    protected boolean isNumericType(Expression expression) {
        return isValid(expression, NumericTypeValidator.class);
    }

    /**
     * Determines whether the given {@link Expression} is of the correct type based on these rules:
     * <ul>
     * <li>The {@link Expression}'s type is a string type.</li>
     * </ul>
     *
     * @param expression The {@link Expression} to validate
     * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code>
     * otherwise
     */
    protected boolean isStringType(Expression expression) {
        return isValid(expression, StringTypeValidator.class);
    }

    /**
     * Determines whether the given {@link Expression} is of the correct type by using the
     * {@link TypeValidator}.
     *
     * @param expression The {@link Expression} to validate
     * @param validatorClass The Java class of the {@link TypeValidator} that will determine if the
     * given {@link Expression} has the right type
     * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code>
     * otherwise
     */
    protected boolean isValid(Expression expression, Class<? extends TypeValidator> validatorClass) {
        TypeValidator validator = getValidator(validatorClass);
        try {
            expression.accept(validator);
            return validator.valid;
        }
        finally {
            validator.valid = false;
        }
    }

    protected boolean isValidWithFindQueryBNF(AbstractExpression expression, String queryBNF) {
        JPQLQueryBNFValidator validator = getExpressionValidator(queryBNF);
        try {
            JPQLQueryBNF childQueryBNF = expression.getParent().findQueryBNF(expression);
            validator.validate(childQueryBNF);
            return validator.isValid();
        }
        finally {
            validator.dispose();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected PathType selectClausePathExpressionPathType() {
        return PathType.ANY_FIELD;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean validateAbsExpression(AbsExpression expression) {

        boolean valid = super.validateAbsExpression(expression);

        if (valid) {
            // The ABS function takes a numeric argument
            valid = validateNumericType(expression.getExpression(), AbsExpression_InvalidNumericExpression);
        }

        return valid;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected int validateArithmeticExpression(ArithmeticExpression expression,
                                               String leftExpressionWrongTypeMessageKey,
                                               String rightExpressionWrongTypeMessageKey) {

        int result = super.validateArithmeticExpression(
            expression,
            leftExpressionWrongTypeMessageKey,
            rightExpressionWrongTypeMessageKey
        );

        // Only validate the left expression if it's still valid
        if (isValid(result, 0)) {
            boolean valid = validateNumericType(expression.getLeftExpression(), leftExpressionWrongTypeMessageKey);
            updateStatus(result, 0, valid);
        }

        // Validate the right expression
        boolean valid = validateNumericType(expression.getRightExpression(), rightExpressionWrongTypeMessageKey);
        updateStatus(result, 1, valid);

        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean validateAvgFunction(AvgFunction expression) {

        // Arguments to the functions AVG must be numeric
        boolean valid = super.validateAvgFunction(expression);

        if (valid) {
            valid = validateNumericType(expression.getExpression(), AvgFunction_InvalidNumericExpression);
        }

        return valid;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected int validateBetweenExpression(BetweenExpression expression) {

        int result = super.validateBetweenExpression(expression);

        // Only add extra validation if the lower and upper expressions are still valid
        if (isValid(result, 1) &&
            isValid(result, 2) &&
            !isEquivalentBetweenType(expression.getExpression(), expression.getLowerBoundExpression()) ||
            !isEquivalentBetweenType(expression.getExpression(), expression.getUpperBoundExpression())) {

            addProblem(expression, BetweenExpression_WrongType);
            updateStatus(result, 1, false);
            updateStatus(result, 2, false);
        }

        return result;
    }

    /**
     * Determines whether the given {@link Expression} is of the correct type based on these rules:
     * <ul>
     * <li>The {@link Expression} returns a boolean value;</li>
     * <li>The {@link Expression}'s type is a boolean type.</li>
     * </ul>
     *
     * @param expression The {@link Expression} to validate
     * @param messageKey The key used to retrieve the localized message describing the problem
     */
    protected boolean validateBooleanType(Expression expression, String messageKey) {

        if (isValid(expression, BooleanPrimaryBNF.ID) && !isBooleanType(expression)) {
            addProblem(expression, messageKey);
            return false;
        }

        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected int validateCollectionMemberExpression(CollectionMemberExpression expression) {

        int result = super.validateCollectionMemberExpression(expression);

        if (isValid(result, 0) && expression.hasEntityExpression()) {
            Expression entityExpression = expression.getEntityExpression();

            // Check for embeddable type
            Object type = helper.getType(entityExpression);

            if (helper.getEmbeddable(type) != null) {
                addProblem(entityExpression, CollectionMemberExpression_Embeddable);
                updateStatus(result, 0, false);
            }
        }

        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean validateComparisonExpression(ComparisonExpression expression) {

        boolean valid = super.validateComparisonExpression(expression);

        // Only validate the left and right if they are still valid
        if (valid && expression.hasLeftExpression() && expression.hasRightExpression()) {

            // The result of the two expressions must be like that of the other argument
            // Comparisons over instances of embeddable class or map entry types are not supported
            if (!isComparisonEquivalentType(expression.getLeftExpression(),
                                            expression.getRightExpression())) {

                addProblem(expression, ComparisonExpression_WrongComparisonType);
                valid = false;
            }
        }

        return valid;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean validateConcatExpression(ConcatExpression expression) {

        boolean result = super.validateConcatExpression(expression);

        // The CONCAT function returns a string that is a concatenation of its arguments
        if (expression.hasExpression()) {
            int index = 0;
            for (Expression child : getChildren(expression.getExpression())) {
                // Don't validate the first expression if it is not valid
                if (index != 0 || result) {
                    result &= validateStringType(child, ConcatExpression_Expression_WrongType);
                }
                index++;
            }
        }

        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void validateConstructorExpression(ConstructorExpression expression) {

        super.validateConstructorExpression(expression);
        String className = expression.getClassName();

        // Only test the constructor if it has been specified
        if (ExpressionTools.stringIsNotEmpty(className)) {
            Object type = helper.getType(className);

            // Unknown type
            if (!helper.isTypeResolvable(type)) {
                int startPosition = position(expression) + 4 /* NEW + whitespace */;
                int endPosition = startPosition + className.length();
                addProblem(expression, startPosition, endPosition, ConstructorExpression_UnknownType, className);
            }
            // Test the arguments' types with the constructors' types
            else if (expression.hasLeftParenthesis() &&
                     expression.hasConstructorItems()) {

                Expression constructorItems = expression.getConstructorItems();

                // Retrieve the constructor's arguments so their type can be calculated
                List<Expression> children = getChildren(constructorItems);
                Object[] typeDeclarations = null;
                boolean constructorFound = false;

                for (Object constructor : helper.getConstructors(type)) {
                    Object[] parameterTypeDeclarations = helper.getMethodParameterTypeDeclarations(constructor);

                    // The number of items match, check their types are equivalent
                    if (children.size() == parameterTypeDeclarations.length) {

                        // The constructor is the default constructor and both have no parameters
                        if (parameterTypeDeclarations.length == 0) {
                            constructor = true;
                        }
                        else {

                            // Populate the type declaration array
                            if (typeDeclarations == null) {
                                typeDeclarations = new Object[children.size()];

                                for (int index = children.size(); --index >= 0; ) {
                                    typeDeclarations[index] = helper.getTypeDeclaration(children.get(index));
                                }
                            }

                            constructorFound = areTypesEquivalent(parameterTypeDeclarations, typeDeclarations);

                            if (constructorFound) {
                                break;
                            }
                        }
                    }
                }

                // No constructor was found matching the argument list
                if (!constructorFound) {
                    int startPosition = position(expression) + 4 /* NEW + whitespace */;
                    int endPosition = startPosition + className.length();
                    addProblem(expression, startPosition, endPosition, ConstructorExpression_UndefinedConstructor);
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void validateCountFunction(CountFunction expression) {

        super.validateCountFunction(expression);

        // The use of DISTINCT with COUNT is not supported for arguments of
        // embeddable types or map entry types (weird because map entry is not
        // allowed in a COUNT expression)
        if (expression.hasExpression() &&
            expression.hasDistinct()) {

            Expression childExpression = expression.getExpression();

            // Check for embeddable type
            Object type = helper.getType(childExpression);

            if (helper.getEmbeddable(type) != null) {
                int distinctLength = Expression.DISTINCT.length() + 1; // +1 = space
                int startIndex  = position(childExpression) - distinctLength;
                int endIndex    = startIndex + length(childExpression) + distinctLength;
                addProblem(expression, startIndex, endIndex, CountFunction_DistinctEmbeddable);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void validateEntryExpression(EntryExpression expression) {
        validateMapIdentificationVariable(expression);
        super.validateEntryExpression(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean validateIdentificationVariable(IdentificationVariable expression, String variable) {

        boolean valid = super.validateIdentificationVariable(expression, variable);

        for (String entityName : helper.entityNames()) {

            // An identification variable must not have the same name as any entity in the same
            // persistence unit, unless it's representing an entity literal
            if (variable.equalsIgnoreCase(entityName)) {

                // An identification variable could represent an entity type literal,
                // validate the parent to make sure it allows it
                if (!isValidWithFindQueryBNF(expression, LiteralBNF.ID)) {
                    int startIndex = position(expression);
                    int endIndex   = startIndex + variable.length();
                    addProblem(expression, startIndex, endIndex, IdentificationVariable_EntityName);
                    valid = false;
                    break;
                }
            }
        }

        return valid;
    }

    /**
     * Determines whether the given {@link Expression} is of the correct type based on these rules:
     * <ul>
     * <li>The {@link Expression} returns a integral value;</li>
     * <li>The {@link Expression}'s type is an integral type (long or integer).</li>
     * </ul>
     *
     * @param expression The {@link Expression} to validate
     * @param queryBNF The unique identifier of the query BNF used to validate the type
     * @param messageKey The key used to retrieve the localized message describing the problem
     * @return <code>false</code> if the given expression was validated and is invalid;
     * <code>true</code> otherwise
     */
    protected boolean validateIntegralType(Expression expression, String queryBNF, String messageKey) {

        if (isValid(expression, queryBNF) && !isIntegralType(expression)) {
            addProblem(expression, messageKey);
            return false;
        }

        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void validateKeyExpression(KeyExpression expression) {
        validateMapIdentificationVariable(expression);
        super.validateKeyExpression(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean validateLengthExpression(LengthExpression expression) {

        boolean valid = super.validateLengthExpression(expression);

        if (valid) {
            valid = validateStringType(expression.getExpression(), LengthExpression_WrongType);
        }

        return valid;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected int validateLocateExpression(LocateExpression expression) {

        int result = super.validateLocateExpression(expression);

        // The first argument is the string to be located; the second argument is the string to be
        // searched; the optional third argument is an integer that represents the string position at
        // which the search is started (by default, the beginning of the string to be searched)
        if (isValid(result, 0)) {
            boolean valid = validateStringType(expression.getFirstExpression(), LocateExpression_FirstExpression_WrongType);
            updateStatus(result, 0, valid);
        }

        if (isValid(result, 1)) {
            boolean valid = validateStringType (expression.getSecondExpression(), LocateExpression_SecondExpression_WrongType);
            updateStatus(result, 1, valid);
        }

        if (isValid(result, 2)) {
            boolean valid = validateNumericType(expression.getThirdExpression(),  LocateExpression_ThirdExpression_WrongType);
            updateStatus(result, 2, valid);
        }

        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean validateLowerExpression(LowerExpression expression) {

        boolean valid = super.validateLowerExpression(expression);

        if (valid) {
            valid = validateStringType(expression.getExpression(), LowerExpression_WrongType);
        }

        return valid;
    }

    protected void validateMapIdentificationVariable(EncapsulatedIdentificationVariableExpression expression) {

        // The KEY, VALUE, and ENTRY operators may only be applied to
        // identification variables that correspond to map-valued associations
        // or map-valued element collections
        if (expression.hasExpression()) {

            Expression childExpression = expression.getExpression();
            String variableName = literal(childExpression, LiteralType.IDENTIFICATION_VARIABLE);

            // Retrieve the identification variable's type without traversing the type parameters
            if (ExpressionTools.stringIsNotEmpty(variableName)) {
                Object typeDeclaration = helper.getTypeDeclaration(childExpression);
                Object type = helper.getType(typeDeclaration);

                if (!getTypeHelper().isMapType(type)) {
                    addProblem(
                        childExpression,
                        EncapsulatedIdentificationVariableExpression_NotMapValued,
                        expression.getIdentifier()
                    );
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected int validateModExpression(ModExpression expression) {

        int result = super.validateModExpression(expression);

        // Don't validate the first expression if it's not valid
        if (isValid(result, 0)) {

            boolean valid = validateIntegralType(
                expression.getFirstExpression(),
                expression.parameterExpressionBNF(0),
                ModExpression_FirstExpression_WrongType
            );

            updateStatus(result, 0, valid);
        }

        // Don't validate the second expression if it's not valid
        if (isValid(result, 1)) {

            boolean valid = validateIntegralType(
                expression.getSecondExpression(),
                expression.parameterExpressionBNF(1),
                ModExpression_SecondExpression_WrongType
            );

            updateStatus(result, 1, valid);
        }

        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void validateNotExpression(NotExpression expression) {
        super.validateNotExpression(expression);
        validateBooleanType(expression.getExpression(), NotExpression_WrongType);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void validateNullComparisonExpression(NullComparisonExpression expression) {

        super.validateNullComparisonExpression(expression);

        // Null comparisons over instances of embeddable class types are not supported
        StateFieldPathExpression pathExpression = getStateFieldPathExpression(expression.getExpression());

        if (pathExpression != null) {
            Object type = helper.getType(pathExpression);

            if (helper.getEmbeddable(type) != null) {
                addProblem(pathExpression, NullComparisonExpression_InvalidType, pathExpression.toParsedText());
            }
        }
    }

    /**
     * Determines whether the given {@link Expression} is of the correct type based on these rules:
     * <ul>
     * <li>The {@link Expression} returns a numeric value;</li>
     * <li>The {@link Expression}'s type is an numeric type.</li>
     * </ul>
     *
     * @param expression The {@link Expression} to validate
     * @param messageKey The key used to retrieve the localized message describing the problem
     * @return <code>false</code> if the given expression was validated and is invalid;
     * <code>true</code> otherwise
     */
    protected boolean validateNumericType(Expression expression, String messageKey) {

        if (isValid(expression, SimpleArithmeticExpressionBNF.ID) && !isNumericType(expression)) {
            addProblem(expression, messageKey);
            return false;
        }

        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean validateSqrtExpression(SqrtExpression expression) {

        boolean valid = super.validateSqrtExpression(expression);

        if (valid) {
            // The SQRT function takes a numeric argument
            valid = validateNumericType(expression.getExpression(), SqrtExpression_WrongType);
        }

        return valid;
    }

    /**
     * Determines whether the given {@link Expression} is of the correct type based on these rules:
     * <ul>
     * <li>The {@link Expression} returns a String value;</li>
     * <li>The {@link Expression}'s type is a String type.</li>
     * </ul>
     *
     * @param expression The {@link Expression} to validate
     * @param messageKey The key used to retrieve the localized message describing the problem
     * @return <code>false</code> if the given expression was validated and is invalid;
     * <code>true</code> otherwise
     */
    protected boolean validateStringType(Expression expression, String messageKey) {

        if (isValid(expression, StringPrimaryBNF.ID) && !isStringType(expression)) {
            addProblem(expression, messageKey, expression.toParsedText());
            return false;
        }

        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected int validateSubstringExpression(SubstringExpression expression) {

        int result = super.validateSubstringExpression(expression);

        if (isValid(result, 0)) {
            boolean valid = validateStringType(
                expression.getFirstExpression(),
                SubstringExpression_FirstExpression_WrongType
            );
            updateStatus(result, 0, valid);
        }

        // The second and third arguments of the SUBSTRING function denote the starting position and
        // length of the substring to be returned. These arguments are integers
        if (isValid(result, 1)) {

            boolean valid = validateIntegralType(
                expression.getSecondExpression(),
                expression.getParameterQueryBNFId(1),
                SubstringExpression_SecondExpression_WrongType
            );

            updateStatus(result, 1, valid);
        }

        // The third argument is optional for JPA 2.0
        if (isValid(result, 2) && getJPAVersion().isNewerThanOrEqual(JPAVersion.VERSION_2_0)) {

            boolean valid = validateIntegralType(
                expression.getThirdExpression(),
                expression.getParameterQueryBNFId(2),
                SubstringExpression_ThirdExpression_WrongType
            );

            updateStatus(result, 2, valid);
        }

        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean validateSumFunction(SumFunction expression) {

        boolean valid = super.validateSumFunction(expression);

        if (valid) {
            // Arguments to the functions SUM must be numeric
            valid = validateNumericType(expression.getExpression(), SumFunction_WrongType);
        }

        return valid;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @SuppressWarnings("null")
    protected boolean validateUpdateItem(UpdateItem expression) {

        // First validate the path expression
        boolean valid = super.validateUpdateItem(expression);

        if (valid) {

            // Retrieve the entity to make sure the state field is part of it
            AbstractSchemaName abstractSchemaName = findAbstractSchemaName(expression);
            String entityName = (abstractSchemaName != null) ? abstractSchemaName.getText() : null;

             if (ExpressionTools.stringIsNotEmpty(entityName)) {
                 Object entity = helper.getEntityNamed(entityName);

                // Check the existence of the state field on the entity
                if ((entity != null) && expression.hasSpaceAfterStateFieldPathExpression()) {
                    StateFieldPathExpression pathExpression = getStateFieldPathExpression(expression.getStateFieldPathExpression());
                    String stateFieldValue = (pathExpression != null) ? pathExpression.toParsedText() : null;

                    if (ExpressionTools.stringIsNotEmpty(stateFieldValue)) {

                        // State field without a dot
                        if (stateFieldValue.indexOf(".") == -1) {
                            Object mapping = helper.getMappingNamed(entity, stateFieldValue);

                             if (mapping == null) {
                                 addProblem(pathExpression, UpdateItem_NotResolvable, stateFieldValue);
                             }
                             else {
                                 validateUpdateItemTypes(expression, helper.getMappingType(mapping));
                             }
                        }
                        else {
                            Object type = helper.getType(pathExpression);

                            if (!helper.isTypeResolvable(type)) {
                                 addProblem(pathExpression, UpdateItem_NotResolvable, stateFieldValue);
                            }
                            else {
                                 validateUpdateItemTypes(expression, type);
                            }
                        }
                    }
                 }
             }
        }

        return valid;
    }

    protected void validateUpdateItemTypes(UpdateItem expression, Object type) {

        if (expression.hasNewValue()) {

            Expression newValue = expression.getNewValue();
            ITypeHelper typeHelper = getTypeHelper();
            boolean nullValue = isNullValue(newValue);

            // A NULL value is ignored, except if the type is a primitive, null cannot be
            // assigned to a mapping of primitive type
            if (nullValue) {
                if (typeHelper.isPrimitiveType(type)) {
                    addProblem(expression, UpdateItem_NullNotAssignableToPrimitive);
                }
                return;
            }

            Object newValueType = getType(newValue);

            // Do a quick check for known JDK types:
            // 1) Date/Time/Timestamp
            // 2) Any classes related to a number, eg long/Long etc
            if (!helper.isTypeResolvable(newValueType) ||
                typeHelper.isDateType(type) && typeHelper.isDateType(newValueType) ||
                (typeHelper.isNumericType(type)         || typeHelper.isPrimitiveType(type)) &&
                (typeHelper.isNumericType(newValueType) || typeHelper.isPrimitiveType(newValueType))) {

                return;
            }

            // The new value's type can't be assigned to the item's type
            if (!helper.isAssignableTo(newValueType, type)) {
                addProblem(
                    expression,
                    UpdateItem_NotAssignable,
                    helper.getTypeName(newValueType),
                    helper.getTypeName(type)
                );
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean validateUpperExpression(UpperExpression expression) {

        boolean valid = super.validateUpperExpression(expression);

        if (valid) {
            // The UPPER function convert a string to upper case,
            // with regard to the locale of the database
            valid = validateStringType(expression.getExpression(), UpperExpression_WrongType);
        }

        return valid;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void validateValueExpression(ValueExpression expression) {
        validateMapIdentificationVariable(expression);
        super.validateValueExpression(expression);
    }

    /**
     * This visitor validates expression that is a boolean literal to make sure the type is a
     * <b>Boolean</b>.
     */
    protected class BooleanTypeValidator extends TypeValidator {

        /**
         * {@inheritDoc}
         */
        @Override
        protected boolean isRightType(Object type) {
            return getTypeHelper().isBooleanType(type);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(AllOrAnyExpression expression) {
            // ALL|ANY|SOME always returns a boolean value
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(AndExpression expression) {
            // AND always returns a boolean value
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(BetweenExpression expression) {
            // BETWEEN always returns a boolean value
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(ComparisonExpression expression) {
            // A comparison always returns a boolean value
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(EmptyCollectionComparisonExpression expression) {
            // IS EMPTY always returns a boolean value
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(ExistsExpression expression) {
            // EXISTS always returns a boolean value
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(KeywordExpression expression) {
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(LikeExpression expression) {
            // LIKE always returns a boolean value
            valid = true;
        }

         /**
         * {@inheritDoc}
         */
        @Override
        public void visit(NotExpression expression) {
            // NOT always returns a boolean value
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(NullComparisonExpression expression) {
            // IS NULL always returns a boolean value
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(OrExpression expression) {
            // OR always returns a boolean value
            valid = true;
        }
    }

    protected static class NullValueVisitor extends AbstractExpressionVisitor {

        /**
         * Determines whether the {@link Expression} visited represents {@link Expression#NULL}.
         */
        protected boolean valid;

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(KeywordExpression expression) {
            valid = expression.getText() == Expression.NULL;
        }
    }

    /**
     * This visitor validates expression that is a numeric literal to make sure the type is an
     * instance of <b>Number</b>.
     */
    protected class NumericTypeValidator extends TypeValidator {

        /**
         * {@inheritDoc}
         */
        @Override
        protected boolean isRightType(Object type) {
            return getTypeHelper().isNumericType(type);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(AbsExpression expression) {
            // ABS always returns a numeric value
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(AdditionExpression expression) {
            // An addition expression always returns a numeric value
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(ArithmeticFactor expression) {
            // +/- is always numeric value
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(AvgFunction expression) {
            // AVG always returns a double
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CountFunction expression) {
            // COUNT always returns a long
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(DivisionExpression expression) {
            // A division expression always returns a numeric value
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(IndexExpression expression) {
            // INDEX always returns an integer
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(LengthExpression expression) {
            // LENGTH always returns an integer
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(LocateExpression expression) {
            // LOCATE always returns an integer
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(MaxFunction expression) {
            // SUM always returns a numeric value
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(MinFunction expression) {
            // SUM always returns a numeric value
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(ModExpression expression) {
            // MOD always returns an integer
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(MultiplicationExpression expression) {
            // A multiplication expression always returns a numeric value
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(NumericLiteral expression) {
            // A numeric literal is by definition valid
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SizeExpression expression) {
            // SIZE always returns an integer
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SqrtExpression expression) {
            // SQRT always returns a double
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SubtractionExpression expression) {
            // A subtraction expression always returns a numeric value
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SumFunction expression) {
            // SUM always returns a long
            valid = true;
        }
    }

    protected static class ResultVariableInOrderByVisitor extends AbstractExpressionVisitor {

        public boolean result;

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(IdentificationVariable expression) {
            expression.getParent().accept(this);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(OrderByClause expression) {
            result = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(OrderByItem expression) {
            expression.getParent().accept(this);
        }
    }

    /**
     * This visitor validates that the {@link Expression} is a string primary and to make sure the
     * type is String.
     */
    protected class StringTypeValidator extends TypeValidator {

        /**
         * {@inheritDoc}
         */
        @Override
        protected boolean isRightType(Object type) {
            return getTypeHelper().isStringType(type);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(ConcatExpression expression) {
            // CONCAT always returns a string
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(LowerExpression expression) {
            // LOWER always returns a string
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(StringLiteral expression) {
            // A string literal is by definition valid
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SubstringExpression expression) {
            // SUBSTRING always returns a string
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(TrimExpression expression) {
            // TRIM always returns a string
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(UpperExpression expression) {
            // UPPER always returns a string
            valid = true;
        }
    }

    /**
     * The basic validator for validating the type of an {@link Expression}.
     */
    protected abstract class TypeValidator extends AbstractExpressionVisitor {

        /**
         * Determines whether the expression that was visited returns a number.
         */
        protected boolean valid;

        /**
         * Determines whether the given type is the expected type.
         *
         * @param type The type to validate
         * @return <code>true</code> if the given type is of the expected type; <code>false</code> if
         * it's not the right type
         */
        protected abstract boolean isRightType(Object type);

        /**
         * {@inheritDoc}
         */
        @Override
        public final void visit(CaseExpression expression) {
            Object type = getType(expression);
            valid = isRightType(type);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public final void visit(CoalesceExpression expression) {
            Object type = getType(expression);
            valid = isRightType(type);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public final void visit(InputParameter expression) {
            // An input parameter can't be validated until the query
            // is executed so it is assumed to be valid
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(NullExpression expression) {
            // The missing expression is validated by GrammarValidator
            valid = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public final void visit(NullIfExpression expression) {
            expression.getFirstExpression().accept(this);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public final void visit(StateFieldPathExpression expression) {
            Object type = getType(expression);
            valid = isRightType(type);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public final void visit(SubExpression expression) {
            expression.getExpression().accept(this);
        }
    }

    protected static class UpdateClauseAbstractSchemaNameFinder extends AbstractExpressionVisitor {

        protected AbstractSchemaName expression;

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(AbstractSchemaName expression) {
            this.expression = expression;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CollectionExpression expression) {
            expression.getParent().accept(this);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(RangeVariableDeclaration expression) {
            expression.getRootObject().accept(this);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(UpdateClause expression) {
            expression.getRangeVariableDeclaration().accept(this);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(UpdateItem expression) {
            expression.getParent().accept(this);
        }
    }
}
