/*
 * 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;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.persistence.jpa.jpql.parser.AbstractExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractTraverseParentVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AnonymousExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.BadExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression;
import org.eclipse.persistence.jpa.jpql.parser.DeleteClause;
import org.eclipse.persistence.jpa.jpql.parser.DeleteStatement;
import org.eclipse.persistence.jpa.jpql.parser.Expression;
import org.eclipse.persistence.jpa.jpql.parser.ExpressionRegistry;
import org.eclipse.persistence.jpa.jpql.parser.ExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.FromClause;
import org.eclipse.persistence.jpa.jpql.parser.GroupByClause;
import org.eclipse.persistence.jpa.jpql.parser.HavingClause;
import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar;
import org.eclipse.persistence.jpa.jpql.parser.JPQLQueryBNF;
import org.eclipse.persistence.jpa.jpql.parser.NullExpression;
import org.eclipse.persistence.jpa.jpql.parser.OrderByClause;
import org.eclipse.persistence.jpa.jpql.parser.SelectClause;
import org.eclipse.persistence.jpa.jpql.parser.SelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.SimpleFromClause;
import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectClause;
import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.SubExpression;
import org.eclipse.persistence.jpa.jpql.parser.UnknownExpression;
import org.eclipse.persistence.jpa.jpql.parser.UpdateClause;
import org.eclipse.persistence.jpa.jpql.parser.UpdateStatement;
import org.eclipse.persistence.jpa.jpql.parser.WhereClause;
import org.eclipse.persistence.jpa.jpql.utility.CollectionTools;

/**
 * The abstract definition of a validator, which provides helper methods and visitors.
 * <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 AbstractGrammarValidator
 * @see AbstractSemanticValidator
 *
 * @version 2.5
 * @since 2.4
 * @author Pascal Filion
 */
@SuppressWarnings("nls")
public abstract class AbstractValidator extends AnonymousExpressionVisitor {

    /**
     * This visitor is responsible to traverse the children of a {@link CollectionExpression} in
     * order to properly validate the {@link Expression}.
     */
    private BypassChildCollectionExpressionVisitor bypassChildCollectionExpressionVisitor;

    /**
     * This visitor is responsible to traverse the parent hierarchy and to skip {@link SubExpression}
     * if it's a parent.
     */
    private BypassParentSubExpressionVisitor bypassParentSubExpressionVisitor;

    /**
     * This visitor gathers the children of a {@link CollectionExpression} or a single visited
     * {@link Expression}.
     */
    private ChildrenCollectorVisitor childrenCollectorVisitor;

    /**
     * This visitor is used to retrieve a variable name from various type of
     * {@link org.eclipse.persistence.jpa.jpql.parser.Expression JPQL Expression}.
     */
    private LiteralVisitor literalVisitor;

    /**
     *
     *
     * @since 2.5
     */
    private NestedArrayVisitor nestedArrayVisitor;

    /**
     * This visitor is responsible to traverse the parent hierarchy and to retrieve the owning clause
     * of the {@link Expression} being visited.
     */
    private OwningClauseVisitor owningClauseVisitor;

    /**
     * This visitor is responsible to traverse the parent hierarchy and to retrieve the owning
     * statement of the {@link Expression} being visited.
     *
     * @since 2.4
     */
    private OwningStatementVisitor owningStatementVisitor;

    /**
     * The list of {@link JPQLQueryProblem} describing grammatical and semantic issues found in the query.
     */
    private Collection<JPQLQueryProblem> problems;

    /**
     * This visitor determines whether the visited {@link Expression} is a subquery or not.
     *
     * @since 2.5
     */
    private SubqueryVisitor subqueryVisitor;

    /**
     * The {@link JPQLQueryBNFValidator} mapped by the BNF IDs.
     */
    private Map<String, JPQLQueryBNFValidator> validators;

    /**
     * Creates a new <code>AbstractValidator</code>.
     */
    protected AbstractValidator() {
        super();
        initialize();
    }

    /**
     * Adds a new validation problem that was found in the given {@link Expression}.
     *
     * @param expression The {@link Expression} that is either not following the BNF grammar or that
     * has semantic problems
     * @param startPosition The position where the problem was encountered
     * @param endPosition The position where the problem ends, inclusively
     * @param messageKey The key used to retrieve the localized message describing the problem
     * @param messageArguments The list of arguments that can be used to format the localized
     * description of the problem
     */
    protected void addProblem(Expression expression,
                              int startPosition,
                              int endPosition,
                              String messageKey,
                              String... messageArguments) {

        problems.add(buildProblem(expression, startPosition, endPosition, messageKey, messageArguments));
    }

    /**
     * Adds a new validation problem that was found in the given {@link Expression}.
     *
     * @param expression The {@link Expression} that is either not following the BNF grammar or that
     * has semantic problems
     * @param startPosition The position where the problem was encountered
     * @param messageKey The key used to retrieve the localized message describing the problem
     * @param messageArguments The list of arguments that can be used to format the localized
     * description of the problem
     */
    protected void addProblem(Expression expression,
                              int startPosition,
                              String messageKey,
                              String... messageArguments) {

        addProblem(expression, startPosition, startPosition, messageKey, messageArguments);
    }

    /**
     * Adds a new validation problem that was found in the given {@link Expression}. The start index
     * is the position of the given {@link Expression} within the JPQL query and the end index is
     * the end position of the {@link Expression} within the JPQL query.
     *
     * @param expression The {@link Expression} that is either not following the BNF grammar or that
     * has semantic problems
     * @param messageKey The key used to retrieve the localized message describing the problem
     */
    protected void addProblem(Expression expression, String messageKey) {
        addProblem(expression, messageKey, ExpressionTools.EMPTY_STRING_ARRAY);
    }

    /**
     * Adds a new validation problem that was found in the given {@link Expression}. The start index
     * is the position of the given {@link Expression} within the JPQL query and the end index is
     * the end position of the {@link Expression} within the JPQL query.
     *
     * @param expression The {@link Expression} that is either not following the BNF grammar or that
     * has semantic problems
     * @param messageKey The key used to retrieve the localized message describing the problem
     * @param arguments The list of arguments that can be used to format the localized description of
     * the problem
     */
    protected void addProblem(Expression expression, String messageKey, String... arguments) {
        int startPosition = expression.getOffset();
        int endPosition   = startPosition + length(expression);
        addProblem(expression, startPosition, endPosition, messageKey, arguments);
    }

    protected ChildrenCollectorVisitor buildChildrenCollector() {
        return new ChildrenCollectorVisitor();
    }

    /**
     * Creates the visitor that can retrieve some information about various literal.
     *
     * @return A new {@link LiteralVisitor}
     */
    protected abstract LiteralVisitor buildLiteralVisitor();

    /**
     * Creates the visitor that traverses an {@link Expression} and determines if it's a nested array
     * or not.
     *
     * @return A new {@link NestedArrayVisitor}
     * @since 2.5
     */
    protected NestedArrayVisitor buildNestedArrayVisitor() {
        return new NestedArrayVisitor();
    }

    /**
     * Creates the visitor that traverses the parent hierarchy of any {@link Expression} and stops at
     * the first {@link Expression} that is a clause.
     *
     * @return A new {@link OwningClauseVisitor}
     */
    protected abstract OwningClauseVisitor buildOwningClauseVisitor();

    /**
     * Creates the visitor that traverses the parent hierarchy of any {@link Expression} and stops at
     * the first {@link Expression} that is a statement.
     *
     * @return A new {@link OwningStatementVisitor}
     * @since 2.4
     */
    protected OwningStatementVisitor buildOwningStatementVisitor() {
        return new OwningStatementVisitor();
    }

    /**
     * Creates a new validation problem that was found in the given {@link Expression}.
     *
     * @param expression The {@link Expression} that is either not following the BNF grammar or that
     * has semantic problems
     * @param startPosition The position where the problem was encountered
     * @param endPosition The position where the problem ends, inclusively
     * @param messageKey The key used to retrieve the localized message describing the problem
     * @param messageArguments The list of arguments that can be used to format the localized
     * description of the problem
     * @return The {@link JPQLQueryProblem} describing a problem
     */
    protected JPQLQueryProblem buildProblem(Expression expression,
                                            int startPosition,
                                            int endPosition,
                                            String messageKey,
                                            String... messageArguments) {

        return new DefaultJPQLQueryProblem(
            expression,
            startPosition,
            endPosition,
            messageKey,
            messageArguments
        );
    }

    /**
     * Creates the visitor that checks if the visited expression is a subquery or not..
     *
     * @return A new {@link SubqueryVisitor}
     * @since 2.5
     */
    protected SubqueryVisitor buildSubqueryVisitor() {
        return new SubqueryVisitor();
    }

    /**
     * Disposes this visitor.
     */
    public void dispose() {
        problems = null;
    }

    protected BypassChildCollectionExpressionVisitor getBypassChildCollectionExpressionVisitor() {
        if (bypassChildCollectionExpressionVisitor == null) {
            bypassChildCollectionExpressionVisitor = new BypassChildCollectionExpressionVisitor();
        }
        return bypassChildCollectionExpressionVisitor;
    }

    protected BypassParentSubExpressionVisitor getBypassParentSubExpressionVisitor() {
        if (bypassParentSubExpressionVisitor == null) {
            bypassParentSubExpressionVisitor = new BypassParentSubExpressionVisitor();
        }
        return bypassParentSubExpressionVisitor;
    }

    /**
     * Returns a list containing either the given {@link Expression} if it's not a {@link
     * CollectionExpression} or the children of the given {@link CollectionExpression}.
     *
     * @param expression The {@link Expression} to visit
     * @return A list containing either the given {@link Expression} or the children of {@link
     * CollectionExpression}
     */
    protected List<Expression> getChildren(Expression expression) {
        ChildrenCollectorVisitor visitor = getChildrenCollectorVisitor();
        try {
            visitor.expressions = new LinkedList<Expression>();
            expression.accept(visitor);
            return visitor.expressions;
        }
        finally {
            visitor.expressions = null;
        }
    }

    protected ChildrenCollectorVisitor getChildrenCollectorVisitor() {
        if (childrenCollectorVisitor == null) {
            childrenCollectorVisitor = buildChildrenCollector();
        }
        return childrenCollectorVisitor;
    }

    /**
     * Returns the registry containing the {@link JPQLQueryBNF JPQLQueryBNFs} and the {@link
     * org.eclipse.persistence.jpa.jpql.parser.ExpressionFactory ExpressionFactories} that are used
     * to properly parse a JPQL query.
     *
     * @return The registry containing the information related to the JPQL grammar
     */
    protected ExpressionRegistry getExpressionRegistry() {
        return getGrammar().getExpressionRegistry();
    }

    protected JPQLQueryBNFValidator getExpressionValidator(String queryBNF) {
        JPQLQueryBNFValidator validator = validators.get(queryBNF);
        if (validator == null) {
            validator = new JPQLQueryBNFValidator(getExpressionRegistry().getQueryBNF(queryBNF));
            validators.put(queryBNF, validator);
        }
        return validator;
    }

    /**
     * Returns the {@link JPQLGrammar} that defines how the JPQL query was parsed.
     *
     * @return The {@link JPQLGrammar} that was used to parse the JPQL query
     */
    protected abstract JPQLGrammar getGrammar();

    /**
     * Returns the version of the Java Persistence this entity for which it was defined.
     *
     * @return The version of the Java Persistence being used
     */
    protected JPAVersion getJPAVersion() {
        return getGrammar().getJPAVersion();
    }

    /**
     * Returns the {@link JPQLQueryBNFValidator} that can be used to validate an {@link Expression}
     * by making sure its BNF is part of the given BNF.
     *
     * @param queryBNF The BNF used to determine the validity of an {@link Expression}
     * @return A {@link JPQLQueryBNFValidator} that can determine if an {@link Expression} follows
     * the given BNF
     */
    protected JPQLQueryBNFValidator getJPQLQueryBNFValidator(JPQLQueryBNF queryBNF) {
        JPQLQueryBNFValidator validator = validators.get(queryBNF.getId());
        if (validator == null) {
            validator = new JPQLQueryBNFValidator(queryBNF);
            validators.put(queryBNF.getId(), validator);
        }
        return validator;
    }

    /**
     * Returns the {@link JPQLQueryBNFValidator} that can be used to validate an {@link Expression}
     * by making sure its BNF is part of the given BNF.
     *
     * @param queryBNF The BNF used to determine the validity of an {@link Expression}
     * @return A {@link JPQLQueryBNFValidator} that can determine if an {@link Expression} follows
     * the given BNF
     */
    protected JPQLQueryBNFValidator getJPQLQueryBNFValidator(String queryBNF) {
        return getJPQLQueryBNFValidator(getQueryBNF(queryBNF));
    }

    /**
     * Returns the visitor that can retrieve some information about various literal.
     *
     * @return A {@link LiteralVisitor}
     */
    protected LiteralVisitor getLiteralVisitor() {
        if (literalVisitor == null) {
            literalVisitor = buildLiteralVisitor();
        }
        return literalVisitor;
    }

    /**
     * Returns the visitor that can determine if an {@link Expression} represents a nested array.
     *
     * @return A {@link NestedArrayVisitor}
     * @since 2.5
     */
    protected NestedArrayVisitor getNestedArrayVisitor() {
        if (nestedArrayVisitor == null) {
            nestedArrayVisitor = buildNestedArrayVisitor();
        }
        return nestedArrayVisitor;
    }

    /**
     * Returns the visitor that traverses the parent hierarchy of any {@link Expression} and stops at
     * the first {@link Expression} that is a clause.
     *
     * @return {@link OwningClauseVisitor}
     */
    protected OwningClauseVisitor getOwningClauseVisitor() {
        if (owningClauseVisitor == null) {
            owningClauseVisitor = buildOwningClauseVisitor();
        }
        return owningClauseVisitor;
    }

    /**
     * Returns the visitor that traverses the parent hierarchy of any {@link Expression} and stops at
     * the first {@link Expression} that is a statement.
     *
     * @return {@link OwningStatementVisitor}
     * @since 2.4
     */
    protected OwningStatementVisitor getOwningStatementVisitor() {
        if (owningStatementVisitor == null) {
            owningStatementVisitor = buildOwningStatementVisitor();
        }
        return owningStatementVisitor;
    }

    /**
     * Returns the persistence provider name.
     *
     * @return The name of the persistence provider, <code>null</code> should never be returned
     * @since 2.5
     */
    protected String getProvider() {
        return getGrammar().getProvider();
    }

    /**
     * Returns the version of the persistence provider.
     *
     * @return The version of the persistence provider, if one is extending the default JPQL grammar
     * defined in the Java Persistence specification, otherwise returns an empty string
     * @since 2.4
     */
    protected String getProviderVersion() {
        return getGrammar().getProviderVersion();
    }

    /**
     * Retrieves the BNF object that was registered for the given unique identifier.
     *
     * @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} to retrieve
     * @return The {@link JPQLQueryBNF} representing a section of the grammar
     */
    protected JPQLQueryBNF getQueryBNF(String queryBNFId) {
        return getGrammar().getExpressionRegistry().getQueryBNF(queryBNFId);
    }

    /**
     * Returns the visitor that checks if the visited expression is a subquery or not.
     *
     * @return {@link SubqueryVisitor}
     * @since 2.5
     */
    protected SubqueryVisitor getSubqueryVisitor() {
        if (subqueryVisitor == null) {
            subqueryVisitor = buildSubqueryVisitor();
        }
        return subqueryVisitor;
    }

    /**
     * Initializes this validator.
     */
    protected void initialize() {
        validators = new HashMap<String, JPQLQueryBNFValidator>();
    }

    /**
     * Determines whether the given {@link Expression} represents a nested array or not. To be a
     * nested array, the given {@link Expression} is a {@link SubExpression} and its child is a
     * {@link CollectionExpression}.
     *
     * @return <code>true</code> if the given {@link Expression} is a nested array; <code>false</code> otherwise
     * @since 2.5
     */
    protected boolean isNestedArray(Expression expression) {
        return nestedArraySize(expression) > -1;
    }

    /**
     * Determines whether the given {@link Expression} is a subquery.
     *
     * @param expression The {@link Expression} to check its type
     * @return <code>true</code> if the given {@link Expression} is a subquery; <code>false</code> otherwise
     * @since 2.5
     */
    protected boolean isSubquery(Expression expression) {
        SubqueryVisitor visitor = getSubqueryVisitor();
        try {
            expression.accept(visitor);
            return visitor.expression != null;
        }
        finally {
            visitor.expression = null;
        }
    }

    /**
     * Determines whether the given {@link Expression} is valid by checking its {@link JPQLQueryBNF}
     * with the given {@link JPQLQueryBNF}.
     *
     * @param expression The {@link Expression} to validate based on the query BNF
     * @param queryBNF The {@link JPQLQueryBNF} that determines if the given {@link Expression} is valid
     * @return <code>true</code> if the {@link Expression}'s {@link JPQLQueryBNF} is either the
     * {@link JPQLQueryBNF} or a child of it; <code>false</code> otherwise
     */
    protected boolean isValid(Expression expression, JPQLQueryBNF queryBNF) {
        JPQLQueryBNFValidator validator = getJPQLQueryBNFValidator(queryBNF);
        try {
            expression.accept(validator);
            return validator.valid;
        }
        finally {
            validator.valid = false;
        }
    }

    /**
     * Determines whether the given {@link Expression} is valid by checking its {@link JPQLQueryBNF}
     * with the {@link JPQLQueryBNF} associated with the given unique identifier.
     *
     * @param expression The {@link Expression} to validate based on the query BNF
     * @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} that determines if the
     * given {@link Expression} is valid
     * @return <code>true</code> if the {@link Expression}'s {@link JPQLQueryBNF} is either the
     * {@link JPQLQueryBNF} or a child of it; <code>false</code> otherwise
     */
    protected boolean isValid(Expression expression, String queryBNFId) {
        return isValid(expression, getQueryBNF(queryBNFId));
    }

    /**
     * Determines whether the given {@link Expression} is valid by checking its {@link JPQLQueryBNF}
     * with the list of {@link JPQLQueryBNF} associated with the given unique identifiers.
     *
     * @param expression The {@link Expression} to validate based on the query BNF
     * @param queryBNFIds The unique identifier of the {@link JPQLQueryBNF} that determines if the
     * given {@link Expression} is valid
     * @return <code>true</code> if the {@link Expression}'s {@link JPQLQueryBNF} is either the
     * {@link JPQLQueryBNF} or a child of it; <code>false</code> otherwise
     */
    protected boolean isValid(Expression expression, String... queryBNFIds) {

        for (String queryBNFId : queryBNFIds) {
            if (isValid(expression, queryBNFId)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Determines whether the given {@link Expression} part is an expression of the given query BNF.
     * The {@link CollectionExpression} that may be the direct child of the given {@link Expression}
     * will be bypassed.
     *
     * @param expression The {@link Expression} to validate based on the query BNF
     * @return <code>true</code> if the {@link Expression} part is a child of the given query BNF;
     * <code>false</code> otherwise
     */
    protected boolean isValidWithChildCollectionBypass(Expression expression, String queryBNF) {
        JPQLQueryBNFValidator validator = getExpressionValidator(queryBNF);
        BypassChildCollectionExpressionVisitor bypassValidator = getBypassChildCollectionExpressionVisitor();
        try {
            bypassValidator.visitor = validator;
            expression.accept(bypassValidator);
            return validator.valid;
        }
        finally {
            bypassValidator.visitor = null;
            validator.valid = false;
        }
    }

    /**
     * Determines whether the given {@link Expression} is part of a subquery.
     *
     * @param expression The {@link Expression} to start scanning its location
     * @return <code>true</code> if the given {@link Expression} is part of a subquery; <code>false</code>
     * if it's part of the top-level query
     * @since 2.4
     */
    protected boolean isWithinSubquery(Expression expression) {
        OwningStatementVisitor visitor = getOwningStatementVisitor();
        try {
            expression.accept(visitor);
            return visitor.simpleSelectStatement != null;
        }
        finally {
            visitor.dispose();
        }
    }

    /**
     * Determines whether the given {@link Expression} is part of the top-level query.
     *
     * @param expression The {@link Expression} to start scanning its location
     * @return <code>true</code> if the given {@link Expression} is part of the top-level query;
     * <code>false</code> if it's part of a subquery
     * @since 2.4
     */
    protected boolean isWithinTopLevelQuery(Expression expression) {
        OwningStatementVisitor visitor = getOwningStatementVisitor();
        try {
            expression.accept(visitor);
            return visitor.deleteStatement != null ||
                   visitor.selectStatement != null ||
                   visitor.updateStatement != null;
        }
        finally {
            visitor.dispose();
        }
    }

    /**
     * Returns the length of the string representation of the given {@link Expression}.
     *
     * @param expression The {@link Expression} to retrieve the length of its string
     * @return The length of the string representation of the given {@link Expression}
     */
    protected int length(Expression expression) {
        return expression.getLength();
    }

    /**
     * Retrieves the "literal" from the given {@link Expression}. The literal to retrieve depends on
     * the given {@link LiteralType type}. The literal is basically a string value like an
     * identification variable name, an input parameter, a path expression, an abstract schema name,
     * etc.
     *
     * @param expression The {@link Expression} to visit
     * @param type The {@link LiteralType} helps to determine what to retrieve from the visited
     * {@link Expression}
     * @return A value from the given {@link Expression} or an empty string if the given {@link
     * Expression} and the {@link LiteralType} do not match
     */
    protected String literal(Expression expression, LiteralType type) {
        LiteralVisitor visitor = getLiteralVisitor();
        try {
            visitor.setType(type);
            expression.accept(visitor);
            return visitor.literal;
        }
        finally {
            visitor.literal = ExpressionTools.EMPTY_STRING;
        }
    }

    /**
     * Returns the number of items in the nested array if the given {@link Expression} represents one.
     * To be a nested array, the given {@link Expression} is a {@link SubExpression} and its child is
     * a {@link CollectionExpression}.
     *
     * @return The number of items in the array or -1 if the {@link Expression} is not a nested array
     * @since 2.5
     */
    protected int nestedArraySize(Expression expression) {
        NestedArrayVisitor visitor = getNestedArrayVisitor();
        try {
            visitor.nestedArraySize = -1;
            expression.accept(visitor);
            return visitor.nestedArraySize;
        }
        finally {
            visitor.nestedArraySize = -1;
        }
    }

    /**
     * Calculates the position of the given expression by calculating the length of what is before.
     *
     * @param expression The expression to determine its position within the parsed tree
     * @return The length of the string representation of what comes before the given expression
     */
    protected int position(Expression expression) {
        return expression.getOffset();
    }

    /**
     * Returns the current number of problems that were registered during validation.
     *
     * @return The current number of problems
     * @since 2.4
     */
    public final int problemsSize() {
        return problems.size();
    }

    /**
     * Sets the collection that will be used to store {@link JPQLQueryProblem problems} this
     * validator will find in the JPQL query.
     *
     * @param problems A non-<code>null</code> collection that will be used to store the {@link
     * JPQLQueryProblem problems} if any was found
     * @exception NullPointerException The Collection cannot be <code>null</code>
     */
    public void setProblems(Collection<JPQLQueryProblem> problems) {
        Assert.isNotNull(problems, "The Collection cannot be null");
        this.problems = problems;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void visit(Expression expression) {
        expression.acceptChildren(this);
    }

    /**
     * This visitor is responsible to traverse the children of a {@link CollectionExpression} in
     * order to properly validate the {@link Expression}.
     */
    public static class BypassChildCollectionExpressionVisitor extends AnonymousExpressionVisitor {

        /**
         * The visitor that will visit the {@link Expression}.
         */
        public JPQLQueryBNFValidator visitor;

        /**
         * Creates a new <code>BypassChildCollectionExpressionVisitor</code>.
         */
        public BypassChildCollectionExpressionVisitor() {
            super();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CollectionExpression expression) {
            for (Expression child : expression.children()) {
                child.accept(this);
                if (!visitor.valid) {
                    break;
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void visit(Expression expression) {
            expression.accept(visitor);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(NullExpression expression) {
            // Ignore this, it should be validated by another validator
        }
    }

    /**
     * This visitor is responsible to traverse the parent hierarchy and to skip {@link SubExpression}
     * if it's a parent.
     */
    public static class BypassParentSubExpressionVisitor extends AnonymousExpressionVisitor {

        /**
         * The {@link ExpressionVisitor} that will visit the {@link Expression}.
         */
        public ExpressionVisitor visitor;

        /**
         * Creates a new <code>BypassParentSubExpressionVisitor</code>.
         */
        public BypassParentSubExpressionVisitor() {
            super();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void visit(Expression expression) {
            expression.accept(visitor);
        }

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

    /**
     * This visitor gathers the children of a {@link CollectionExpression} or a single visited
     * {@link Expression}.
     */
    public static class ChildrenCollectorVisitor extends AnonymousExpressionVisitor {

        /**
         * The unique {@link Expression} that was visited or the children of {@link CollectionExpression}.
         */
        protected List<Expression> expressions;

        /**
         * Creates a new <code>ChildrenCollectorVisitor</code>.
         */
        public ChildrenCollectorVisitor() {
            super();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CollectionExpression expression) {
            CollectionTools.addAll(expressions, expression.children());
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void visit(Expression expression) {
            expressions.add(expression);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(NullExpression expression) {
            // Don't add it
        }
    }

    /**
     * This visitor validates any {@link Expression} by checking its BNF against some BNFs.
     */
    public static class JPQLQueryBNFValidator extends AnonymousExpressionVisitor {

        /**
         *
         */
        protected boolean bypassCompound;

        /**
         * The {@link JPQLQueryBNF} used to determine if the expression's BNF is valid.
         */
        private JPQLQueryBNF queryBNF;

        /**
         * Determines whether the visited {@link Expression}'s BNF is valid based on the BNF that was
         * used for validation.
         */
        protected boolean valid;

        /**
         * Creates a new <code>JPQLQueryBNFValidator</code>.
         *
         * @param queryBNF The {@link JPQLQueryBNF} used to determine if the expression's BNF is valid
         */
        public JPQLQueryBNFValidator(JPQLQueryBNF queryBNF) {
            super();
            this.queryBNF = queryBNF;
        }

        private void allJPQLQueryBNFs(Set<String> queryBNFIds, JPQLQueryBNF queryBNF) {
            if (queryBNFIds.add(queryBNF.getId()) && (bypassCompound || !queryBNF.isCompound())) {
                for (JPQLQueryBNF childQueryBNF : queryBNF.nonCompoundChildren()) {
                    allJPQLQueryBNFs(queryBNFIds, childQueryBNF);
                }
            }
        }

        /**
         * Disposes of the internal data.
         */
        public void dispose() {
            valid = false;
            bypassCompound = false;
        }

        /**
         * Determines whether the visited {@link Expression} is valid or not based on the {@link
         * JPQLQueryBNF} that was specified.
         *
         * @return <code>true</code> if the {@link Expression} is valid; <code>false</code> otherwise
         */
        public boolean isValid() {
            return valid;
        }

        /**
         * Sets
         *
         * @param bypassCompound
         */
        public void setBypassCompound(boolean bypassCompound) {
            this.bypassCompound = bypassCompound;
        }

        /**
         * Validates the given {@link JPQLQueryBNF} by making sure it is the one expected or one of
         * the children from the "root" BNF passed to this validator's constructor.
         *
         * @param queryBNF The {@link JPQLQueryBNF} to validate
         */
        public void validate(JPQLQueryBNF queryBNF) {

            // By setting the flag to false will assure that if this validator is used for
            // more than one item, it will reflect the global validity state. If all are
            // valid, then the last expression will set the flag to true
            valid = false;

            // Quick check
            if (queryBNF.getId() == this.queryBNF.getId()) {
                valid = true;
            }
            // Retrieve all the children from the "root" JPQLQueryBNF and
            // check if the BNF to validate is one of those children
            else {
                Set<String> allQueryBNFIds = new HashSet<String>();
                allJPQLQueryBNFs(allQueryBNFIds, this.queryBNF);
                valid = allQueryBNFIds.contains(queryBNF.getId());
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(BadExpression expression) {
            // This is not a valid expression
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CollectionExpression expression) {
            // A collection expression is never valid
            valid = false;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void visit(Expression expression) {
            validate(((AbstractExpression) expression).getQueryBNF());
        }

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

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

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(UnknownExpression expression) {
            // This is not a valid expression
        }
    }

    protected static class NestedArrayVisitor extends AbstractExpressionVisitor {

        /**
         * The number of items contained in the nested array or -1 if the {@link Expression} does not
         * represent a nested array.
         */
        public int nestedArraySize;

        /**
         * Internal flag used to determine if a sub-expression is traversed, which is required when
         * representing a nested array.
         */
        protected boolean subExpression;

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CollectionExpression expression) {
            nestedArraySize = subExpression ? expression.childrenSize() : -1;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SubExpression expression) {
            subExpression = true;
            expression.getExpression().accept(this);
            subExpression = false;
        }
    }

    /**
     * This visitor retrieves the clause owning the visited {@link Expression}.
     */
    public static class OwningClauseVisitor extends AbstractTraverseParentVisitor {

        public DeleteClause deleteClause;
        public FromClause fromClause;
        public GroupByClause groupByClause;
        public HavingClause havingClause;
        public OrderByClause orderByClause;
        public SelectClause selectClause;
        public SimpleFromClause simpleFromClause;
        public SimpleSelectClause simpleSelectClause;
        public UpdateClause updateClause;
        public WhereClause whereClause;

        /**
         * Creates a new <code>OwningClauseVisitor</code>.
         */
        public OwningClauseVisitor() {
            super();
        }

        /**
         * Disposes the internal data.
         */
        public void dispose() {
            deleteClause       = null;
            fromClause         = null;
            groupByClause      = null;
            havingClause       = null;
            orderByClause      = null;
            selectClause       = null;
            simpleFromClause   = null;
            simpleSelectClause = null;
            updateClause       = null;
            whereClause        = null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(DeleteClause expression) {
            deleteClause = expression;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(FromClause expression) {
            fromClause = expression;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(GroupByClause expression) {
            groupByClause = expression;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(HavingClause expression) {
            havingClause = expression;
        }

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

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SelectClause expression) {
            selectClause = expression;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SimpleFromClause expression) {
            simpleFromClause = expression;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SimpleSelectClause expression) {
            simpleSelectClause = expression;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(UpdateClause expression) {
            updateClause = expression;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(WhereClause expression) {
            whereClause = expression;
        }
    }

    /**
     * This visitor retrieves the statement owning the visited {@link Expression}.
     */
    protected static class OwningStatementVisitor extends AbstractTraverseParentVisitor {

        public DeleteStatement deleteStatement;
        public SelectStatement selectStatement;
        public SimpleSelectStatement simpleSelectStatement;
        public UpdateStatement updateStatement;

        /**
         * Disposes the internal data.
         */
        protected void dispose() {
            deleteStatement       = null;
            selectStatement       = null;
            simpleSelectStatement = null;
            updateStatement       = null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(DeleteStatement expression) {
            deleteStatement = expression;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SelectStatement expression) {
            selectStatement = expression;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SimpleSelectStatement expression) {
            simpleSelectStatement = expression;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(UpdateStatement expression) {
            updateStatement = expression;
        }
    }

    /**
     * This visitor retrieves the statement owning the visited {@link Expression}.
     */
    protected static class SubqueryVisitor extends AbstractExpressionVisitor {

        /**
         * The subquery is the visited {@link Expression} is a subquery.
         */
        private SimpleSelectStatement expression;

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