/*
 * Copyright (c) 2006, 2019 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
//
//     09/02/2019-3.0 Alexandre Jacob
//        - 527415: Fix code when locale is tr, az or lt
package org.eclipse.persistence.jpa.jpql.tools;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Stack;
import org.eclipse.persistence.jpa.jpql.AbstractValidator.JPQLQueryBNFValidator;
import org.eclipse.persistence.jpa.jpql.Assert;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.JPAVersion;
import org.eclipse.persistence.jpa.jpql.JPQLQueryDeclaration.Type;
import org.eclipse.persistence.jpa.jpql.LiteralType;
import org.eclipse.persistence.jpa.jpql.WordParser;
import org.eclipse.persistence.jpa.jpql.parser.AbsExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractConditionalClause;
import org.eclipse.persistence.jpa.jpql.parser.AbstractDoubleEncapsulatedExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractFromClause;
import org.eclipse.persistence.jpa.jpql.parser.AbstractOrderByClause;
import org.eclipse.persistence.jpa.jpql.parser.AbstractPathExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSchemaName;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSelectClause;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSingleEncapsulatedExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractTraverseParentVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractTripleEncapsulatedExpression;
import org.eclipse.persistence.jpa.jpql.parser.AdditionExpression;
import org.eclipse.persistence.jpa.jpql.parser.AggregateFunction;
import org.eclipse.persistence.jpa.jpql.parser.AllOrAnyExpression;
import org.eclipse.persistence.jpa.jpql.parser.AndExpression;
import org.eclipse.persistence.jpa.jpql.parser.AnonymousExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.ArithmeticExpression;
import org.eclipse.persistence.jpa.jpql.parser.ArithmeticExpressionFactory;
import org.eclipse.persistence.jpa.jpql.parser.ArithmeticFactor;
import org.eclipse.persistence.jpa.jpql.parser.ArithmeticPrimaryBNF;
import org.eclipse.persistence.jpa.jpql.parser.AvgFunction;
import org.eclipse.persistence.jpa.jpql.parser.BadExpression;
import org.eclipse.persistence.jpa.jpql.parser.BetweenExpression;
import org.eclipse.persistence.jpa.jpql.parser.CaseExpression;
import org.eclipse.persistence.jpa.jpql.parser.CaseOperandBNF;
import org.eclipse.persistence.jpa.jpql.parser.CoalesceExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionMemberDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.CollectionMemberExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionValuedPathExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionValuedPathExpressionBNF;
import org.eclipse.persistence.jpa.jpql.parser.ComparisonExpression;
import org.eclipse.persistence.jpa.jpql.parser.ComparisonExpressionFactory;
import org.eclipse.persistence.jpa.jpql.parser.CompoundExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConcatExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConditionalExpressionBNF;
import org.eclipse.persistence.jpa.jpql.parser.ConstructorExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConstructorItemBNF;
import org.eclipse.persistence.jpa.jpql.parser.CountFunction;
import org.eclipse.persistence.jpa.jpql.parser.DateTime;
import org.eclipse.persistence.jpa.jpql.parser.DeleteClause;
import org.eclipse.persistence.jpa.jpql.parser.DeleteStatement;
import org.eclipse.persistence.jpa.jpql.parser.DivisionExpression;
import org.eclipse.persistence.jpa.jpql.parser.EmptyCollectionComparisonExpression;
import org.eclipse.persistence.jpa.jpql.parser.EntityTypeLiteral;
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.ExpressionFactory;
import org.eclipse.persistence.jpa.jpql.parser.ExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.FromClause;
import org.eclipse.persistence.jpa.jpql.parser.FunctionExpression;
import org.eclipse.persistence.jpa.jpql.parser.GroupByClause;
import org.eclipse.persistence.jpa.jpql.parser.GroupByItemBNF;
import org.eclipse.persistence.jpa.jpql.parser.HavingClause;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.IdentifierRole;
import org.eclipse.persistence.jpa.jpql.parser.InExpression;
import org.eclipse.persistence.jpa.jpql.parser.InExpressionItemBNF;
import org.eclipse.persistence.jpa.jpql.parser.IndexExpression;
import org.eclipse.persistence.jpa.jpql.parser.InputParameter;
import org.eclipse.persistence.jpa.jpql.parser.InternalBetweenExpressionBNF;
import org.eclipse.persistence.jpa.jpql.parser.InternalJoinBNF;
import org.eclipse.persistence.jpa.jpql.parser.JPQLExpression;
import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar;
import org.eclipse.persistence.jpa.jpql.parser.JPQLQueryBNF;
import org.eclipse.persistence.jpa.jpql.parser.Join;
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.LocateExpression;
import org.eclipse.persistence.jpa.jpql.parser.LogicalExpression;
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.NewValueBNF;
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.ObjectExpression;
import org.eclipse.persistence.jpa.jpql.parser.OnClause;
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.OrderByItem.Ordering;
import org.eclipse.persistence.jpa.jpql.parser.OrderByItemBNF;
import org.eclipse.persistence.jpa.jpql.parser.QueryPosition;
import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclarationBNF;
import org.eclipse.persistence.jpa.jpql.parser.ResultVariable;
import org.eclipse.persistence.jpa.jpql.parser.ScalarExpressionBNF;
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.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.SubqueryBNF;
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.TreatExpression;
import org.eclipse.persistence.jpa.jpql.parser.TrimExpression;
import org.eclipse.persistence.jpa.jpql.parser.TypeExpression;
import org.eclipse.persistence.jpa.jpql.parser.UnknownExpression;
import org.eclipse.persistence.jpa.jpql.parser.UpdateClause;
import org.eclipse.persistence.jpa.jpql.parser.UpdateItem;
import org.eclipse.persistence.jpa.jpql.parser.UpdateStatement;
import org.eclipse.persistence.jpa.jpql.parser.UpperExpression;
import org.eclipse.persistence.jpa.jpql.parser.ValueExpression;
import org.eclipse.persistence.jpa.jpql.parser.WhenClause;
import org.eclipse.persistence.jpa.jpql.parser.WhereClause;
import org.eclipse.persistence.jpa.jpql.tools.ContentAssistProposals.ClassType;
import org.eclipse.persistence.jpa.jpql.tools.resolver.Declaration;
import org.eclipse.persistence.jpa.jpql.tools.resolver.Resolver;
import org.eclipse.persistence.jpa.jpql.tools.resolver.StateFieldResolver;
import org.eclipse.persistence.jpa.jpql.tools.spi.IEntity;
import org.eclipse.persistence.jpa.jpql.tools.spi.IManagedType;
import org.eclipse.persistence.jpa.jpql.tools.spi.IMapping;
import org.eclipse.persistence.jpa.jpql.tools.spi.IType;
import org.eclipse.persistence.jpa.jpql.tools.utility.filter.AndFilter;
import org.eclipse.persistence.jpa.jpql.utility.CollectionTools;
import org.eclipse.persistence.jpa.jpql.utility.filter.Filter;
import org.eclipse.persistence.jpa.jpql.utility.filter.NullFilter;
import static org.eclipse.persistence.jpa.jpql.parser.AbstractExpression.*;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.*;

/**
 * The visitor provides support for finding the possible proposals within a JPQL query at a certain
 * position.
 * <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.
 *
 * @version 2.5
 * @since 2.4
 * @author Pascal Filion
 */
@SuppressWarnings({"nls", "unused"}) // unused used for the import statement: see bug 330740
public abstract class AbstractContentAssistVisitor extends AnonymousExpressionVisitor {

    /**
     * This is used to change the position of the cursor in order to add possible proposals
     */
    protected Stack<Integer> corrections;

    /**
     * The cached helpers that are used by this visitor to add valid content assist proposals.
     */
    protected Map<Class<?>, Object> helpers;

    /**
     * This map contains the {@link Filter} that is used to determine when an identifier is a valid
     * proposal, some of them depends on the expression's type.
     */
    protected Map<String, Filter<Expression>> identifierFilters;

    /**
     * Used to prevent and infinite recursion when one of the visit method is virtually asking a
     * child expression to be visited.
     */
    protected Stack<Expression> lockedExpressions;

    /**
     * The set of possible proposals gathered based on the position in the query.
     */
    protected DefaultContentAssistProposals proposals;

    /**
     * The context used to query information about the JPQL query.
     */
    protected final JPQLQueryContext queryContext;

    /**
     * Contains the position of the cursor within the parsed {@link Expression} from the root node
     * up to the deepest leaf node.
     */
    protected QueryPosition queryPosition;

    /**
     * A virtual space is used to move the position of the cursor by adding an extra space in order
     * to find some proposals within an expression. This is usually used when the trailing whitespace
     * is not owned by the child expression but by one of its parents.
     */
    protected Stack<Integer> virtualSpaces;

    /**
     * The current word, which was retrieved from the JPQL query based on the position of the cursor.
     * The word is the partial string found to the left of the cursor and up to the cursor.
     */
    protected String word;

    /**
     * This is used to retrieve words from the actual JPQL query.
     */
    protected WordParser wordParser;

    /**
     * This {@link Filter} is used to say the {@link Expression} is invalid without doing anything.
     */
    protected static final Filter<Expression> INVALID_IDENTIFIER_FILTER = new Filter<Expression>() {
        public boolean accept(Expression expression) {
            return false;
        }
    };

    /**
     * A constant for the length of a whitespace, which is 1.
     */
    protected static final int SPACE_LENGTH = 1;

    /**
     * This {@link Filter} is used to say the {@link Expression} is valid without doing anything.
     */
    protected static final Filter<Expression> VALID_IDENTIFIER_FILTER = new Filter<Expression>() {
        public boolean accept(Expression expression) {
            return true;
        }
    };

    /**
     * Creates a new <code>AbstractContentAssistVisitor</code>.
     *
     * @param queryContext The context used to query information about the JPQL query
     * @exception NullPointerException The {@link JPQLQueryContext} cannot be <code>null</code>
     */
    protected AbstractContentAssistVisitor(JPQLQueryContext queryContext) {
        super();
        Assert.isNotNull(queryContext, "The JPQLQueryContext cannot be null");
        this.queryContext = queryContext;
        initialize();
    }

    /**
     * Adds the given JPQL identifier as a valid proposal if its role is {@link IdentifierRole#AGGREGATE}
     * and the beginning starts with the current word.
     *
     * @param identifier The JPQL identifier to add as a valid proposal if it passes the checks
     */
    protected void addAggregateIdentifier(String identifier) {
        if (isAggregate(identifier)) {
            addIdentifier(identifier);
        }
    }

    /**
     * Adds the JPQL identifiers that are registered with the given {@link JPQLQueryBNF} as valid
     * proposals if their role is {@link IdentifierRole#AGGREGATE} and the beginning starts with the
     * current word.
     *
     * @param queryBNF The {@link JPQLQueryBNF} for which the registered JPQL identifiers will be
     * added as proposals if they pass the checks
     */
    protected void addAggregateIdentifiers(JPQLQueryBNF queryBNF) {
        for (String identifier : queryBNF.getIdentifiers()) {
            addAggregateIdentifier(identifier);
        }
    }

    /**
     * Adds the JPQL identifiers that are registered with the given {@link JPQLQueryBNF} as valid
     * proposals if their role is {@link IdentifierRole#AGGREGATE} and the beginning starts with the
     * current word.
     *
     * @param queryBNFId The unique of the {@link JPQLQueryBNF} for which the registered JPQL
     * identifiers will be added as proposals if they pass the checks
     */
    protected void addAggregateIdentifiers(String queryBNFId) {
        addAggregateIdentifiers(getQueryBNF(queryBNFId));
    }

    /**
     * Adds the JPQL identifiers which correspond to the arithmetic operators as valid proposals. The
     * word has to be an empty string.
     */
    protected void addArithmeticIdentifiers() {
        if (word.length() == 0) {
            addExpressionFactoryIdentifiers(ArithmeticExpressionFactory.ID);
        }
    }

    /**
     * Adds the given JPQL identifier as a valid proposal if its role is {@link IdentifierRole#CLAUSE}
     * and the beginning starts with the current word.
     *
     * @param identifier The JPQL identifier to add as a valid proposal if it passes the checks
     */
    protected void addClauseIdentifier(String identifier) {
        if (isClause(identifier)) {
            addIdentifier(identifier);
        }
    }

    /**
     * Adds the JPQL identifiers that are registered with the given {@link JPQLQueryBNF} as valid
     * proposals if their role is {@link IdentifierRole#CLAUSE} and the beginning starts with the
     * current word.
     *
     * @param queryBNF The {@link JPQLQueryBNF} for which the registered JPQL identifiers will be
     * added as proposals if they pass the checks
     */
    protected void addClauseIdentifiers(JPQLQueryBNF queryBNF) {
        for (String identifier : queryBNF.getIdentifiers()) {
            addClauseIdentifier(identifier);
        }
    }

    /**
     * Adds the JPQL identifiers that are registered with the given {@link JPQLQueryBNF} as valid
     * proposals if their role is {@link IdentifierRole#CLAUSE} and the beginning starts with the
     * current word.
     *
     * @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} for which the registered
     * JPQL identifiers will be
     * added as proposals if they pass the checks
     */
    protected void addClauseIdentifiers(String queryBNFId) {
        addClauseIdentifiers(getQueryBNF(queryBNFId));
    }

    /**
     * Adds the JPQL identifiers which correspond to the comparison operators as valid proposals. The
     * word has to be an empty string.
     *
     * @param expression The {@link Expression} is preceding the position of the cursor and will help
     * to determine which of the comparison operators are valid
     */
    protected void addComparisonIdentifiers(Expression expression) {

        if (word.length() == 0) {

            for (String identifier : getExpressionFactory(ComparisonExpressionFactory.ID).identifiers()) {
                Filter<Expression> filter = getFilter(identifier);
                if (filter.accept(expression)) {
                    addIdentifier(identifier);
                }
            }
        }
    }

    /**
     * Adds the composite JPQL identifier by following the given rules:
     *
     * <ul>
     * <li>If the word is empty and the offset is -1, then use the rule used by {@link
     * #addIdentifier(String)};</li>
     * <li>Otherwise checks the ending of the JPQL query with a portion of the identifier from the
     * entire length of the identifier to the given offset by cutting off the trailing characters.</li>
     * </ul>
     *
     * @param identifier The composite JPQL identifier to add as valid proposal. The composite part
     * means it is an identifier composed of more than one word, example: <code><b>GROUP BY</b></code>
     * @param offset The smallest length of the given identifier to test against the ending of the
     * JPQL query based on the position of the cursor. The offset is exclusive
     */
    protected void addCompositeIdentifier(String identifier, int offset) {

        // No need to check by fragmenting the composite identifier
        if ((word.length() == 0) && (offset == -1)) {
            addIdentifier(identifier);
        }
        else if (isValidVersion(identifier)) {

            // Make sure the offset is not -1
            offset = Math.max(offset, 0);

            // Cut the composite identifier by removing the ending portion one character at a time
            // and see if the JPQL query is ending with that same text
            // Example: "SELECT e FROM Employee e ORDER B" and the JPQL identifier is "ORDER BY", the
            //          test will do "ORDER B", "ORDER ", "ORDER", "ORD", "OR", "O" and if it does
            //          match then we have to make sure there is a whitespace before to make sure it's
            //          a complete word
            int position = queryPosition.getPosition();

            for (int index = identifier.length(); --index > offset; ) {
                String fragment = identifier.substring(0, index);

                // The text ends with the beginning portion of the JPQL identifier,
                // make sure it has a whitespace before it. There is no case a non-alphanumeric
                // character should also be checked, like '(' or '+'
                if (wordParser.endsWithIgnoreCase(position, fragment)) {

                    char character = wordParser.character(position - fragment.length() - 1);

                    if (Character.isWhitespace(character)) {
                        proposals.addIdentifier(identifier);
                        break;
                    }
                }
            }
        }
    }

    /**
     * Adds the given JPQL identifier as a valid proposal if its role is {@link IdentifierRole#COMPOUND_FUNCTION}
     * and the beginning starts with the current word.
     *
     * @param identifier The JPQL identifier to add as a valid proposal if it passes the checks
     * @param expression The {@link Expression} represents the fragment that is before the current
     * word and it helps to filter out some of the compound identifiers
     * @param hasIs Flag indicating if the <code><b>IS</b></code> identifier was found before the word,
     * which would also be before the <code><b>NOT</b></code> identifier if it was also found
     * @param hasNot Flag indicating if the <code><b>NOT</b></code> identifier was found before the word
     */
    protected void addCompoundIdentifier(String identifier,
                                         Expression expression,
                                         boolean hasIs,
                                         boolean hasNot) {

        if (isCompoundFunction(identifier)) {

            Filter<Expression> filter = getFilter(identifier);

            // "IS <identifier>" (examples of possible identifiers: IS NULL, IS NOT NULL)
            if (hasIs && !hasNot) {
                if (identifier.startsWith("IS ") && filter.accept(expression)) {
                    addCompositeIdentifier(identifier, 0);
                }
            }
            // "NOT <identifier>" (examples of possible identifiers: NOT EMPTY, NOT BETWEEN)
            else if (!hasIs && hasNot) {
                if (identifier.startsWith("NOT ") && filter.accept(expression)) {
                    addCompositeIdentifier(identifier, 0);
                }
            }
            // "IS NOT <identifier>" (examples of possible identifiers: IS NOT NULL, IS NOT EMPTY)
            else if (hasIs && hasNot) {
                if (identifier.startsWith("IS NOT ") && filter.accept(expression)) {
                    addCompositeIdentifier(identifier, 0);
                }
            }
            // "<identifier>" (examples of possible identifiers: BETWEEN, MEMBER, MEMBER OF)
            else if (filter.accept(expression)) {
                addIdentifier(identifier);
            }
        }
    }

    /**
     * Adds the JPQL identifiers that are registered with the given {@link JPQLQueryBNF} as valid
     * proposals if their role is {@link IdentifierRole#COMPOUND_FUNCTION} and the beginning starts
     * with the current word.
     *
     * @param queryBNF The {@link JPQLQueryBNF} for which the registered JPQL identifiers will be
     * added as proposals if they pass the checks
     * @param expression The {@link Expression} represents the fragment that is before the current
     * word and it helps to filter out some of the compound identifiers
     * @param hasIs Flag indicating if the <code><b>IS</b></code> identifier was found before the word,
     * which would also be before the <code><b>NOT</b></code> identifier if it was also found
     * @param hasNot Flag indicating if the <code><b>NOT</b></code> identifier was found before the word
     */
    protected void addCompoundIdentifiers(JPQLQueryBNF queryBNF,
                                          Expression expression,
                                          boolean hasIs,
                                          boolean hasNot) {

        for (String identifier : queryBNF.getIdentifiers()) {
            addCompoundIdentifier(identifier, expression, hasIs, hasNot);
        }
    }

    /**
     * Adds the JPQL identifiers that are registered with the given {@link JPQLQueryBNF} as valid
     * proposals if their role is {@link IdentifierRole#COMPOUND_FUNCTION} and the beginning starts
     * with the current word.
     *
     * @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} for which the registered
     * JPQL identifiers will be added as proposals if they pass the checks
     * @param expression The {@link Expression} represents the fragment that is before the current
     * word and it helps to filter out some of the compound identifiers
     * @param hasIs Flag indicating if the <code><b>IS</b></code> identifier was found before the word,
     * which would also be before the <code><b>NOT</b></code> identifier if it was also found
     * @param hasNot Flag indicating if the <code><b>NOT</b></code> identifier was found before the word
     */
    protected void addCompoundIdentifiers(String queryBNFId,
                                          Expression expression,
                                          boolean hasIs,
                                          boolean hasNot) {

        addCompoundIdentifiers(getQueryBNF(queryBNFId), expression, hasIs, hasNot);
    }

    /**
     * Adds the entities as possible content assist proposals but will be filtered using the current word.
     */
    protected void addEntities() {
        for (IEntity entity : queryContext.getProvider().entities()) {
            if (isValidProposal(entity.getName(), word)) {
                proposals.addEntity(entity);
            }
        }
    }

    /**
     * Adds the entities as possible content assist proposals but will be filtered using the current
     * word and the entity's type will have to be assignable from the given {@link IType}.
     *
     * @param type The {@link IType} used to filter the abstract schema types
     */
    protected void addEntities(IType type) {

        for (IEntity entity : queryContext.getProvider().entities()) {

            if (isValidProposal(entity.getName(), word) &&
                type.isAssignableTo(entity.getType())) {

                proposals.addEntity(entity);
            }
        }
    }

    /**
     * Adds the given enum constant as a valid proposal.
     *
     * @param enumType The {@link IType} of the {@link Enum}
     * @param enumConstant The enum constant to be added as a valid proposal
     */
    protected void addEnumConstant(IType enumType, String enumConstant) {
        proposals.addEnumConstant(enumType, enumConstant);
    }

    /**
     * Adds the constants of the given enum type as valid proposals if the beginning starts with the
     * given word.
     *
     * @param enumType The {@link IType} of the enum type
     * @param word The word is used to filter the enum constants, which can be <code>null</code> or
     * an empty string
     */
    protected void addEnumConstants(IType enumType, String word) {

        for (String enumConstant : enumType.getEnumConstants()) {
            if (ExpressionTools.startWithIgnoreCase(enumConstant, word)) {
                addEnumConstant(enumType, enumConstant);
            }
        }
    }

    /**
     * Adds the JPQL identifiers that were registered with the given {@link ExpressionFactory}.
     *
     * @param expressionFactory The factory for which its registered JPQL identifiers are added as
     * valid proposals, if they meet the checks
     */
    protected void addExpressionFactoryIdentifiers(ExpressionFactory expressionFactory) {
        for (String identifier : expressionFactory.identifiers()) {
            addIdentifier(identifier);
        }
    }

    /**
     * Adds the JPQL identifiers that were registered with the {@link ExpressionFactory} with the
     * given unique identifier.
     *
     * @param expressionFactoryId The unique identifier of the factory for which its registered JPQL
     * identifiers are added as valid proposals, if they meet the checks
     */
    protected void addExpressionFactoryIdentifiers(String expressionFactoryId) {
        addExpressionFactoryIdentifiers(getExpressionFactory(expressionFactoryId));
    }

    /**
     * Adds the given JPQL identifier as a valid proposal if its role is {@link IdentifierRole#FUNCTION}
     * and the beginning starts with the current word.
     *
     * @param identifier The JPQL identifier to add as a valid proposal if it passes the checks
     */
    protected void addFunctionIdentifier(String identifier) {
        if (isFunction(identifier)) {
            addIdentifier(identifier);
        }
    }

    /**
     * Adds the JPQL identifiers for which their role is {@link IdentifierRole#FUNCTION FUNCTION} by
     * determining the {@link JPQLQueryBNF} that represents the fragment for which the given {@link
     * Expression} was parsed.
     * <p>
     * For instance: "<code>SELECT e, AVG(e.name) FROM Employee e</code>" and the given expression is
     * "<code>AVG(e.name)</code>", then the BNF should be the select item BNF.
     * <p>
     * If the BNF allows for a subquery and the expression is encapsulated by parenthesis, then
     * <code><b>SELECT</b></code> will also be added.
     * <p>
     * The identification variables will also be added.
     *
     * @param expression The {@link Expression} is used to determine the {@link JPQLQueryBNF} to use
     * when retrieving the JPQL identifiers representing a function
     */
    protected void addFunctionIdentifiers(Expression expression) {

        JPQLQueryBNF queryBNF = expression.getParent().findQueryBNF(expression);

        addIdentificationVariables();
        addFunctionIdentifiers(queryBNF);

        if (isValid(queryBNF, SubqueryBNF.ID, true) && isEncapsulated(expression)) {
            addIdentifier(SELECT);
        }
    }

    /**
     * Adds the JPQL identifiers that are registered with the given {@link JPQLQueryBNF} as valid
     * proposals if their role is {@link IdentifierRole#FUNCTION} and the beginning starts with the
     * current word.
     *
     * @param queryBNF The {@link JPQLQueryBNF} for which the registered JPQL identifiers will be
     * added as proposals if they pass the checks
     */
    protected void addFunctionIdentifiers(JPQLQueryBNF queryBNF) {
        for (String identifier : queryBNF.getIdentifiers()) {
            addFunctionIdentifier(identifier);
        }
    }

    /**
     * Adds the JPQL identifiers that are registered with the given {@link JPQLQueryBNF} as valid
     * proposals if their role is {@link IdentifierRole#FUNCTION} and the beginning starts with the
     * current word.
     *
     * @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} for which the registered
     * JPQL identifiers will be added as proposals if they pass the checks
     */
    protected void addFunctionIdentifiers(String queryBNFId) {
        addFunctionIdentifiers(getQueryBNF(queryBNFId));
    }

    /**
     * Adds the given identification variable as a valid proposal.
     *
     * @param identificationVariable The identification variable to add as a proposal if it passes the checks
     */
    protected void addIdentificationVariable(String identificationVariable) {

        if (ExpressionTools.stringIsNotEmpty(identificationVariable) &&
            isValidProposal(identificationVariable, word)) {

            proposals.addIdentificationVariable(identificationVariable);
        }
    }

    /**
     * Adds all the identification variables defined in the current query's <b>FROM</b> clause.
     */
    protected void addIdentificationVariables() {
        addIdentificationVariables(null, IdentificationVariableType.ALL);
    }

    /**
     * Adds the possible identification variables as valid proposals but filter them based on the
     * given type.
     * <p>
     * For instance, if the type is {@link IdentificationVariableType#LEFT}, then any identification
     * variables that have been defined before the given {@link Expression} are valid proposals, but
     * those defined after are not valid proposals.
     *
     * @param expression The {@link Expression} where the content assist was invoked, which helps to
     * determine how to stop adding identification variables
     * @param type Which type of identification variables to add as valid proposals
     */
    protected void addIdentificationVariables(Expression expression, IdentificationVariableType type) {

        for (Declaration declaration : queryContext.getDeclarations()) {
            boolean stop = type.add(this, declaration, expression);
            if (stop) {
                break;
            }
        }
    }

    /**
     * Adds the given identifier as a proposal if it passes the checks.
     *
     * @param identifier The JPQL identifier to add as a proposal
     */
    protected void addIdentifier(String identifier) {

        if (isValidVersion(identifier) &&
            isValidProposal(identifier, word)) {

            proposals.addIdentifier(identifier);
        }
    }

    /**
     * Adds the join specification identifiers as proposals.
     */
    protected void addJoinIdentifiers() {
        addIdentifier(INNER_JOIN);
        addIdentifier(INNER_JOIN_FETCH);
        addIdentifier(JOIN);
        addIdentifier(JOIN_FETCH);
        addIdentifier(LEFT_JOIN);
        addIdentifier(LEFT_JOIN_FETCH);
        addIdentifier(LEFT_OUTER_JOIN);
        addIdentifier(LEFT_OUTER_JOIN_FETCH);
    }

    /**
     * Adds the identification variables defined in the current query's <b>FROM</b> clause that are
     * declared before the given {@link Expression}.
     *
     * @param expression The {@link Expression} used to determine at which declaration to stop
     */
    protected void addLeftIdentificationVariables(Expression expression) {
        addIdentificationVariables(expression, IdentificationVariableType.LEFT);
    }

    /**
     * Adds the logical identifiers, which are <code><b>AND</b></code> and <code><b>OR</b></code>.
     */
    protected void addLogicalIdentifiers() {
        addIdentifier(AND);
        addIdentifier(OR);
    }

    /**
     * Adds the given identification variable as a proposal if it passes the checks. If an entity is
     * found, then it will be registered.
     *
     * @param identificationVariable An identification variable that is
     */
    protected void addRangeIdentificationVariable(String identificationVariable) {

        if (ExpressionTools.stringIsNotEmpty(identificationVariable) &&
            isValidProposal(identificationVariable, word)) {

            // Resolve the identification variable
            Resolver resolver = queryContext.getResolver(identificationVariable);
            IEntity entity = queryContext.getProvider().getEntity(resolver.getType());

            // The identification variable identifies an entity, add the extra information
            if (entity != null) {
                proposals.addRangeIdentificationVariable(identificationVariable, entity);
            }
            else {
                proposals.addIdentificationVariable(identificationVariable);
            }
        }
    }

    /**
     * Adds the result variables defined in the <code>SELECT</code> clause as valid proposals.
     */
    protected void addResultVariables() {
        for (String resultVariable : queryContext.getResultVariables()) {
            addIdentificationVariable(resultVariable);
        }
    }

    /**
     * Adds a virtual space on the stack.
     */
    protected final void addVirtualSpace() {
        virtualSpaces.add(SPACE_LENGTH);
    }

    /**
     * Determines whether the given {@link Expression} can be followed by an arithmetic operator.
     *
     * @param expression The {@link Expression} that found left of the cursor, which determines if
     * the arithmetic operators are appendable or not
     * @return <code>true</code> if the operators are appendable; <code>false</code> otherwise
     */
    protected boolean areArithmeticSymbolsAppendable(Expression expression) {
        return isAppendable(expression, AppendableType.ARITHMETIC);
    }

    /**
     * Determines whether the given {@link Expression} can be followed by a comparison operator.
     *
     * @param expression The {@link Expression} that found left of the cursor, which determines if
     * the comparison operators are appendable or not
     * @return <code>true</code> if the operators are appendable; <code>false</code> otherwise
     */
    protected boolean areComparisonSymbolsAppendable(Expression expression) {
        return isAppendable(expression, AppendableType.COMPARISON);
    }

    /**
     * Determines whether the given {@link Expression} can be followed by a logical operator.
     *
     * @param expression The {@link Expression} that found left of the cursor, which determines if
     * the logical operators are appendable or not
     * @return <code>true</code> if the logical identifiers are appendable; <code>false</code> otherwise
     */
    protected boolean areLogicalSymbolsAppendable(Expression expression) {
        return isAppendable(expression, AppendableType.LOGICAL);
    }

    protected abstract AcceptableTypeVisitor buildAcceptableTypeVisitor();

    protected AppendableExpressionVisitor buildAppendableExpressionVisitor() {
        return new AppendableExpressionVisitor(this);
    }

    protected Filter<Expression> buildCollectionCompoundTypeFilter() {
        return new Filter<Expression>() {
            public boolean accept(Expression expression) {
                IType type = queryContext.getType(expression);
                TypeHelper typeHelper = queryContext.getTypeHelper();
                return typeHelper.isCollectionType(type) ||
                       typeHelper.isMapType(type);
            }
        };
    }

    protected CollectionExpressionVisitor buildCollectionExpressionVisitor() {
        return new CollectionExpressionVisitor();
    }

    protected CollectionMappingFilter buildCollectionMappingFilter() {
        return new CollectionMappingFilter();
    }

    protected ConcatExpressionCollectionHelper buildConcatExpressionCollectionHelper() {
        return new ConcatExpressionCollectionHelper(this);
    }

    protected ConditionalClauseCollectionHelper buildConditionalClauseCollectionHelper() {
        return new ConditionalClauseCollectionHelper(this);
    }

    protected ConstrutorCollectionHelper buildConstrutorCollectionHelper() {
        return new ConstrutorCollectionHelper(this);
    }

    protected DeclarationVisitor buildDeclarationVisitor() {
        return new DeclarationVisitor();
    }

    protected DefaultMappingCollector buildDefaultMappingCollector() {
        return new DefaultMappingCollector();
    }

    protected DeleteClauseCollectionHelper buildDeleteClauseCollectionHelper() {
        return new DeleteClauseCollectionHelper(this);
    }

    protected DeleteClauseStatementHelper buildDeleteClauseStatementHelper() {
        return new DeleteClauseStatementHelper(this);
    }

    protected Filter<Expression> buildDifferentComparisonFilter() {
        return new DifferentComparisonFilter();
    }

    protected DoubleEncapsulatedCollectionHelper buildDoubleEncapsulatedCollectionHelper() {
        return new DoubleEncapsulatedCollectionHelper(this);
    }

    protected EncapsulatedExpressionVisitor buildEncapsulatedExpressionVisitor() {
        return new EncapsulatedExpressionVisitor();
    }

    /**
     * Creates a new {@link QueryPosition} containing the corrected positions starting at the given
     * {@link Expression} and traversing the children at is always at the right side of the tree.
     *
     * @param invalidExpression The invalid {@link Expression} for which a new {@link QueryPosition}
     * will be calculated
     * @param startingPointExpression The {@link Expression} from which the calculation of the positions will start
     * @param virtualSpace Single element array that will be used to store the flag indicating if a
     * virtual space should be added or not
     * @return The new {@link QueryPosition} that contains the position of the cursor starting from
     * the given {@link Expression} down to the right leaf
     */
    protected QueryPosition buildEndingPositionFromInvalidExpression(Expression invalidExpression,
                                                                     Expression startingPointExpression,
                                                                     boolean[] virtualSpace) {

        EndingQueryPositionBuilder visitor = getEndingQueryPositionBuilder();

        try {

            visitor.prepare(invalidExpression);
            startingPointExpression.accept(visitor);

            virtualSpace[0] = visitor.hasVirtualSpace();
            return visitor.getQueryPosition();
        }
        finally {
            visitor.dispose();
        }
    }

    protected EndingQueryPositionBuilder buildEndingQueryPositionBuilder() {
        return new EndingQueryPositionBuilder(this);
    }

    protected EnumVisitor buildEnumVisitor() {
        return new EnumVisitor();
    }

    protected FilteringMappingCollector buildFilteringMappingCollector(AbstractPathExpression expression,
                                                                       Resolver resolver,
                                                                       Filter<IMapping> filter,
                                                                       String pattern) {

        return new FilteringMappingCollector(
            resolver,
            buildMappingFilter(expression, filter),
            pattern
        );
    }

    protected FollowingClausesVisitor buildFollowingClausesVisitor() {
        return new FollowingClausesVisitor();
    }

    protected FollowingInvalidExpressionVisitor buildFollowingInvalidExpressionVisitor() {
        return new FollowingInvalidExpressionVisitor(this);
    }

    protected FromClauseCollectionHelper buildFromClauseCollectionHelper() {
        return new FromClauseCollectionHelper(this);
    }

    protected FromClauseStatementHelper buildFromClauseStatementHelper() {
        return new FromClauseStatementHelper(this);
    }

    protected GroupByClauseCollectionHelper buildGroupByClauseCollectionHelper() {
        return new GroupByClauseCollectionHelper(this);
    }

    protected GroupByClauseStatementHelper buildGroupByClauseStatementHelper() {
        return new GroupByClauseStatementHelper(this);
    }

    protected HavingClauseStatementHelper buildHavingClauseStatementHelper() {
        return new HavingClauseStatementHelper(this);
    }

    protected IncompleteCollectionExpressionVisitor buildIncompleteCollectionExpressionVisitor() {
        return new IncompleteCollectionExpressionVisitor();
    }

    protected InvalidExpressionVisitor buildInvalidExpressionVisitor() {
        return new InvalidExpressionVisitor();
    }

    protected JoinCollectionHelper buildJoinCollectionHelper() {
        return new JoinCollectionHelper(this);
    }

    /**
     * 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 buildJPQLQueryBNFValidator(JPQLQueryBNF queryBNF) {
        return new JPQLQueryBNFValidator(queryBNF);
    }

    protected MappingCollector buildMappingCollector(AbstractPathExpression expression,
                                                     Resolver resolver,
                                                     Filter<IMapping> filter) {

        return buildFilteringMappingCollector(
            expression,
            resolver,
            filter,
            ExpressionTools.EMPTY_STRING
        );
    }

    protected Filter<IMapping> buildMappingFilter(AbstractPathExpression expression,
                                                  Filter<IMapping> filter) {

        // Wrap the filter with another Filter that will make sure only the
        // mappings with the right type will be accepted, for instance, AVG(e.|
        // can only accept state fields with a numeric type
        IType type = getAcceptableType(expression.getParent());

        // No need to filter
        if (type == null) {
            return filter;
        }

        // This will filter the property mappings
        return new AndFilter<IMapping>(new MappingTypeFilter(type, this), filter);
    }

    protected Filter<IMapping> buildMappingFilter(Expression expression) {
        MappingFilterBuilder visitor = getMappingFilterBuilder();
        try {
            expression.accept(visitor);
            return visitor.filter;
        }
        finally {
            visitor.dispose();
        }
    }

    protected MappingFilterBuilder buildMappingFilterBuilder() {
        return new MappingFilterBuilder(this);
    }

    protected Filter<Expression> buildNonCollectionCompoundTypeFilter() {
        return new Filter<Expression>() {
            public boolean accept(Expression expression) {
                IType type = queryContext.getType(expression);
                TypeHelper typeHelper = queryContext.getTypeHelper();
                return !typeHelper.isCollectionType(type) &&
                       !typeHelper.isMapType(type);
            }
        };
    }

    protected NotExpressionVisitor buildNotExpressionVisitor() {
        return new NotExpressionVisitor();
    }

    protected OrderByClauseCollectionHelper buildOrderByClauseCollectionHelper() {
        return new OrderByClauseCollectionHelper(this);
    }

    protected OrderByClauseStatementHelper buildOrderByClauseStatementHelper() {
        return new OrderByClauseStatementHelper(this);
    }

    protected PropertyMappingFilter buildPropertyMappingFilter() {
        return new PropertyMappingFilter();
    }

    /**
     * Prepares this visitor by prepopulating it with the necessary data that is required to properly
     * gather the list of proposals based on the given caret position.
     *
     * @param position The position of the cursor within the JPQL query
     * @return The proposals that are valid choices for the given position
     */
    public ContentAssistProposals buildProposals(int position) {
        return buildProposals(position, ContentAssistExtension.NULL_HELPER);
    }

    /**
     * Prepares this visitor by prepopulating it with the necessary data that is required to properly
     * gather the list of proposals based on the given caret position.
     *
     * @param position The position of the cursor within the JPQL query
     * @param extension This extension can be used to provide additional support to JPQL content assist
     * that is outside the scope of providing proposals related to JPA metadata. It adds support for
     * providing suggestions related to class names, enum constants, table names, column names
     * @return The proposals that are valid choices for the given position
     */
    public ContentAssistProposals buildProposals(int position, ContentAssistExtension extension) {

        try {

            JPQLExpression jpqlExpression = queryContext.getJPQLExpression();
            String jpqlQuery = queryContext.getJPQLQuery();

            // Calculate the position of the cursor within the parsed tree
            queryPosition = jpqlExpression.buildPosition(jpqlQuery, position);

            // Retrieve the word from the JPQL query (which is the text before the position of the cursor)
            wordParser = new WordParser(jpqlQuery);
            wordParser.setPosition(position);
            word = wordParser.partialWord();

            // Now visit the deepest leaf first and calculate the possible proposals
            proposals = new DefaultContentAssistProposals(queryContext.getGrammar(), extension);
            queryPosition.getExpression().accept(this);

            return proposals;
        }
        finally {
            dispose();
        }
    }

    protected RangeVariableDeclarationVisitor buildRangeVariableDeclarationVisitor() {
        return new RangeVariableDeclarationVisitor();
    }

    protected ResultVariableVisitor buildResultVariableVisitor() {
        return new ResultVariableVisitor();
    }

    protected SelectClauseCollectionHelper buildSelectClauseCollectionHelper() {
        return new SelectClauseCollectionHelper(this);
    }

    protected SelectClauseStatementHelper buildSelectClauseStatementHelper() {
        return new SelectClauseStatementHelper(this);
    }

    protected SimpleFromClauseStatementHelper buildSimpleFromClauseStatementHelper() {
        return new SimpleFromClauseStatementHelper(this);
    }

    protected SimpleGroupByClauseStatementHelper buildSimpleGroupByClauseStatementHelper() {
        return new SimpleGroupByClauseStatementHelper(this);
    }

    protected SimpleHavingClauseStatementHelper buildSimpleHavingClauseStatementHelper() {
        return new SimpleHavingClauseStatementHelper(this);
    }

    protected SimpleSelectClauseCollectionHelper buildSimpleSelectClauseCollectionHelper() {
        return new SimpleSelectClauseCollectionHelper(this);
    }

    protected SimpleSelectClauseStatementHelper buildSimpleSelectClauseStatementHelper() {
        return new SimpleSelectClauseStatementHelper(this);
    }

    protected SimpleWhereClauseSelectStatementHelper buildSimpleWhereClauseSelectStatementHelper() {
        return new SimpleWhereClauseSelectStatementHelper(this);
    }

    protected SubqueryAppendableExpressionVisitor buildSubqueryAppendableExpressionVisitor() {
        return new SubqueryAppendableExpressionVisitor();
    }

    protected SubqueryVisitor buildSubqueryVisitor() {
        return new SubqueryVisitor();
    }

    protected TripleEncapsulatedCollectionHelper buildTripleEncapsulatedCollectionHelper() {
        return new TripleEncapsulatedCollectionHelper(this);
    }

    protected UpdateClauseStatementHelper buildUpdateClauseStatementHelper() {
        return new UpdateClauseStatementHelper(this);
    }

    protected UpdateItemCollectionHelper buildUpdateItemCollectionHelper() {
        return new UpdateItemCollectionHelper(this);
    }

    protected VisitParentVisitor buildVisitParentVisitor() {
        return new VisitParentVisitor(this);
    }

    protected WhenClauseConditionalClauseCollectionHelper buildWhenClauseConditionalClauseCollectionHelper() {
        return new WhenClauseConditionalClauseCollectionHelper(this);
    }

    protected WhereClauseDeleteStatementHelper buildWhereClauseDeleteStatementHelper() {
        return new WhereClauseDeleteStatementHelper(this);
    }

    protected WhereClauseSelectStatementHelper buildWhereClauseSelectStatementHelper() {
        return new WhereClauseSelectStatementHelper(this);
    }

    protected WhereClauseUpdateStatementHelper buildWhereClauseUpdateStatementHelper() {
        return new WhereClauseUpdateStatementHelper(this);
    }

    protected WithinInvalidExpressionVisitor buildWithinInvalidExpressionVisitor() {
        return new WithinInvalidExpressionVisitor();
    }

    /**
     * Disposes of the internal data.
     */
    public void dispose() {

        word          = null;
        proposals     = null;
        wordParser    = null;
        queryPosition = null;

        // Not required but during debugging, this is important to be reset
        corrections.setSize(1);
        virtualSpaces.setSize(1);
        lockedExpressions.clear();
    }

    /**
     * Retrieves the {@link RangeVariableDeclaration} that defines the entity name and identification
     * variable for the given {@link UpdateClause}.
     *
     * @return Either the {@link RangeVariableDeclaration} if the JPQL query defines it or <code>null</code>
     * if it was not defined
     */
    protected RangeVariableDeclaration findRangeVariableDeclaration(UpdateClause expression) {
        RangeVariableDeclarationVisitor visitor = getRangeVariableDeclarationVisitor();
        try {
            expression.getRangeVariableDeclaration().accept(visitor);
            return visitor.expression;
        }
        finally {
            visitor.dispose();
        }
    }

    /**
     * Determines the root {@link IType} that any type should be assignable. If the {@link IType} is
     * {@link Number}, than any subclasses will be allowed.
     *
     * @param expression The {@link Expression} to visit, including its parent hierarchy until an
     * {@link Expression} requires a certain {@link IType}
     * @return The root {@link IType} allowed or <code>null</code> if anything is allowed
     */
    protected IType getAcceptableType(Expression expression) {
        AcceptableTypeVisitor visitor = getExpressionTypeVisitor();
        try {
            expression.accept(visitor);
            return visitor.type;
        }
        finally {
            visitor.dispose();
        }
    }

    protected AppendableExpressionVisitor getAppendableExpressionVisitor() {
        AppendableExpressionVisitor helper = getHelper(AppendableExpressionVisitor.class);
        if (helper == null) {
            helper = buildAppendableExpressionVisitor();
            registerHelper(AppendableExpressionVisitor.class, helper);
        }
        return helper;
    }

    /**
     * Casts the given {@link Expression} to a {@link CollectionExpression} if it is actually an
     * object of that type.
     *
     * @param expression The {@link Expression} to cast
     * @return The given {@link Expression} if it is a {@link CollectionExpression} or <code>null</code>
     * if it is any other object
     */
    protected CollectionExpression getCollectionExpression(Expression expression) {
        CollectionExpressionVisitor visitor = getCollectionExpressionVisitor();
        try {
            expression.accept(visitor);
            return visitor.expression;
        }
        finally {
            visitor.dispose();
        }
    }

    /**
     * Returns the visitor that collects the {@link CollectionExpression} if it's been visited.
     *
     * @return The {@link CollectionExpressionVisitor}
     * @see #buildCollectionExpressionVisitor()
     */
    protected CollectionExpressionVisitor getCollectionExpressionVisitor() {
        CollectionExpressionVisitor visitor = getHelper(CollectionExpressionVisitor.class);
        if (visitor == null) {
            visitor = buildCollectionExpressionVisitor();
            registerHelper(CollectionExpressionVisitor.class, visitor);
        }
        return visitor;
    }

    protected ConcatExpressionCollectionHelper getConcatExpressionCollectionHelper() {
        ConcatExpressionCollectionHelper helper = getHelper(ConcatExpressionCollectionHelper.class);
        if (helper == null) {
            helper = buildConcatExpressionCollectionHelper();
            registerHelper(ConcatExpressionCollectionHelper.class, helper);
        }
        return helper;
    }

    protected ConditionalClauseCollectionHelper getConditionalClauseCollectionHelper() {
        ConditionalClauseCollectionHelper helper = getHelper(ConditionalClauseCollectionHelper.class);
        if (helper == null) {
            helper = buildConditionalClauseCollectionHelper();
            registerHelper(ConditionalClauseCollectionHelper.class, helper);
        }
        return helper;
    }

    protected ConstrutorCollectionHelper getConstructorCollectionHelper() {
        ConstrutorCollectionHelper helper = getHelper(ConstrutorCollectionHelper.class);
        if (helper == null) {
            helper = buildConstrutorCollectionHelper();
            registerHelper(ConstrutorCollectionHelper.class, helper);
        }
        return helper;
    }

    protected DeclarationVisitor getDeclarationVisitor() {
        DeclarationVisitor helper = getHelper(DeclarationVisitor.class);
        if (helper == null) {
            helper = buildDeclarationVisitor();
            registerHelper(DeclarationVisitor.class, helper);
        }
        return helper;
    }

    protected MappingCollector getDefaultMappingCollector() {
        DefaultMappingCollector helper = getHelper(DefaultMappingCollector.class);
        if (helper == null) {
            helper = buildDefaultMappingCollector();
            registerHelper(DefaultMappingCollector.class, helper);
        }
        return helper;
    }

    protected DeleteClauseCollectionHelper getDeleteClauseCollectionHelper() {
        DeleteClauseCollectionHelper helper = getHelper(DeleteClauseCollectionHelper.class);
        if (helper == null) {
            helper = buildDeleteClauseCollectionHelper();
            registerHelper(DeleteClauseCollectionHelper.class, helper);
        }
        return helper;
    }

    protected DeleteClauseStatementHelper getDeleteClauseStatementHelper() {
        DeleteClauseStatementHelper helper = getHelper(DeleteClauseStatementHelper.class);
        if (helper == null) {
            helper = buildDeleteClauseStatementHelper();
            registerHelper(DeleteClauseStatementHelper.class, helper);
        }
        return helper;
    }

    protected DoubleEncapsulatedCollectionHelper getDoubleEncapsulatedCollectionHelper() {
        DoubleEncapsulatedCollectionHelper helper = getHelper(DoubleEncapsulatedCollectionHelper.class);
        if (helper == null) {
            helper = buildDoubleEncapsulatedCollectionHelper();
            registerHelper(DoubleEncapsulatedCollectionHelper.class, helper);
        }
        return helper;
    }

    protected EncapsulatedExpressionVisitor getEncapsulatedExpressionVisitor() {
        EncapsulatedExpressionVisitor helper = getHelper(EncapsulatedExpressionVisitor.class);
        if (helper == null) {
            helper = buildEncapsulatedExpressionVisitor();
            registerHelper(EncapsulatedExpressionVisitor.class, helper);
        }
        return helper;
    }

    protected EndingQueryPositionBuilder getEndingQueryPositionBuilder() {
        EndingQueryPositionBuilder visitor = getHelper(EndingQueryPositionBuilder.class);
        if (visitor == null) {
            visitor = buildEndingQueryPositionBuilder();
            registerHelper(EndingQueryPositionBuilder.class, visitor);
        }
        return visitor;
    }

    protected EnumVisitor getEnumVisitor() {
        EnumVisitor helper = getHelper(EnumVisitor.class);
        if (helper == null) {
            helper = buildEnumVisitor();
            helpers.put(EnumVisitor.class, helper);
        }
        return helper;
    }

    /**
     * Retrieves the registered {@link ExpressionFactory} that was registered for the given unique identifier.
     *
     * @param expressionFactoryId The unique identifier of the {@link ExpressionFactory} to retrieve
     * @return The {@link ExpressionFactory} mapped with the given unique identifier
     */
    protected ExpressionFactory getExpressionFactory(String expressionFactoryId) {
        return queryContext.getExpressionRegistry().getExpressionFactory(expressionFactoryId);
    }

    protected AcceptableTypeVisitor getExpressionTypeVisitor() {
        AcceptableTypeVisitor helper = getHelper(AcceptableTypeVisitor.class);
        if (helper == null) {
            helper = buildAcceptableTypeVisitor();
            registerHelper(AcceptableTypeVisitor.class, helper);
        }
        return helper;
    }

    protected Filter<Expression> getFilter(String identifier) {
        Filter<Expression> filter = identifierFilters.get(identifier);
        return (filter != null) ? filter : INVALID_IDENTIFIER_FILTER;
    }

    protected FollowingClausesVisitor getFollowingClausesVisitor() {
        FollowingClausesVisitor helper = getHelper(FollowingClausesVisitor.class);
        if (helper == null) {
            helper = buildFollowingClausesVisitor();
            helpers.put(FollowingClausesVisitor.class, helper);
        }
        return helper;
    }

    protected FollowingInvalidExpressionVisitor getFollowingInvalidExpressionVisitor() {
        FollowingInvalidExpressionVisitor helper = getHelper(FollowingInvalidExpressionVisitor.class);
        if (helper == null) {
            helper = buildFollowingInvalidExpressionVisitor();
            registerHelper(FollowingInvalidExpressionVisitor.class, helper);
        }
        return helper;
    }

    protected FromClauseCollectionHelper getFromClauseCollectionHelper() {
        FromClauseCollectionHelper helper = getHelper(FromClauseCollectionHelper.class);
        if (helper == null) {
            helper = buildFromClauseCollectionHelper();
            registerHelper(FromClauseCollectionHelper.class, helper);
        }
        return helper;
    }

    protected FromClauseStatementHelper getFromClauseStatementHelper() {
        FromClauseStatementHelper helper = getHelper(FromClauseStatementHelper.class);
        if (helper == null) {
            helper = buildFromClauseStatementHelper();
            registerHelper(FromClauseStatementHelper.class, helper);
        }
        return helper;
    }

    protected GroupByClauseCollectionHelper getGroupByClauseCollectionHelper() {
        GroupByClauseCollectionHelper helper = getHelper(GroupByClauseCollectionHelper.class);
        if (helper == null) {
            helper = buildGroupByClauseCollectionHelper();
            registerHelper(GroupByClauseCollectionHelper.class, helper);
        }
        return helper;
    }

    protected GroupByClauseStatementHelper getGroupByClauseStatementHelper() {
        GroupByClauseStatementHelper helper = getHelper(GroupByClauseStatementHelper.class);
        if (helper == null) {
            helper = buildGroupByClauseStatementHelper();
            registerHelper(GroupByClauseStatementHelper.class, helper);
        }
        return helper;
    }

    protected HavingClauseStatementHelper getHavingClauseStatementHelper() {
        HavingClauseStatementHelper helper = getHelper(HavingClauseStatementHelper.class);
        if (helper == null) {
            helper = buildHavingClauseStatementHelper();
            registerHelper(HavingClauseStatementHelper.class, helper);
        }
        return helper;
    }

    /**
     * Retrieves the helper associated with the given helper class.
     *
     * @param helperClass The Java class of the helper to retrieve
     * @return The helper being requested
     */
    @SuppressWarnings("unchecked")
    protected final <T> T getHelper(Class<T> helperClass) {
        return (T) helpers.get(helperClass);
    }

    /**
     * Retrieves the role of the given identifier. A role helps to describe the purpose of the
     * identifier in a query.
     *
     * @param identifier The identifier for which its role is requested
     * @return The role of the given identifier
     */
    protected IdentifierRole getIdentifierRole(String identifier) {
        return queryContext.getExpressionRegistry().getIdentifierRole(identifier);
    }

    protected IncompleteCollectionExpressionVisitor getIncompleteCollectionExpressionVisitor() {
        IncompleteCollectionExpressionVisitor helper = getHelper(IncompleteCollectionExpressionVisitor.class);
        if (helper == null) {
            helper = buildIncompleteCollectionExpressionVisitor();
            registerHelper(IncompleteCollectionExpressionVisitor.class, helper);
        }
        return helper;
    }

    protected InvalidExpressionVisitor getInvalidExpressionVisitor() {
        InvalidExpressionVisitor visitor = getHelper(InvalidExpressionVisitor.class);
        if (visitor == null) {
            visitor = buildInvalidExpressionVisitor();
            registerHelper(InvalidExpressionVisitor.class, visitor);
        }
        return visitor;
    }

    protected JoinCollectionHelper getJoinCollectionHelper() {
        JoinCollectionHelper helper = getHelper(JoinCollectionHelper.class);
        if (helper == null) {
            helper = buildJoinCollectionHelper();
            registerHelper(JoinCollectionHelper.class, helper);
        }
        return helper;
    }

    /**
     * Returns the latest supported JPQL grammar, which is required when checking the validity of a
     * JPQL identifier. This is due to the fact that any JPQL identifiers defined in a more recent
     * spec will be seen to be registered in version 1.0 of the spec. By returning the latest version,
     * any JPQL identifiers should have a version registered.
     *
     * @return The latest {@link JPQLGrammar} supported by the provider
     */
    protected abstract JPQLGrammar getLatestGrammar();

    protected Filter<IMapping> getMappingCollectionFilter() {
        CollectionMappingFilter helper = getHelper(CollectionMappingFilter.class);
        if (helper == null) {
            helper = buildCollectionMappingFilter();
            registerHelper(CollectionMappingFilter.class, helper);
        }
        return helper;
    }

    protected MappingFilterBuilder getMappingFilterBuilder() {
        MappingFilterBuilder helper = getHelper(MappingFilterBuilder.class);
        if (helper == null) {
            helper = buildMappingFilterBuilder();
            helpers.put(MappingFilterBuilder.class, helper);
        }
        return helper;
    }

    protected Filter<IMapping> getMappingPropertyFilter() {
        PropertyMappingFilter helper = getHelper(PropertyMappingFilter.class);
        if (helper == null) {
            helper = buildPropertyMappingFilter();
            helpers.put(PropertyMappingFilter.class, helper);
        }
        return helper;
    }

    protected NotExpressionVisitor getNotExpressionVisitor() {
        NotExpressionVisitor helper = getHelper(NotExpressionVisitor.class);
        if (helper == null) {
            helper = buildNotExpressionVisitor();
            registerHelper(NotExpressionVisitor.class, helper);
        }
        return helper;
    }

    protected OrderByClauseCollectionHelper getOrderByClauseCollectionHelper() {
        OrderByClauseCollectionHelper helper = getHelper(OrderByClauseCollectionHelper.class);
        if (helper == null) {
            helper = buildOrderByClauseCollectionHelper();
            registerHelper(OrderByClauseCollectionHelper.class, helper);
        }
        return helper;
    }

    protected OrderByClauseStatementHelper getOrderByClauseStatementHelper() {
        OrderByClauseStatementHelper helper = getHelper(OrderByClauseStatementHelper.class);
        if (helper == null) {
            helper = buildOrderByClauseStatementHelper();
            registerHelper(OrderByClauseStatementHelper.class, helper);
        }
        return helper;
    }

    /**
     * Retrieves the {@link JPQLQueryBNF} 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 queryContext.getExpressionRegistry().getQueryBNF(queryBNFId);
    }

    protected RangeVariableDeclarationVisitor getRangeVariableDeclarationVisitor() {
        RangeVariableDeclarationVisitor helper = getHelper(RangeVariableDeclarationVisitor.class);
        if (helper == null) {
            helper = buildRangeVariableDeclarationVisitor();
            registerHelper(RangeVariableDeclarationVisitor.class, helper);
        }
        return helper;
    }

    protected ResultVariableVisitor getResultVariableVisitor() {
        ResultVariableVisitor helper = getHelper(ResultVariableVisitor.class);
        if (helper == null) {
            helper = buildResultVariableVisitor();
            registerHelper(ResultVariableVisitor.class, helper);
        }
        return helper;
    }

    protected SelectClauseCollectionHelper getSelectClauseCollectionHelper() {
        SelectClauseCollectionHelper helper = getHelper(SelectClauseCollectionHelper.class);
        if (helper == null) {
            helper = buildSelectClauseCollectionHelper();
            registerHelper(SelectClauseCollectionHelper.class, helper);
        }
        return helper;
    }

    protected SelectClauseStatementHelper getSelectClauseStatementHelper() {
        SelectClauseStatementHelper helper = getHelper(SelectClauseStatementHelper.class);
        if (helper == null) {
            helper = buildSelectClauseStatementHelper();
            registerHelper(SelectClauseStatementHelper.class, helper);
        }
        return helper;
    }

    protected SimpleFromClauseStatementHelper getSimpleFromClauseStatementHelper() {
        SimpleFromClauseStatementHelper helper = getHelper(SimpleFromClauseStatementHelper.class);
        if (helper == null) {
            helper = buildSimpleFromClauseStatementHelper();
            registerHelper(SimpleFromClauseStatementHelper.class, helper);
        }
        return helper;
    }

    protected SimpleGroupByClauseStatementHelper getSimpleGroupByClauseStatementHelper() {
        SimpleGroupByClauseStatementHelper helper = getHelper(SimpleGroupByClauseStatementHelper.class);
        if (helper == null) {
            helper = buildSimpleGroupByClauseStatementHelper();
            registerHelper(SimpleGroupByClauseStatementHelper.class, helper);
        }
        return helper;
    }

    protected SimpleHavingClauseStatementHelper getSimpleHavingClauseStatementHelper() {
        SimpleHavingClauseStatementHelper helper = getHelper(SimpleHavingClauseStatementHelper.class);
        if (helper == null) {
            helper = buildSimpleHavingClauseStatementHelper();
            registerHelper(SimpleHavingClauseStatementHelper.class, helper);
        }
        return helper;
    }

    protected SimpleSelectClauseCollectionHelper getSimpleSelectClauseCollectionHelper() {
        SimpleSelectClauseCollectionHelper helper = getHelper(SimpleSelectClauseCollectionHelper.class);
        if (helper == null) {
            helper = buildSimpleSelectClauseCollectionHelper();
            registerHelper(SimpleSelectClauseCollectionHelper.class, helper);
        }
        return helper;
    }

    protected SimpleSelectClauseStatementHelper getSimpleSelectClauseStatementHelper() {
        SimpleSelectClauseStatementHelper helper = getHelper(SimpleSelectClauseStatementHelper.class);
        if (helper == null) {
            helper = buildSimpleSelectClauseStatementHelper();
            registerHelper(SimpleSelectClauseStatementHelper.class, helper);
        }
        return helper;
    }

    protected SimpleWhereClauseSelectStatementHelper getSimpleWhereClauseSelectStatementHelper() {
        SimpleWhereClauseSelectStatementHelper helper = getHelper(SimpleWhereClauseSelectStatementHelper.class);
        if (helper == null) {
            helper = buildSimpleWhereClauseSelectStatementHelper();
            registerHelper(SimpleWhereClauseSelectStatementHelper.class, helper);
        }
        return helper;
    }

    protected SubqueryAppendableExpressionVisitor getSubqueryAppendableExpressionVisitor() {
        SubqueryAppendableExpressionVisitor helper = getHelper(SubqueryAppendableExpressionVisitor.class);
        if (helper == null) {
            helper = buildSubqueryAppendableExpressionVisitor();
            registerHelper(SubqueryAppendableExpressionVisitor.class, helper);
        }
        return helper;
    }

    protected SubqueryVisitor getSubqueryVisitor() {
        SubqueryVisitor helper = getHelper(SubqueryVisitor.class);
        if (helper == null) {
            helper = buildSubqueryVisitor();
            registerHelper(SubqueryVisitor.class, helper);
        }
        return helper;
    }

    protected TripleEncapsulatedCollectionHelper getTripleEncapsulatedCollectionHelper() {
        TripleEncapsulatedCollectionHelper helper = getHelper(TripleEncapsulatedCollectionHelper.class);
        if (helper == null) {
            helper = buildTripleEncapsulatedCollectionHelper();
            registerHelper(TripleEncapsulatedCollectionHelper.class, helper);
        }
        return helper;
    }

    protected UpdateClauseStatementHelper getUpdateClauseStatementHelper() {
        UpdateClauseStatementHelper helper = getHelper(UpdateClauseStatementHelper.class);
        if (helper == null) {
            helper = buildUpdateClauseStatementHelper();
            registerHelper(UpdateClauseStatementHelper.class, helper);
        }
        return helper;
    }

    protected UpdateItemCollectionHelper getUpdateItemCollectionHelper() {
        UpdateItemCollectionHelper helper = getHelper(UpdateItemCollectionHelper.class);
        if (helper == null) {
            helper = buildUpdateItemCollectionHelper();
            registerHelper(UpdateItemCollectionHelper.class, helper);
        }
        return helper;
    }

    protected VisitParentVisitor getVisitParentVisitor() {
        VisitParentVisitor helper = getHelper(VisitParentVisitor.class);
        if (helper == null) {
            helper = buildVisitParentVisitor();
            registerHelper(VisitParentVisitor.class, helper);
        }
        return helper;
    }

    protected WhenClauseConditionalClauseCollectionHelper getWhenClauseConditionalClauseCollectionHelper() {
        WhenClauseConditionalClauseCollectionHelper helper = getHelper(WhenClauseConditionalClauseCollectionHelper.class);
        if (helper == null) {
            helper = buildWhenClauseConditionalClauseCollectionHelper();
            registerHelper(WhenClauseConditionalClauseCollectionHelper.class, helper);
        }
        return helper;
    }

    protected WhereClauseDeleteStatementHelper getWhereClauseDeleteStatementHelper() {
        WhereClauseDeleteStatementHelper helper = getHelper(WhereClauseDeleteStatementHelper.class);
        if (helper == null) {
            helper = buildWhereClauseDeleteStatementHelper();
            registerHelper(WhereClauseDeleteStatementHelper.class, helper);
        }
        return helper;
    }

    protected WhereClauseSelectStatementHelper getWhereClauseSelectStatementHelper() {
        WhereClauseSelectStatementHelper helper = getHelper(WhereClauseSelectStatementHelper.class);
        if (helper == null) {
            helper = buildWhereClauseSelectStatementHelper();
            registerHelper(WhereClauseSelectStatementHelper.class, helper);
        }
        return helper;
    }

    protected WhereClauseUpdateStatementHelper getWhereClauseUpdateStatementHelper() {
        WhereClauseUpdateStatementHelper helper = getHelper(WhereClauseUpdateStatementHelper.class);
        if (helper == null) {
            helper = buildWhereClauseUpdateStatementHelper();
            registerHelper(WhereClauseUpdateStatementHelper.class, helper);
        }
        return helper;
    }

    protected WithinInvalidExpressionVisitor getWithinInvalidExpressionVisitor() {
        WithinInvalidExpressionVisitor helper = getHelper(WithinInvalidExpressionVisitor.class);
        if (helper == null) {
            helper = buildWithinInvalidExpressionVisitor();
            registerHelper(WithinInvalidExpressionVisitor.class, helper);
        }
        return helper;
    }

    protected boolean hasClausesDefinedBetween(Expression expression,
                                               String afterIdentifier,
                                               String beforeIdentifier) {

        FollowingClausesVisitor visitor = getFollowingClausesVisitor();

        try {
            visitor.afterIdentifier  = afterIdentifier;
            visitor.beforeIdentifier = beforeIdentifier;

            expression.accept(visitor);

            return visitor.hasFollowUpClauses;
        }
        finally {
            visitor.dispose();
        }
    }

    /**
     * Determines whether there is a virtual space to be considered or not.
     *
     * @return <code>true</code> if there is a virtual space to count as a real one or not
     */
    protected final boolean hasVirtualSpace() {
        return virtualSpaces.peek() > 0;
    }

    /**
     * Initializes this visitor.
     */
    protected void initialize() {

        helpers = new HashMap<Class<?>, Object>();
        lockedExpressions = new Stack<Expression>();

        virtualSpaces = new Stack<Integer>();
        virtualSpaces.add(0);

        corrections = new Stack<Integer>();
        corrections.add(0);

        identifierFilters = new HashMap<String, Filter<Expression>>();
        identifierFilters.put(DIFFERENT,             VALID_IDENTIFIER_FILTER);
        identifierFilters.put(EQUAL,                 VALID_IDENTIFIER_FILTER);
        identifierFilters.put(MEMBER,                VALID_IDENTIFIER_FILTER);
        identifierFilters.put(MEMBER_OF,             VALID_IDENTIFIER_FILTER);
        identifierFilters.put(NOT_MEMBER,            VALID_IDENTIFIER_FILTER);
        identifierFilters.put(NOT_MEMBER_OF,         VALID_IDENTIFIER_FILTER);
        identifierFilters.put(IS_NULL,               VALID_IDENTIFIER_FILTER);
        identifierFilters.put(IS_NOT_NULL,           VALID_IDENTIFIER_FILTER);
        identifierFilters.put(IN,                    VALID_IDENTIFIER_FILTER);
        identifierFilters.put(NOT_IN,                VALID_IDENTIFIER_FILTER);

        // 'EMPTY', 'NOT EMPTY'
        Filter<Expression> filter = buildCollectionCompoundTypeFilter();
        identifierFilters.put(IS_EMPTY,     filter);
        identifierFilters.put(IS_NOT_EMPTY, filter);

        // 'BETWEEN', 'NOT BETWEEN'
        filter = buildNonCollectionCompoundTypeFilter();
        identifierFilters.put(BETWEEN,     filter);
        identifierFilters.put(NOT_BETWEEN, filter);
        identifierFilters.put(LIKE,        filter);
        identifierFilters.put(NOT_LIKE,    filter);

        // '<', '<=', '>=', '>'
        filter = buildDifferentComparisonFilter();
        identifierFilters.put(GREATER_THAN,          filter);
        identifierFilters.put(GREATER_THAN_OR_EQUAL, filter);
        identifierFilters.put(LOWER_THAN,            filter);
        identifierFilters.put(LOWER_THAN_OR_EQUAL,   filter);
    }

    /**
     * Determines whether the given JPQL identifier used in an aggregate expression; for instance
     * <b>AND</b>.
     *
     * @param identifier The identifier to validate
     * @return <code>true</code> if the given identifier is used in an aggregate expression;
     * <code>false</code> otherwise
     */
    protected boolean isAggregate(String identifier) {
        return getIdentifierRole(identifier) == IdentifierRole.AGGREGATE;
    }

    /**
     * Determines whether a certain type of JPQL identifiers can be appended to the JPQL query based
     * on the given {@link Expression}.
     *
     * @param expression The {@link Expression} that determines what can be appended
     * @param appendableType The type of identifiers to append to the JPQL query
     * @return <code>true</code> if the JPQL identifiers with the given type can be appended to the
     * JPQL query; <code>false</code> if they cannot
     */
    protected boolean isAppendable(Expression expression, AppendableType appendableType) {
        AppendableExpressionVisitor visitor = getAppendableExpressionVisitor();
        try {
            visitor.appendableType = appendableType;
            expression.accept(visitor);
            return visitor.isAppendable();
        }
        finally {
            visitor.dispose();
        }
    }

    /**
     * Determines whether
     *
     * @param expression
     * @return
     */
    protected boolean isAppendableToCollection(Expression expression) {
        IncompleteCollectionExpressionVisitor visitor = getIncompleteCollectionExpressionVisitor();
        try {
            expression.accept(visitor);
            return visitor.isComplete();
        }
        finally {
            visitor.dispose();
        }
    }

    /**
     * Determines whether the given JPQL identifier used in a clause; for instance <b>SELECT</b>.
     *
     * @param identifier The identifier to validate
     * @return <code>true</code> if the given identifier is a clause; <code>false</code> otherwise
     */
    protected boolean isClause(String identifier) {
        return getIdentifierRole(identifier) == IdentifierRole.CLAUSE;
    }

    /**
     * Determines whether the identifiers identifying clauses can be appended to the JPQL query based
     * on the given {@link Expression}.
     *
     * @param expression The {@link Expression} that determines what can be appended
     * @return <code>true</code> if the following clauses can be appended to the JPQL query;
     * <code>false</code> if they cannot
     */
    protected boolean isClauseAppendable(Expression expression) {
        return isAppendable(expression, AppendableType.CLAUSE);
    }

    /**
     * Determines whether the given {@link Expression} is grammatically complete.
     *
     * @param expression The {@link Expression} to verify its grammar integrity
     * @return <code>true</code> if the {@link Expression} is grammatically complete;
     * <code>false</code> otherwise
     */
    protected boolean isComplete(Expression expression) {
        return isAppendable(expression, AppendableType.COMPLETE);
    }

    /**
     * Determines whether the given {@link Expression} can be used as the left side of a compound
     * expression.
     *
     * @param expression The {@link Expression} that determines if the JPQL identifiers with
     * {@link IdentifierRole#COMPOUND_FUNCTION} can be used to create a compound expression
     * @return <code>true</code> if the compound identifiers can be appended to the JPQL query;
     * <code>false</code> if they cannot
     */
    protected boolean isCompoundable(Expression expression) {
        return isAppendable(expression, AppendableType.COMPOUNDABLE);
    }

    /**
     * Determines whether the given JPQL identifier used in a compound expression; an example would
     * be <b>BETWEEN</b> or <b>MEMBER</b>.
     *
     * @param identifier The identifier to validate
     * @return <code>true</code> if the given identifier is used in a compound expression;
     * <code>false</code> otherwise
     */
    protected boolean isCompoundFunction(String identifier) {

        // Only the full JPQL identifier is valid
        if (identifier == IS || identifier == OF) {
            return false;
        }

        return getIdentifierRole(identifier) == IdentifierRole.COMPOUND_FUNCTION;
    }

    /**
     * Determines whether the given {@link AbstractPathExpression} is found within a declaration expression.
     *
     * @param expression The {@link AbstractPathExpression} to visit
     * @return <code>true</code> if the visited {@link CollectionValuedPathExpression} is owned by
     * a {@link RangeVariableDeclaration}, which indicates it is used to define the "root" object;
     * <code>false</code> if it is not
     */
    protected boolean isDeclaration(AbstractPathExpression expression) {
        DeclarationVisitor visitor = getDeclarationVisitor();
        try {
            expression.accept(visitor);
            return visitor.isDeclaration();
        }
        finally {
            visitor.dispose();
        }
    }

    /**
     * Determines whether the given {@link Expression} is being encapsulated or not.
     *
     * @param expression The {@link Expression} to scan for encapsulation
     * @return <code>true</code> if the given {@link Expression} is within parenthesis;
     * <code>false</code> otherwise
     */
    protected boolean isEncapsulated(Expression expression) {
        EncapsulatedExpressionVisitor visitor = getEncapsulatedExpressionVisitor();
        try {
            expression.accept(visitor);
            return visitor.isEncapsulated();
        }
        finally {
            visitor.dispose();
        }
    }

    /**
     * Determines whether the given {@link AbstractPathExpression} could potentially represent a
     * fully qualified enum constant, which is dictated by the location of the path expression within
     * the query. Only a few location allows an enum constant.
     *
     * @param expression The {@link AbstractPathExpression} to visit
     * @return <code>true</code> if the path expression represents a enum constant;
     * <code>false</code> otherwise
     */
    protected boolean isEnumAllowed(AbstractPathExpression expression) {
        EnumVisitor visitor = getEnumVisitor();
        try {
            visitor.pathExpression = expression;
            expression.accept(visitor);
            return visitor.isValid();
        }
        finally {
            visitor.dispose();
        }
    }

    /**
     * Determines whether the given {@link Expression} is preceded by an invalid expression.
     *
     * @param expression The {@link Expression} to visit
     * @return <code>true</code> if the visited {@link Expression} is part of a collection of
     * expressions and an invalid expression precede it; <code>false</code> otherwise
     */
    protected boolean isFollowingInvalidExpression(Expression expression) {
        FollowingInvalidExpressionVisitor visitor = getFollowingInvalidExpressionVisitor();
        try {
            visitor.expression = expression;
            expression.accept(visitor);
            return visitor.isFollowingInvalidExpression();
        }
        finally {
            visitor.dispose();
        }
    }

    /**
     * Determines whether the given JPQL identifier is a function, an example would be <b>AVG</b>.
     *
     * @param identifier The identifier to validate
     * @return <code>true</code> if the given identifier is a function; <code>false</code> otherwise
     */
    protected boolean isFunction(String identifier) {
        return getIdentifierRole(identifier) == IdentifierRole.FUNCTION;
    }

    /**
     * Determines whether the given {@link Expression} is in a subquery or in the top-level query.
     *
     * @param expression The {@link Expression} to visit its parent hierarchy
     * @return <code>true</code> if the owning query is a subquery; <code>false</code> if it's the
     * top-level query
     */
    protected boolean isInSubquery(Expression expression) {
        SubqueryVisitor visitor = getSubqueryVisitor();
        try {
            expression.accept(visitor);
            return visitor.isInSubquery();
        }
        finally {
            visitor.dispose();
        }
    }

    /**
     * Determines whether the given {@link Expression} represents an invalid fragment.
     *
     * @param expression The {@link Expression} to visit
     * @return <code>true</code> if the {@link Expression} is an invalid fragment;
     * <code>false</code> otherwise
     */
    protected boolean isInvalidExpression(Expression expression) {
        InvalidExpressionVisitor visitor = getInvalidExpressionVisitor();
        try {
            expression.accept(visitor);
            return visitor.isInvalid();
        }
        finally {
            visitor.dispose();
        }
    }

    /**
     * Determines whether a <code><b>JOIN FETCH</b></code> expression can be identified by with an
     * identification variable or not.
     *
     * @return <code>true</code> if the expression can have an identification variable;
     * <code>false</code> otherwise
     */
    protected abstract boolean isJoinFetchIdentifiable();

    /**
     * Determines whether the given {@link Expression} has been set has the lock to prevent an
     * infinite recursion.
     *
     * @param expression The {@link Expression} to check if it is locked
     * @return <code>true</code> if the given {@link Expression} has been marked as locked;
     * <code>false</code> otherwise
     */
    protected boolean isLocked(Expression expression) {
        return !lockedExpressions.empty() && (lockedExpressions.peek() == expression);
    }

    /**
     * Determines whether the given {@link Expression} represents the negated expression.
     *
     * @param expression The {@link Expression} to visit
     * @return <code>true</code> if the {@link Expression} is {@link NotExpression};
     * <code>false</code> otherwise
     */
    protected boolean isNotExpression(Expression expression) {
        NotExpressionVisitor visitor = getNotExpressionVisitor();
        try {
            expression.accept(visitor);
            return visitor.isNotExpression();
        }
        finally {
            visitor.dispose();
        }
    }

    /**
     * Determines whether the given position is within the given word.
     * <p>
     * Example: position=0, word="JPQL" {@literal =>} true
     * Example: position=1, word="JPQL" {@literal =>} true
     * Example: position=4, word="JPQL" {@literal =>} true
     * Example: position=5, word="JPQL" {@literal =>} true
     * Example: position=5, offset 2, (actual cursor position is 3), word="JPQL" {@literal =>} true
     *
     * @param position The position of the cursor
     * @param offset The offset to adjust the position
     * @param word The word to check if the cursor is positioned in it
     * @return <code>true</code> if the given position is within the given word;
     * <code>false</code> otherwise
     */
    protected boolean isPositionWithin(int position, int offset, String word) {
        return (position >= offset) && (position - offset <= word.length());
    }

    /**
     * Determines whether the given position is within the given word.
     * <p>
     * Example: position=0, word="JPQL" {@literal =>} true
     * Example: position=1, word="JPQL" {@literal =>} true
     * Example: position=4, word="JPQL" {@literal =>} true
     * Example: position=5, word="JPQL" {@literal =>} true
     *
     * @param position The position of the cursor
     * @param word The word to check if the cursor is positioned in it
     * @return <code>true</code> if the given position is within the given word;
     * <code>false</code> otherwise
     */
    protected boolean isPositionWithin(int position, String word) {
        return isPositionWithin(position, 0, word);
    }

    /**
     * Determines whether the JPQL identifier starting a subquery (<code><b>SELECT</b></code>) can
     * be appended based on the given {@link Expression} which is preceding the position of the cursor.
     *
     * @param expression The {@link Expression} that precedes the position of the cursor
     * @return <code>true</code> if a subquery can be appended; <code>false</code> otherwise
     */
    protected boolean isSubqueryAppendable(Expression expression) {
        AbstractAppendableExpressionVisitor visitor = getSubqueryAppendableExpressionVisitor();
        try {
            expression.accept(visitor);
            return visitor.isAppendable();
        }
        finally {
            visitor.dispose();
        }
    }

    /**
     * Determines whether the given {@link Expression} part is an expression of the given query BNF.
     *
     * @param expression The {@link Expression} to validate based on the query BNF
     * @param queryBNF
     * @return <code>true</code> if the {@link Expression} part is a child of the given query BNF;
     * <code>false</code> otherwise
     */
    protected boolean isValid(Expression expression, JPQLQueryBNF queryBNF) {
        JPQLQueryBNFValidator validator = buildJPQLQueryBNFValidator(queryBNF);
        try {
            expression.accept(validator);
            return validator.isValid();
        }
        finally {
            validator.dispose();
        }
    }

    /**
     * Determines whether the given {@link Expression} part is an expression of the given query BNF.
     *
     * @param expression The {@link Expression} to validate based on the query BNF
     * @param queryBNFId
     * @return <code>true</code> if the {@link Expression} part is a child of the given query BNF;
     * <code>false</code> otherwise
     */
    protected boolean isValid(Expression expression, String queryBNFId) {
        return isValid(expression, getQueryBNF(queryBNFId));
    }

    /**
     * Determines whether the given {@link JPQLQueryBNF} part is the given parent {@link JPQLQueryBNF}.
     *
     * @param parentQueryBNF The "root" of the BNF used to determine if the other is a descendant
     * @param queryBNF The BNF to check if it is a descendant of the parent
     * @return <code>true</code> if the {@link JPQLQueryBNF} is a descendant of the given parent
     * {@link JPQLQueryBNF}; <code>false</code> otherwise
     */
    protected boolean isValid(JPQLQueryBNF parentQueryBNF, JPQLQueryBNF queryBNF) {
        return isValid(parentQueryBNF, queryBNF, false);
    }

    /**
     * Determines whether the given {@link JPQLQueryBNF} part is the given parent {@link JPQLQueryBNF}.
     *
     * @param parentQueryBNF The "root" of the BNF used to determine if the other is a descendant
     * @param queryBNF The BNF to check if it is a descendant of the parent
     * @param bypassCompound Indicates whether a {@link JPQLQueryBNF} representing a compound
     * expression should be considered when doing the validation
     * @return <code>true</code> if the {@link JPQLQueryBNF} is a descendant of the given parent
     * {@link JPQLQueryBNF}; <code>false</code> otherwise
     */
    protected boolean isValid(JPQLQueryBNF parentQueryBNF, JPQLQueryBNF queryBNF, boolean bypassCompound) {
        JPQLQueryBNFValidator validator = buildJPQLQueryBNFValidator(parentQueryBNF);
        try {
            validator.setBypassCompound(bypassCompound);
            validator.validate(queryBNF);
            return validator.isValid();
        }
        finally {
            validator.dispose();
        }
    }

    /**
     * Determines whether the given {@link JPQLQueryBNF} part is the given parent {@link JPQLQueryBNF}.
     *
     * @param parentQueryBNF The "root" of the BNF used to determine if the other is a descendant
     * @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} to check if it is a
     * descendant of the parent
     * @param bypassCompound Indicates whether a {@link JPQLQueryBNF} representing a compound
     * expression should be considered when doing the validation
     * @return <code>true</code> if the {@link JPQLQueryBNF} is a descendant of the given parent
     * {@link JPQLQueryBNF}; <code>false</code> otherwise
     */
    protected boolean isValid(JPQLQueryBNF parentQueryBNF, String queryBNFId, boolean bypassCompound) {
        return isValid(parentQueryBNF, getQueryBNF(queryBNFId), bypassCompound);
    }

    /**
     * Determines whether the given proposal is a valid, which is based on the content of the given
     * word. If the word is not an empty string, the proposal must start with the content of the word.
     *
     * @param proposal The proposal to validate
     * @param word The word, which is what was parsed before the position of the cursor
     * @return <code>true</code> if the proposal is valid; <code>false</code> otherwise
     */
    protected boolean isValidProposal(String proposal, String word) {

        // There is no word to match the first letters
        if (word.length() == 0) {
            return true;
        }

        char character = word.charAt(0);

        if (character == '+' ||
            character == '-' ||
            character == '*' ||
            character == '/') {

            return true;
        }

        // The word is longer than the proposal
        if (word.length() > proposal.length()) {
            return false;
        }

        // Check to see if the proposal starts with the word
        for (int index = 0, length = word.length(); index < length; index++) {

            char character1 = proposal.charAt(index);
            char character2 = word  .charAt(index);

            // If characters don't match but case may be ignored, try converting
            // both characters to uppercase. If the results match, then the
            // comparison scan should continue
            char upperCase1 = Character.toUpperCase(character1);
            char upperCase2 = Character.toUpperCase(character2);

            if (upperCase1 != upperCase2) {
                return false;
            }

            // Unfortunately, conversion to uppercase does not work properly for
            // the Georgian alphabet, which has strange rules about case
            // conversion. So we need to make one last check before exiting
            if (Character.toLowerCase(upperCase1) != Character.toLowerCase(upperCase2)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Determines whether the given JPQL identifier can be a valid proposal, i.e. if it's part of the
     * grammar of the JPA version that was used to parse the JPQL query.
     *
     * @param identifier The JPQL identifier to validate
     * @return <code>true</code> if the given identifier is part of the current JPA version or was
     * defined in previous release; <code>false</code> otherwise
     */
    protected boolean isValidVersion(String identifier) {
        JPAVersion identifierVersion = getLatestGrammar().getExpressionRegistry().getIdentifierVersion(identifier);
        return queryContext.getJPAVersion().isNewerThanOrEqual(identifierVersion);
    }

    /**
     * Determines whether the given {@link Expression} is part of an invalid fragment
     *
     * @param expression The {@link Expression} to verify its location within the JPQL query
     * @return <code>true</code> if the given {@link Expression} is within an invalid fragment;
     * <code>false</code> if it is not
     */
    protected boolean isWithinInvalidExpression(Expression expression) {
        WithinInvalidExpressionVisitor validator = getWithinInvalidExpressionVisitor();
        try {
            expression.accept(validator);
            return validator.isWithinInvalidExpression();
        }
        finally {
            validator.dispose();
        }
    }

    /**
     * Registers the given helper associated with the given helper class.
     *
     * @param helperClass The Java class of the helper to retrieve
     * @param helper The helper being registered
     * @see #getHelper(Class)
     */
    protected final <T> void registerHelper(Class<T> helperClass, T helper) {
        helpers.put(helperClass, helper);
    }

    /**
     * Removes the last virtual space from the stack.
     */
    protected final void removeVirtualSpace() {
        virtualSpaces.pop();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return proposals.toString();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(AbsExpression expression) {
        super.visit(expression);
        visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(AbstractSchemaName expression) {

        // First visit with the adjustment
        corrections.add(queryPosition.getPosition(expression));
        super.visit(expression);
        corrections.pop();

        // Now visit without the adjustment
        super.visit(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(AdditionExpression expression) {
        super.visit(expression);
        visitArithmeticExpression(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(AllOrAnyExpression expression) {
        super.visit(expression);
        visitSingleEncapsulatedExpression(expression, IdentificationVariableType.NONE, ALL, ANY, SOME);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(AndExpression expression) {
        super.visit(expression);
        visitLogicalExpression(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(ArithmeticFactor expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();
        int length = 1;

        if (position == length) {
            addIdentificationVariables();
            addFunctionIdentifiers(ArithmeticPrimaryBNF.ID);
        }
        else if (expression.hasSpaceAfterArithmeticOperator() || hasVirtualSpace()) {
            length++;

            if (position == length) {
                addIdentificationVariables();
                addFunctionIdentifiers(ArithmeticPrimaryBNF.ID);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(AvgFunction expression) {
        super.visit(expression);
        visitAggregateFunction(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(BadExpression expression) {
        super.visit(expression);
        visitInvalidExpression(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(BetweenExpression expression) {
        super.visit(expression);

        int position = queryPosition.getPosition(expression) - corrections.peek();
        int length = 0;

        if (expression.hasExpression()) {
            length += expression.getExpression().getLength() + SPACE_LENGTH;
        }

        // Within "NOT BETWEEN" or "BETWEEN"
        if (expression.hasNot() && isPositionWithin(position, length, NOT_BETWEEN) ||
           !expression.hasNot() && isPositionWithin(position, length, BETWEEN)) {

            proposals.addIdentifier(BETWEEN);
            proposals.addIdentifier(NOT_BETWEEN);
        }
        // After "BETWEEN "
        else if (expression.hasSpaceAfterBetween()) {
            length += expression.getIdentifier().length() + SPACE_LENGTH;

            // TODO: Check for the BETWEEN's expression type
            // Right after "BETWEEN "
            if (position == length) {
                addIdentificationVariables();
                addFunctionIdentifiers(expression.getBoundExpressionQueryBNFId());
            }

            // After lower bound
            if (expression.hasLowerBoundExpression()) {

                // Check for something like "<lower bound> <word>"
                int lowerBoundLength = expression.getLowerBoundExpression().getLength();

                if (!expression.hasAnd() &&
                    (position > length) && (position < length + lowerBoundLength) &&
                    isAppendableToCollection(expression.getLowerBoundExpression())) {

                    addIdentifier(AND);
                }

                length += lowerBoundLength;

                if (expression.hasSpaceAfterLowerBound()) {
                    length++;

                    // Right before "AND"
                    if (position == length) {
                        proposals.addIdentifier(AND);
                    }
                    else {
                        // Within "AND"
                        if (expression.hasAnd() && isPositionWithin(position, length, AND)) {
                            proposals.addIdentifier(AND);
                        }
                        // After "AND "
                        else if (expression.hasSpaceAfterAnd()) {
                            length += AND.length() + SPACE_LENGTH;

                            // TODO: Check for the BETWEEN's expression type
                            if (position == length) {
                                addIdentificationVariables();
                                addFunctionIdentifiers(InternalBetweenExpressionBNF.ID);
                            }
                        }
                        else if (!expression.hasAnd() &&
                                  expression.hasUpperBoundExpression()) {

                            length += expression.getUpperBoundExpression().getLength();

                            if (position == length) {
                                addIdentifier(AND);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(CaseExpression expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();

        // Within "CASE"
        if (isPositionWithin(position, CASE)) {
            addIdentifier(CASE);
            addIdentificationVariables();
            addFunctionIdentifiers(expression.getParent().findQueryBNF(expression));
        }
        // After "CASE "
        else if (expression.hasSpaceAfterCase()) {
            int length = CASE.length() + SPACE_LENGTH;

            // Right after "CASE "
            if (position == length) {
                addIdentificationVariables();
                addFunctionIdentifiers(CaseOperandBNF.ID);
                proposals.addIdentifier(WHEN);
            }

            // After "<case operand> "
            if (expression.hasCaseOperand() &&
                expression.hasSpaceAfterCaseOperand()) {

                length += expression.getCaseOperand().getLength() + SPACE_LENGTH;

                // Right after "<case operand> "
                if (position == length) {
                    proposals.addIdentifier(WHEN);
                }
            }

            // After "<when clauses> "
            if (expression.hasWhenClauses() &&
                expression.hasSpaceAfterWhenClauses()) {

                length += expression.getWhenClauses().getLength() + SPACE_LENGTH;

                // Right after "<when clauses> "
                if (isPositionWithin(position, length, ELSE)) {
                    proposals.addIdentifier(ELSE);
                }

                // After "ELSE "
                if (expression.hasElse() &&
                    expression.hasSpaceAfterElse()) {

                    length += ELSE.length() + SPACE_LENGTH;

                    // Right after "ELSE "
                    if (position == length) {
                        addIdentificationVariables();
                        addFunctionIdentifiers(ScalarExpressionBNF.ID);
                    }

                    // After "<else expression> "
                    if (expression.hasElseExpression() &&
                        expression.hasSpaceAfterElseExpression()) {

                        length += expression.getElseExpression().getLength() + SPACE_LENGTH;

                        // Right after "<else expression> "
                        if (isPositionWithin(position, length, END)) {
                            proposals.addIdentifier(END);
                        }
                    }
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(CoalesceExpression expression) {
        super.visit(expression);

        if (isFollowingInvalidExpression(expression)) {
            return;
        }

        int position = queryPosition.getPosition(expression) - corrections.peek();

        // Within the identifier
        if (isPositionWithin(position, COALESCE)) {
            addIdentifier(COALESCE);
            addIdentificationVariables();
            addFunctionIdentifiers(expression.getParent().findQueryBNF(expression));
        }
        // Right after "<identifier>("
        else if (expression.hasLeftParenthesis()) {
            int length = COALESCE.length() + 1 /* '(' */;

            if (position == length) {
                addIdentificationVariables();
                addFunctionIdentifiers(expression.getEncapsulatedExpressionQueryBNFId());
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(CollectionExpression expression) {
        if (!isLocked(expression)) {
            super.visit(expression);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(CollectionMemberDeclaration expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();

        // Within "IN"
        if (isPositionWithin(position, IN)) {

            if (!isWithinInvalidExpression(expression)) {
                proposals.addIdentifier(IN);
            }

            addIdentificationVariables();
            addFunctionIdentifiers(expression.getParent().findQueryBNF(expression));
        }

        // In a subquery only
        // After "IN "
        if (isInSubquery(expression) && expression.hasSpaceAfterIn()) {
            int length = IN.length() + SPACE_LENGTH;

            // Right after "IN "
            if (position == length) {
                // TODO: Type.SuperQueryIdentificationVariable
                addLeftIdentificationVariables(expression);
            }
        }
        // In a top-level query or subquery
        // After "IN("
        else if (expression.hasLeftParenthesis()) {
            int length = IN.length() + 1 /* '(' */;

            // Right after "IN("
            if (position == length) {
                addLeftIdentificationVariables(expression);
                addFunctionIdentifiers(CollectionValuedPathExpressionBNF.ID);
            }

            // After "<collection-valued path expression>)"
            if (expression.hasRightParenthesis()) {
                length += expression.getCollectionValuedPathExpression().getLength() + 1 /* ')' */;

                // Right after "<collection-valued path expression>)"
                if ((position == length) && !expression.hasSpaceAfterRightParenthesis()) {
                    proposals.addIdentifier(AS);
                }

                if (expression.hasSpaceAfterRightParenthesis()) {
                    length++;
                }

                // Within "AS"
                if (isPositionWithin(position, length, AS)) {
                    proposals.addIdentifier(AS);
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(CollectionMemberExpression expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();
        String identifier = expression.getIdentifier();
        int length = 0;

        if (expression.hasEntityExpression()) {
            length = expression.getEntityExpression().getLength() + SPACE_LENGTH;
        }

        // Within the <identifier>
        if (isPositionWithin(position, length, identifier)) {
            proposals.addIdentifier(NOT_MEMBER);
            proposals.addIdentifier(NOT_MEMBER_OF);
            proposals.addIdentifier(MEMBER);
            proposals.addIdentifier(MEMBER_OF);
        }
        // After the <identifier>
        else if (expression.hasOf() && expression.hasSpaceAfterOf() ||
                !expression.hasOf() && expression.hasSpaceAfterMember()) {

            length += identifier.length() + SPACE_LENGTH;

            // Right after the <identifier>
            if (position == length) {
                if (!expression.hasOf()) {
                    addIdentifier(OF);
                }
                addIdentificationVariables();
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(CollectionValuedPathExpression expression) {
        super.visit(expression);

        if (!isFollowingInvalidExpression(expression)) {

            visitPathExpression(expression);

            if (isDeclaration(expression)) {
                proposals.setClassNamePrefix(word, ClassType.INSTANTIABLE);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(ComparisonExpression expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();
        int length = 0;

        if (expression.hasLeftExpression()) {
            length += expression.getLeftExpression().getLength() + SPACE_LENGTH;
        }

        // Within the comparison operator
        if (isPositionWithin(position, length, expression.getComparisonOperator())) {
            addExpressionFactoryIdentifiers(ComparisonExpressionFactory.ID);
        }

        // After the comparison operator
        length += expression.getComparisonOperator().length();

        if (expression.hasSpaceAfterIdentifier()) {
            length++;
        }

        // Right after the comparison operator
        if (position == length) {
            addIdentificationVariables();
            addFunctionIdentifiers(expression.getRightExpressionQueryBNFId());
            addClauseIdentifiers(expression.getRightExpressionQueryBNFId());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(ConcatExpression expression) {
        super.visit(expression);
        visitCollectionExpression(expression, CONCAT, getConcatExpressionCollectionHelper());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(ConstructorExpression expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();

        // NEW
        if (isPositionWithin(position, NEW)) {
            proposals.addIdentifier(NEW);
        }
        // After "NEW "
        else if (expression.hasSpaceAfterNew()) {
            int length = NEW.length() + SPACE_LENGTH;
            String className = expression.getClassName();
            int classNameLength = className.length();

            // Right after "NEW " or within the fully qualified class name
            if ((position >= length) && (position <= length + classNameLength)) {
                proposals.setClassNamePrefix(className.substring(0, position - length), ClassType.INSTANTIABLE);
            }
            // After "("
            else if (expression.hasLeftParenthesis()) {
                length += classNameLength + SPACE_LENGTH;

                // Right after "("
                if (position == length) {
                    addIdentificationVariables();
                    addFunctionIdentifiers(ConstructorItemBNF.ID);
                }
                else {
                    visitCollectionExpression(expression, NEW, getConstructorCollectionHelper());
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(CountFunction expression) {
        super.visit(expression);
        visitAggregateFunction(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(DateTime expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();

        // Within the identifier
        if (expression.isCurrentDate()      && isPositionWithin(position, CURRENT_DATE) ||
            expression.isCurrentTime()      && isPositionWithin(position, CURRENT_TIME) ||
            expression.isCurrentTimestamp() && isPositionWithin(position, CURRENT_TIMESTAMP)) {

            proposals.addIdentifier(CURRENT_DATE);
            proposals.addIdentifier(CURRENT_TIME);
            proposals.addIdentifier(CURRENT_TIMESTAMP);

            addFunctionIdentifiers(expression.getParent().findQueryBNF(expression));
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(DeleteClause expression) {
        if (!isLocked(expression)) {
            super.visit(expression);
            visitCollectionExpression(expression, DELETE_FROM, getDeleteClauseCollectionHelper());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(DeleteStatement expression) {
        if (!isLocked(expression)) {
            super.visit(expression);
            visitStatement(expression, getDeleteClauseStatementHelper());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(DivisionExpression expression) {
        super.visit(expression);
        visitArithmeticExpression(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(EmptyCollectionComparisonExpression expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();
        int length = 0;

        if (expression.hasExpression()) {
            length = expression.getExpression().getLength() + SPACE_LENGTH;
        }

        // Within the <identifier>
        if (isPositionWithin(position, length, expression.getIdentifier())) {
            proposals.addIdentifier(IS_EMPTY);
            proposals.addIdentifier(IS_NOT_EMPTY);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(EntityTypeLiteral expression) {

        // Adjust the position to be the "beginning" of the expression by adding a "correction"
        corrections.add(queryPosition.getPosition(expression));
        super.visit(expression);
        corrections.pop();

        // Add the possible abstract schema names
        addEntities();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(EntryExpression expression) {
        super.visit(expression);
        visitSingleEncapsulatedExpression(expression, IdentificationVariableType.COLLECTION);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(ExistsExpression expression) {
        super.visit(expression);
        visitSingleEncapsulatedExpression(expression, IdentificationVariableType.NONE, EXISTS, NOT_EXISTS);
    }

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

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(FromClause expression) {
        if (!isLocked(expression)) {
            super.visit(expression);
            visitCollectionExpression(expression, FROM, getFromClauseCollectionHelper());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(FunctionExpression expression) {
        super.visit(expression);
        visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(GroupByClause expression) {
        if (!isLocked(expression)) {
            super.visit(expression);
            visitCollectionExpression(expression, GROUP_BY, getGroupByClauseCollectionHelper());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(HavingClause expression) {
        if (!isLocked(expression)) {
            super.visit(expression);
            visitCollectionExpression(expression, expression.getIdentifier(), getConditionalClauseCollectionHelper());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(IdentificationVariable expression) {

        // First visit with the adjustment
        corrections.add(queryPosition.getPosition(expression));
        super.visit(expression);
        corrections.pop();

        // Now visit without the adjustment
        super.visit(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(IdentificationVariableDeclaration expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();

        if ((position > 0) && (expression.hasSpace() || hasVirtualSpace())) {

            Expression rangeVariableDeclaration = expression.getRangeVariableDeclaration();
            int length = rangeVariableDeclaration.getLength() + SPACE_LENGTH;

            if (position == length) {
                if (isComplete(rangeVariableDeclaration)) {
                    addJoinIdentifiers();
                }
            }
            else {
                visitCollectionExpression(expression, ExpressionTools.EMPTY_STRING, getJoinCollectionHelper());
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(IndexExpression expression) {
        super.visit(expression);
        visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(InExpression expression) {
        expression.accept(getVisitParentVisitor());
        int position = queryPosition.getPosition(expression) - corrections.peek();
        int length = 0;

        if (expression.hasExpression()) {
            length = expression.getExpression().getLength() + SPACE_LENGTH;
        }

        // Within "IN"
        if (isPositionWithin(position, length, expression.getIdentifier())) {

            // Make sure there is an expression left of 'IN'
            if (length > 2) {
                proposals.addIdentifier(IN);
                proposals.addIdentifier(NOT_IN);
            }

            addIdentificationVariables();
            addFunctionIdentifiers(expression.getParent().findQueryBNF(expression));
        }
        // After "IN("
        else if (expression.hasLeftParenthesis()) {
            length += expression.getIdentifier().length() + SPACE_LENGTH;

            // Right after "IN("
            if (position == length) {
                addFunctionIdentifiers(InExpressionItemBNF.ID);
                proposals.addIdentifier(SELECT);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(InputParameter expression) {
        // No content assist can be provider for an input parameter
        super.visit(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(Join expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();
        String identifier = expression.getIdentifier();
        boolean joinFetch = expression.hasFetch();

        // Within "<join>"
        if (isPositionWithin(position, identifier)) {

            // Add JOIN identifiers
            proposals.addIdentifier(JOIN);
            proposals.addIdentifier(INNER_JOIN);
            proposals.addIdentifier(LEFT_JOIN);
            proposals.addIdentifier(LEFT_OUTER_JOIN);

            // Add JOIN FETCH identifiers if allowed or
            // if there is no 'AS identification_variable'
            if (isJoinFetchIdentifiable() ||
               !expression.hasAs() && !expression.hasIdentificationVariable()) {

                proposals.addIdentifier(JOIN_FETCH);
                proposals.addIdentifier(INNER_JOIN_FETCH);
                proposals.addIdentifier(LEFT_JOIN_FETCH);
                proposals.addIdentifier(LEFT_OUTER_JOIN_FETCH);
            }
        }
        // After "<join> "
        else if (expression.hasSpaceAfterJoin()) {
            int length = identifier.length() + SPACE_LENGTH;

            // Right after "<join> "
            if (position == length) {

                // Only add some JOIN identifiers if the actual identifier is shorter or incomplete
                if (identifier == LEFT) {
                    addIdentifier(LEFT_JOIN);
                    addIdentifier(LEFT_OUTER_JOIN);

                    if (isJoinFetchIdentifiable() ||
                        !expression.hasAs() && !expression.hasIdentificationVariable()) {

                        addIdentifier(LEFT_JOIN_FETCH);
                        addIdentifier(LEFT_OUTER_JOIN_FETCH);
                    }
                }
                else if (identifier == INNER) {
                    addIdentifier(INNER_JOIN);

                    if (isJoinFetchIdentifiable() ||
                        !expression.hasAs() && !expression.hasIdentificationVariable()) {

                        addIdentifier(INNER_JOIN_FETCH);
                    }
                }
                else if (identifier.equals("LEFT_OUTER")) {
                    addIdentifier(LEFT_OUTER_JOIN);

                    if (isJoinFetchIdentifiable() ||
                        !expression.hasAs() && !expression.hasIdentificationVariable()) {

                        addIdentifier(LEFT_OUTER_JOIN_FETCH);
                    }
                }
                else {
                    addLeftIdentificationVariables(expression);
                }
            }

            // After "join association path expression "
            if (expression.hasJoinAssociationPath() &&
                expression.hasSpaceAfterJoinAssociation()) {

                length += expression.getJoinAssociationPath().getLength() + SPACE_LENGTH;

                // Right after "join association path expression "
                // Make sure to verify if AS can be added if it's a JOIN FETCH expression
                if (!joinFetch || joinFetch && isJoinFetchIdentifiable()) {

                    if (isPositionWithin(position, length, AS)) {
                        addIdentifier(AS);
                    }

                    if (expression.hasAs()) {
                        length += 2;

                        if (expression.hasSpaceAfterAs()) {
                            length++;
                        }
                    }
                }

                if (joinFetch || expression.hasIdentificationVariable()) {

                    length += expression.getIdentificationVariable().getLength();

                    if (expression.hasSpaceAfterIdentificationVariable()) {
                        length += SPACE_LENGTH;
                    }

                    // After "join association path expression [AS] identification variable"
                    if (position == length) {
                        addIdentifier(ON);
                    }
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(JPQLExpression expression) {

        int position = queryPosition.getPosition(expression) - corrections.peek();

        // At the beginning of the query
        if (position == 0) {
            addIdentifier(DELETE_FROM);
            addIdentifier(SELECT);
            addIdentifier(UPDATE);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(KeyExpression expression) {
        super.visit(expression);
        visitSingleEncapsulatedExpression(expression, IdentificationVariableType.LEFT_COLLECTION);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(KeywordExpression expression) {
        super.visit(expression);

        int position = queryPosition.getPosition(expression) - corrections.peek();
        String keyword = expression.getText();

        // Within the identifier
        if ((keyword == TRUE)  && isPositionWithin(position, TRUE)  ||
            (keyword == FALSE) && isPositionWithin(position, FALSE) ||
            (keyword == NULL)  && isPositionWithin(position, NULL)) {

            proposals.addIdentifier(TRUE);
            proposals.addIdentifier(FALSE);
            proposals.addIdentifier(NULL);

            addIdentificationVariables();
            addFunctionIdentifiers(expression.getParent().findQueryBNF(expression));
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(LengthExpression expression) {
        super.visit(expression);
        visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(LikeExpression expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();

        if (expression.hasStringExpression()) {
            int length = expression.getStringExpression().getLength() + SPACE_LENGTH;

            // Within "LIKE" or "NOT LIKE"
            if (isPositionWithin(position, length, expression.getIdentifier())) {
                proposals.addIdentifier(LIKE);
                proposals.addIdentifier(NOT_LIKE);
            }
            // After "LIKE " or "NOT LIKE "
            else if (expression.hasSpaceAfterLike()) {
                length += expression.getIdentifier().length() + SPACE_LENGTH;

                // After "<pattern value> "
                if (expression.hasPatternValue() &&
                    expression.hasSpaceAfterPatternValue()) {

                    length += expression.getPatternValue().getLength() + SPACE_LENGTH;

                    // Within "ESCAPE"
                    if (isPositionWithin(position, length, ESCAPE)) {
                        proposals.addIdentifier(ESCAPE);
                    }
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(LocateExpression expression) {
        super.visit(expression);
        visitCollectionExpression(expression, LOCATE, getTripleEncapsulatedCollectionHelper());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(LowerExpression expression) {
        super.visit(expression);
        visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(MaxFunction expression) {
        super.visit(expression);
        visitAggregateFunction(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(MinFunction expression) {
        super.visit(expression);
        visitAggregateFunction(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(ModExpression expression) {
        super.visit(expression);
        visitCollectionExpression(expression, MOD, getDoubleEncapsulatedCollectionHelper());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(MultiplicationExpression expression) {
        super.visit(expression);
        visitArithmeticExpression(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(NotExpression expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();

        // Within "NOT"
        if (isPositionWithin(position, NOT)) {
            proposals.addIdentifier(NOT);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(NullComparisonExpression expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();
        int length = 0;

        if (expression.hasExpression()) {
            length += expression.getExpression().getLength() + SPACE_LENGTH;
        }

        // Within "IS NULL" or "IS NOT NULL"
        if (isPositionWithin(position, length, expression.getIdentifier())) {
            proposals.addIdentifier(IS_NULL);
            proposals.addIdentifier(IS_NOT_NULL);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(NullExpression expression) {
        // No content assist can be provider
        super.visit(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(NullIfExpression expression) {
        super.visit(expression);
        visitCollectionExpression(expression, NULLIF, getDoubleEncapsulatedCollectionHelper());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(NumericLiteral expression) {
        // No content assist can be provider for a numerical value
        super.visit(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(ObjectExpression expression) {
        super.visit(expression);
        visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(OnClause expression) {
        if (!isLocked(expression)) {
            super.visit(expression);
            visitCollectionExpression(expression, expression.getIdentifier(), getConditionalClauseCollectionHelper());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(OrderByClause expression) {
        if (!isLocked(expression)) {
            super.visit(expression);
            visitCollectionExpression(expression, ORDER_BY, getOrderByClauseCollectionHelper());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(OrderByItem expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();

        // After the order by item
        if (expression.hasExpression()) {
            int length = expression.getExpression().getLength();

            // Within the expression
            if ((position > -1) && (position <= length)) {

                if (!expression.hasNulls() &&
                    !expression.hasOrdering()) {

                    addIdentifier(ASC);
                    addIdentifier(DESC);
                }
            }
            // After the expression
            else if (expression.hasSpaceAfterExpression()) {
                length++;

                // Right before "ASC" or "DESC"
                if (position == length) {
                    proposals.addIdentifier(ASC);
                    proposals.addIdentifier(DESC);
                }
                else {
                    // Right after the space
                    Ordering ordering = expression.getOrdering();

                    // Within "ASC" or "DESC"
                    if ((ordering != Ordering.DEFAULT) &&
                        isPositionWithin(position, length, ordering.name())) {

                        proposals.addIdentifier(ASC);
                        proposals.addIdentifier(DESC);
                    }
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(OrExpression expression) {
        super.visit(expression);
        visitLogicalExpression(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(RangeVariableDeclaration expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();

        // After "<abstract schema name> "
        if (expression.hasRootObject()) {
            int length = expression.getRootObject().getLength();

            // After "<abstract schema name> "
            if (expression.hasSpaceAfterRootObject()) {
                length++;

                // Right after "<abstract schema name> "
                if (position == length) {
                    addIdentifier(AS);
                }
                // Within 'AS'
                else if (expression.hasAs() && isPositionWithin(position, length, AS)) {
                    addIdentifier(AS);
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(ResultVariable expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();

        if (expression.hasSelectExpression()) {
            Expression selectItem = expression.getSelectExpression();
            int length = selectItem.getLength() + SPACE_LENGTH;

            // Right before "AS"
            if (position == length) {

                if (isComplete(selectItem)) {
                    addIdentifier(AS);
                }

                if (areArithmeticSymbolsAppendable(selectItem)) {
                    addArithmeticIdentifiers();
                }
            }
            // Within "AS"
            else if (expression.hasAs() && isPositionWithin(position, length, AS)) {

                if (isComplete(selectItem)) {
                    addIdentifier(AS);
                }

                addFunctionIdentifiers(selectItem);
            }
        }
        // Now add other functions as well, example " A|S e" could become "AVG e"
        else if (expression.hasAs() && isPositionWithin(position, AS)) {
            addFunctionIdentifiers(expression);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SelectClause expression) {
        if (!isLocked(expression)) {
            super.visit(expression);
            visitCollectionExpression(expression, SELECT, getSelectClauseCollectionHelper());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SelectStatement expression) {
        if (!isLocked(expression)) {
            super.visit(expression);
            visitStatement(expression, getSelectClauseStatementHelper());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SimpleFromClause expression) {
        if (!isLocked(expression)) {
            super.visit(expression);
            visitCollectionExpression(expression, FROM, getFromClauseCollectionHelper());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SimpleSelectClause expression) {
        if (!isLocked(expression)) {
            super.visit(expression);
            visitCollectionExpression(expression, SELECT, getSimpleSelectClauseCollectionHelper());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SimpleSelectStatement expression) {
        if (!isLocked(expression)) {
            // Don't continue traversing the parent hierarchy because a subquery
            // will handle all the possible proposals
            visitStatement(expression, getSimpleSelectClauseStatementHelper());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SizeExpression expression) {
        super.visit(expression);
        visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SqrtExpression expression) {
        expression.accept(getVisitParentVisitor());
        visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(StateFieldPathExpression expression) {
        super.visit(expression);
        if (!isFollowingInvalidExpression(expression)) {
            visitPathExpression(expression);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(StringLiteral expression) {
        // No content assist required
        super.visit(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SubExpression expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();

        if (position == 1) {
            addFunctionIdentifiers(expression);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SubstringExpression expression) {
        super.visit(expression);
        visitCollectionExpression(expression, SUBSTRING, getTripleEncapsulatedCollectionHelper());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SubtractionExpression expression) {
        super.visit(expression);
        visitArithmeticExpression(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SumFunction expression) {
        super.visit(expression);
        visitAggregateFunction(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(TreatExpression expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();

        // Within "TREAT"
        if (isPositionWithin(position, TREAT)) {
            addIdentifier(TREAT);
            addIdentificationVariables();
            addFunctionIdentifiers(expression.getParent().findQueryBNF(expression));
        }
        // After "TREAT("
        else if (expression.hasLeftParenthesis()) {
            int length = TREAT.length() + 1;

            // Right after "TREAT("
            if (position == length) {
                addLeftIdentificationVariables(expression);
            }

            // After "<collection-valued path expression> "
            if (expression.hasCollectionValuedPathExpression() &&
                expression.hasSpaceAfterCollectionValuedPathExpression()) {

                Expression collectionValuedPathExpression = expression.getCollectionValuedPathExpression();
                length += collectionValuedPathExpression.getLength() + SPACE_LENGTH;

                // Within "AS"
                if (isPositionWithin(position, length, AS)) {
                    addIdentifier(AS);

                    // If the entity type is not specified, then we can add
                    // the possible abstract schema names
                    if (!expression.hasEntityType()) {

                        // If the type of the path expression is resolvable,
                        // then filter the abstract schema types
                        IType type = queryContext.getType(collectionValuedPathExpression);

                        if (type.isResolvable()) {
                            addEntities(type);
                        }
                        else {
                            addEntities();
                        }
                    }
                }
            }

            // After "AS "
            if (expression.hasAs() &&
                expression.hasSpaceAfterAs()) {

                length += AS.length() + SPACE_LENGTH;

                // Right after "AS "
                if (position == length) {
                    // If the type of the path expression is resolvable,
                    // then filter the abstract schema types
                    IType type = queryContext.getType(expression.getCollectionValuedPathExpression());

                    if (type.isResolvable()) {
                        addEntities(type);
                    }
                    else {
                        addEntities();
                    }
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(TrimExpression expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();
        int length = 0;

        // Within "TRIM"
        if (isPositionWithin(position, TRIM)) {
            addIdentifier(TRIM);
            addIdentificationVariables();
            addFunctionIdentifiers(expression.getParent().findQueryBNF(expression));
        }
        // After "TRIM("
        else if (expression.hasLeftParenthesis()) {
            length += TRIM.length() + 1;

            // Right after "TRIM("
            if (position == length) {
                addIdentifier(BOTH);
                addIdentifier(LEADING);
                addIdentifier(TRAILING);

                if (!expression.hasTrimCharacter() &&
                    !expression.hasFrom()) {

                    addIdentificationVariables();
                    addFunctionIdentifiers(StringPrimaryBNF.ID);
                }
            }

            // Within the trim specification
            if (expression.hasSpecification()) {
                String specification = expression.getSpecification().name();

                if (isPositionWithin(position, length, specification)) {
                    addIdentifier(BOTH);
                    addIdentifier(LEADING);
                    addIdentifier(TRAILING);

                    if (!expression.hasTrimCharacter() &&
                        !expression.hasFrom()) {

                        addIdentificationVariables();
                        addFunctionIdentifiers(StringPrimaryBNF.ID);
                    }
                }

                length += specification.length();
            }

            if (expression.hasSpaceAfterSpecification()) {
                length += SPACE_LENGTH;
            }

            // Trim character
            if (expression.hasTrimCharacter()) {
                length += expression.getTrimCharacter().getLength();
            }

            if (expression.hasSpaceAfterTrimCharacter()) {
                length += SPACE_LENGTH;
            }

            // Right after "<trim_character> "
            if (position == length) {
                addIdentifier(FROM);

                if (!expression.hasFrom()) {
                    addIdentificationVariables();
                    addFunctionIdentifiers(StringPrimaryBNF.ID);
                }
            }

            if (expression.hasFrom()) {

                // Within "FROM"
                if (isPositionWithin(position, length, FROM)) {
                    addIdentifier(FROM);
                }

                length += FROM.length();
            }

            if (expression.hasSpaceAfterFrom()) {
                length += SPACE_LENGTH;
            }

            // Right after "FROM "
            if (position == length) {
                addIdentificationVariables();
                addFunctionIdentifiers(StringPrimaryBNF.ID);
            }

            // Right after the string literal but there is no trim character,
            // nor FROM and there is a virtual space
            if (expression.hasExpression()) {
                length += expression.getExpression().getLength();

                if ((position == length + virtualSpaces.peek()) &&
                    !expression.hasTrimCharacter() &&
                    !expression.hasFrom()) {

                    addIdentifier(FROM);
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(TypeExpression expression) {
        super.visit(expression);
        visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(UnknownExpression expression) {
        super.visit(expression);
        visitInvalidExpression(expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(UpdateClause expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();

        // Within "UPDATE"
        if (isPositionWithin(position, UPDATE)) {
            proposals.addIdentifier(UPDATE);
        }
        // After "UPDATE "
        else if (expression.hasSpaceAfterUpdate()) {
            int length = UPDATE.length() + SPACE_LENGTH;

            // Right after "UPDATE "
            if (position == length) {
                addEntities();
            }
            // After "<range variable declaration> "
            else if (expression.hasRangeVariableDeclaration()) {

                RangeVariableDeclaration rangeVariableDeclaration = findRangeVariableDeclaration(expression);

                if ((rangeVariableDeclaration != null) &&
                     rangeVariableDeclaration.hasRootObject() &&
                     rangeVariableDeclaration.hasSpaceAfterRootObject()) {

                    length += rangeVariableDeclaration.getRootObject().getLength() + SPACE_LENGTH;

                    // Example: "UPDATE System s"
                    if (!expression.hasSet()        &&
                        !rangeVariableDeclaration.hasAs() &&
                        isPositionWithin(position, length, SET)) {

                        addIdentifier(SET);
                    }
                    // Example: "UPDATE System s "
                    // Example: "UPDATE System AS s "
                    else {

                        if (rangeVariableDeclaration.hasAs()) {
                            length += 2;
                        }

                        if (rangeVariableDeclaration.hasSpaceAfterAs()) {
                            length++;
                        }

                        if (rangeVariableDeclaration.hasIdentificationVariable()) {
                            length += rangeVariableDeclaration.getIdentificationVariable().getLength();
                        }

                        if (expression.hasSpaceAfterRangeVariableDeclaration()) {
                            length++;
                        }

                        // Within "SET"
                        if ((rangeVariableDeclaration.hasAs() && rangeVariableDeclaration.hasIdentificationVariable() ||
                            !rangeVariableDeclaration.hasAs() && rangeVariableDeclaration.hasIdentificationVariable()) &&
                            isPositionWithin(position, length, SET)) {

                            addIdentifier(SET);
                        }
                        // After "SET "
                        else if (expression.hasSet() &&
                                 expression.hasSpaceAfterSet()) {

                            length += SET.length() + SPACE_LENGTH;

                            // Right after "SET "
                            if (position == length) {
                                addIdentificationVariables();
                            }
                            // Within the new value expressions
                            else {
                                visitCollectionExpression(expression, ExpressionTools.EMPTY_STRING, getUpdateItemCollectionHelper());
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(UpdateItem expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();
        int length = 0;

        // At the beginning
        if (position == length) {
            addIdentificationVariables();
        }
        else if (expression.hasStateFieldPathExpression() &&
                 expression.hasSpaceAfterStateFieldPathExpression()) {

            length += expression.getStateFieldPathExpression().getLength() + SPACE_LENGTH;

            // Within "="
            if (position == length) {
                proposals.addIdentifier(EQUAL);
            }
            // After "="
            else if (expression.hasEqualSign()) {
                length++;

                // Right after "="
                if (position == length) {
                    proposals.addIdentifier(EQUAL);
                    addIdentificationVariables();
                    addFunctionIdentifiers(NewValueBNF.ID);
                }
                else if (expression.hasSpaceAfterEqualSign()) {
                    length++;

                    // Right after "= "
                    if (position == length) {
                        addIdentificationVariables();
                        addFunctionIdentifiers(NewValueBNF.ID);
                    }
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(UpdateStatement expression) {
        if (!isLocked(expression)) {
            super.visit(expression);
            visitStatement(expression, getUpdateClauseStatementHelper());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(UpperExpression expression) {
        super.visit(expression);
        visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(ValueExpression expression) {
        super.visit(expression);
        visitSingleEncapsulatedExpression(expression, IdentificationVariableType.LEFT_COLLECTION);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(WhenClause expression) {
        super.visit(expression);
        int position = queryPosition.getPosition(expression) - corrections.peek();

        // Within "WHEN"
        if (isPositionWithin(position, WHEN)) {
            if (!isWithinInvalidExpression(expression)) {
                proposals.addIdentifier(WHEN);
            }
        }
        // After "WHEN "
        else if (expression.hasSpaceAfterWhen()) {
            int length = 5 /* WHEN + space */;

            // Right after "WHEN "
            if (position == length) {
                visitCollectionExpression(expression, WHEN, getWhenClauseConditionalClauseCollectionHelper());
            }
            else {
                length += expression.getWhenExpression().getLength();

                // After "WHEN <expression> " => THEN
                if (expression.hasSpaceAfterWhenExpression()) {
                    length++;

                    // Right after "WHEN <expression> " => THEN
                    if (position == length) {

                        // Check an incomplete WHEN expression
                        visitCollectionExpression(expression, WHEN, getWhenClauseConditionalClauseCollectionHelper());

                        // If the expression is complete, then "THEN" is a valid proposal
                        if (isComplete(expression)) {
                            proposals.addIdentifier(THEN);
                        }
                    }
                    else if (expression.hasThen()) {

                        // Within "THEN"
                        if (isPositionWithin(position, length, THEN)) {
                            proposals.addIdentifier(THEN);
                        }
                        else {
                            length += 4 /* THEN */;

                            // After "WHEN <expression> THEN "
                            if (expression.hasSpaceAfterThen()) {
                                length++;

                                // Right after "WHEN <expression> THEN "
                                if (position == length) {
                                    addIdentificationVariables();
                                    addFunctionIdentifiers(ScalarExpressionBNF.ID);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(WhereClause expression) {
        if (!isLocked(expression)) {
            super.visit(expression);
            visitCollectionExpression(expression, expression.getIdentifier(), getConditionalClauseCollectionHelper());
        }
    }

    /**
     * Visits the given {@link AggregateFunction} and attempts to find valid proposals.
     *
     * @param expression The {@link AggregateFunction} to inspect
     */
    protected void visitAggregateFunction(AggregateFunction expression) {

        if (isFollowingInvalidExpression(expression)) {
            return;
        }

        int position = queryPosition.getPosition(expression) - corrections.peek();
        String identifier = expression.getIdentifier();

        // Within "<identifier>"
        if (isPositionWithin(position, identifier)) {
            addIdentifier(identifier);
            addFunctionIdentifiers(expression);
        }
        // After "<identifier>("
        else if (expression.hasLeftParenthesis()) {
            int length = identifier.length() + 1 /* '(' */;
            boolean hasDistinct = expression.hasDistinct();

            // Within "DISTINCT"
            if (hasDistinct && isPositionWithin(position, length, DISTINCT) ) {
                addIdentifier(DISTINCT);
            }
            // After "("
            else {
                if (hasDistinct && expression.hasSpaceAfterDistinct()) {
                    length += DISTINCT.length() + SPACE_LENGTH;
                }

                // Right after "(" or right after "(DISTINCT "
                if (position == length) {
                    if (!hasDistinct) {
                        addIdentifier(DISTINCT);
                    }
                    addIdentificationVariables();
                    addFunctionIdentifiers(expression.getEncapsulatedExpressionQueryBNFId());
                }
            }
        }
    }

    /**
     * Visits the given {@link ArithmeticExpression} and attempts to find valid proposals.
     *
     * @param expression The {@link ArithmeticExpression} to inspect
     */
    protected void visitArithmeticExpression(ArithmeticExpression expression) {

        int position = queryPosition.getPosition(expression) - corrections.peek();
        int length = 0;

        if (expression.hasLeftExpression()) {
            length += expression.getLeftExpression().getLength() + SPACE_LENGTH;
        }

        // Within the arithmetic sign
        if (isPositionWithin(position, length, PLUS)) {
            addAggregateIdentifiers(expression.getQueryBNF());
        }
        // After the arithmetic sign, with or without the space
        else if (expression.hasSpaceAfterIdentifier()) {
            length += 2;

            // Right after the space
            if (position == length) {
                addIdentificationVariables();
                addFunctionIdentifiers(expression.getRightExpressionQueryBNFId());
            }
        }
    }

    /**
     * Adds the possible proposals for the given {@link Expression expression} based on the location of
     * the cursor and the content of the expression.
     *
     * @param expression The {@link Expression expression} being visited
     * @param identifier The JPQL identifier of the {@link Expression} being visited, if the {@link
     * Expression} does not have an identifier, than an empty string should be passed
     * @param helper This helper completes the behavior of this method by retrieving the information
     * from the given {@link Expression}
     */
    protected <T extends Expression> void visitCollectionExpression(T expression,
                                                                    String identifier,
                                                                    CollectionExpressionHelper<T> helper) {

        if (isFollowingInvalidExpression(expression)) {
            return;
        }

        int position = queryPosition.getPosition(expression) - corrections.peek();
        boolean hasIdentifier = (identifier.length() > 0);
        boolean virtualSpace = hasVirtualSpace();

        // Within the identifier
        if (hasIdentifier && isPositionWithin(position, identifier)) {
            helper.addIdentifier(expression, identifier);
        }
        // After "<identifier>(" or "<identifier> "
        else if (helper.hasDelimiterAfterIdentifier(expression)) {
            int length = !hasIdentifier ? 0 : identifier.length() + 1 /* delimiter, either space or ( */;
            length += helper.preExpressionLength(expression);

            // Right after "<identifier>(" or "<identifier> "
            if (position == length) {
                helper.addTheBeginningOfChild(expression, null, 0, false);
            }
            // Within the encapsulated expressions
            else {

                // Create a collection representation of the encapsulated expression(s)
                CollectionExpression collectionExpression = helper.buildCollectionExpression(expression);
                boolean hasComma = false;
                boolean previousHasComma = false;

                // Determine the maximum children count, it is possible the query contains more children
                // than the expession's grammar would actually allow. The content assist will only
                // provide assistance from the first child to the last allowed child
                int childrenCount = collectionExpression.childrenSize();
                int count = Math.min(childrenCount, helper.maxCollectionSize(expression));

                // Iterate through each child of the collection
                for (int index = 0; index < count; index++) {

                    Expression child = collectionExpression.getChild(index);
                    int childLength = 0;

                    // At the beginning of the child
                    if (position == length) {
                        helper.addTheBeginningOfChild(expression, collectionExpression, index, hasComma);
                        break;
                    }
                    // Each expression within the collection has to be separated by a comma, the previous
                    // expression and the expression at the current index are not separated by a comma
                    // Example: "SELECT e FROM Employee e GROUP" <- [ "Employee e", "GROUP" ]
                    else if ((index > 0) && !hasComma) {

                        length += child.getLength();

                        // At the end of the child
                        if (position == length) {
                            helper.addAtTheEndOfChild(expression, collectionExpression, index, hasComma, false);
                            break;
                        }

                        // To be valid, each child has to be separated by a comma,
                        // ask the helper if it should continue with the next child
                        if (!helper.canContinue(expression, collectionExpression, index)) {
                            break;
                        }
                        // Special case when reaching the end of the collection
                        else if (index + 1 == count) {
                            helper.addAtTheEndOfChild(expression, collectionExpression, index, hasComma, false);
                        }
                    }
                    else {
                        childLength = child.getLength();

                        // At the end of the child
                        if ((position == length + childLength) ||
                            (virtualSpace && (position == length + childLength + SPACE_LENGTH))) {

                            helper.addAtTheEndOfChild(expression, collectionExpression, index, hasComma, virtualSpace);
                            break;
                        }
                    }

                    // Now add the child's length and length used by the comma and space
                    length += childLength;

                    // Move after the comma
                    previousHasComma = hasComma;
                    hasComma = collectionExpression.hasComma(index);

                    if (hasComma) {

                        // Two items were not separated by a comma and the next one is, this is invalid
                        if ((index > 0) && !previousHasComma) {
                            return;
                        }

                        length++;

                        // After the comma, add the proposals
                        if (position == length) {
                            helper.addTheBeginningOfChild(expression, collectionExpression, index + 1, hasComma);
                            break;
                        }
                    }

                    // Move after the space that follows the comma
                    if (collectionExpression.hasSpace(index)) {
                        length++;

                        // At the end of the child
                        if (!hasComma && (position == length)) {
                            helper.addAtTheEndOfChild(expression, collectionExpression, index, hasComma, true);
                        }
                    }

                    // Nothing more can be looked at
                    if (position < length) {
                        break;
                    }
                }
            }
        }
    }

    protected void visitEndingExpression(Expression expression) {

        // Keep track of the original QueryPosition, the new one will differ
        // to properly accommodate for the invalid portion of the query
        QueryPosition oldQueryPosition = queryPosition;

        //
        // Step 1
        //
        // Create a new QueryPosition for which the positions is set to be at the end
        // of the valid fragment of the JPQL query, which is up to right before the
        // beginning of the invalid fragment
        // Example: "SELECT e FROM Employee e S| WHERE e.name = 'JPQL'" <- | is the position of the cursor
        //          Valid fragment: "SELECT e FROM Employee e "
        //          Invalid fragment: "S WHERE e.name = 'JPQL'"
        queryPosition = buildEndingPositionFromInvalidExpression(expression, expression, new boolean[1]);

        // Adjust the position to include the whitespace owned by the parent expression
        expression = queryPosition.getExpression();

        while (expression != null) {

            int position = queryPosition.getPosition(expression);

            if (position == -1) {
                expression = null;
            }
            else {
                queryPosition.addPosition(expression, position + 1);
                expression = expression.getParent();
            }
        }

        //
        // Step 2
        //
        // Now make sure the correction is reset temporarily
        corrections.add(0);
        addVirtualSpace();

        // Now traverse the tree starting at the leaf from the valid fragment
        queryPosition.getExpression().accept(this);

        // Revert the data
        queryPosition = oldQueryPosition;
        corrections.pop();
        removeVirtualSpace();
    }

    protected void visitEnumConstant(AbstractPathExpression expression) {

        int position = queryPosition.getPosition(expression);
        String text = expression.toActualText();
        int lastDotIndex = text.lastIndexOf(DOT);

        // Check to see if an enum constant can be used at the expression's location
        if (isEnumAllowed(expression)) {
            boolean enumConstant = false;

            // The position is after the last dot, check for enum constants
            if (position > lastDotIndex) {

                // Retrieve the enum type if the path up to the last dot is a fully qualified enum type
                String enumType = expression.toParsedText().substring(0, lastDotIndex);
                IType type = queryContext.getType(enumType);

                // The path expression before the last dot is an enum type
                if (type.isResolvable() && type.isEnum()) {
                    enumConstant = true;

                    // Now retrieve the portion of the enum constant based on the cursor position
                    String word = text.substring(lastDotIndex + 1, position);

                    // Add the enum constants and filter them based on what's already proposed
                    addEnumConstants(type, word);
                }
            }

            // Enum type
            if (!enumConstant) {

                // Now retrieve the portion of the enum constant based on the cursor position
                text = text.substring(0, position);

                // Set the possible starting of a fully qualified enum type
                proposals.setClassNamePrefix(text, ClassType.ENUM);
            }
        }
    }

    protected void visitInvalidExpression(Expression expression) {

        if (!isLocked(expression)) {
            int position = queryPosition.getPosition(expression) - corrections.peek();
            boolean virtualSpace = (position == 1) && (word.length() == 0);

            // 1. Within the first word of the invalid fragment
            // 2. Or after the ending whitespace
            // Otherwise no need to do anything if beyond it
            if (isPositionWithin(position, word) || virtualSpace) {

                lockedExpressions.add(expression);

                // Keep track of the original QueryPosition, the new one will differ
                // to properly accommodate for the invalid portion of the query
                QueryPosition oldQueryPosition = queryPosition;

                boolean[] spaces = { false };

                //
                // Step 1
                //
                // Create a new QueryPosition for which the positions is set to be at the end
                // of the valid fragment of the JPQL query, which is up to right before the
                // beginning of the invalid fragment
                // Example: "SELECT e FROM Employee e S| WHERE e.name = 'JPQL'" <- | is the position of the cursor
                //          Valid fragment: "SELECT e FROM Employee e "
                //          Invalid fragment: "S WHERE e.name = 'JPQL'"
                queryPosition = buildEndingPositionFromInvalidExpression(expression, expression.getRoot(), spaces);

                //
                // Step 2
                //
                // Now make sure the correction is reset temporarily
                corrections.add(0);

                // This means the valid fragment ends with a whitespace, keep track of it
                if (spaces[0] || virtualSpace) {
                    addVirtualSpace();
                }

                // Now traverse the tree starting at the leaf from the valid fragment
                queryPosition.getExpression().accept(this);

                // Revert the data
                queryPosition = oldQueryPosition;
                corrections.pop();
                lockedExpressions.pop();

                if (spaces[0] || virtualSpace) {
                    removeVirtualSpace();
                }
            }
        }
    }

    /**
     * Visits the given {@link LogicalExpression} and attempts to find valid proposals.
     *
     * @param expression The {@link LogicalExpression} to inspect
     */
    protected void visitLogicalExpression(LogicalExpression expression) {

        if (isFollowingInvalidExpression(expression)) {
            return;
        }

        int position = queryPosition.getPosition(expression) - corrections.peek();
        String identifier = expression.getIdentifier();
        int length = 0;

        if (expression.hasLeftExpression()) {
            length += expression.getLeftExpression().getLength() + SPACE_LENGTH;
        }

        // Within "AND" or "OR"
        if (isPositionWithin(position, length, identifier)) {
            proposals.addIdentifier(identifier);
        }
        // After "AND " or "OR "
        else if (expression.hasSpaceAfterIdentifier()) {
            length += identifier.length() + SPACE_LENGTH;

            // Right after "AND " or "OR "
            if (position == length) {
                addIdentificationVariables();
                addFunctionIdentifiers(expression.getRightExpressionQueryBNFId());
            }
        }
    }

    /**
     * Visits the given {@link AbstractPathExpression} and attempts to find valid proposals.
     * <p>
     * Note: A path expression can represent many things: state field, relationship field, collection
     * field, enum constant, etc. This will consider all variations.
     *
     * @param expression The {@link AbstractPathExpression} to inspect
     */
    protected void visitPathExpression(AbstractPathExpression expression) {

        int position = queryPosition.getPosition(expression);
        String text = expression.toActualText();
        int dotIndex = text.indexOf(DOT);

        if (position > -1) {

            String variableName = queryContext.literal(
                expression.getIdentificationVariable(),
                LiteralType.IDENTIFICATION_VARIABLE
            );

            boolean variable = ExpressionTools.stringIsNotEmpty(variableName);

            // The position if after the identification variable
            if ((dotIndex > -1) && (position > dotIndex)) {

                // Retrieve the filter based on the location of the state field path, for instance, in
                // a JOIN or IN expression, the filter has to filter out the property and accept the
                // fields of collection type
                visitPathExpression(expression, buildMappingFilter(expression));

                // Don't do anything if the first path is not an identification variable,
                // which means it's either KEY() or VALUE()
                if (variable) {

                    // Attempts to resolve a possible fully qualified enum constant
                    visitEnumConstant(expression);

                    // Attempts to resolve third party option
                    visitThirdPartyPathExpression(expression, variableName);
                }
            }
            // The position is within the identification variable but don't do anything if the
            // identification variable is either KEY() or VALUE()
            else if (variable) {
                corrections.add(queryPosition.getPosition(expression));
                visit(expression);
                corrections.pop();
            }
        }
    }

    /**
     * Visits the given {@link AbstractPathExpression} by attempting to resolve the path.
     *
     * @param expression The {@link AbstractPathExpression} to inspect
     * @param filter The {@link Filter} is used to filter out {@link IMapping} that are not valid
     * based on their type and the type that is allowed
     */
    protected void visitPathExpression(AbstractPathExpression expression, Filter<IMapping> filter) {

        MappingCollector mappingCollector = getDefaultMappingCollector();
        int position = queryPosition.getPosition(expression);
        boolean mappingCollectorCreated = false;
        Resolver resolver = null;
        int length = 0;

        for (int index = 0, count = expression.pathSize(); index < count; index++) {
            String path = expression.getPath(index);

            // We're at the position, create the ChoiceBuilder
            if (position <= length + path.length()) {

                if (length == position) {
                    path = ExpressionTools.EMPTY_STRING;
                }
                else if (position - length > -1) {
                    path = path.substring(0, position - length);
                }

                // Special case where the path expression only has the
                // identification variable set
                if (resolver == null) {
                    break;
                }

                mappingCollector = buildFilteringMappingCollector(expression, resolver, filter, path);
                mappingCollectorCreated = true;
                break;
            }
            // The path is entirely before the position of the cursor
            else {
                // The first path is always an identification variable
                if (resolver == null) {
                    resolver = queryContext.getResolver(expression.getIdentificationVariable());
                }
                // Any other path is a property or collection-valued path
                else if ((index + 1 < count) || expression.endsWithDot()) {
                    Resolver childResolver = resolver.getChild(path);
                    if (childResolver == null) {
                        childResolver = new StateFieldResolver(resolver, path);
                        resolver.addChild(path, childResolver);
                        resolver = childResolver;
                    }
                }

                // Move the cursor after the path and dot
                length += path.length() + 1;
            }
        }

        if (!mappingCollectorCreated && (resolver != null)) {
            mappingCollector = buildMappingCollector(expression, resolver, filter);
        }

        proposals.addMappings(mappingCollector.buildProposals());
    }

    /**
     * Adds the possible proposals for the given {@link AbstractSingleEncapsulatedExpression expression}
     * based on the location of the cursor and the content of the expression.
     *
     * @param expression The {@link AbstractSingleEncapsulatedExpression expression} being visited
     * @param identificationVariableType The type of identification variables that can be added as
     * possible proposals
     */
    protected void visitSingleEncapsulatedExpression(AbstractSingleEncapsulatedExpression expression,
                                                     IdentificationVariableType identificationVariableType) {

        visitSingleEncapsulatedExpression(
            expression,
            identificationVariableType,
            expression.getIdentifier()
        );
    }

    /**
     * Adds the possible proposals for the given {@link AbstractSingleEncapsulatedExpression expression}
     * based on the location of the cursor and the content of the expression.
     *
     * @param expression The {@link AbstractSingleEncapsulatedExpression expression} being visited
     * @param identificationVariableType The type of identification variables that can be added as
     * possible proposals
     * @param expressionIdentifiers Sometimes the expression may have more than one possible identifier,
     * such as <b>ALL</b>, <b>ANY</b> and <b>SOME</b> are a possible JPQL identifier for a single
     * expression ({@link AllOrAnyExpression}
     */
    protected void visitSingleEncapsulatedExpression(AbstractSingleEncapsulatedExpression expression,
                                                     IdentificationVariableType identificationVariableType,
                                                     String... expressionIdentifiers) {

        if (isFollowingInvalidExpression(expression)) {
            return;
        }

        int position = queryPosition.getPosition(expression) - corrections.peek();
        String actualIdentifier = expression.getIdentifier();
        boolean added = false;

        for (String identifier : expressionIdentifiers) {

            // Within the identifier
            if (isPositionWithin(position, actualIdentifier)) {

                addFunctionIdentifiers(expression);

                // If the expression is marked as bad, then the identifiers
                // for the encapsulated expression are no added
                if (!isWithinInvalidExpression(expression)) {
                    for (String jpqlIdentifier : expressionIdentifiers) {
                        proposals.addIdentifier(identifier);
                    }
                }
                // Remove any identifier that got added by addAdditionalFunctions()
                else {
                    for (String jpqlIdentifier : expressionIdentifiers) {
                        proposals.removeIdentifier(identifier);
                    }
                }
            }
            // Right after "<identifier>("
            else if (expression.hasLeftParenthesis()) {
                int length = identifier.length() + 1 /* '(' */;

                if (!added && (position == length)) {
                    added = true;

                    addIdentificationVariables(expression, identificationVariableType);

                    String queryBNF = expression.getEncapsulatedExpressionQueryBNFId();
                    addFunctionIdentifiers(queryBNF);
                    addClauseIdentifiers(queryBNF);
                }
            }
        }
    }

    /**
     * Visits the given {@link AbstractSelectStatement} and checks to see if the identifiers of the
     * following clauses can be added a valid proposals.
     *
     * @param expression
     * @param helper This helper handles one clause from the given <code><b>SELECT</b></code> statement
     */
    @SuppressWarnings("unchecked")
    protected <T extends Expression> void visitStatement(T expression, StatementHelper<T> helper) {

        lockedExpressions.add(expression);

        try {
            int position = queryPosition.getPosition(expression);
            int length = 0;

            while (helper != null) {

                // Add the length of the clause to the current length
                Expression clause = helper.getClause(expression);
                length += clause.getLength();

                // Within the clause, not handled here
                if (position < length) {
                    break;
                }

                // Now check if the clause is complete
                boolean complete = false;

                // At the end of the clause
                if (position == length) {
                    // Check to see if the following clause identifiers can be appended
                    // Example: "SELECT e f|"                 <- FROM can be a valid proposal
                    // Example: "SELECT AVG(e.age)|"          <- No clause identifiers can be added
                    // Example: "SELECT e FROM Employee e|"   <- No clause identifiers can be added
                    // Example: "SELECT e FROM Employee e H|" <- HAVING can be a valid proposal
                    complete = isClauseAppendable(clause);
                }
                // Check whether a whitespace is after the clause (owned by the select statement)
                else if (helper.hasSpaceAfterClause(expression)) {
                    length++;

                    if (position == length) {

                        // Ask the helper to visit the clause's expression
                        // Example: "SELECT e FROM Employee e WHERE e.name = 'JPQL' |"
                        //           AND|OR can be added as valid proposals
                        visitEndingExpression(clause);

                        // Check to see if the following clause identifiers can be appended
                        // Example: "SELECT e |"                  <- FROM can be a valid proposal
                        // Example: "SELECT AVG(e.age) |"         <- FROM can be a valid proposal
                        // Example: "SELECT e FROM Employee e |"  <- The following clause identifiers can be valid proposals
                        complete = helper.isClauseComplete(expression);
                    }
                }

                // Continue to with the next helper
                if (!complete) {
                    helper = (StatementHelper<T>) helper.getNextHelper();
                }
                // Add the following clause identifiers until one is defined
                else {

                    // Append any internal clause identifiers
                    helper.addInternalClauseProposals(expression);

                    // Iterate through the helpers for the following clauses
                    do {
                        helper = (StatementHelper<T>) helper.getNextHelper();

                        if (helper != null) {

                            // Add the clause identifiers
                            helper.addClauseProposals();

                            // The following clauses cannot be added because either
                            // the clause is required or defined
                            if (helper.isRequired() || helper.hasClause(expression)) {
                                helper = null;
                            }
                        }

                    }
                    while (helper != null);
                }
            }
        }
        finally {
            lockedExpressions.pop();
        }
    }

    /**
     * Visits the given {@link AbstractPathExpression} and attempts to find valid proposals that is
     * not provided by the default implementation. Subclasses can add additional proposals that is
     * outside of the scope of generic JPA metadata.
     *
     * @param expression The {@link AbstractPathExpression} to inspect
     * @param variableName The beginning of the path expression
     */
    protected void visitThirdPartyPathExpression(AbstractPathExpression expression,
                                                 String variableName) {
    }

    // Made static for performance reasons.
    protected static class AbstractAppendableExpressionVisitor extends AnonymousExpressionVisitor {

        /**
         * Flag used to determine if JPQL identifiers can be appended to the expression.
         */
        protected boolean appendable;

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

        /**
         * Determines whether JPQL identifiers can be appended to the expression.
         *
         * @return <code>true</code>
         */
        public boolean isAppendable() {
            return appendable;
        }
    }

    // Made static for performance reasons.
    /**
     * Common helper with visitor reference.
     */
    private static abstract class AbstractVisitorHelper {

        /**
         * Enclosing visitor instance.
         */
        protected final AbstractContentAssistVisitor visitor;

        AbstractVisitorHelper(AbstractContentAssistVisitor visitor) {
            this.visitor = visitor;
        }

    }

    // Made static for performance reasons.
    /**
     * This helper handles adding proposals within a conditional expression that might be parsed as
     * a single expression or has a collection of expression, which means the fragment is either
     * incomplete or invalid.
     */
    protected static abstract class AbstractConditionalClauseCollectionHelper<T extends Expression>
            extends AbstractVisitorHelper implements CollectionExpressionHelper<T> {

        protected AbstractConditionalClauseCollectionHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addAtTheEndOfChild(Expression expression,
                                       CollectionExpression collectionExpression,
                                       int index,
                                       boolean hasComma,
                                       boolean virtualSpace) {

            // Coming from addTheBeginningOfChild(), require to bypass the above check
            if (index < 0) {
                index = (-index / 10) - 1;
            }

            // The only thing that is appendable is an arithmetic operator
            // Example: "SELECT e FROM Employee e WHERE e.name|"
            // Example: "SELECT e FROM Employee e WHERE I|"
            if ((index == 0) && !virtualSpace) {

                Expression child = collectionExpression.getChild(0);

                if (visitor.areArithmeticSymbolsAppendable(child)) {
                    visitor.addArithmeticIdentifiers();
                }
            }
            else {

                Object[] result = findChild(collectionExpression, index);

                if (result == null) {
                    return;
                }

                Expression child = (Expression) result[0];
                boolean hasIs  = (Boolean) result[1];
                boolean hasNot = (Boolean) result[2];

                // If 'IS' or 'IS NOT' is present, then none of the following are valid proposals
                if (!hasIs && !hasNot) {

                    if (visitor.areLogicalSymbolsAppendable(child)) {
                        visitor.addLogicalIdentifiers();
                    }

                    if (visitor.areArithmeticSymbolsAppendable(child)) {
                        visitor.addArithmeticIdentifiers();
                    }

                    if (visitor.areComparisonSymbolsAppendable(child)) {
                        visitor.addComparisonIdentifiers(child);
                    }
                }

                if (visitor.isCompoundable(child)) {
                    visitor.addCompoundIdentifiers(ConditionalExpressionBNF.ID, child, hasIs, hasNot);
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        public void addIdentifier(Expression expression, String identifier) {
            visitor.proposals.addIdentifier(identifier);
        }

        /**
         * {@inheritDoc}
         */
        public void addTheBeginningOfChild(Expression expression,
                                           CollectionExpression collectionExpression,
                                           int index,
                                           boolean hasComma) {

            if (index == 0) {

                visitor.addIdentificationVariables();
                visitor.addFunctionIdentifiers(ConditionalExpressionBNF.ID);

                if ((collectionExpression != null) &&
                        visitor.isSubqueryAppendable(collectionExpression.getChild(index))) {

                    visitor.addIdentifier(SELECT);
                }
            }
            else {
                addAtTheEndOfChild(expression, collectionExpression, index * -10, hasComma, true);
            }
        }

        /**
         * {@inheritDoc}
         */
        public boolean canContinue(Expression expression,
                                   CollectionExpression collectionExpression,
                                   int index) {

            Expression child = collectionExpression.getChild(index);

            if (visitor.isNotExpression(child)) {
                return true;
            }

            String text = child.toParsedText();

            return text.equalsIgnoreCase(IS)  ||
                   text.equalsIgnoreCase(NOT) ||
                   text.equalsIgnoreCase("IS NOT");
        }

        /**
         * Handles a special case for a compound identifier like 'IS EMPTY' or 'IS NOT EMPTY'.
         *
         * @param collectionExpression The {@link CollectionExpression} is used to find the {@link
         * Expression} to use when filtering out compound identifier
         * @param index The position to start the search, which goes from that index to the beginning
         * @return An array of three elements. The first one is the child {@link Expression} that can
         * be used when filtering out compound identifier. The second boolean element indicates if
         * <code><b>IS</b></code> was detected after the child. The third boolean element indicates if
         * <code><b>NOT</b></code> was detected after the child, which would also be after <code><b>IS</b></code>
         * if it was detected. <code>null</code> is returned if nothing could be found
         */
        protected Object[] findChild(CollectionExpression collectionExpression, int index) {

            boolean notFound = false;
            boolean isFound = false;
            boolean scanPrevious = false;

            for (; index > -1; index--) {

                Expression child = collectionExpression.getChild(index);
                String text = child.toParsedText();

                // Handle 'NOT'
                if (text.equalsIgnoreCase(NOT) || visitor.isNotExpression(child)) {

                    // Two consecutive 'NOT' or 'IS' is invalid or 'NOT IS' is not valid
                    if (isFound || notFound) {
                        break;
                    }

                    notFound = true;
                }
                // Handle 'IS'
                else if (text.equalsIgnoreCase(IS)) {

                    // Two consecutive 'IS' is invalid
                    if (isFound) {
                        break;
                    }

                    isFound = true;
                }
                else if ("IS NOT".equalsIgnoreCase(text)) {

                    // Two consecutive 'NOT' or 'IS' is invalid or 'NOT IS' is not valid
                    if (isFound || notFound) {
                        break;
                    }

                    isFound  = true;
                    notFound = true;
                }
                // Anything else
                else {

                    // Make sure the previous item is not 'IS', this can happen
                    // when the correction value is changed (happens with IdentificationVariable)
                    if (index > 0) {
                        Object[] result = findChild(collectionExpression, index - 1);
                        isFound  |= (Boolean) result[1];
                        notFound |= (Boolean) result[2];
                    }

                    return new Object[] { child, isFound, notFound };
                }
            }

            return null;
        }

        /**
         * {@inheritDoc}
         */
        public int maxCollectionSize(Expression expression) {
            // The actual number is 0 but an incomplete fragment like "WHERE e.phoneNumbers IS N"
            // is a collection of 3 expressions
            return Integer.MAX_VALUE;
        }

        /**
         * {@inheritDoc}
         */
        public int preExpressionLength(Expression expression) {
            return 0;
        }

        /**
         * {@inheritDoc}
         */
        public JPQLQueryBNF queryBNF(Expression expression, int index) {
            return visitor.getQueryBNF(ConditionalExpressionBNF.ID);
        }
    }

    // Made static for performance reasons.
    protected static abstract class AbstractFromClauseStatementHelper<T extends AbstractSelectStatement>
            extends AbstractVisitorHelper implements StatementHelper<T> {

        protected AbstractFromClauseStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addClauseProposals() {
            visitor.addIdentifier(FROM);
        }

        /**
         * {@inheritDoc}
         */
        public void addInternalClauseProposals(T expression) {
            // Does not have internal clauses
        }

        /**
         * {@inheritDoc}
         */
        public Expression getClause(T expression) {
            return expression.getFromClause();
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasClause(T expression) {
            return expression.hasFromClause();
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasSpaceAfterClause(T expression) {
            return expression.hasSpaceAfterFrom();
        }

        /**
         * {@inheritDoc}
         */
        public boolean isClauseComplete(T expression) {
            return visitor.isComplete(expression.getFromClause());
        }

        /**
         * {@inheritDoc}
         */
        public boolean isRequired() {
            return true;
        }
    }

    // Made static for performance reasons.
    protected static abstract class AbstractGroupByClauseStatementHelper<T extends AbstractSelectStatement>
            extends AbstractVisitorHelper implements StatementHelper<T> {

        protected AbstractGroupByClauseStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addClauseProposals() {
            visitor.addCompositeIdentifier(GROUP_BY, -1);
        }

        /**
         * {@inheritDoc}
         */
        public void addInternalClauseProposals(T expression) {
            // Does not have internal clauses
        }

        /**
         * {@inheritDoc}
         */
        public Expression getClause(T expression) {
            return expression.getGroupByClause();
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasClause(T expression) {
            return expression.hasGroupByClause();
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasSpaceAfterClause(T expression) {
            return expression.hasSpaceAfterGroupBy();
        }

        /**
         * {@inheritDoc}
         */
        public boolean isClauseComplete(T expression) {
            return visitor.isComplete(expression.getGroupByClause());
        }

        /**
         * {@inheritDoc}
         */
        public boolean isRequired() {
            return false;
        }
    }

    // Made static for performance reasons.
    protected static abstract class AbstractHavingClauseStatementHelper<T extends AbstractSelectStatement>
            extends AbstractVisitorHelper implements StatementHelper<T> {

        protected AbstractHavingClauseStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addClauseProposals() {
            visitor.addIdentifier(HAVING);
        }

        /**
         * {@inheritDoc}
         */
        public void addInternalClauseProposals(T expression) {
            // Does not have internal clauses
        }

        /**
         * {@inheritDoc}
         */
        public Expression getClause(T expression) {
            return expression.getHavingClause();
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasClause(T expression) {
            return expression.hasHavingClause();
        }

        /**
         * {@inheritDoc}
         */
        public boolean isClauseComplete(T expression) {
            return  visitor.isComplete(expression.getHavingClause());
        }

        /**
         * {@inheritDoc}
         */
        public boolean isRequired() {
            return false;
        }
    }

    // Made static for performance reasons.
    protected static abstract class AbstractSelectClauseCollectionHelper<T extends AbstractSelectClause>
            extends AbstractVisitorHelper implements CollectionExpressionHelper<T> {

        protected AbstractSelectClauseCollectionHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addAtTheEndOfChild(T expression,
                                       CollectionExpression collectionExpression,
                                       int index,
                                       boolean hasComma,
                                       boolean virtualSpace) {

            // The only thing that is appendable is an arithmetic operator
            // Example: "SELECT e.name|"
            // Example: "SELECT e|"
            if (queryBNF(expression, index).handleAggregate()) {

                Expression child = collectionExpression.getChild(index);

                if (visitor.areArithmeticSymbolsAppendable(child)) {
                    visitor.addArithmeticIdentifiers();
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        public void addIdentifier(T expression, String identifier) {
            visitor.proposals.addIdentifier(identifier);
        }

        /**
         * {@inheritDoc}
         */
        public void addTheBeginningOfChild(T expression,
                                           CollectionExpression collectionExpression,
                                           int index,
                                           boolean hasComma) {

            if ((index == 0) || hasComma) {
                visitor.addIdentificationVariables();
                visitor.addFunctionIdentifiers(expression.getSelectItemQueryBNFId());
            }
        }

        /**
         * {@inheritDoc}
         */
        public CollectionExpression buildCollectionExpression(T expression) {
            CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getSelectExpression());
            if (collectionExpression == null) {
                collectionExpression = expression.buildCollectionExpression();
            }
            return collectionExpression;
        }

        /**
         * {@inheritDoc}
         */
        public boolean canContinue(T expression, CollectionExpression collectionExpression, int index) {
            return false;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasDelimiterAfterIdentifier(T expression) {
            return expression.hasSpaceAfterSelect();
        }

        /**
         * {@inheritDoc}
         */
        public int maxCollectionSize(T expression) {
            return Integer.MAX_VALUE;
        }

        /**
         * {@inheritDoc}
         */
        public int preExpressionLength(T expression) {
            return 0;
        }

        /**
         * {@inheritDoc}
         */
        public JPQLQueryBNF queryBNF(T expression, int index) {
            return visitor.getQueryBNF(expression.getSelectItemQueryBNFId());
        }
    }

    // Made static for performance reasons.
    protected static abstract class AbstractSelectClauseStatementHelper
            extends AbstractVisitorHelper implements StatementHelper<AbstractSelectStatement> {

        protected AbstractSelectClauseStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addClauseProposals() {
            visitor.addIdentifier(SELECT);
        }

        /**
         * {@inheritDoc}
         */
        public void addInternalClauseProposals(AbstractSelectStatement expression) {
            // Does not have internal clauses
        }

        /**
         * {@inheritDoc}
         */
        public Expression getClause(AbstractSelectStatement expression) {
            return expression.getSelectClause();
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasClause(AbstractSelectStatement expression) {
            return expression.hasSelectClause();
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasSpaceAfterClause(AbstractSelectStatement expression) {
            return expression.hasSpaceAfterSelect();
        }

        /**
         * {@inheritDoc}
         */
        public boolean isClauseComplete(AbstractSelectStatement expression) {
            return visitor.isComplete(expression.getSelectClause());
        }

        /**
         * {@inheritDoc}
         */
        public boolean isRequired() {
            return true;
        }

    }

    // Made static for performance reasons.
    protected static abstract class AbstractWhereClauseSelectStatementHelper<T extends AbstractSelectStatement>
            extends AbstractVisitorHelper implements StatementHelper<T> {

        protected AbstractWhereClauseSelectStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addClauseProposals() {
            visitor.addIdentifier(WHERE);
        }

        /**
         * {@inheritDoc}
         */
        public void addInternalClauseProposals(T expression) {
            // Does not have internal clauses
        }

        /**
         * {@inheritDoc}
         */
        public Expression getClause(T expression) {
            return expression.getWhereClause();
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasClause(T expression) {
            return expression.hasWhereClause();
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasSpaceAfterClause(T expression) {
            return expression.hasSpaceAfterWhere();
        }

        /**
         * {@inheritDoc}
         */
        public boolean isClauseComplete(T expression) {
            return visitor.isComplete(expression.getWhereClause());
        }

        /**
         * {@inheritDoc}
         */
        public boolean isRequired() {
            return false;
        }
    }

    // Made static for performance reasons.
    /**
     * This visitor retrieves the permitted type from the path expression's parent. For instance,
     * <b>SUM</b> or <b>AVG</b> only accepts state fields that have a numeric type.
     */
    protected static abstract class AcceptableTypeVisitor extends AbstractExpressionVisitor {

        /**
         * The type that is retrieved based on the expression, it determines what is acceptable.
         */
        protected IType type;

        /**
         * Disposes of the internal data.
         */
        public void dispose() {
            type = null;
        }

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

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

    // Made static for performance reasons.
    /**
     * This visitor scans the visited {@link Expression} and determines if a JPQL identifier can be
     * added ("appended") when the position of the cursor is at the end of the expression.
     * <p>
     * For instance:
     * <ul>
     * <li>In "<code>SELECT e, AVG(e.age) F|</code>", F is parsed as a result variable but
     * can also be seen as the first letter for <b>FROM</b>;</li>
     * <li>In "<code>SELECT e FROM Employee e WHERE e.name |</code>", the compound identifiers can be
     * added, eg: 'IS NOT NULL', or '=', etc</li>
     * <li>In "<code>SELECT e FROM Employee e WHERE e.name NOT B|</code>", only the composite
     * identifier "BETWEEN" and "NOT BETWEEN" can be added because the <code>NOT</code> expression
     * does not have a valid expression: "B" is not a valid expression.</li>
     * <li>In "<code>SELECT e FROM Employee e FROM e.age|</code>", the arithmetic and comparison
     * identifiers are allowed, but the logical and compound identifiers.</li>
     * </ul>
     */
    protected static class AppendableExpressionVisitor extends AbstractAppendableExpressionVisitor {

        /**
         * Enclosing visitor instance.
         */
        protected final AbstractContentAssistVisitor visitor;

        /**
         * The type of the JPQL identifiers can can be possible proposals.
         */
        protected AppendableType appendableType;

        /**
         * Internal flag indicating if a clause is being visited which can have a collection of children.
         */
        protected boolean clauseOfItems;

        /**
         * Caches the visited {@link CollectionExpression} so a child could use it.
         */
        protected CollectionExpression collectionExpression;

        /**
         * Internal flag indicating the {@link Expression} being visited is a conditional expression.
         */
        protected boolean conditionalExpression;

        /**
         * When visiting a {@link CollectionExpression}, this indicates if there is a comma before the
         * child being visited.
         */
        protected boolean hasComma;

        /**
         * When visiting a {@link CollectionExpression}, this indicates the position within that
         * collection of the child being visited.
         */
        protected int positionInCollection;

        /**
         * Internal flag indicating the {@link Expression} being visited is encapsulated by parenthesis.
         */
        protected boolean subExpression;

        /**
         * Creates a new <code>AppendableExpressionVisitor</code>.
         */
        AppendableExpressionVisitor(AbstractContentAssistVisitor visitor) {
            super();
            this.visitor = visitor;
            this.positionInCollection = -1;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(AbsExpression expression) {
            appendable = !conditionalExpression &&
                         expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(AbstractSchemaName expression) {
            appendable = !conditionalExpression;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(AdditionExpression expression) {
            if (expression.hasRightExpression()) {
                expression.getRightExpression().accept(this);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(AllOrAnyExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(AndExpression expression) {
            if (expression.hasRightExpression()) {
                expression.getRightExpression().accept(this);
            }
        }

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

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(AvgFunction expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

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

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(BetweenExpression expression) {

            // Example 2: "x BETWEEN y A" is not complete
            // Example 1: "x between y SQRT(e.age)" is seen as complete
            if (!expression.hasAnd()) {

                String variable = visitor.queryContext.literal(
                    expression.getUpperBoundExpression(),
                    LiteralType.IDENTIFICATION_VARIABLE
                );

                if (variable != ExpressionTools.EMPTY_STRING) {
                    appendable = false;
                }
                else {
                    expression.getUpperBoundExpression().accept(this);
                }
            }
            else {
                boolean oldConditionalExpression = conditionalExpression;
                conditionalExpression = false;
                expression.getUpperBoundExpression().accept(this);
                conditionalExpression = oldConditionalExpression;
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CaseExpression expression) {
            appendable = !conditionalExpression &&
                         expression.hasEnd();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CoalesceExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CollectionExpression expression) {

            collectionExpression = expression;
            positionInCollection = expression.childrenSize() - 1;
            hasComma = expression.hasComma(positionInCollection - 1);

            if ((appendableType == AppendableType.CLAUSE) &&
                (positionInCollection + 1 < collectionExpression.childrenSize())) {

                appendable = false;
            }
            else {
                expression.accept(positionInCollection, this);
            }

            hasComma = false;
            positionInCollection = -1;
            collectionExpression = null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CollectionMemberDeclaration expression) {

            if (appendableType == AppendableType.COMPLETE) {
                if (expression.hasIdentificationVariable()) {
                    expression.getIdentificationVariable().accept(this);
                }
            }
            else {
                // Only the identifier is parsed
                appendable = !expression.hasAs()                         &&
                             !expression.hasSpaceAfterIn()               &&
                             !expression.hasLeftParenthesis()            &&
                             !expression.hasRightParenthesis()           &&
                             !expression.hasIdentificationVariable()     &&
                             !expression.hasSpaceAfterRightParenthesis() &&
                             !expression.hasCollectionValuedPathExpression();
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CollectionMemberExpression expression) {

            // Only AND, OR, NOT or clause identifiers can be followed this expression
            appendable = (appendableType == AppendableType.LOGICAL   ||
                          appendableType == AppendableType.COMPLETE) &&
                          expression.hasCollectionValuedPathExpression();

            if (appendable) {
                boolean oldConditionalExpression = conditionalExpression;
                conditionalExpression = false;
                expression.getCollectionValuedPathExpression().accept(this);
                conditionalExpression = oldConditionalExpression;
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CollectionValuedPathExpression expression) {
            appendable = !conditionalExpression &&
                      !expression.endsWithDot();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(ComparisonExpression expression) {

            if (appendableType == AppendableType.ARITHMETIC ||
                appendableType == AppendableType.COMPARISON ||
                appendableType == AppendableType.COMPOUNDABLE) {

                appendable = false;
            }
            // "x = y |" <- AND and OR are valid proposals
            // "(x = y) |" <- AND and OR are valid proposals
            else if (appendableType == AppendableType.LOGICAL) {
                AppendableType oldAppendableType = appendableType;
                appendableType = AppendableType.COMPLETE;
                expression.getRightExpression().accept(this);
                appendableType = oldAppendableType;
            }
            else if (expression.hasRightExpression()) {
                boolean oldConditionalExpression = conditionalExpression;
                conditionalExpression = false;
                expression.getRightExpression().accept(this);
                conditionalExpression = oldConditionalExpression;
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(ConcatExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(ConstructorExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CountFunction expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(DateTime expression) {

            if (conditionalExpression && (appendableType == AppendableType.COMPLETE)) {
                appendable = false;
            }
            else {
                appendable = // Valid ending
                             (!expression.isJDBCDate()                    ||
                              expression.toActualText().endsWith("}"))    &&
                             // Valid AppendableType
                             (appendableType == AppendableType.ARITHMETIC ||
                              appendableType == AppendableType.COMPARISON ||
                              appendableType == AppendableType.COMPLETE   ||
                              appendableType == AppendableType.COMPOUNDABLE);
            }
        }

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

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

            if (expression.hasWhereClause()) {
                expression.getWhereClause().accept(this);
            }
            else {
                expression.getDeleteClause().accept(this);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(DivisionExpression expression) {
            if (expression.hasRightExpression()) {
                expression.getRightExpression().accept(this);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(EmptyCollectionComparisonExpression expression) {

            // Only AND, OR, NOT or clause identifiers can follow this expression
            // when the type is LOGICAL or CLAUSE
            appendable = appendableType == AppendableType.COMPLETE ||
                         appendableType == AppendableType.LOGICAL;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(EntityTypeLiteral expression) {
            appendable = !conditionalExpression;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(EntryExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(ExistsExpression expression) {

            // Only AND, OR, NOT or clause identifiers can follow this expression
            // when the type is LOGICAL or CLAUSE
            appendable = (appendableType == AppendableType.COMPLETE ||
                          appendableType == AppendableType.LOGICAL) &&
                          expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void visit(Expression expression) {

            // Anything other than what's a valid conditional expression is not valid
            // "... WHERE AVG(a.ge) " <- Invalid and the following clauses cannot be appended
            appendable = false;
        }

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

            if (expression.hasAsOfClause()) {
                expression.getAsOfClause().accept(this);
            }
            else if (expression.hasHierarchicalQueryClause()) {
                expression.getHierarchicalQueryClause().accept(this);
            }
            else if (expression.hasDeclaration()) {
                clauseOfItems = true;
                expression.getDeclaration().accept(this);
                clauseOfItems = false;
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(FunctionExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

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

            if (expression.hasGroupByItems()) {
                clauseOfItems = true;
                expression.getGroupByItems().accept(this);
                clauseOfItems = false;
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(HavingClause expression) {
            if (expression.hasConditionalExpression()) {
                conditionalExpression = true;
                expression.getConditionalExpression().accept(this);
                conditionalExpression = false;
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(IdentificationVariable expression) {

            if (appendableType == AppendableType.COMPLETE) {
                appendable = true;
            }
            // The WHERE/HAVING clauses do not have a collection of expressions but has an
            // aggregation of expressions. If the appendable type is CLASUE, then the only
            // possible way for this to be valid is to have a collection of expressions where
            // the first child is the conditional expression and the second child is the
            // beginning of the following clause.
            // Example: "SELECT e FROM Employee e G|" <- appendable
            // Example: "SELECT e FROM Employee e WHERE e.phoneNumbers IS NOT E|" <- Not appendable
            else if (clauseOfItems || (!clauseOfItems && (appendableType == AppendableType.CLAUSE))) {
                appendable = !hasComma                   &&
                             (positionInCollection > -1) &&
                             !visitor.isFollowingInvalidExpression(expression);
            }
            else {

                // Special case if what's before is 'IS' or 'IS NOT', then it's not appendable
                if (positionInCollection > 1) {
                    Expression child = collectionExpression.getChild(positionInCollection - 1);
                    String text = child.toActualText();
                    appendable = !text.equals(IS) && !text.equals("IS NOT");
                }
                else {
                    switch (appendableType) {
                        case ARITHMETIC: {
                            appendable = false;
                            break;
                        }
                        case SUBQUERY:
                        case LOGICAL:
                        case COMPOUNDABLE: {
                            appendable = true;
//                            appendable = (positionInCollection > -1) || subExpression;
                            break;
                        }
                        case CLAUSE: {
                            appendable = false;
                            break;
                        }
                        case COMPARISON:
                        case COMPLETE: {
                            appendable = true;
                            break;
                        }
                    }
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(IdentificationVariableDeclaration expression) {

            // 1) The FROM clause needs to have more than one identification variable declaration
            //    before the next clauses identifiers can be added. Example: "SELECT e FROM E" where
            //    'E' cannot be the beginning of a clause identifier
            // 2) The next clause identifiers cannot be added if there is a comma before the last
            //    item. Example: "SELECT e FROM Employee e, I" where 'I' cannot be the beginning of
            //    a clause identifier
            if ((appendableType != AppendableType.COMPLETE) && ((positionInCollection == -1) || hasComma)) {
                appendable = false;
            }
            else if (expression.hasJoins()) {
                expression.getJoins().accept(this);
            }
            else {
                expression.getRangeVariableDeclaration().accept(this);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(IndexExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(InExpression expression) {

            // Only AND, OR, NOT or clause identifiers can follow this expression
            // when the type is LOGICAL or CLAUSE
            appendable = (appendableType == AppendableType.COMPLETE ||
                          appendableType == AppendableType.LOGICAL) &&
                          expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(InputParameter expression) {
            appendable = !conditionalExpression;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(Join expression) {

            if (expression.hasOnClause()) {
                expression.getOnClause().accept(this);
            }
            else if (expression.hasFetch()) {
                if (expression.hasAs()) {
                    appendable = expression.hasIdentificationVariable();
                }
                else {
                    appendable = expression.hasJoinAssociationPath();
                }
            }
            else {
                appendable = expression.hasIdentificationVariable();
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(KeyExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(KeywordExpression expression) {
            // Nothing can be added right after NULL, TRUE, FALSE
            appendable = !conditionalExpression &&
                      (appendableType == AppendableType.LOGICAL ||
                       appendableType == AppendableType.COMPLETE);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(LengthExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(LikeExpression expression) {

            // Only AND, OR, NOT or clause identifiers can follow this expression
            // when the type is LOGICAL or CLAUSE
            appendable = appendableType == AppendableType.COMPLETE ||
                         appendableType == AppendableType.LOGICAL;

            if (appendable) {

                boolean oldConditionalExpression = conditionalExpression;
                conditionalExpression = false;

                if (expression.hasEscape()) {
                    expression.getEscapeCharacter().accept(this);
                }
                else {
                    expression.getPatternValue().accept(this);
                }

                conditionalExpression = oldConditionalExpression;
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(LocateExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(LowerExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(MaxFunction expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(MinFunction expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(ModExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(MultiplicationExpression expression) {
            if (expression.hasRightExpression()) {
                expression.getRightExpression().accept(this);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(NotExpression expression) {

            // Example: "NOT B" can only have compound identifiers like 'NOT BETWEEN'
            if (expression.hasExpression()) {

                String variable = visitor.queryContext.literal(
                    expression.getExpression(),
                    LiteralType.IDENTIFICATION_VARIABLE
                );

                if (variable != ExpressionTools.EMPTY_STRING) {
                    appendable = (appendableType == AppendableType.COMPOUNDABLE);
                }
                else {
                    boolean oldConditionalExpression = conditionalExpression;
                    conditionalExpression = false;
                    expression.getExpression().accept(this);
                    conditionalExpression = oldConditionalExpression;
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(NullComparisonExpression expression) {

            // Only AND, OR, NOT or clause identifiers can follow this expression
            // when the type is LOGICAL or CLAUSE
            appendable = appendableType == AppendableType.COMPLETE ||
                         appendableType == AppendableType.LOGICAL;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(NullIfExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(NumericLiteral expression) {
            appendable = !conditionalExpression;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(ObjectExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(OnClause expression) {
            if (expression.hasConditionalExpression()) {
                conditionalExpression = true;
                expression.getConditionalExpression().accept(this);
                conditionalExpression = false;
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(OrderByClause expression) {
            if (expression.hasOrderByItems()) {
                clauseOfItems = true;
                expression.getOrderByItems().accept(this);
                clauseOfItems = false;
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(OrderByItem expression) {
            appendable = expression.hasSpaceAfterExpression() &&
                         expression.isDefault() ||
                         expression.hasSpaceAfterOrdering() &&
                        !expression.isNullsFirst() && !expression.isNullsLast();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(OrExpression expression) {
            if (expression.hasRightExpression()) {
                expression.getRightExpression().accept(this);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(RangeVariableDeclaration expression) {

            if (appendableType == AppendableType.COMPLETE) {
                if (expression.hasIdentificationVariable()) {
                    expression.getIdentificationVariable().accept(this);
                }
            }
            else {

                // Only the abstract schema name is parsed
                appendable = !expression.hasSpaceAfterRootObject() &&
                             !expression.hasAs() &&
                             !expression.hasIdentificationVariable();

                if (!appendable &&
                    expression.hasRootObject() &&
                    expression.hasIdentificationVariable()) {

                    expression.getIdentificationVariable().accept(this);
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(ResultVariable expression) {
            // The result variable is parsed without AS
            appendable = !expression.hasAs() &&
                         !expression.hasSpaceAfterAs() &&
                          expression.hasResultVariable();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SelectClause expression) {
            if (expression.hasSelectExpression()) {
                clauseOfItems = true;
                expression.getSelectExpression().accept(this);
                clauseOfItems = false;
            }
        }

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

            if (expression.hasUnionClauses()) {
                expression.getUnionClauses().accept(this);
            }
            else if (expression.hasOrderByClause()) {
                expression.getOrderByClause().accept(this);
            }
            else if (expression.hasHavingClause()) {
                expression.getHavingClause().accept(this);
            }
            else if (expression.hasGroupByClause()) {
                expression.getGroupByClause().accept(this);
            }
            else if (expression.hasWhereClause()) {
                expression.getWhereClause().accept(this);
            }
            else if (expression.hasFromClause()) {
                expression.getFromClause().accept(this);
            }
            else {
                expression.getSelectClause().accept(this);
            }
        }

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

            if (expression.hasAsOfClause()) {
                expression.getAsOfClause().accept(this);
            }
            else if (expression.hasHierarchicalQueryClause()) {
                expression.getHierarchicalQueryClause().accept(this);
            }
            else if (expression.hasDeclaration()) {
                clauseOfItems = true;
                expression.getDeclaration().accept(this);
                clauseOfItems = true;
            }
        }

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

            if (expression.hasSelectExpression()) {
                clauseOfItems = true;
                expression.getSelectExpression().accept(this);
                clauseOfItems = true;
            }
        }

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

            if (subExpression && (appendableType == AppendableType.ARITHMETIC)) {
                appendable = true;
            }
            else {

                if (expression.hasHavingClause()) {
                    expression.getHavingClause().accept(this);
                }
                else if (expression.hasGroupByClause()) {
                    expression.getGroupByClause().accept(this);
                }
                else if (expression.hasWhereClause()) {
                    expression.getWhereClause().accept(this);
                }
                else if (expression.hasFromClause()) {
                    expression.getFromClause().accept(this);
                }
                else {
                    expression.getSelectClause().accept(this);
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SizeExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SqrtExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(StateFieldPathExpression expression) {

            // "e.employee." cannot be appended with something
            if (expression.endsWithDot()) {
                appendable = false;
            }
            // "... WHERE e.age "    <- Invalid to be followed by another clause
            // "... GROUP BY e.age " <- Valid to be followed by another clause
            else if (appendableType == AppendableType.CLAUSE ||
                     appendableType == AppendableType.COMPLETE) {

                appendable = !conditionalExpression;
            }
            else {

                // Resolve the mapping so we can determine what is valid based on its type
                IMapping mapping = visitor.queryContext.getMapping(expression);

                if (mapping == null) {
                    appendable = false;
                }
                else {
                    IType type = mapping.getType();

                    switch (appendableType) {
                        case ARITHMETIC: {
                            // e.name (String) cannot be followed by +,-,/,*
                            // e.age (int) can be followed by an arithmetic operator
                            appendable = visitor.queryContext.getTypeHelper().isNumericType(type);
                            break;
                        }
                        case COMPARISON: {
                            TypeHelper typeHelper = visitor.queryContext.getTypeHelper();
                            appendable = !typeHelper.isCollectionType(type) &&
                                         !typeHelper.isMapType(type);
                            break;
                        }
                        case COMPOUNDABLE: {
                            // The type will be calculated later
                            appendable = true;
                            break;
                        }
                        case LOGICAL: {
                            appendable = visitor.queryContext.getTypeHelper().isBooleanType(type);
                            break;
                        }
                    }
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(StringLiteral expression) {
            appendable = !conditionalExpression &&
                      expression.hasCloseQuote();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SubExpression expression) {

            if (appendableType == AppendableType.COMPLETE) {
                appendable = expression.hasRightParenthesis();
            }
            else if (expression.hasExpression()) {

                subExpression = true;
                boolean oldConditionalExpression = conditionalExpression;
                conditionalExpression = false;

                expression.getExpression().accept(this);
                appendable &= expression.hasRightParenthesis();

                subExpression = false;
                conditionalExpression = oldConditionalExpression;
            }
            else {
                // Only a subquery could be a valid proposal, everything
                // else cannot start an encapsulated expression
                appendable = (appendableType == AppendableType.SUBQUERY);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SubstringExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SubtractionExpression expression) {
            if (expression.hasRightExpression()) {
                expression.getRightExpression().accept(this);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SumFunction expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(TreatExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(TrimExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(TypeExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

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

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

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(UpdateStatement expression) {
            if (expression.hasWhereClause()) {
                expression.getWhereClause().accept(this);
            }
            else {
                expression.getUpdateClause().accept(this);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(UpperExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(ValueExpression expression) {
            appendable = !conditionalExpression &&
                      expression.hasRightParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(WhenClause expression) {
            if (expression.hasWhenExpression()) {
                conditionalExpression = true;
                expression.getWhenExpression().accept(this);
                conditionalExpression = false;
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(WhereClause expression) {
            if (expression.hasConditionalExpression()) {
                conditionalExpression = true;
                expression.getConditionalExpression().accept(this);
                conditionalExpression = false;
            }
        }
    }

    // Made static for performance reasons.
    /**
     * This is used to determine how {@link AppendableExpressionVisitor} should perform the check.
     */
    protected static enum AppendableType {

        /**
         * Determines whether the arithmetic operators (+, -, *, /) can be appended as valid proposals.
         */
        ARITHMETIC,

        /**
         * Determines whether the JPQL identifiers identifying a clause (eg: <code><b>WHERE</b></code>)
         * can be appended as valid proposals.
         */
        CLAUSE,

        /**
         * Determines whether the comparison operators {@literal (<, <=, <>, >=, =)} can be appended as valid proposals.
         */
        COMPARISON,

        /**
         * Determines whether the visited {@link Expression} is grammatically complete by making sure
         * its ending is valid.
         */
        COMPLETE,

        /**
         * Determines whether the compound identifiers (eg: <code><b>IS NULL</b></code>) can be
         * appended as valid proposals.
         */
        COMPOUNDABLE,

        /**
         * Determines whether the logical identifiers (<code><b>AND</b></code> and <code><b>OR</b></code>)
         * can be appended as valid proposals.
         */
        LOGICAL,

        /**
         * Determines whether the JPQL identifiers identifying a subquery (eg: <code><b>SELECT</b></code>)
         * can be appended as valid proposals.
         */
        SUBQUERY
    }

    // Made static for performance reasons.
    /**
     * This helper is used to determine how to add proposals within a collection of expressions. Each
     * expression is usually separated by either a whitespace or by a comma.
     */
    protected static interface CollectionExpressionHelper<T extends Expression> {

        /**
         * Adds the proposals because the cursor is at the end of the child at the given position.
         *
         * @param expression The {@link Expression} being visited
         * @param collectionExpression The {@link CollectionExpression} is either the child of the
         * given {@link Expression} or a temporary generated one that usually contains a single item
         * @param index The position of that child in the collection of children
         * @param hasComma Indicates whether a comma is present before the child at the given position;
         * if the index is 0, then this is <code>false</code> by default
         * @param virtualSpace Indicates if this method is called because the cursor is at the end of
         * the child at the specified index but by considering there is a virtual space at the end of
         * that child
         */
        void addAtTheEndOfChild(T expression,
                                CollectionExpression collectionExpression,
                                int index,
                                boolean hasComma,
                                boolean virtualSpace);

        /**
         * Adds the given JPQL identifier as a valid proposal.
         *
         * @param expression The {@link Expression} being visited
         * @param identifier The JPQL identifier to add as a valid proposal
         */
        void addIdentifier(T expression, String identifier);

        /**
         * Adds the proposals because the cursor is at the beginning of the child {@link Expression}
         * at the given position.
         *
         * @param expression The {@link Expression} being visited
         * @param collectionExpression The {@link CollectionExpression} is either the child of the
         * given {@link Expression} or a temporary generated one that usually contains a single item.
         * This can be null if the position is at the beginning
         * @param index The position of the child that was scanned
         * @param hasComma Indicates whether a comma is present before the child at the given position;
         * if the index is 0, then this is <code>false</code> by default
         */
        void addTheBeginningOfChild(T expression,
                                    CollectionExpression collectionExpression,
                                    int index,
                                    boolean hasComma);

        /**
         * Either returns the given {@link Expression}'s child, which is already a {@link CollectionExpression}
         * or requests this helper to return a "virtual" {@link CollectionExpression} that is wrapping
         * the single element.
         *
         * @param expression The parent of the children to retrieve
         * @return The given expression's child or a "virtual" one
         */
        CollectionExpression buildCollectionExpression(T expression);

        /**
         * Asks this helper if the search can continue even though two child expressions are not
         * separated by a comma.
         *
         * @param expression The {@link Expression} being visited
         * @param collectionExpression The {@link CollectionExpression}
         * @param index The position of the child being scanned
         * @return <code>true</code> if the check can continue even though the previous child was not
         * separated by a comma; <code>false</code> to stop the check
         */
        boolean canContinue(T expression, CollectionExpression collectionExpression, int index);

        /**
         * Determines whether a delimiter like a whitespace or an open parenthesis was parsed after
         * the identifier.
         *
         * @param expression The {@link Expression} being visited
         * @return <code>true</code> if something is present; <code>false</code> otherwise
         */
        boolean hasDelimiterAfterIdentifier(T expression);

        /**
         * Returns the maximum number of encapsulated {@link Expression expressions} the {@link Expression}
         * allows. Some expression only allow 2, others 3 and others allow an unlimited number.
         *
         * @param expression The {@link Expression} for which its maximum number of children
         * @return The maximum number of children the expression can have
         */
        int maxCollectionSize(T expression);

        /**
         * Returns the length of anything that can be defined before the first child. An example can
         * be "<code>DISTINCT </code>" in "<code>AVG(DISTINCT e.name)</code>".
         *
         * @param expression The {@link Expression} being visited
         * @return The length of anything that was parsed before the first child or 0 if nothing was parsed
         */
        int preExpressionLength(T expression);

        /**
         * Returns the {@link JPQLQueryBNF} that defines the fragment at the given position.
         *
         * @param expression The {@link Expression} being visited
         * @param index The position of the element to retrieve the BNF defined in the JPQL grammar
         * @return The {@link JPQLQueryBNF} that defines the fragment at the given position
         */
        JPQLQueryBNF queryBNF(T expression, int index);
    }

    // Made static final for performance reasons.
    /**
     * This visitor retrieves the {@link CollectionExpression} if it is visited.
     */
    protected static final class CollectionExpressionVisitor extends AbstractExpressionVisitor {

        /**
         * The {@link CollectionExpression} if it is the {@link Expression} that was visited.
         */
        protected CollectionExpression expression;

        /**
         * Disposes of the internal data.
         */
        public void dispose() {
            expression = null;
        }

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

    // Made static final for performance reasons.
    protected static final class CollectionMappingFilter implements Filter<IMapping> {

        /**
         * {@inheritDoc}
         */
        public boolean accept(IMapping value) {
            // Both association and collection field are accepted
            // Example: e.address is incomplete but it is not the entire path
            // Example: e.projects is the complete path
            return value.isRelationship();
        }
    }

    // Made static final for performance reasons.
    protected static final class ConcatExpressionCollectionHelper
            extends AbstractVisitorHelper implements CollectionExpressionHelper<ConcatExpression> {

        protected ConcatExpressionCollectionHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addAtTheEndOfChild(ConcatExpression expression,
                                       CollectionExpression collectionExpression,
                                       int index,
                                       boolean hasComma,
                                       boolean virtualSpace) {

            // The only thing that is appendable is an arithmetic operator
            // Example: "SELECT e.name|"
            // Example: "SELECT e|"
            if (queryBNF(expression, index).handleAggregate()) {

                Expression child = collectionExpression.getChild(index);

                if ((index == 0) && !virtualSpace) {

                    if (visitor.areArithmeticSymbolsAppendable(child)) {
                        visitor.addArithmeticIdentifiers();
                    }
                }
                else {

                    if (visitor.areArithmeticSymbolsAppendable(child)) {
                        visitor.addArithmeticIdentifiers();
                    }

                    if (visitor.areComparisonSymbolsAppendable(child)) {
                        visitor.addComparisonIdentifiers(child);
                    }
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        public void addIdentifier(ConcatExpression expression, String identifier) {
            visitor.proposals.addIdentifier(identifier);
            visitor.addIdentificationVariables();
            visitor.addFunctionIdentifiers(expression.getParent().findQueryBNF(expression));
        }

        /**
         * {@inheritDoc}
         */
        public void addTheBeginningOfChild(ConcatExpression expression,
                                           CollectionExpression collectionExpression,
                                           int index,
                                           boolean hasComma) {

            visitor.addIdentificationVariables();
            visitor.addFunctionIdentifiers(queryBNF(expression, index));
        }

        /**
         * {@inheritDoc}
         */
        public CollectionExpression buildCollectionExpression(ConcatExpression expression) {
            CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getExpression());
            if (collectionExpression == null) {
                collectionExpression = expression.buildCollectionExpression();
            }
            return collectionExpression;
        }

        /**
         * {@inheritDoc}
         */
        public boolean canContinue(ConcatExpression expression,
                                   CollectionExpression collectionExpression,
                                   int index) {

            return false;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasDelimiterAfterIdentifier(ConcatExpression expression) {
            return expression.hasSpaceAfterIdentifier() ||
                   expression.hasLeftParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        public int maxCollectionSize(ConcatExpression expression) {
            return Integer.MAX_VALUE;
        }

        /**
         * {@inheritDoc}
         */
        public int preExpressionLength(ConcatExpression expression) {
            return 0;
        }

        /**
         * {@inheritDoc}
         */
        public JPQLQueryBNF queryBNF(ConcatExpression expression, int index) {
            return visitor.getQueryBNF(expression.getEncapsulatedExpressionQueryBNFId());
        }

    }

    // Made static final for performance reasons.
    /**
     * This helpers handles adding proposals for {@link AbstractConditionalClause}.
     */
    protected static final class ConditionalClauseCollectionHelper extends AbstractConditionalClauseCollectionHelper<AbstractConditionalClause> {

        protected ConditionalClauseCollectionHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public CollectionExpression buildCollectionExpression(AbstractConditionalClause expression) {
            CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getConditionalExpression());
            if (collectionExpression == null) {
                collectionExpression = expression.buildCollectionExpression();
            }
            return collectionExpression;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasDelimiterAfterIdentifier(AbstractConditionalClause expression) {
            return expression.hasSpaceAfterIdentifier();
        }
    }

    // Made static final for performance reasons.
    protected static final class ConstrutorCollectionHelper
            extends AbstractVisitorHelper implements CollectionExpressionHelper<ConstructorExpression> {

        protected ConstrutorCollectionHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addAtTheEndOfChild(ConstructorExpression expression,
                                       CollectionExpression collectionExpression,
                                       int index,
                                       boolean hasComma,
                                       boolean virtualSpace) {

            // The only thing that is appendable is an arithmetic operator
            // Example: "SELECT e.name|"
            // Example: "SELECT e|"
            if (queryBNF(expression, index).handleAggregate()) {

                Expression child = collectionExpression.getChild(index);

                if ((index == 0) && !virtualSpace) {

                    if (visitor.areArithmeticSymbolsAppendable(child)) {
                        visitor.addArithmeticIdentifiers();
                    }
                }
                else {

                    if (visitor.areArithmeticSymbolsAppendable(child)) {
                        visitor.addArithmeticIdentifiers();
                    }

                    if (visitor.areComparisonSymbolsAppendable(child)) {
                        visitor.addComparisonIdentifiers(child);
                    }
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        public void addIdentifier(ConstructorExpression expression, String identifier) {
            visitor.proposals.addIdentifier(identifier);
        }

        /**
         * {@inheritDoc}
         */
        public void addTheBeginningOfChild(ConstructorExpression expression,
                                           CollectionExpression collectionExpression,
                                           int index,
                                           boolean hasComma) {

            visitor.addIdentificationVariables(expression, IdentificationVariableType.ALL);
            visitor.addFunctionIdentifiers(ConstructorItemBNF.ID);
        }

        /**
         * {@inheritDoc}
         */
        public CollectionExpression buildCollectionExpression(ConstructorExpression expression) {
            CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getConstructorItems());
            if (collectionExpression == null) {
                collectionExpression = expression.buildCollectionExpression();
            }
            return collectionExpression;
        }

        /**
         * {@inheritDoc}
         */
        public boolean canContinue(ConstructorExpression expression,
                                   CollectionExpression collectionExpression,
                                   int index) {

            return false;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasDelimiterAfterIdentifier(ConstructorExpression expression) {
            return expression.hasLeftParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        public int maxCollectionSize(ConstructorExpression expression) {
            return Integer.MAX_VALUE;
        }

        /**
         * {@inheritDoc}
         */
        public int preExpressionLength(ConstructorExpression expression) {
            if (expression.hasSpaceAfterNew()) {
                return expression.getClassName().length() + SPACE_LENGTH;
            }
            return expression.getClassName().length();
        }

        /**
         * {@inheritDoc}
         */
        public JPQLQueryBNF queryBNF(ConstructorExpression expression, int index) {
            return visitor.getQueryBNF(ConstructorItemBNF.ID);
        }
    }

    // Made static final for performance reasons.
    protected static final class DeclarationVisitor extends AnonymousExpressionVisitor {

        /**
         * Indicates if the visited {@link CollectionValuedPathExpression} is found within a
         * declaration expression.
         */
        protected boolean declaration;

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

        /**
         * Determines whether the visited path expression is found within a declaration expression.
         *
         * @return <code>true</code> if the visited {@link CollectionValuedPathExpression} is owned by
         * a {@link RangeVariableDeclaration}, which indicates it is used to define the "root" object;
         * <code>false</code> if it is not
         */
        public boolean isDeclaration() {
            return declaration;
        }

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

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

    // Made static final for performance reasons.
    /**
     * The default implementation of {@link MappingCollector}, which simply returns an empty collection.
     */
    protected static final class DefaultMappingCollector implements MappingCollector {

        /**
         * {@inheritDoc}
         */
        public Collection<IMapping> buildProposals() {
            return Collections.emptyList();
        }
    }

    // Made static final for performance reasons.
    protected static final class DeleteClauseCollectionHelper
            extends AbstractVisitorHelper implements CollectionExpressionHelper<DeleteClause> {

        protected DeleteClauseCollectionHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addAtTheEndOfChild(DeleteClause expression,
                                       CollectionExpression collectionExpression,
                                       int index,
                                       boolean hasComma,
                                       boolean virtualSpace) {
        }

        /**
         * {@inheritDoc}
         */
        public void addIdentifier(DeleteClause expression, String identifier) {
            visitor.proposals.addIdentifier(identifier);
        }

        /**
         * {@inheritDoc}
         */
        public void addTheBeginningOfChild(DeleteClause expression,
                                           CollectionExpression collectionExpression,
                                           int index,
                                           boolean hasComma) {

            if (index == 0) {
                visitor.addEntities();
            }
        }

        /**
         * {@inheritDoc}
         */
        public CollectionExpression buildCollectionExpression(DeleteClause expression) {
            CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getRangeVariableDeclaration());
            if (collectionExpression == null) {
                collectionExpression = expression.buildCollectionExpression();
            }
            return collectionExpression;
        }

        /**
         * {@inheritDoc}
         */
        public boolean canContinue(DeleteClause expression,
                                   CollectionExpression collectionExpression,
                                   int index) {

            return false;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasDelimiterAfterIdentifier(DeleteClause expression) {
            return expression.hasSpaceAfterFrom();
        }

        /**
         * {@inheritDoc}
         */
        public int maxCollectionSize(DeleteClause expression) {
            return Integer.MAX_VALUE;
        }

        /**
         * {@inheritDoc}
         */
        public int preExpressionLength(DeleteClause expression) {
            return 0;
        }

        /**
         * {@inheritDoc}
         */
        public JPQLQueryBNF queryBNF(DeleteClause expression, int index) {
            return visitor.getQueryBNF(RangeVariableDeclarationBNF.ID);
        }
    }

    // Made static final for performance reasons.
    protected static final class DeleteClauseStatementHelper
            extends AbstractVisitorHelper implements StatementHelper<DeleteStatement> {

        protected DeleteClauseStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addClauseProposals() {
            visitor.addIdentifier(DELETE_FROM);
        }

        /**
         * {@inheritDoc}
         */
        public void addInternalClauseProposals(DeleteStatement expression) {
            // Does not have internal clauses
        }

        /**
         * {@inheritDoc}
         */
        public Expression getClause(DeleteStatement expression) {
            return expression.getDeleteClause();
        }

        /**
         * {@inheritDoc}
         */
        public WhereClauseDeleteStatementHelper getNextHelper() {
            return visitor.getWhereClauseDeleteStatementHelper();
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasClause(DeleteStatement expression) {
            return true;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasSpaceAfterClause(DeleteStatement expression) {
            return expression.hasSpaceAfterDeleteClause();
        }

        /**
         * {@inheritDoc}
         */
        public boolean isClauseComplete(DeleteStatement expression) {
            return visitor.isComplete(expression.getDeleteClause());
        }

        /**
         * {@inheritDoc}
         */
        public boolean isRequired() {
            return true;
        }
    }

    // Made static final for performance reasons.
    protected static final class DifferentComparisonFilter
            extends AnonymousExpressionVisitor implements Filter<Expression> {

        /**
         * Determines whether {@literal '<', '<=', '>=', '>'} are valid comparison operators.
         */
        protected boolean valid;

        /**
         * {@inheritDoc}
         */
        public boolean accept(Expression expression) {
            try {
                expression.accept(this);
                return valid;
            }
            finally {
                valid = false;
            }
        }

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

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

    // Made static final for performance reasons.
    protected static final class DoubleEncapsulatedCollectionHelper
            extends AbstractVisitorHelper implements CollectionExpressionHelper<AbstractDoubleEncapsulatedExpression> {

        protected DoubleEncapsulatedCollectionHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addAtTheEndOfChild(AbstractDoubleEncapsulatedExpression expression,
                                       CollectionExpression collectionExpression,
                                       int index,
                                       boolean hasComma,
                                       boolean virtualSpace) {

            // The only thing that is appendable is an arithmetic operator
            // Example: "SELECT e.name|"
            // Example: "SELECT e|"
            if (queryBNF(expression, index).handleAggregate()) {

                Expression child = collectionExpression.getChild(index);

                if ((index == 0) && !virtualSpace) {

                    if (visitor.areArithmeticSymbolsAppendable(child)) {
                        visitor.addArithmeticIdentifiers();
                    }
                }
                else {

                    if (visitor.areArithmeticSymbolsAppendable(child)) {
                        visitor.addArithmeticIdentifiers();
                    }

                    if (visitor.areComparisonSymbolsAppendable(child)) {
                        visitor.addComparisonIdentifiers(child);
                    }
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        public void addIdentifier(AbstractDoubleEncapsulatedExpression expression, String identifier) {
            visitor.proposals.addIdentifier(identifier);
            visitor.addIdentificationVariables();
            visitor.addFunctionIdentifiers(expression.getParent().findQueryBNF(expression));
        }

        /**
         * {@inheritDoc}
         */
        public void addTheBeginningOfChild(AbstractDoubleEncapsulatedExpression expression,
                                           CollectionExpression collectionExpression,
                                           int index,
                                           boolean hasComma) {

            visitor.addIdentificationVariables();
            visitor.addFunctionIdentifiers(queryBNF(expression, index));
        }

        /**
         * {@inheritDoc}
         */
        public CollectionExpression buildCollectionExpression(AbstractDoubleEncapsulatedExpression expression) {
            return expression.buildCollectionExpression();
        }

        /**
         * {@inheritDoc}
         */
        public boolean canContinue(AbstractDoubleEncapsulatedExpression expression,
                                   CollectionExpression collectionExpression,
                                   int index) {

            return false;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasDelimiterAfterIdentifier(AbstractDoubleEncapsulatedExpression expression) {
            return expression.hasSpaceAfterIdentifier() ||
                   expression.hasLeftParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        public int maxCollectionSize(AbstractDoubleEncapsulatedExpression expression) {
            // Both MOD and NULLIF allows a fixed 2 encapsulated expressions
            return 2;
        }

        /**
         * {@inheritDoc}
         */
        public int preExpressionLength(AbstractDoubleEncapsulatedExpression expression) {
            return 0;
        }

        /**
         * {@inheritDoc}
         */
        public JPQLQueryBNF queryBNF(AbstractDoubleEncapsulatedExpression expression, int index) {
            return visitor.getQueryBNF(expression.parameterExpressionBNF(index));
        }
    }

    // Made static final for performance reasons.
    protected static final class EncapsulatedExpressionVisitor extends AnonymousExpressionVisitor {

        /**
         * Determines whether the visited {@link Expression} is being encapsulated or not.
         */
        protected boolean encapsulated;

        /**
         * Internal flag that prevent infinite recursion.
         */
        protected boolean visited;

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

        /**
         * Determines whether the visited {@link Expression} is being encapsulated or not.
         *
         * @return <code>true</code> if the visited {@link Expression} is within parenthesis;
         * <code>false</code> otherwise
         */
        public boolean isEncapsulated() {
            return encapsulated;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void visit(Expression expression) {
            if (!visited) {
                visited = true;
                expression.getParent().accept(this);
                visited = false;
            }
        }

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

    // Made static for performance reasons.
    /**
     * This builder populates a {@link QueryPosition} by traversing the valid portion of the JPQL
     * query. The position is the end of each {@link Expression}.
     * <p>
     * For instance, "SELECT e FROM Employee e O WHERE e.name = 'JPQL'", the valid fragment is
     * "SELECT e FROM Employee e", the positions will be:
     * <ul>
     * <li>JPQLExpression = 24</li>
     * <li>SelectStatement = 24</li>
     * <li>FromClause = 15</li>
     * <li>IdentificationVariableDeclaration = 10</li>
     * <li>RangeVariableDeclaration = 10</li>
     * <li>IdentificationVariable = 1</li>
     * </ul>
     */
    protected static class EndingQueryPositionBuilder
            extends AbstractVisitorHelper implements ExpressionVisitor {

        /**
         * This internal flag helps to determine if the {@link Expression} where the cursor is located
         * was flagged to be invalid. If so, this helps to determine how to handle the calculation of
         * the new position within the query.
         * <p>
         * For instance: "SELECT e FROM Employee e AS" has a bad expression wrapping the identifier
         * "AS", which is parsed as a result variable. In this case, the position would actually be
         * 2 within the bad expression. If the bad expression was something more complex than just a
         * single word, then that expression should not be included in the position.
         */
        protected boolean badExpression;

        /**
         * This is used to correct the length of an {@link Expression}
         */
        protected int correction;

        /**
         * The {@link Expression} containing the invalid fragment.
         */
        protected Expression invalidExpression;

        /**
         * The position of the cursor within the invalid expression.
         */
        protected int positionWithinInvalidExpression;

        /**
         * This {@link QueryPosition} has the position of each {@link Expression} within the valid
         * fragment of the JPQL query.
         */
        public QueryPosition queryPosition;

        /**
         * Indicates whether a virtual space should be added to the stack or not.
         */
        public boolean virtualSpace;

        protected EndingQueryPositionBuilder(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * Disposes the internal data.
         */
        public void dispose() {
            correction                      = 0;
            virtualSpace                    = false;
            queryPosition                   = null;
            invalidExpression               = null;
            positionWithinInvalidExpression = -1;
        }

        /**
         * Returns the new {@link QueryPosition} that was created.
         *
         * @return
         */
        public QueryPosition getQueryPosition() {
            return queryPosition;
        }

        /**
         * Determines whether a virtual space should be added to the stack or not.
         *
         * @return <code>true</code> if a virtual space should be considered; <code>false</code> otherwise
         */
        public boolean hasVirtualSpace() {
            return virtualSpace;
        }

        /**
         * Prepares this visitor before visiting an {@link Expression}. {@link #dispose()} is called
         * after the visit operation is complete.
         *
         * @param invalidExpression The {@link Expression} containing the invalid fragment
         */
        public void prepare(Expression invalidExpression) {

            QueryPosition oldQueryPosition = visitor.queryPosition;

            this.invalidExpression = invalidExpression;
            this.positionWithinInvalidExpression = oldQueryPosition.getPosition(invalidExpression);
            this.queryPosition = new QueryPosition(oldQueryPosition.getPosition(invalidExpression.getParent()));
        }

        /**
         * {@inheritDoc}
         */
        public void visit(AbsExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(AbstractSchemaName expression) {

            if (badExpression) {
                correction = expression.getLength() - positionWithinInvalidExpression;
            }

            if (invalidExpression == expression) {
                queryPosition.setExpression(expression);
                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(AdditionExpression expression) {
            visitCompoundExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(AllOrAnyExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(AndExpression expression) {
            visitCompoundExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(ArithmeticFactor expression) {

            if (!badExpression) {

                if (expression.hasExpression()) {
                    expression.getExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(AvgFunction expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

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

        /**
         * {@inheritDoc}
         */
        public void visit(BetweenExpression expression) {

            if (badExpression) {

                if (!expression.hasExpression() &&
                    !expression.hasNot()        &&
                    positionWithinInvalidExpression <= 7 /* BETWEEN */) {

                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasUpperBoundExpression()) {
                    expression.getUpperBoundExpression().accept(this);
                }
                else if (expression.hasLowerBoundExpression() &&
                        !expression.hasAnd()) {

                    expression.getLowerBoundExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(CaseExpression expression) {

            if (badExpression) {

                if (positionWithinInvalidExpression <= 4 /* CASE */) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (!expression.hasEnd()) {

                    if (expression.hasElseExpression()) {
                        expression.getElseExpression().accept(this);
                    }
                    else if (expression.hasWhenClauses()) {
                        expression.getWhenClauses();
                    }
                    else if (expression.hasCaseOperand()) {
                        expression.getCaseOperand().accept(this);
                    }
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(CoalesceExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(CollectionExpression expression) {

            if (!badExpression) {

                // First find the index of the child expression owning the invalid fragment
                int index = 0;

                for (Expression child : expression.children()) {
                    if (child.isAncestor(invalidExpression)) {
                        break;
                    }
                    index++;
                }

                // This visitor is not used for an invalid expression but for looking at the
                // end of an expression and the whitespace is owned by a parent expression
                if (index == expression.childrenSize()) {
                    index--;
                }

                // Now traverse the child
                Expression child = expression.getChild(index);
                child.accept(this);

                // Rather than marking the CollectionExpression as the Expression to visit
                // with the adjusted QueryPosition, the child that to the left of the invalid
                // expression will be used instead.
                // Example: "SELECT e FROM Employee e WHERE CONCAT(e.name, A|S a)" <- | is the cursor
                //          In this example, "CONCAT(e.name, AS a)" is wrapped with a BadExpression, but
                //          "CONCAT(e.name, " is actually valid so this will allow the new QueryPosition
                //          to be "inside" the CONCAT expression and the proposals will be available
                if (index > 0) {
                    Expression previousChild = expression.getChild(index - 1);

                    if (!visitor.isComplete(previousChild)) {
                        queryPosition.setExpression(previousChild);
                        queryPosition.addPosition(previousChild, previousChild.getLength());
                    }
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                // Adjust the length so it's within the collection expression
                // up to the position of the cursor
                int length = expression.toActualText(index + 1).length() - correction;
                queryPosition.addPosition(expression, length);

                // Now reset the correction so the parent expression does
                // not use the entire length of CollectionExpression
                correction = expression.getLength() - length;
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(CollectionMemberDeclaration expression) {

            if (badExpression) {
                if (positionWithinInvalidExpression <= 2 /* IN */) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasIdentificationVariable()) {
                    expression.getIdentificationVariable().accept(this);
                }
                else if (expression.hasCollectionValuedPathExpression() &&
                        !expression.hasAs()) {

                    expression.getCollectionValuedPathExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(CollectionMemberExpression expression) {

            if (badExpression) {

                if (!expression.hasEntityExpression() &&
                    !expression.hasNot()              &&
                    positionWithinInvalidExpression <= 6 /* MEMBER */) {

                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasCollectionValuedPathExpression()) {
                    expression.getCollectionValuedPathExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(CollectionValuedPathExpression expression) {

            if (badExpression) {
                correction = expression.getLength() - positionWithinInvalidExpression;
            }

            if (invalidExpression == expression) {
                queryPosition.setExpression(expression);
                queryPosition.addPosition(expression, positionWithinInvalidExpression);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(ComparisonExpression expression) {
            visitCompoundExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(ConcatExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(ConstructorExpression expression) {

            if (badExpression) {

                if (positionWithinInvalidExpression <= 3 /* NEW */) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasConstructorItems() &&
                   !expression.hasRightParenthesis()) {

                    expression.getConstructorItems().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(CountFunction expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(DateTime expression) {

            if (!expression.isJDBCDate()) {
                if (badExpression) {
                    correction = expression.getLength() - positionWithinInvalidExpression;
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
                else if (invalidExpression == expression) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(DeleteClause expression) {

            if (badExpression) {

                if (positionWithinInvalidExpression <= 6/* DELETE */) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasRangeVariableDeclaration()) {
                    expression.getRangeVariableDeclaration().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(DeleteStatement expression) {

            if (!badExpression) {

                if (expression.hasWhereClause()) {
                    expression.getWhereClause().accept(this);
                }
                else {
                    expression.getDeleteClause().accept(this);
                    if (expression.hasSpaceAfterDeleteClause()) {
                        virtualSpace = true;
                    }
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(DivisionExpression expression) {
            visitCompoundExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(EmptyCollectionComparisonExpression expression) {

            if (badExpression) {

                if (!expression.hasExpression() &&
                    !expression.hasNot()        &&
                    positionWithinInvalidExpression <= 5 /* EMPTY */) {

                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {
                if (invalidExpression == expression) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(EntityTypeLiteral expression) {

            if (badExpression) {
                correction = expression.getLength() - positionWithinInvalidExpression;
            }

            if (invalidExpression == expression) {
                queryPosition.setExpression(expression);
                queryPosition.addPosition(expression, positionWithinInvalidExpression);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(EntryExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(ExistsExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(FromClause expression) {
            visitAbstractFromClause(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(FunctionExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(GroupByClause expression) {

            if (badExpression) {

                if (positionWithinInvalidExpression <= 5 /* GROUP */) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasGroupByItems()) {
                    expression.getGroupByItems().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(HavingClause expression) {
            visitAbstractConditionalClause(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(IdentificationVariable expression) {

            if (badExpression) {
                correction = expression.getLength() - positionWithinInvalidExpression;
            }

            queryPosition.setExpression(expression);
            queryPosition.addPosition(expression, positionWithinInvalidExpression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(IdentificationVariableDeclaration expression) {

            if (!badExpression) {

                if (expression.hasJoins()) {
                    expression.getJoins().accept(this);
                }
                else if (expression.hasRangeVariableDeclaration()) {
                    expression.getRangeVariableDeclaration().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(IndexExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(InExpression expression) {

            if (badExpression) {

                if (!expression.hasExpression() &&
                    positionWithinInvalidExpression <= 2 /* IN */) {

                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasInItems() &&
                   !expression.hasRightParenthesis()) {

                    expression.getInItems();
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(InputParameter expression) {

            if (!badExpression) {

                if (invalidExpression == expression) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(Join expression) {

            if (badExpression) {

                if (positionWithinInvalidExpression <= 4 /* JOIN */) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasOnClause()) {
                    expression.getOnClause().accept(this);
                }
                else if (expression.hasIdentificationVariable()) {
                    expression.getIdentificationVariable().accept(this);
                    if (expression.hasSpaceAfterIdentificationVariable()) {
                        virtualSpace = true;
                    }
                }
                else if (expression.hasJoinAssociationPath() &&
                        !expression.hasAs()) {

                    expression.getJoinAssociationPath().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(JPQLExpression expression) {

            if (expression.hasQueryStatement()) {
                Expression queryStatement = expression.getQueryStatement();
                queryStatement.accept(this);
                queryPosition.addPosition(expression, queryStatement.getLength() - correction);
            }
            else {
                queryPosition.setExpression(expression);
                queryPosition.addPosition(expression, 0);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(KeyExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(KeywordExpression expression) {

            if (badExpression) {
                correction = expression.getLength() - positionWithinInvalidExpression;
                queryPosition.setExpression(expression);
                queryPosition.addPosition(expression, positionWithinInvalidExpression);
            }
            else if (invalidExpression == expression) {
                queryPosition.setExpression(expression);
                queryPosition.addPosition(expression, positionWithinInvalidExpression);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(LengthExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(LikeExpression expression) {

            if (badExpression) {

                if (!expression.hasStringExpression() &&
                    positionWithinInvalidExpression <= 4 /* LIKE */) {

                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasEscapeCharacter()) {
                    expression.getEscapeCharacter().accept(this);
                }
                else if (expression.hasPatternValue() &&
                        !expression.hasEscape()) {

                    expression.getPatternValue().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(LocateExpression expression) {
            visitAbstractTripleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(LowerExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(MaxFunction expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(MinFunction expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(ModExpression expression) {
            visitAbstractDoubleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(MultiplicationExpression expression) {
            visitCompoundExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(NotExpression expression) {

            if (badExpression) {

                if (positionWithinInvalidExpression <= 3 /* NOT */) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasExpression()) {
                    expression.getExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(NullComparisonExpression expression) {

            if (badExpression) {

                if (!expression.hasExpression() &&
                   !expression.hasNot()         &&
                   positionWithinInvalidExpression <= 2) {

                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasExpression()) {
                    expression.getExpression().accept(this);
                }

                queryPosition.setExpression(expression);
                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(NullExpression expression) {

            if (!badExpression) {

                if (invalidExpression == expression) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, 0);
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(NullIfExpression expression) {
            visitAbstractDoubleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(NumericLiteral expression) {

            if (!badExpression) {

                if (invalidExpression == expression) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(ObjectExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(OnClause expression) {
            visitAbstractConditionalClause(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(OrderByClause expression) {

            if (badExpression) {

                if (positionWithinInvalidExpression <= 5 /* ORDER */) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasOrderByItems()) {
                    expression.getOrderByItems().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(OrderByItem expression) {

            if (!badExpression) {

                if (expression.hasExpression()) {
                    expression.getExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(OrExpression expression) {
            visitCompoundExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(RangeVariableDeclaration expression) {

            if (!badExpression) {

                if (expression.hasIdentificationVariable()) {
                    expression.getIdentificationVariable().accept(this);
                }
                else if (!expression.hasAs()) {
                    expression.getRootObject().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(ResultVariable expression) {

            if (badExpression) {

                // Special case for a result variable with nothing but the AS identifier
                if (!expression.hasResultVariable() &&
                     expression.hasAs() ||
                    !expression.hasSelectExpression()) {

                    correction = expression.getLength() - positionWithinInvalidExpression;

                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasResultVariable()) {
                    expression.getResultVariable().accept(this);
                }
                else if (!expression.hasAs()) {
                    expression.getSelectExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(SelectClause expression) {

            if (badExpression) {

                if (positionWithinInvalidExpression <= 6 /* SELECT */) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasSelectExpression()) {
                    expression.getSelectExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(SelectStatement expression) {

            if (!badExpression) {

                if (expression.hasUnionClauses()) {
                    expression.getUnionClauses().accept(this);
                }
                else if (expression.hasOrderByClause()) {
                    expression.getOrderByClause().accept(this);
                    if (expression.hasSpaceBeforeUnion()) {
                        virtualSpace = true;
                    }
                }
                else if (expression.hasHavingClause()) {
                    expression.getHavingClause().accept(this);
                    if (expression.hasSpaceBeforeOrderBy()) {
                        virtualSpace = true;
                    }
                }
                else if (expression.hasGroupByClause()) {
                    expression.getGroupByClause().accept(this);
                    if (expression.hasSpaceAfterGroupBy()) {
                        virtualSpace = true;
                    }
                }
                else if (expression.hasWhereClause()) {
                    expression.getWhereClause().accept(this);
                    if (expression.hasSpaceAfterWhere()) {
                        virtualSpace = true;
                    }
                }
                else if (expression.hasFromClause()) {
                    expression.getFromClause().accept(this);
                    if (expression.hasSpaceAfterFrom()) {
                        virtualSpace = true;
                    }
                }
                else {
                    expression.getSelectClause().accept(this);
                    if (expression.hasSpaceAfterSelect()) {
                        virtualSpace = true;
                    }
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(SimpleFromClause expression) {
            visitAbstractFromClause(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(SimpleSelectClause expression) {

            if (!badExpression) {

                if (expression.hasSelectExpression()) {
                    expression.getSelectExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(SimpleSelectStatement expression) {

            if (!badExpression) {

                if (expression.hasHavingClause()) {
                    expression.getHavingClause().accept(this);
                }
                else if (expression.hasGroupByClause()) {
                    expression.getGroupByClause().accept(this);
                    if (expression.hasSpaceAfterGroupBy()) {
                        virtualSpace = true;
                    }
                }
                else if (expression.hasWhereClause()) {
                    expression.getWhereClause().accept(this);
                    if (expression.hasSpaceAfterWhere()) {
                        virtualSpace = true;
                    }
                }
                else if (expression.hasFromClause()) {
                    expression.getFromClause().accept(this);
                    if (expression.hasSpaceAfterFrom()) {
                        virtualSpace = true;
                    }
                }
                else {
                    expression.getSelectClause().accept(this);
                    if (expression.hasSpaceAfterSelect()) {
                        virtualSpace = true;
                    }
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(SizeExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(SqrtExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(StateFieldPathExpression expression) {

            if (badExpression) {
                correction = expression.getLength() - positionWithinInvalidExpression;
            }

            if (invalidExpression == expression) {
                queryPosition.setExpression(expression);
                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(StringLiteral expression) {

            if (!badExpression) {

                if (invalidExpression == expression) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(SubExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(SubstringExpression expression) {
            visitAbstractTripleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(SubtractionExpression expression) {
            visitCompoundExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(SumFunction expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(TreatExpression expression) {

            if (badExpression) {

                if (positionWithinInvalidExpression <= 5 /* TREAT */) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasEntityType() &&
                   !expression.hasRightParenthesis()) {

                    expression.getEntityType().accept(this);
                }
                else if (expression.hasCollectionValuedPathExpression() &&
                        !expression.hasAs()) {

                    expression.getCollectionValuedPathExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(TrimExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(TypeExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(UnknownExpression expression) {
            // Nothing to do, this is the expression that needs
            // to be handled by the valid portion of the JPQL query
        }

        /**
         * {@inheritDoc}
         */
        public void visit(UpdateClause expression) {

            if (badExpression) {

                if (positionWithinInvalidExpression <= 6 /* UPDATE */) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasUpdateItems()) {
                    expression.getUpdateItems().accept(this);
                }
                else if (expression.hasRangeVariableDeclaration()) {
                    expression.getRangeVariableDeclaration().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(UpdateItem expression) {

            if (!badExpression) {

                if (expression.hasNewValue()) {
                    expression.getNewValue().accept(this);
                }
                else if (!expression.hasEqualSign() &&
                          expression.hasSpaceAfterStateFieldPathExpression()) {

                    expression.getStateFieldPathExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(UpdateStatement expression) {

            if (!badExpression) {

                if (expression.hasWhereClause()) {
                    expression.getWhereClause().accept(this);
                }
                else {
                    expression.getUpdateClause().accept(this);
                    if (expression.hasSpaceAfterUpdateClause()) {
                        virtualSpace = true;
                    }
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(UpperExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(ValueExpression expression) {
            visitAbstractSingleEncapsulatedExpression(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void visit(WhenClause expression) {

            if (badExpression) {

                if (positionWithinInvalidExpression <= 4 /* WHEN */) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasThenExpression()) {
                    expression.getThenExpression().accept(this);
                }
                else if (expression.hasWhenExpression() &&
                         expression.hasThen()) {

                    expression.getWhenExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void visit(WhereClause expression) {
            visitAbstractConditionalClause(expression);
        }

        protected void visitAbstractConditionalClause(AbstractConditionalClause expression) {

            if (!badExpression) {

                if (expression.hasConditionalExpression()) {
                    expression.getConditionalExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        protected void visitAbstractDoubleEncapsulatedExpression(AbstractDoubleEncapsulatedExpression expression) {

            if (badExpression) {

                if (positionWithinInvalidExpression <= expression.getIdentifier().length()) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasSecondExpression() &&
                   !expression.hasRightParenthesis()) {

                    expression.getSecondExpression().accept(this);
                }
                else if (expression.hasFirstExpression() &&
                        !expression.hasSpaceAfterComma()) {

                    expression.getFirstExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        protected void visitAbstractFromClause(AbstractFromClause expression) {

            if (badExpression) {

                if (positionWithinInvalidExpression <= 4 /* FROM */) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasAsOfClause()) {
                    expression.getAsOfClause().accept(this);
                }
                else if (expression.hasHierarchicalQueryClause()) {

                    expression.getHierarchicalQueryClause().accept(this);

                    if (expression.hasSpaceAfterHierarchicalQueryClause()) {
                        virtualSpace = true;
                    }
                }
                else if (expression.hasDeclaration()) {

                    expression.getDeclaration().accept(this);

                    if (expression.hasSpaceAfterDeclaration()) {
                        virtualSpace = true;
                    }
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        protected void visitAbstractSingleEncapsulatedExpression(AbstractSingleEncapsulatedExpression expression) {

            if (badExpression) {

                if (positionWithinInvalidExpression <= expression.getIdentifier().length()) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasEncapsulatedExpression() &&
                   !expression.hasRightParenthesis()) {

                    expression.getExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        protected void visitAbstractTripleEncapsulatedExpression(AbstractTripleEncapsulatedExpression expression) {

            if (badExpression) {

                if (positionWithinInvalidExpression <= expression.getIdentifier().length()) {
                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasThirdExpression() &&
                   !expression.hasRightParenthesis()) {

                    expression.getThirdExpression().accept(this);
                }
                else if (expression.hasSecondExpression() &&
                        !expression.hasSecondComma()) {

                    expression.getSecondExpression().accept(this);
                }
                else if (expression.hasFirstExpression() &&
                        !expression.hasFirstComma()) {

                    expression.getFirstExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }

        protected void visitCompoundExpression(CompoundExpression expression) {

            if (badExpression) {

                if (positionWithinInvalidExpression <= expression.getIdentifier().length() &&
                    !expression.hasLeftExpression()) {

                    queryPosition.setExpression(expression);
                    queryPosition.addPosition(expression, positionWithinInvalidExpression);
                }
            }
            else {

                if (expression.hasRightExpression()) {
                    expression.getRightExpression().accept(this);
                }

                if (queryPosition.getExpression() == null) {
                    queryPosition.setExpression(expression);
                }

                queryPosition.addPosition(expression, expression.getLength() - correction);
            }
        }
    }

    // Made static final for performance reasons.
    /**
     * This visitor determines whether a path expression can be resolved as a fully qualified enum
     * type and an enum constant.
     * <p>
     * The valid locations are:
     * <ul>
     * <li>{@link CollectionMemberExpression} : entity_or_value_expression (before <code><b>MEMBER</b></code> identifier);</li>
     * <li>{@link InExpression} : One of the items;</li>
     * <li>{@link CaseExpression} : The <code><b>ELSE</b> expression</code>;</li>
     * <li>{@link WhenClause} : The <code><b>WHEN</b></code> or <code><b>THEN</b></code> expressions;</li>
     * <li>{@link FunctionExpression} : One of the function items;</li>
     * <li>{@link ComparisonExpression} : The left or right expression if the comparison identifier
     *     is either <code><b>=</b></code> or <code><b>&lt;&gt;</b></code>;</li>
     * <li>{@link UpdateItem} : The new value;</li>
     * <li>{@link ConstructorExpression} : One of the constructor items;</li>
     * <li>{@link CoalesceExpression} : The expression at index 1 or greater;</li>
     * <li>{@link NullIfExpression} : The second expression;</li>
     * </ul>
     */
    protected static final class EnumVisitor extends AbstractExpressionVisitor {

        /**
         * The {@link AbstractPathExpression} being scanned for its location within the JPQL query.
         */
        protected AbstractPathExpression pathExpression;

        /**
         * Determines whether the path expression could potentially represent a fully qualified
         * enum constant, which is dictated by the location of the path expression within the query.
         * Only a few location allows an enum constant.
         */
        protected boolean valid;

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

        /**
         * Determines whether the path expression could potentially represent a fully qualified
         * enum constant, which is dictated by the location of the path expression within the query.
         * Only a few location allows an enum constant.
         *
         * @return <code>true</code> if the path expression represents a enum constant;
         * <code>false</code> otherwise
         */
        public boolean isValid() {
            return valid;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CaseExpression expression) {
            valid = (pathExpression == expression.getElseExpression());
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CoalesceExpression expression) {
            // TODO
            valid = (pathExpression == expression.getExpression());
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CollectionMemberExpression expression) {
            valid = (pathExpression == expression.getEntityExpression());
        }

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

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(ComparisonExpression expression) {
            String identifier = expression.getComparisonOperator();
            valid = ((identifier == EQUAL) || (identifier == DIFFERENT));
        }

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

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

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(InExpression expression) {
            valid = (pathExpression != expression.getExpression());
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(NullIfExpression expression) {
            valid = (pathExpression == expression.getSecondExpression());
        }

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

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(UpdateItem expression) {
            valid = (pathExpression == expression.getNewValue());
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(WhenClause expression) {
            valid = (pathExpression == expression.getThenExpression() ||
                     pathExpression == expression.getWhenExpression());
        }
    }

    // Made static final for performance reasons.
    /**
     * This {@link MappingCollector} returns the possible mappings (non-collection type or
     * collection type) from a managed type.
     */
    protected static final class FilteringMappingCollector implements MappingCollector {

        /**
         * The {@link Filter} used to filter out either the collection type properties or the non-
         * collection type properties.
         */
        protected final Filter<IMapping> filter;

        /**
         * This resolver is used to retrieve the managed type, which is the parent path of this one.
         */
        protected final Resolver resolver;

        /**
         * The suffix is used to determine if the mapping name needs to be filtered out or not.
         */
        protected final String suffix;

        /**
         * Creates a new <code>FilteringMappingCollector</code>.
         *
         * @param resolver This resolver is used to retrieve the managed type, which is the parent
         * path of this one
         * @param filter The filter used to filter out either the collection type properties or the
         * non-collection type properties
         * @param suffix The suffix is used to determine if the property name needs to be filtered out
         * or not
         */
        FilteringMappingCollector(Resolver resolver, Filter<IMapping> filter, String suffix) {
            super();
            this.filter   = filter;
            this.suffix   = suffix;
            this.resolver = resolver;
        }

        protected void addFilteredMappings(IManagedType managedType, List<IMapping> mappings) {

            Filter<IMapping> filter = buildFilter(suffix);

            for (IMapping mapping : managedType.mappings()) {
                if (filter.accept(mapping)) {
                    mappings.add(mapping);
                }
            }
        }

        protected Filter<IMapping> buildFilter(String suffix) {
            if (suffix.length() == 0) {
                return filter;
            }
            return new AndFilter<IMapping>(filter, buildMappingNameFilter(suffix));
        }

        protected Filter<IMapping> buildMappingNameFilter(final String suffix) {
            return new Filter<IMapping>() {
                public boolean accept(IMapping mapping) {
                    return mapping.getName().startsWith(suffix);
                }
            };
        }

        /**
         * {@inheritDoc}
         */
        public Collection<IMapping> buildProposals() {

            IManagedType managedType = resolver.getManagedType();

            if (managedType == null) {
                return Collections.emptyList();
            }

            ArrayList<IMapping> mappings = new ArrayList<IMapping>();
            addFilteredMappings(managedType, mappings);
            return mappings;
        }
    }

    // Made static for performance reasons.
    protected static class FollowingClausesVisitor extends AbstractTraverseParentVisitor {

        /**
         * The JPQL identifier of the clause used to determine if there is any clause defined after it.
         */
        protected String afterIdentifier;

        /**
         * The JPQL identifier of the clause used to determine if there is any clause defined before it.
         */
        protected String beforeIdentifier;

        /**
         * Determines whether there is at least one clause defined after the clause defined by {@link #afterIdentifier}.
         */
        protected boolean hasFollowUpClauses;

        /**
         * Disposes of the internal data.
         */
        public void dispose() {
            afterIdentifier    = null;
            beforeIdentifier   = null;
            hasFollowUpClauses = false;
        }

        /**
         * Determines if the <code><b>FROM</b></code> clause has been defined or not. The end limit
         * of the check is also taken into consideration.
         *
         * @param expression The <code><b>SELECT</b></code> expression being scanned for what has been
         * defined between the range of clauses
         * @return <code>true</code> if the <code><b>FROM</b></code> clause is defined;
         * <code>false</code> otherwise
         */
        protected boolean hasFromClause(AbstractSelectStatement expression) {
            // No need to check if the check is after the clause itself
            return (afterIdentifier != FROM) && expression.hasFromClause();
        }

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

            if (afterIdentifier == SELECT) {

                if (beforeIdentifier == WHERE) {
                    hasFollowUpClauses = hasFromClause(expression);
                }
                else if (beforeIdentifier == GROUP_BY) {
                    hasFollowUpClauses = hasFromClause(expression) ||
                                         expression.hasWhereClause();
                }
                else if (beforeIdentifier == HAVING) {
                    hasFollowUpClauses = hasFromClause(expression)   ||
                                         expression.hasWhereClause() ||
                                         expression.hasGroupByClause();
                }
                else if (beforeIdentifier == ORDER_BY) {
                    hasFollowUpClauses = hasFromClause(expression)     ||
                                         expression.hasWhereClause()   ||
                                         expression.hasGroupByClause() ||
                                         expression.hasHavingClause();
                }
                else if (beforeIdentifier == null) {
                    hasFollowUpClauses = hasFromClause(expression)     ||
                                         expression.hasWhereClause()   ||
                                         expression.hasGroupByClause() ||
                                         expression.hasHavingClause()  ||
                                         expression.hasOrderByClause();
                }
                else {
                    hasFollowUpClauses = hasFromClause(expression);
                }
            }
            else if (afterIdentifier == FROM) {

                if (beforeIdentifier == GROUP_BY) {
                    hasFollowUpClauses = hasFromClause(expression) ||
                                         expression.hasWhereClause();
                }
                else if (beforeIdentifier == HAVING) {
                    hasFollowUpClauses = hasFromClause(expression)   ||
                                         expression.hasWhereClause() ||
                                         expression.hasGroupByClause();
                }
                else if (beforeIdentifier == ORDER_BY) {
                    hasFollowUpClauses = hasFromClause(expression)     ||
                                         expression.hasWhereClause()   ||
                                         expression.hasGroupByClause() ||
                                         expression.hasHavingClause();
                }
                else if (beforeIdentifier == null) {
                    hasFollowUpClauses = hasFromClause(expression)     ||
                                         expression.hasWhereClause()   ||
                                         expression.hasGroupByClause() ||
                                         expression.hasHavingClause()  ||
                                         expression.hasOrderByClause();
                }
                else {
                    hasFollowUpClauses = hasFromClause(expression);
                }
            }
            else if (afterIdentifier == WHERE) {

                if (beforeIdentifier == HAVING) {
                    hasFollowUpClauses = expression.hasGroupByClause();
                }
                else if (beforeIdentifier == ORDER_BY) {
                    hasFollowUpClauses = expression.hasGroupByClause() ||
                                         expression.hasHavingClause();
                }
                else if (beforeIdentifier == null) {
                    hasFollowUpClauses = expression.hasGroupByClause() ||
                                         expression.hasHavingClause()  ||
                                         expression.hasOrderByClause();
                }
            }
            else if (afterIdentifier == GROUP_BY) {

                if (beforeIdentifier == ORDER_BY) {
                    hasFollowUpClauses = expression.hasHavingClause();
                }
                else if (beforeIdentifier == null) {
                    hasFollowUpClauses = expression.hasHavingClause() ||
                                         expression.hasOrderByClause();
                }
            }
            else if (afterIdentifier == HAVING) {
                hasFollowUpClauses = expression.hasOrderByClause();
            }
            else {
                hasFollowUpClauses = hasFromClause(expression);
            }
        }

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

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

            if (afterIdentifier == SELECT) {

                if (beforeIdentifier == WHERE) {
                    hasFollowUpClauses = hasFromClause(expression);
                }
                else if (beforeIdentifier == GROUP_BY) {
                    hasFollowUpClauses = hasFromClause(expression) ||
                                         expression.hasWhereClause();
                }
                else if (beforeIdentifier == HAVING) {
                    hasFollowUpClauses = hasFromClause(expression)   ||
                                         expression.hasWhereClause() ||
                                         expression.hasGroupByClause();
                }
                else if (beforeIdentifier == null) {
                    hasFollowUpClauses = hasFromClause(expression)     ||
                                         expression.hasWhereClause()   ||
                                         expression.hasGroupByClause() ||
                                         expression.hasHavingClause();
                }
            }
            else if (afterIdentifier == FROM) {

                if (beforeIdentifier == GROUP_BY) {
                    hasFollowUpClauses = hasFromClause(expression) ||
                                         expression.hasWhereClause();
                }
                else if (beforeIdentifier == HAVING) {
                    hasFollowUpClauses = hasFromClause(expression)   ||
                                         expression.hasWhereClause() ||
                                         expression.hasGroupByClause();
                }
                else if (beforeIdentifier == null) {
                    hasFollowUpClauses = hasFromClause(expression)     ||
                                         expression.hasWhereClause()   ||
                                         expression.hasGroupByClause() ||
                                         expression.hasHavingClause();
                }
            }
            else if (afterIdentifier == WHERE) {

                if (beforeIdentifier == HAVING) {
                    hasFollowUpClauses = expression.hasGroupByClause();
                }
                else if (beforeIdentifier == null) {
                    hasFollowUpClauses = expression.hasGroupByClause() ||
                                         expression.hasHavingClause();
                }
            }
        }
    }

    // Made static final for performance reasons.
    protected static final class FollowingInvalidExpressionVisitor extends AbstractTraverseParentVisitor {

        /**
         * Enclosing visitor instance.
         */
        protected final AbstractContentAssistVisitor visitor;

        /**
         * The {@link Expression} used to determine if it follows an invalid fragment or not.
         */
        protected Expression expression;

        /**
         * Determines whether the visited {@link Expression} is preceded by an invalid expression.
         */
        protected boolean followingInvalidExpression;

        protected FollowingInvalidExpressionVisitor(AbstractContentAssistVisitor visitor) {
            super();
            this.visitor = visitor;
        }

        /**
         * Disposes of the internal data.
         */
        public void dispose() {
            expression = null;
            followingInvalidExpression = false;
        }

        /**
         * Determines whether the visited {@link Expression} is preceded by an invalid expression.
         *
         * @return <code>true</code> if the visited {@link Expression} is part of a collection of
         * expressions and an invalid expression precede it; <code>false</code> otherwise
         */
        public boolean isFollowingInvalidExpression() {
            return followingInvalidExpression;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CollectionExpression expression) {

            int index = expression.childrenSize();

            // Find the index of the expression for which the tree is been visited
            while (--index >= 0) {
                Expression child = expression.getChild(index);

                if (child == this.expression) {
                    break;
                }
            }

            // Check to see if an expression before the index is invalid
            while (--index >= 0) {
                Expression child = expression.getChild(index);
                followingInvalidExpression = visitor.isInvalidExpression(child);

                if (followingInvalidExpression) {
                    index = -1;
                }
            }
        }

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

    // Made static for performance reasons.
    protected static class FromClauseCollectionHelper
            extends AbstractVisitorHelper implements CollectionExpressionHelper<AbstractFromClause> {

        protected FromClauseCollectionHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addAtTheEndOfChild(AbstractFromClause expression,
                                       CollectionExpression collectionExpression,
                                       int index,
                                       boolean hasComma,
                                       boolean virtualSpace) {

            // At the end of a range variable declaration, JOIN clauses can be added
            // Example: "SELECT e FROM Employee e |"
            // Example: "SELECT e FROM Employee e, Address a |"
            if (((index == 0) || hasComma) && virtualSpace) {
                // "... JOIN e.mananager m |" <- Valid to add JOIN identifiers
                // "... JOIN e.mananager m ON e.name |" <- Invalid to add JOIN identifiers
                if (visitor.isComplete(collectionExpression.getChild(0))) {
                    visitor.addJoinIdentifiers();
                    visitor.addIdentifier(ON);
                }
            }
            // Special case to handle a range variable declaration that can also
            // be either the beginning of the following clauses
            else {
                 boolean end = (index + 1 == collectionExpression.childrenSize());

                 if ((index > 0) && end && !hasComma) {

                    int position = visitor.queryPosition.getPosition();

                    if (!visitor.hasClausesDefinedBetween(expression, FROM, HAVING)) {
                        visitor.addCompositeIdentifier(GROUP_BY, 4 /* GROUP - 1 */);
                    }

                    if (!visitor.hasClausesDefinedBetween(expression, FROM, ORDER_BY)) {
                        visitor.addCompositeIdentifier(ORDER_BY, 4 /* ORDER - 1 */);
                    }
                 }
            }
        }

        /**
         * {@inheritDoc}
         */
        public void addIdentifier(AbstractFromClause expression, String identifier) {
            visitor.proposals.addIdentifier(identifier);
        }

        /**
         * {@inheritDoc}
         */
        public void addTheBeginningOfChild(AbstractFromClause expression,
                                           CollectionExpression collectionExpression,
                                           int index,
                                           boolean hasComma) {

            // 1. At the beginning of the FROM declaration, entities are valid proposals
            // 2. To be valid elsewhere, the declarations have to be separated by a comma
            //    "SELECT e FROM Employee e W|" <- entity names are not valid proposals, only 'WHERE' is
            if ((index == 0) || hasComma) {
                visitor.addEntities();
            }
            // In any other case, the JOIN identifiers are the only valid choices
            // "SELECT e FROM Employee e J|" <- only 'JOIN' and 'JOIN FETCH' are valid proposals
            else if ((index > 0) && !hasComma) {
                visitor.addJoinIdentifiers();
            }

            // The identifier for a collection member declaration can only be added
            // after the first declaration, as long as there is a comma before it
            // "SELECT e FROM Employee e, |" <- 'IN' is a valid proposal
            // "SELECT e FROM Employee e, I|" <- 'IN' is a valid proposal
            // "SELECT e FROM Employee e I|" <- 'IN' is NOT a valid proposal
            if ((index > 0) && hasComma) {
                visitor.addIdentifier(IN);
            }
        }

        /**
         * {@inheritDoc}
         */
        public CollectionExpression buildCollectionExpression(AbstractFromClause expression) {
            CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getDeclaration());
            if (collectionExpression == null) {
                collectionExpression = expression.buildCollectionExpression();
            }
            return collectionExpression;
        }

        /**
         * {@inheritDoc}
         */
        public boolean canContinue(AbstractFromClause expression,
                                   CollectionExpression collectionExpression,
                                   int index) {

            return true;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasDelimiterAfterIdentifier(AbstractFromClause expression) {
            return expression.hasSpaceAfterFrom();
        }

        /**
         * {@inheritDoc}
         */
        public int maxCollectionSize(AbstractFromClause expression) {
            return Integer.MAX_VALUE;
        }

        /**
         * {@inheritDoc}
         */
        public int preExpressionLength(AbstractFromClause expression) {
            return 0;
        }

        /**
         * {@inheritDoc}
         */
        public JPQLQueryBNF queryBNF(AbstractFromClause expression, int index) {
            return visitor.getQueryBNF(expression.getDeclarationQueryBNFId());
        }
    }

    // Made static for performance reasons.
    protected static class FromClauseStatementHelper extends AbstractFromClauseStatementHelper<SelectStatement> {

        protected FromClauseStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public WhereClauseSelectStatementHelper getNextHelper() {
            return visitor.getWhereClauseSelectStatementHelper();
        }
    }

    // Made static final for performance reasons.
    protected static final class GroupByClauseCollectionHelper
            extends AbstractVisitorHelper implements CollectionExpressionHelper<GroupByClause> {

        protected GroupByClauseCollectionHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addAtTheEndOfChild(GroupByClause expression,
                                       CollectionExpression collectionExpression,
                                       int index,
                                       boolean hasComma,
                                       boolean virtualSpace) {
        }

        /**
         * {@inheritDoc}
         */
        public void addIdentifier(GroupByClause expression, String identifier) {
            visitor.proposals.addIdentifier(identifier);
        }

        /**
         * {@inheritDoc}
         */
        public void addTheBeginningOfChild(GroupByClause expression,
                                           CollectionExpression collectionExpression,
                                           int index,
                                           boolean hasComma) {

            if ((index == 0) || hasComma) {
                visitor.addFunctionIdentifiers(GroupByItemBNF.ID);
                visitor.addIdentificationVariables();
            }
        }

        /**
         * {@inheritDoc}
         */
        public CollectionExpression buildCollectionExpression(GroupByClause expression) {
            CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getGroupByItems());
            if (collectionExpression == null) {
                collectionExpression = expression.buildCollectionExpression();
            }
            return collectionExpression;
        }

        /**
         * {@inheritDoc}
         */
        public boolean canContinue(GroupByClause expression,
                                   CollectionExpression collectionExpression,
                                   int index) {

            return false;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasDelimiterAfterIdentifier(GroupByClause expression) {
            return expression.hasSpaceAfterGroupBy();
        }

        /**
         * {@inheritDoc}
         */
        public int maxCollectionSize(GroupByClause expression) {
            return Integer.MAX_VALUE;
        }

        /**
         * {@inheritDoc}
         */
        public int preExpressionLength(GroupByClause expression) {
            return 0;
        }

        /**
         * {@inheritDoc}
         */
        public JPQLQueryBNF queryBNF(GroupByClause expression, int index) {
            return visitor.getQueryBNF(GroupByItemBNF.ID);
        }
    }

    // Made static final for performance reasons.
    protected static final class GroupByClauseStatementHelper extends AbstractGroupByClauseStatementHelper<SelectStatement> {

        protected GroupByClauseStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public HavingClauseStatementHelper getNextHelper() {
            return visitor.getHavingClauseStatementHelper();
        }
    }

    // Made static final for performance reasons.
    protected static final class HavingClauseStatementHelper extends AbstractHavingClauseStatementHelper<SelectStatement> {

        protected HavingClauseStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public OrderByClauseStatementHelper getNextHelper() {
            return visitor.getOrderByClauseStatementHelper();
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasSpaceAfterClause(SelectStatement expression) {
            return expression.hasSpaceBeforeOrderBy();
        }
    }

    // Made static for performance reasons.
    /**
     * The various ways of retrieving identification variables from the declaration expression.
     */
    protected static enum IdentificationVariableType {

        /**
         * Retrieves all the identification variables declared in the declaration portion of the
         * expression, which is either in the <b>FROM</b> clause of a <b>SELECT</b> query or
         * <b>DELETE</b> query or in the <b>UPDATE</b> query.
         */
        ALL {

            /**
             * {@inheritDoc}
             */
            @Override
            protected boolean add(AbstractContentAssistVisitor contentAssist,
                                  Declaration declaration,
                               Expression expression) {

                if (declaration.getType() == Type.RANGE) {
                    contentAssist.addRangeIdentificationVariable(declaration.getVariableName());
                }
                else {
                    contentAssist.addIdentificationVariable(declaration.getVariableName());
                }

                for (Join join : declaration.getJoins()) {

                    String variableName = contentAssist.queryContext.literal(
                        join.getIdentificationVariable(),
                        LiteralType.IDENTIFICATION_VARIABLE
                    );

                    contentAssist.addIdentificationVariable(variableName);
                }

                return false;
            }
        },

        /**
         * Only retrieves the identification variables that map a path expression defined in a
         * <b>JOIN</b> expression or in a <b>IN</b> expression.
         */
        COLLECTION {

            /**
             * {@inheritDoc}
             */
            @Override
            protected boolean add(AbstractContentAssistVisitor contentAssist,
                                  Declaration declaration,
                               Expression expression) {

                contentAssist.addIdentificationVariable(declaration.getVariableName());

                for (Join join : declaration.getJoins()) {

                    String variableName = contentAssist.queryContext.literal(
                        join.getIdentificationVariable(),
                        LiteralType.IDENTIFICATION_VARIABLE
                    );

                    contentAssist.addIdentificationVariable(variableName);
                }

                return false;
            }
        },

        /**
         * Only retrieves the identification variables that have been declared to the left of the
         * expression requested them, both range and collection type variables are collected.
         */
        LEFT {

            /**
             * {@inheritDoc}
             */
            @Override
            protected boolean add(AbstractContentAssistVisitor contentAssist,
                                  Declaration declaration,
                               Expression expression) {

                boolean shouldStop = declaration.getDeclarationExpression().isAncestor(expression);

                if (shouldStop && !declaration.getJoins().contains(expression)) {
                    return true;
                }

                if (declaration.getType() == Type.RANGE) {
                    contentAssist.addRangeIdentificationVariable(declaration.getVariableName());
                }
                else {
                    contentAssist.addIdentificationVariable(declaration.getVariableName());
                }

                for (Join join : declaration.getJoins()) {

                    if (join.isAncestor(expression)) {
                        return true;
                    }

                    String variableName = contentAssist.queryContext.literal(
                        join.getIdentificationVariable(),
                        LiteralType.IDENTIFICATION_VARIABLE
                    );

                    contentAssist.addIdentificationVariable(variableName);
                }

                return false;
            }
        },

        /**
         * Only retrieves the identification variables that map a path expression defined in a
         * <b>JOIN</b> expression or in a <b>IN</b> expression but that have been declared to the
         * left of the expression requested them.
         */
        LEFT_COLLECTION {

            /**
             * {@inheritDoc}
             */
            @Override
            protected boolean add(AbstractContentAssistVisitor contentAssist,
                                  Declaration declaration,
                               Expression expression) {

                boolean shouldStop = declaration.getDeclarationExpression().isAncestor(expression);

                if (shouldStop && declaration.getJoins().contains(expression)) {
                    return true;
                }

                if (!shouldStop && (declaration.getType() == Type.COLLECTION)) {
                    contentAssist.addIdentificationVariable(declaration.getVariableName());
                }
                else {

                    for (Join join : declaration.getJoins()) {

                        if (join.isAncestor(expression)) {
                            return true;
                        }

                        String variableName = contentAssist.queryContext.literal(
                            join.getIdentificationVariable(),
                            LiteralType.IDENTIFICATION_VARIABLE
                        );

                        contentAssist.addIdentificationVariable(variableName);
                    }
                }

                return false;
            }
        },

        /**
         * Simply indicates the identification variables should not be collected.
         */
        NONE {

            /**
             * {@inheritDoc}
             */
            @Override
            protected boolean add(AbstractContentAssistVisitor contentAssist,
                                  Declaration declaration,
                               Expression expression) {

                // Nothing to do, stop immediately
                return true;
            }
        };

        /**
         * Adds the identification variables defined in the given {@link Declaration}.
         *
         * @param contentAssist Backpointer to the content assist class
         * @param declaration The {@link Declaration} in the order they are declared in the declaration clause
         * @param expression The {@link Expression} being visited, which can help to determine when to
         * stop collecting identification variables
         */
        protected abstract boolean add(AbstractContentAssistVisitor contentAssist,
                                       Declaration declaration,
                                       Expression expression);
    }

    // Made static for performance reasons.
    /**
     * This visitor is used when a clause or a compound expression was parsed with a collection of
     * expressions representing an invalid fragment.
     * <p>
     * Example: <code>SELECT e FROM Employee e GROUP B</code>
     * <p>
     * In this example, the <code><b>FROM</b></code> clause contains a collection of two
     * identification variable declarations, in a valid query, it would be separated by a comma, but
     * this one just means it's incomplete and "GROUP B" is the beginning of the <code><b>GROUP BY</b></code>
     * clause.
     */
    protected static class IncompleteCollectionExpressionVisitor extends AbstractExpressionVisitor {

        /**
         * The clause being visited, which is marked by its JPQL identifier.
         */
        protected String clause;

        /**
         * Determines whether an {@link Expression} that was visited is complete or if some part is missing.
         */
        protected boolean complete;

        /**
         * This flag is used to make sure only the last expression in a collection is tested. A single
         * expression cannot be used to check the "completeness".
         */
        protected boolean insideCollection;

        /**
         * Returns the list of identifiers for the clauses following the given identifier.
         *
         * @param afterIdentifier The JPQL identifier of the clause for which the list of following
         * clauses is built
         * @return The list of JPQL identifiers defining the clauses following the clause specified
         * by the given identifier
         */
        protected List<String> compositeIdentifiersAfter(String afterIdentifier) {

            if (clause == FROM) {
                return CollectionTools.list(GROUP_BY, ORDER_BY);
            }

            if (clause == WHERE) {
                return CollectionTools.list(GROUP_BY, ORDER_BY);
            }

            if (clause == HAVING) {
                return CollectionTools.list(ORDER_BY);
            }

            return new LinkedList<String>();
        }

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

        /**
         * Determines whether an {@link Expression} that was visited is complete or if some part is missing.
         *
         * @return <code>true</code> if the visited {@link Expression} is grammatically complete;
         * <code>false</code> if it is incomplete
         */
        public boolean isComplete() {
            return complete;
        }

        /**
         * Determines whether the given JPQL fragment, which is the parsed text of the expression
         * invalid collection expression or the portion of it based on the cursor position within the
         * collection, is a composite identifier or not.
         *
         * @param clause The JPQL identifier of the clause having the collection of expressions
         * @param fragment The parsed text of the expression to determine if it's the beginning of the
         * composite identifier or not. The fragment is all lower case characters
         */
        protected boolean isPossibleCompositeIdentifier(String clause, String fragment) {

            for (String identifier : compositeIdentifiersAfter(clause)) {
                if (identifier.toLowerCase(Locale.ROOT).startsWith(fragment)) {
                    return true;
                }
            }

            return false;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CollectionExpression expression) {

            int lastIndex = expression.childrenSize() - 1;
            insideCollection = true;

            if (!expression.hasComma(lastIndex - 1)) {
                expression.getChild(lastIndex).accept(this);
            }

            insideCollection = false;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(FromClause expression) {
            clause = FROM;
            expression.getDeclaration().accept(this);
            clause = null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(GroupByClause expression) {
            clause = GROUP_BY;
            expression.getGroupByItems().accept(this);
            clause = null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(HavingClause expression) {
            clause = HAVING;
            expression.getConditionalExpression().accept(this);
            clause = null;
        }

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

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(IdentificationVariableDeclaration expression) {
            if (insideCollection && !expression.hasJoins()) {
                expression.getRangeVariableDeclaration().accept(this);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(OrderByClause expression) {
            clause = ORDER_BY;
            expression.getOrderByItems().accept(this);
            clause = null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(RangeVariableDeclaration expression) {

            if (insideCollection) {

                // The "root" object could be the first identifier of a composite identifier
                // (eg: GROUP) and the identification variable is 'B'
                complete = !expression.hasAs() && !expression.hasIdentificationVariable();

                // Special case for composite identifiers
                if (!complete) {
                    String fragment = expression.toParsedText().toLowerCase(Locale.ROOT);
                    complete = isPossibleCompositeIdentifier(clause, fragment);
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(WhereClause expression) {
            clause = WHERE;
            expression.getConditionalExpression().accept(this);
            clause = null;
        }
    }

    // Made static final for performance reasons.
    /**
     * This visitor determines if the visited {@link Expression} is one of the two that represents
     * an invalid expression.
     */
    protected static final class InvalidExpressionVisitor extends AbstractExpressionVisitor {

        /**
         * The invalid {@link Expression}, which is either {@link UnknownExpression} or {@link BadExpression}.
         */
        protected Expression expression;

        /**
         * Disposes of the internal data.
         */
        public void dispose() {
            expression = null;
        }

        /**
         * Determines whether the visited {@link Expression} represents an invalid fragment.
         *
         * @return <code>true</code> if the {@link Expression} is an invalid fragment;
         * <code>false</code> otherwise
         */
        public boolean isInvalid() {
            return expression != null;
        }

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

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

    // Made static final for performance reasons.
    protected static final class JoinCollectionHelper
            extends AbstractVisitorHelper implements CollectionExpressionHelper<IdentificationVariableDeclaration> {

        protected JoinCollectionHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addAtTheEndOfChild(IdentificationVariableDeclaration expression,
                                       CollectionExpression collectionExpression,
                                       int index,
                                       boolean hasComma,
                                       boolean virtualSpace) {
        }

        /**
         * {@inheritDoc}
         */
        public void addIdentifier(IdentificationVariableDeclaration expression, String identifier) {
            visitor.proposals.addIdentifier(identifier);
        }

        /**
         * {@inheritDoc}
         */
        public void addTheBeginningOfChild(IdentificationVariableDeclaration expression,
                                           CollectionExpression collectionExpression,
                                           int index,
                                           boolean hasComma) {

            visitor.addJoinIdentifiers();
        }

        /**
         * {@inheritDoc}
         */
        public CollectionExpression buildCollectionExpression(IdentificationVariableDeclaration expression) {
            CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getJoins());
            if (collectionExpression == null) {
                collectionExpression = expression.buildCollectionExpression();
            }
            return collectionExpression;
        }

        /**
         * {@inheritDoc}
         */
        public boolean canContinue(IdentificationVariableDeclaration expression,
                                   CollectionExpression collectionExpression,
                                   int index) {

            return false;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasDelimiterAfterIdentifier(IdentificationVariableDeclaration expression) {
            return expression.hasSpace();
        }

        /**
         * {@inheritDoc}
         */
        public int maxCollectionSize(IdentificationVariableDeclaration expression) {
            return Integer.MAX_VALUE;
        }

        /**
         * {@inheritDoc}
         */
        public int preExpressionLength(IdentificationVariableDeclaration expression) {
            return expression.getRangeVariableDeclaration().getLength();
        }

        /**
         * {@inheritDoc}
         */
        public JPQLQueryBNF queryBNF(IdentificationVariableDeclaration expression, int index) {
            return visitor.getQueryBNF(InternalJoinBNF.ID);
        }
    }

    // Made static for performance reasons.
    /**
     * A collector is responsible to retrieve the possible proposals by using the mappings that can
     * complete a path expression.
     */
    protected static interface MappingCollector {

        /**
         * Retrieves the possible proposals that can be used to complete a path expression based on
         * the position of the cursor.
         *
         * @return The possible proposals
         */
        Collection<IMapping> buildProposals();
    }

    // Made static final for performance reasons.
    /**
     * This visitor is responsible to create the right {@link Filter} based on the type of the {@link Expression}.
     */
    protected static final class MappingFilterBuilder extends AbstractTraverseParentVisitor {

        /**
         * Enclosing visitor instance.
         */
        protected final AbstractContentAssistVisitor visitor;

        /**
         * The {@link Filter} that will filter the various type of {@link IMapping IMappings} based
         * on the location of the of the path expression within the JPQL query.
         */
        protected Filter<IMapping> filter;

        protected MappingFilterBuilder(AbstractContentAssistVisitor visitor) {
            super();
            this.visitor = visitor;
        }

        /**
         * Disposes of the internal data.
         */
        public void dispose() {
            filter = null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(AbsExpression expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(AvgFunction expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(BetweenExpression expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CoalesceExpression expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CollectionMemberDeclaration expression) {
            filter = visitor.getMappingCollectionFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CollectionValuedPathExpression expression) {
            filter = visitor.getMappingCollectionFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(ConcatExpression expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(CountFunction expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(EmptyCollectionComparisonExpression expression) {
            filter = visitor.getMappingCollectionFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(FunctionExpression expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(HavingClause expression) {
            // No need to filter, everything is allowed
            // Example: SELECT e FROM Employee e WHERE e.|
            //          1. Could become 'e.name = 'JPQL''
            //          2. Could become 'e.employees IS NOT NULL'
            //          3. Could become e.address.zipcode = 27519
            filter = NullFilter.instance();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(Join expression) {
            filter = visitor.getMappingCollectionFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(JPQLExpression expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(LengthExpression expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(LocateExpression expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(LowerExpression expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(MaxFunction expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(MinFunction expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(ModExpression expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(NullComparisonExpression expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(OnClause expression) {
            // No need to filter, everything is allowed
            // Example: SELECT e FROM Employee e WHERE e.|
            //          1. Could become 'e.name = 'JPQL''
            //          2. Could become 'e.employees IS NOT NULL'
            //          3. Could become e.address.zipcode = 27519
            filter = NullFilter.instance();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SizeExpression expression) {
            filter = visitor.getMappingCollectionFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SqrtExpression expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SubstringExpression expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(SumFunction expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(TreatExpression expression) {
            filter = visitor.getMappingCollectionFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(TrimExpression expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(UpperExpression expression) {
            filter = visitor.getMappingPropertyFilter();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(WhenClause expression) {
            // No need to filter, everything is allowed
            // Example: SELECT e FROM Employee e WHERE e.|
            //          1. Could become 'e.name = 'JPQL''
            //          2. Could become 'e.employees IS NOT NULL'
            //          3. Could become e.address.zipcode = 27519
            filter = NullFilter.instance();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(WhereClause expression) {
            // No need to filter, everything is allowed
            // Example: SELECT e FROM Employee e WHERE e.|
            //          1. Could become 'e.name = 'JPQL''
            //          2. Could become 'e.employees IS NOT NULL'
            //          3. Could become e.address.zipcode = 27519
            filter = NullFilter.instance();
        }
    }

    // Made static final for performance reasons.
    /**
     * This {@link Filter} is responsible to filter out the mappings that can't have their type
     * assignable to the one passed in.
     */
    protected static final class MappingTypeFilter
            extends AbstractVisitorHelper implements Filter<IMapping> {

        /**
         * The type used to determine if the mapping's type is a valid type.
         */
        protected final IType type;

        /**
         * Creates a new <code>MappingTypeFilter</code>.
         *
         * @param type The type used to determine if the mapping's type is a valid type
         */
        MappingTypeFilter(IType type, AbstractContentAssistVisitor visitor) {
            super(visitor);
            this.type = type;
        }

        /**
         * {@inheritDoc}
         */
        public boolean accept(IMapping value) {

            // A reference mapping always can be used for further traversal
            if (value.isRelationship() &&
               !value.isCollection()) {

                return true;
            }

            // Determine if it's assignable to the desired type
            IType mappingType = value.getType();
            mappingType = visitor.queryContext.getTypeHelper().convertPrimitive(mappingType);
            return mappingType.isAssignableTo(type);
        }
    }

    // Made static final for performance reasons.
    protected static final class NotExpressionVisitor extends AbstractExpressionVisitor {

        /**
         * The {@link NotExpression} if it is the {@link Expression} being visited otherwise <code>null</code>.
         */
        protected NotExpression expression;

        /**
         * Disposes of the internal data.
         */
        public void dispose() {
            expression = null;
        }

        /**
         * Determines whether the {@link Expression} being visited is {@link NotExpression} or not.
         *
         * @return <code>true</code> if the {@link Expression} is {@link NotExpression};
         *  <code>null</code> otherwise
         */
        public boolean isNotExpression() {
            return expression != null;
        }

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

    // Made static final for performance reasons.
    protected static final class OrderByClauseCollectionHelper
            extends AbstractVisitorHelper implements CollectionExpressionHelper<AbstractOrderByClause> {

        protected OrderByClauseCollectionHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addAtTheEndOfChild(AbstractOrderByClause expression,
                                       CollectionExpression collectionExpression,
                                       int index,
                                       boolean hasComma,
                                       boolean virtualSpace) {

            OrderByItem item = (OrderByItem) collectionExpression.getChild(index);

            if (item.getOrdering() == Ordering.DEFAULT) {
                visitor.addIdentifier(ASC);
                visitor.addIdentifier(DESC);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void addIdentifier(AbstractOrderByClause expression, String identifier) {
            visitor.proposals.addIdentifier(identifier);
        }

        /**
         * {@inheritDoc}
         */
        public void addTheBeginningOfChild(AbstractOrderByClause expression,
                                           CollectionExpression collectionExpression,
                                           int index,
                                           boolean hasComma) {

            if ((index == 0) || hasComma) {
                visitor.addIdentificationVariables();
                visitor.addResultVariables();
            }
        }

        /**
         * {@inheritDoc}
         */
        public CollectionExpression buildCollectionExpression(AbstractOrderByClause expression) {
            CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getOrderByItems());
            if (collectionExpression == null) {
                collectionExpression = expression.buildCollectionExpression();
            }
            return collectionExpression;
        }

        /**
         * {@inheritDoc}
         */
        public boolean canContinue(AbstractOrderByClause expression,
                                   CollectionExpression collectionExpression,
                                   int index) {

            return false;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasDelimiterAfterIdentifier(AbstractOrderByClause expression) {
            return expression.hasSpaceAfterIdentifier();
        }

        /**
         * {@inheritDoc}
         */
        public int maxCollectionSize(AbstractOrderByClause expression) {
            return Integer.MAX_VALUE;
        }

        /**
         * {@inheritDoc}
         */
        public int preExpressionLength(AbstractOrderByClause expression) {
            return 0;
        }

        /**
         * {@inheritDoc}
         */
        public JPQLQueryBNF queryBNF(AbstractOrderByClause expression, int index) {
            return visitor.getQueryBNF(OrderByItemBNF.ID);
        }
    }

    // Made static for performance reasons.
    protected static class OrderByClauseStatementHelper
            extends AbstractVisitorHelper implements StatementHelper<SelectStatement> {

        protected OrderByClauseStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addClauseProposals() {
            visitor.addCompositeIdentifier(ORDER_BY, -1);
        }

        /**
         * {@inheritDoc}
         */
        public void addInternalClauseProposals(SelectStatement expression) {
            // Does not have internal clauses
        }

        /**
         * {@inheritDoc}
         */
        public Expression getClause(SelectStatement expression) {
            return expression.getOrderByClause();
        }

        /**
         * {@inheritDoc}
         */
        public StatementHelper<SelectStatement> getNextHelper() {
            return null;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasClause(SelectStatement expression) {
            return expression.hasOrderByClause();
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasSpaceAfterClause(SelectStatement expression) {
            return false;
        }

        /**
         * {@inheritDoc}
         */
        public boolean isClauseComplete(SelectStatement expression) {
            return visitor.isComplete(expression.getOrderByClause());
        }

        /**
         * {@inheritDoc}
         */
        public boolean isRequired() {
            return false;
        }
    }

    // Made static final for performance reasons.
    protected static final class PropertyMappingFilter implements Filter<IMapping> {

        /**
         * {@inheritDoc}
         */
        public boolean accept(IMapping value) {
            return !value.isTransient() &&
                   !value.isCollection();
        }
    }

    // Made static final for performance reasons.
    protected static final class RangeVariableDeclarationVisitor extends AbstractExpressionVisitor {

        /**
         * The {@link RangeVariableDeclaration} if it was visited otherwise <code>null</code>.
         */
        protected RangeVariableDeclaration expression;

        /**
         * Disposes of the internal data.
         */
        public void dispose() {
            expression = null;
        }

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

    // Made static final for performance reasons.
    protected static final class ResultVariableVisitor extends AbstractExpressionVisitor {

        /**
         * The {@link ResultVariable} if it was visited otherwise <code>null</code>.
         */
        protected ResultVariable expression;

        /**
         * Disposes of the internal data.
         */
        public void dispose() {
            expression = null;
        }

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

    // Made static final for performance reasons.
    protected static final class SelectClauseCollectionHelper extends AbstractSelectClauseCollectionHelper<SelectClause> {

        protected SelectClauseCollectionHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void addAtTheEndOfChild(SelectClause expression,
                                       CollectionExpression collectionExpression,
                                       int index,
                                       boolean hasComma,
                                       boolean virtualSpace) {

            super.addAtTheEndOfChild(expression, collectionExpression, index, hasComma, virtualSpace);

            Expression child = collectionExpression.getChild(index);

            // "SELECT e |" <- Valid to add AS
            // "SELECT AVG(e |" <- Invalid to add AS
            if (virtualSpace && visitor.isComplete(child)) {
                visitor.addIdentifier(AS);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void addTheBeginningOfChild(SelectClause expression,
                                           CollectionExpression collectionExpression,
                                           int index,
                                           boolean hasComma) {

            super.addTheBeginningOfChild(expression, collectionExpression, index, hasComma);

            if (index == 0) {
                visitor.addIdentifier(DISTINCT);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int preExpressionLength(SelectClause expression) {

            int length = 0;

            if (expression.hasDistinct()) {
                length = 8 /* DISTINCT */;

                if (expression.hasSpaceAfterDistinct()) {
                    length++;
                }
            }

            return length;
        }
    }

    // Made static final for performance reasons.
    protected static final class SelectClauseStatementHelper extends AbstractSelectClauseStatementHelper {

        protected SelectClauseStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public FromClauseStatementHelper getNextHelper() {
            return visitor.getFromClauseStatementHelper();
        }
    }

    // Made static for performance reasons.
    protected static class SimpleFromClauseStatementHelper extends AbstractFromClauseStatementHelper<SimpleSelectStatement> {

        protected SimpleFromClauseStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public SimpleWhereClauseSelectStatementHelper getNextHelper() {
            return visitor.getSimpleWhereClauseSelectStatementHelper();
        }
    }

    // Made static final for performance reasons.
    protected static final class SimpleGroupByClauseStatementHelper extends AbstractGroupByClauseStatementHelper<SimpleSelectStatement> {

        protected SimpleGroupByClauseStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public SimpleHavingClauseStatementHelper getNextHelper() {
            return visitor.getSimpleHavingClauseStatementHelper();
        }
    }

    // Made static final for performance reasons.
    protected static final class SimpleHavingClauseStatementHelper extends AbstractHavingClauseStatementHelper<SimpleSelectStatement> {

        protected SimpleHavingClauseStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public StatementHelper<SimpleSelectStatement> getNextHelper() {
            return null;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasSpaceAfterClause(SimpleSelectStatement expression) {
            return false;
        }
    }

    // Made static final for performance reasons.
    protected static final class SimpleSelectClauseCollectionHelper extends AbstractSelectClauseCollectionHelper<SimpleSelectClause> {

        protected SimpleSelectClauseCollectionHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

    }

    // Made static final for performance reasons.
    protected static final class SimpleSelectClauseStatementHelper extends AbstractSelectClauseStatementHelper {

        protected SimpleSelectClauseStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public StatementHelper<SimpleSelectStatement> getNextHelper() {
            return visitor.getSimpleFromClauseStatementHelper();
        }
    }

    // Made static final for performance reasons.
    protected static final class SimpleWhereClauseSelectStatementHelper extends AbstractWhereClauseSelectStatementHelper<SimpleSelectStatement> {

        protected SimpleWhereClauseSelectStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public StatementHelper<SimpleSelectStatement> getNextHelper() {
            return visitor.getSimpleGroupByClauseStatementHelper();
        }
    }

    // Made static for performance reasons.
    /**
     * This helper helps to add JPQL identifiers for the clauses that make up a query statement and
     * also chains the clauses within the query.
     */
    protected static interface StatementHelper<T extends Expression> {

        /**
         * Adds the JPQL identifier of the clause being scanned by this helper.
         * <p>
         * Note: The identifier should not be added directly to the list, it needs to be filtered out
         * based on the location of the cursor, it can be within a word.
         */
        void addClauseProposals();

        /**
         * Adds the JPQL identifier of the internal clause being scanned by this helper. For instance,
         * the <code><b>FROM</b></code> clause could add its own sub-clauses.
         * <p>
         * Note: The identifier should not be added directly to the list, it needs to be filtered out
         * based on the location of the cursor, it can be within a word.
         *
         * @param expression The {@link AbstractSelectStatement} being visited
         */
        void addInternalClauseProposals(T expression);

        /**
         * Returns the clause being scanned by this helper. It is safe to type cast the clause because
         * {@link #hasClause(Expression)} is called before this one.
         *
         * @param expression The {@link AbstractSelectStatement} being visited
         * @return The clause being scanned
         */
        Expression getClause(T expression);

        /**
         * Returns the {@link StatementHelper} that will scan the following clause, which is
         * based on the grammar and not on the actual existence of the clause in the parsed tree.
         *
         * @return The {@link StatementHelper} for the next clause
         */
        StatementHelper<? extends T> getNextHelper();

        /**
         * Determines whether the clause exists in the parsed tree.
         *
         * @param expression The {@link AbstractSelectStatement} being visited
         * @return <code>true</code> if the clause has been parsed; <code>false</code> otherwise
         */
        boolean hasClause(T expression);

        /**
         * Determines whether there is a space (owned by the <b>SELECT</b> statement) after the clause
         * being scanned by this helper.
         *
         * @param expression The {@link AbstractSelectStatement} being visited
         * @return <code>true</code> if a space follows the clause; <code>false</code> otherwise
         */
        boolean hasSpaceAfterClause(T expression);

        /**
         * Determines whether the clause being scanned is complete or not.
         *
         * @param expression The {@link AbstractSelectStatement} being visited
         * @return <code>true</code> if the clause is complete; <code>false</code> otherwise
         */
        boolean isClauseComplete(T expression);

        /**
         * Determines whether the clause is required in order to make the JPQL query grammatically valid.
         *
         * @return <code>true</code> if the clause has to be defined; <code>false</code> if the clause
         * is optional
         */
        boolean isRequired();
    }

    // Made static final for performance reasons.
    protected static final class SubqueryAppendableExpressionVisitor extends AbstractAppendableExpressionVisitor {

        /**
         * For a subquery <code><b>SELECT</b></code> clause identifier to be appendable, it has to be
         * encapsulated by a {@link SubExpression}.
         */
        protected boolean subExpression;

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

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

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

    // Made static final for performance reasons.
    /**
     * This visitor determines if an {@link Expression} is in a subquery.
     */
    protected static final class SubqueryVisitor extends AbstractTraverseParentVisitor {

        /**
         * The subquery {@link Expression} if it's the first clause visitor. Otherwise it will be
         * <code>null</code> if the {@link Expression} is in the top-level query.
         */
        protected SimpleSelectStatement expression;

        /**
         * Disposes of the internal data.
         */
        public void dispose() {
            expression = null;
        }

        /**
         * Determines whether the visited {@link Expression} is in a subquery or in the top-level query.
         *
         * @return <code>true</code> if the owning query is a subquery; <code>false</code> if it's the
         * top-level query
         */
        public boolean isInSubquery() {
            return expression != null;
        }

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

    // Made static final for performance reasons.
    protected static final class TripleEncapsulatedCollectionHelper
            extends AbstractVisitorHelper implements CollectionExpressionHelper<AbstractTripleEncapsulatedExpression> {

        protected TripleEncapsulatedCollectionHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addAtTheEndOfChild(AbstractTripleEncapsulatedExpression expression,
                                       CollectionExpression collectionExpression,
                                       int index,
                                       boolean hasComma,
                                       boolean virtualSpace) {

            // The only thing that is appendable is an arithmetic operator
            // Example: "SELECT e.name|"
            // Example: "SELECT e|"
            if (queryBNF(expression, index).handleAggregate()) {

                Expression child = collectionExpression.getChild(index);

                if ((index == 0) && !virtualSpace) {

                    if (visitor.areArithmeticSymbolsAppendable(child)) {
                        visitor.addArithmeticIdentifiers();
                    }
                }
                else {

                    if (visitor.areArithmeticSymbolsAppendable(child)) {
                        visitor.addArithmeticIdentifiers();
                    }

                    if (visitor.areComparisonSymbolsAppendable(child)) {
                        visitor.addComparisonIdentifiers(child);
                    }
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        public void addIdentifier(AbstractTripleEncapsulatedExpression expression, String identifier) {
            visitor.proposals.addIdentifier(identifier);
            visitor.addFunctionIdentifiers(expression);
        }

        /**
         * {@inheritDoc}
         */
        public void addTheBeginningOfChild(AbstractTripleEncapsulatedExpression expression,
                                           CollectionExpression collectionExpression,
                                           int index,
                                           boolean hasComma) {

            visitor.addIdentificationVariables();
            visitor.addFunctionIdentifiers(queryBNF(expression, index));
        }

        /**
         * {@inheritDoc}
         */
        public CollectionExpression buildCollectionExpression(AbstractTripleEncapsulatedExpression expression) {
            return expression.buildCollectionExpression();
        }

        /**
         * {@inheritDoc}
         */
        public boolean canContinue(AbstractTripleEncapsulatedExpression expression,
                                   CollectionExpression collectionExpression,
                                   int index) {

            return false;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasDelimiterAfterIdentifier(AbstractTripleEncapsulatedExpression expression) {
            return expression.hasSpaceAfterIdentifier() ||
                   expression.hasLeftParenthesis();
        }

        /**
         * {@inheritDoc}
         */
        public int maxCollectionSize(AbstractTripleEncapsulatedExpression expression) {
            // Both LOCATE and SUBSTRING can allow up to 3 encapsulated expressions
            return 3;
        }

        /**
         * {@inheritDoc}
         */
        public int preExpressionLength(AbstractTripleEncapsulatedExpression expression) {
            return 0;
        }

        /**
         * {@inheritDoc}
         */
        public JPQLQueryBNF queryBNF(AbstractTripleEncapsulatedExpression expression, int index) {
            return visitor.getQueryBNF(expression.getParameterQueryBNFId(index));
        }
    }

    // Made static final for performance reasons.
    protected static final class UpdateClauseStatementHelper
            extends AbstractVisitorHelper implements StatementHelper<UpdateStatement> {

        protected UpdateClauseStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addClauseProposals() {
            visitor.addIdentifier(UPDATE);
        }

        /**
         * {@inheritDoc}
         */
        public void addInternalClauseProposals(UpdateStatement expression) {
            // Does not have internal clauses
        }

        /**
         * {@inheritDoc}
         */
        public Expression getClause(UpdateStatement expression) {
            return expression.getUpdateClause();
        }

        /**
         * {@inheritDoc}
         */
        public StatementHelper<UpdateStatement> getNextHelper() {
            return visitor.getWhereClauseUpdateStatementHelper();
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasClause(UpdateStatement expression) {
            return true;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasSpaceAfterClause(UpdateStatement expression) {
            return expression.hasSpaceAfterUpdateClause();
        }

        /**
         * {@inheritDoc}
         */
        public boolean isClauseComplete(UpdateStatement expression) {
            return visitor.isComplete(expression.getUpdateClause());
        }

        /**
         * {@inheritDoc}
         */
        public boolean isRequired() {
            return true;
        }
    }

    // Made static final for performance reasons.
    protected static final class UpdateItemCollectionHelper
            extends AbstractVisitorHelper implements CollectionExpressionHelper<UpdateClause> {

        protected UpdateItemCollectionHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addAtTheEndOfChild(UpdateClause expression,
                                       CollectionExpression collectionExpression,
                                       int index,
                                       boolean hasComma,
                                       boolean virtualSpace) {

            visitor.addAggregateIdentifiers(NewValueBNF.ID);
        }

        /**
         * {@inheritDoc}
         */
        public void addIdentifier(UpdateClause expression, String identifier) {
            visitor.proposals.addIdentifier(identifier);
        }

        /**
         * {@inheritDoc}
         */
        public void addTheBeginningOfChild(UpdateClause expression,
                                           CollectionExpression collectionExpression,
                                           int index,
                                           boolean hasComma) {

            visitor.addIdentificationVariables();
        }

        /**
         * {@inheritDoc}
         */
        public CollectionExpression buildCollectionExpression(UpdateClause expression) {
            CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getUpdateItems());
            if (collectionExpression == null) {
                collectionExpression = expression.buildCollectionExpression();
            }
            return collectionExpression;
        }

        /**
         * {@inheritDoc}
         */
        public boolean canContinue(UpdateClause expression,
                                   CollectionExpression collectionExpression,
                                   int index) {

            return false;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasDelimiterAfterIdentifier(UpdateClause expression) {
            return expression.hasSpaceAfterUpdate();
        }

        /**
         * {@inheritDoc}
         */
        public int maxCollectionSize(UpdateClause expression) {
            return Integer.MAX_VALUE;
        }

        /**
         * {@inheritDoc}
         */
        public int preExpressionLength(UpdateClause expression) {
            // There is a SPACE_LENGTH less, it's added automatically
            return UPDATE.length() +
                   SPACE_LENGTH    +
                   expression.getRangeVariableDeclaration().getLength() +
                   SPACE_LENGTH    +
                   SET.length();
        }

        /**
         * {@inheritDoc}
         */
        public JPQLQueryBNF queryBNF(UpdateClause expression, int index) {
            return visitor.getQueryBNF(NewValueBNF.ID);
        }
    }

    // Made static final for performance reasons.
    /**
     * This visitor is meant to adjust the corrections stack when traversing an {@link Expression} in
     * order to increase the list of valid proposals.
     * <p>
     * For instance, if the query is "<code>SELECT e FROM Employee e WHERE IN</code>" and the cursor
     * is at the end of the query, then <code>IN</code> would be parsed with {@link InExpression}.
     * However, due to how {@link AbstractContentAssistVisitor} works, the identifier <code>INDEX</code>
     * is not added as a valid proposal. This visitor adds that functionality.
     */
    protected static final class VisitParentVisitor extends AnonymousExpressionVisitor {

        /**
         * Enclosing visitor instance.
         */
        protected final AbstractContentAssistVisitor visitor;

        protected VisitParentVisitor(AbstractContentAssistVisitor visitor) {
            this.visitor = visitor;
        }

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

        /**
         * {@inheritDoc}
         */
        @Override
        public void visit(InExpression expression) {

            int position = visitor.queryPosition.getPosition(expression) - visitor.corrections.peek();
            int length = 0;

            if (expression.hasExpression()) {
                length += expression.getExpression().getLength() + SPACE_LENGTH;
            }

            // Within "IN"
            if (visitor.isPositionWithin(position, length, expression.getIdentifier())) {

                boolean hasOnlyIdentifier = !expression.hasExpression() &&
                                            !expression.hasInItems();

                if (hasOnlyIdentifier) {
                    visitor.corrections.add(visitor.queryPosition.getPosition(expression));
                }

                super.visit(expression);

                if (hasOnlyIdentifier) {
                    visitor.corrections.pop();
                }
            }
            else {
                super.visit(expression);
            }
        }
    }

    // Made static final for performance reasons.
    /**
     * This helpers handles adding proposals for {@link WhenClause}.
     */
    protected static final class WhenClauseConditionalClauseCollectionHelper extends AbstractConditionalClauseCollectionHelper<WhenClause> {

        protected WhenClauseConditionalClauseCollectionHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public CollectionExpression buildCollectionExpression(WhenClause expression) {
            CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getWhenExpression());
            if (collectionExpression == null) {
                collectionExpression = expression.buildWhenCollectionExpression();
            }
            return collectionExpression;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasDelimiterAfterIdentifier(WhenClause expression) {
            return expression.hasSpaceAfterWhen();
        }
    }

    // Made static final for performance reasons.
    protected static final class WhereClauseDeleteStatementHelper
            extends AbstractVisitorHelper implements StatementHelper<DeleteStatement> {

        protected WhereClauseDeleteStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addClauseProposals() {
            visitor.addIdentifier(WHERE);
        }

        /**
         * {@inheritDoc}
         */
        public void addInternalClauseProposals(DeleteStatement expression) {
            // Does not have internal clauses
        }

        /**
         * {@inheritDoc}
         */
        public Expression getClause(DeleteStatement expression) {
            return expression.getWhereClause();
        }

        /**
         * {@inheritDoc}
         */
        public StatementHelper<? extends DeleteStatement> getNextHelper() {
            return null;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasClause(DeleteStatement expression) {
            return expression.hasWhereClause();
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasSpaceAfterClause(DeleteStatement expression) {
            return false;
        }

        /**
         * {@inheritDoc}
         */
        public boolean isClauseComplete(DeleteStatement expression) {
            return visitor.isComplete(expression.getWhereClause());
        }

        /**
         * {@inheritDoc}
         */
        public boolean isRequired() {
            return false;
        }
    }

    // Made static final for performance reasons.
    protected static final class WhereClauseSelectStatementHelper extends AbstractWhereClauseSelectStatementHelper<SelectStatement> {

        protected WhereClauseSelectStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public StatementHelper<SelectStatement> getNextHelper() {
            return visitor.getGroupByClauseStatementHelper();
        }
    }

    // Made static final for performance reasons.
    protected static final class WhereClauseUpdateStatementHelper
            extends AbstractVisitorHelper implements StatementHelper<UpdateStatement> {

        protected WhereClauseUpdateStatementHelper(AbstractContentAssistVisitor visitor) {
            super(visitor);
        }

        /**
         * {@inheritDoc}
         */
        public void addClauseProposals() {
            visitor.addIdentifier(WHERE);
        }

        /**
         * {@inheritDoc}
         */
        public void addInternalClauseProposals(UpdateStatement expression) {
            // Does not have internal clauses
        }

        /**
         * {@inheritDoc}
         */
        public Expression getClause(UpdateStatement expression) {
            return expression.getWhereClause();
        }

        /**
         * {@inheritDoc}
         */
        public StatementHelper<? extends UpdateStatement> getNextHelper() {
            return null;
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasClause(UpdateStatement expression) {
            return expression.hasWhereClause();
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasSpaceAfterClause(UpdateStatement expression) {
            return false;
        }

        /**
         * {@inheritDoc}
         */
        public boolean isClauseComplete(UpdateStatement expression) {
            return visitor.isComplete(expression.getWhereClause());
        }

        /**
         * {@inheritDoc}
         */
        public boolean isRequired() {
            return false;
        }
    }

    // Made static final for performance reasons.
    protected static final class WithinInvalidExpressionVisitor extends AbstractTraverseParentVisitor {

        /**
         * Determines whether the visited {@link Expression} is an descendant of either a bad or
         * invalid expression.
         */
        protected boolean withinInvalidExpression;

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

        /**
         * Determines whether the visited {@link Expression} is part of an invalid fragment
         *
         * @return <code>true</code> if the visited {@link Expression} is within an invalid fragment;
         * <code>false</code> if it is not
         */
        public boolean isWithinInvalidExpression() {
            return withinInvalidExpression;
        }

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

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