/*******************************************************************************
 * Copyright (c) 2006, 2014 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 v1.0 and Eclipse Distribution License v. 1.0
 * which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation
 *
 ******************************************************************************/
package org.eclipse.persistence.jpa.jpql;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.AbstractEncapsulatedExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractFromClause;
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.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.ArithmeticExpression;
import org.eclipse.persistence.jpa.jpql.parser.ArithmeticExpressionBNF;
import org.eclipse.persistence.jpa.jpql.parser.ArithmeticFactor;
import org.eclipse.persistence.jpa.jpql.parser.ArithmeticPrimaryBNF;
import org.eclipse.persistence.jpa.jpql.parser.ArithmeticTermBNF;
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.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.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.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.FromClause;
import org.eclipse.persistence.jpa.jpql.parser.FunctionExpression;
import org.eclipse.persistence.jpa.jpql.parser.GroupByClause;
import org.eclipse.persistence.jpa.jpql.parser.HavingClause;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariableBNF;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.InExpression;
import org.eclipse.persistence.jpa.jpql.parser.IndexExpression;
import org.eclipse.persistence.jpa.jpql.parser.InputParameter;
import org.eclipse.persistence.jpa.jpql.parser.InternalOrderByItemBNF;
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.JoinAssociationPathExpressionBNF;
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.LikeExpressionEscapeCharacterBNF;
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.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.RangeDeclarationBNF;
import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.ResultVariable;
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.SubExpression;
import org.eclipse.persistence.jpa.jpql.parser.SubstringExpression;
import org.eclipse.persistence.jpa.jpql.parser.SubtractionExpression;
import org.eclipse.persistence.jpa.jpql.parser.SumFunction;
import org.eclipse.persistence.jpa.jpql.parser.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 static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.*;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.*;

/**
 * The base validator responsible to gather the problems found in a JPQL query by validating it
 * against the provided JPQL grammar. The semantic of the JPQL query is not validated by this visitor.
 * <p>
 * Provisional API: This interface is part of an interim API that is still under development and
 * expected to change significantly before reaching stability. It is available at this early stage
 * to solicit feedback from pioneering adopters on the understanding that any code that uses this
 * API will almost certainly be broken (repeatedly) as the API evolves.
 *
 * @see AbstractSemanticValidator
 *
 * @version 2.5.1
 * @since 2.4
 * @author Pascal Filion
 */
@SuppressWarnings("nls")
public abstract class AbstractGrammarValidator extends AbstractValidator {

	/**
	 * This visitor determines whether the visited {@link Expression} is the {@link CollectionExpression}.
	 */
	private CollectionExpressionVisitor collectionExpressionVisitor;

	/**
	 * This validator validates a {@link CollectionExpression} by making sure each item is separated
	 * by a comma.
	 */
	private CollectionSeparatedByCommaValidator collectionSeparatedByCommaValidator;

	/**
	 * This validator validates a {@link CollectionExpression} by making sure each item is separated
	 * by a space.
	 */
	private CollectionSeparatedBySpaceValidator collectionSeparatedBySpaceValidator;

	/**
	 * This visitor is responsible to retrieve the visited {@link Expression} if it is a
	 * {@link ComparisonExpression}.
	 */
	private ComparisonExpressionVisitor comparisonExpressionVisitor;

	/**
	 * The registered expression helper mapped by the unique JPQL identifier of the same expression
	 * to validate.
	 */
	private Map<String, Object> helpers;

	/**
	 * The cached {@link InputParameter InputParameters} that are present in the entire JPQL query.
	 */
	private Collection<InputParameter> inputParameters;

	/**
	 * The {@link JPQLGrammar} that defines how the JPQL query was parsed.
	 */
	private JPQLGrammar jpqlGrammar;

	/**
	 * Creates a new <code>AbstractGrammarValidator</code>.
	 *
	 * @param jpqlGrammar The {@link JPQLGrammar} that defines how the JPQL query was parsed, which
	 * cannot be <code>null</code>
	 * @exception NullPointerException If the given {@link JPQLGrammar} is <code>null</code>
	 */
	protected AbstractGrammarValidator(JPQLGrammar jpqlGrammar) {
		super();
		Assert.isNotNull(jpqlGrammar, "The JPQLGrammar cannot be null");
		this.jpqlGrammar = jpqlGrammar;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<AbsExpression> absExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<AbsExpression> helper = getHelper(ABS);
		if (helper == null) {
			helper = buildAbsExpressionHelper();
			registerHelper(ABS, helper);
		}
		return helper;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<AllOrAnyExpression> allOrAnyExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<AllOrAnyExpression> helper = getHelper(ALL);
		if (helper == null) {
			helper = buildAllOrAnyExpressionHelper();
			registerHelper(ALL, helper);
		}
		return helper;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<AvgFunction> avgFunctionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<AvgFunction> helper = getHelper(AVG);
		if (helper == null) {
			helper = buildAvgFunctionHelper();
			registerHelper(AVG, helper);
		}
		return helper;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<AbsExpression> buildAbsExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<AbsExpression>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(AbsExpression expression) {
				return AbsExpression_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(AbsExpression expression) {
				return AbsExpression_MissingExpression;
			}
			public String leftParenthesisMissingKey(AbsExpression expression) {
				return AbsExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(AbsExpression expression) {
				return AbsExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<AllOrAnyExpression> buildAllOrAnyExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<AllOrAnyExpression>(this) {
			@Override
			public String[] arguments(AllOrAnyExpression expression) {
				return new String[] { expression.getIdentifier() };
			}
			@Override
			public String encapsulatedExpressionInvalidKey(AllOrAnyExpression expression) {
				return AllOrAnyExpression_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(AllOrAnyExpression expression) {
				return AllOrAnyExpression_MissingExpression;
			}
			public String leftParenthesisMissingKey(AllOrAnyExpression expression) {
				return AllOrAnyExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(AllOrAnyExpression expression) {
				return AllOrAnyExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<AvgFunction> buildAvgFunctionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<AvgFunction>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(AvgFunction expression) {
				return AvgFunction_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(AvgFunction expression) {
				return AvgFunction_MissingExpression;
			}
			public String leftParenthesisMissingKey(AvgFunction expression) {
				return AvgFunction_MissingLeftParenthesis;
			}
			@Override
			public int lengthBeforeEncapsulatedExpression(AvgFunction expression) {
				return (expression.hasDistinct() ? 8 /* DISTINCT */ : 0) +
				       (expression.hasSpaceAfterDistinct() ? 1 : 0);
			}
			public String rightParenthesisMissingKey(AvgFunction expression) {
				return AvgFunction_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<CoalesceExpression> buildCoalesceExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<CoalesceExpression>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(CoalesceExpression expression) {
				return CoalesceExpression_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(CoalesceExpression expression) {
				return CoalesceExpression_MissingExpression;
			}
			@Override
			protected boolean isEncapsulatedExpressionValid(CoalesceExpression expression) {
				return isValidWithChildCollectionBypass(expression.getExpression(), expression.getEncapsulatedExpressionQueryBNFId());
			}
			public String leftParenthesisMissingKey(CoalesceExpression expression) {
				return CoalesceExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(CoalesceExpression expression) {
				return CoalesceExpression_MissingRightParenthesis;
			}
		};
	}

	/**
	 * Creates a visitor that collects the {@link CollectionExpression} if it's been visited.
	 *
	 * @return A new {@link CollectionExpressionVisitor}
	 */
	protected CollectionExpressionVisitor buildCollectionExpressionVisitor() {
		return new CollectionExpressionVisitor();
	}

	protected AbstractSingleEncapsulatedExpressionHelper<ConcatExpression> buildConcatExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<ConcatExpression>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(ConcatExpression expression) {
				return ConcatExpression_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(ConcatExpression expression) {
				return ConcatExpression_MissingExpression;
			}
			@Override
			public boolean isEncapsulatedExpressionValid(ConcatExpression expression) {
				// Done in visit(ConcatExpression)
				return true;
			}
			public String leftParenthesisMissingKey(ConcatExpression expression) {
				return ConcatExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(ConcatExpression expression) {
				return ConcatExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<CountFunction> buildCountFunctionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<CountFunction>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(CountFunction expression) {
				return CountFunction_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(CountFunction expression) {
				return CountFunction_MissingExpression;
			}
			public String leftParenthesisMissingKey(CountFunction expression) {
				return CountFunction_MissingLeftParenthesis;
			}
			@Override
			public int lengthBeforeEncapsulatedExpression(CountFunction expression) {
				return (expression.hasDistinct() ? 8 /* DISTINCT */ : 0) +
				       (expression.hasSpaceAfterDistinct() ? 1 : 0);
			}
			public String rightParenthesisMissingKey(CountFunction expression) {
				return CountFunction_MissingRightParenthesis;
			}
		};
	}

	protected DateTimeVisitor buildDateTimeVisitor() {
		return new DateTimeVisitor();
	}

	protected AbstractSingleEncapsulatedExpressionHelper<EntryExpression> buildEntryExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<EntryExpression>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(EntryExpression expression) {
				return EntryExpression_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(EntryExpression expression) {
				return EntryExpression_MissingExpression;
			}
			@Override
			public boolean isEncapsulatedExpressionValid(EntryExpression expression) {
				return isValid(expression.getExpression(), IdentificationVariableBNF.ID);
			}
			public String leftParenthesisMissingKey(EntryExpression expression) {
				return EntryExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(EntryExpression expression) {
				return EntryExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<ExistsExpression> buildExistsExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<ExistsExpression>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(ExistsExpression expression) {
				return ExistsExpression_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(ExistsExpression expression) {
				return ExistsExpression_MissingExpression;
			}
			public String leftParenthesisMissingKey(ExistsExpression expression) {
				return ExistsExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(ExistsExpression expression) {
				return ExistsExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<FunctionExpression> buildFunctionExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<FunctionExpression>(this) {
			@Override
			public String[] arguments(FunctionExpression expression) {
				return new String[] { expression.getIdentifier() };
			}
			@Override
			@SuppressWarnings("fallthrough")
			protected String encapsulatedExpressionInvalidKey(FunctionExpression expression) {
				switch (expression.getParameterCount()) {
					case ONE: {
						Expression children = expression.getExpression();
						int childrenCount = getChildren(children).size();
						if (childrenCount > 1) {
							return FunctionExpression_MoreThanOneExpression;
						}
					}
					case ZERO: {
						return FunctionExpression_HasExpression;
					}
					default: {
						return FunctionExpression_InvalidExpression;
					}
				}
			}
			@Override
			@SuppressWarnings("fallthrough")
			protected String encapsulatedExpressionMissingKey(FunctionExpression expression) {
				switch (expression.getParameterCount()) {
					case ONE: {
						Expression children = expression.getExpression();
						int childrenCount = getChildren(children).size();
						if (childrenCount == 0) {
							return FunctionExpression_MissingOneExpression;
						}
					}
					default: {
						return FunctionExpression_MissingExpression;
					}
				}
			}
			@Override
			protected boolean isEncapsulatedExpressionMissing(FunctionExpression expression) {
				switch (expression.getParameterCount()) {
					case ONE:
					case ONE_OR_MANY: return !expression.hasExpression();
					default:          return false;
				}
			}
			@Override
			protected boolean isEncapsulatedExpressionValid(FunctionExpression expression) {
				switch (expression.getParameterCount()) {
					case ONE: {
						return isValid(
							expression.getExpression(),
							expression.getEncapsulatedExpressionQueryBNFId()
						);
					}
					case ONE_OR_MANY: {
						return isValidWithChildCollectionBypass(
							expression.getExpression(),
							expression.getEncapsulatedExpressionQueryBNFId()
						);
					}
					case ZERO_OR_ONE: {
						return !expression.hasExpression() ||
						       isValid(expression.getExpression(), expression.getEncapsulatedExpressionQueryBNFId());
					}
					default: {
						return true;
					}
				}
			}
			public String leftParenthesisMissingKey(FunctionExpression expression) {
				return null; // never happens
			}
			@Override
			protected int lengthBeforeEncapsulatedExpression(FunctionExpression expression) {
				return expression.getFunctionName().length() +
				       (expression.hasComma() ? 1 : 0) +
				       (expression.hasSpaceAfterComma() ? 1 : 0);
			}
			public String rightParenthesisMissingKey(FunctionExpression expression) {
				return FunctionExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<IndexExpression> buildIndexExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<IndexExpression>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(IndexExpression expression) {
				return IndexExpression_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(IndexExpression expression) {
				return IndexExpression_MissingExpression;
			}
			@Override
			public boolean isEncapsulatedExpressionValid(IndexExpression expression) {
				return isValid(expression.getExpression(), IdentificationVariableBNF.ID);
			}
			public String leftParenthesisMissingKey(IndexExpression expression) {
				return IndexExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(IndexExpression expression) {
				return IndexExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<KeyExpression> buildKeyExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<KeyExpression>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(KeyExpression expression) {
				return KeyExpression_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(KeyExpression expression) {
				return KeyExpression_MissingExpression;
			}
			@Override
			public boolean isEncapsulatedExpressionValid(KeyExpression expression) {
				return isValid(expression.getExpression(), IdentificationVariableBNF.ID);
			}
			public String leftParenthesisMissingKey(KeyExpression expression) {
				return KeyExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(KeyExpression expression) {
				return KeyExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<LengthExpression> buildLengthExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<LengthExpression>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(LengthExpression expression) {
				return LengthExpression_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(LengthExpression expression) {
				return LengthExpression_MissingExpression;
			}
			public String leftParenthesisMissingKey(LengthExpression expression) {
				return LengthExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(LengthExpression expression) {
				return LengthExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractTripleEncapsulatedExpressionHelper<LocateExpression> buildLocateExpressionHelper() {
		return new AbstractTripleEncapsulatedExpressionHelper<LocateExpression>(this) {
			@Override
			protected String firstCommaMissingKey() {
				return LocateExpression_MissingFirstComma;
			}
			@Override
			protected String firstExpressionInvalidKey() {
				return LocateExpression_InvalidFirstExpression;
			}
			@Override
			protected String firstExpressionMissingKey() {
				return LocateExpression_MissingFirstExpression;
			}
			public String identifier(LocateExpression expression) {
				return LOCATE;
			}
			public String leftParenthesisMissingKey(LocateExpression expression) {
				return LocateExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(LocateExpression expression) {
				return LocateExpression_MissingRightParenthesis;
			}
			@Override
			protected String secondCommaMissingKey() {
				return LocateExpression_MissingSecondComma;
			}
			@Override
			protected String secondExpressionInvalidKey() {
				return LocateExpression_InvalidSecondExpression;
			}
			@Override
			protected String secondExpressionMissingKey() {
				return LocateExpression_MissingSecondExpression;
			}
			@Override
			protected String thirdExpressionInvalidKey() {
				return LocateExpression_InvalidThirdExpression;
			}
			@Override
			protected String thirdExpressionMissingKey() {
				return LocateExpression_MissingThirdExpression;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<LowerExpression> buildLowerExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<LowerExpression>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(LowerExpression expression) {
				return LowerExpression_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(LowerExpression expression) {
				return LowerExpression_MissingExpression;
			}
			public String leftParenthesisMissingKey(LowerExpression expression) {
				return LowerExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(LowerExpression expression) {
				return LowerExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<MaxFunction> buildMaxFunctionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<MaxFunction>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(MaxFunction expression) {
				return MaxFunction_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(MaxFunction expression) {
				return MaxFunction_MissingExpression;
			}
			public String leftParenthesisMissingKey(MaxFunction expression) {
				return MaxFunction_MissingLeftParenthesis;
			}
			@Override
			public int lengthBeforeEncapsulatedExpression(MaxFunction expression) {
				return (expression.hasDistinct() ? 8 /* DISTINCT */ : 0) +
				       (expression.hasSpaceAfterDistinct() ? 1 : 0);
			}
			public String rightParenthesisMissingKey(MaxFunction expression) {
				return MaxFunction_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<MinFunction> buildMinFunctionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<MinFunction>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(MinFunction expression) {
				return MinFunction_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(MinFunction expression) {
				return MinFunction_MissingExpression;
			}
			public String leftParenthesisMissingKey(MinFunction expression) {
				return MinFunction_MissingLeftParenthesis;
			}
			@Override
			public int lengthBeforeEncapsulatedExpression(MinFunction expression) {
				return (expression.hasDistinct() ? 8 /* DISTINCT */ : 0) +
				       (expression.hasSpaceAfterDistinct() ? 1 : 0);
			}
			public String rightParenthesisMissingKey(MinFunction expression) {
				return MinFunction_MissingRightParenthesis;
			}
		};
	}

	protected AbstractDoubleEncapsulatedExpressionHelper<ModExpression> buildModExpressionHelper() {
		return new AbstractDoubleEncapsulatedExpressionHelper<ModExpression>(this) {
			@Override
			protected String firstExpressionInvalidKey() {
				return ModExpression_InvalidFirstExpression;
			}
			@Override
			protected String firstExpressionMissingKey() {
				return ModExpression_MissingFirstExpression;
			}
			public String leftParenthesisMissingKey(ModExpression expression) {
				return ModExpression_MissingLeftParenthesis;
			}
			@Override
			protected String missingCommaKey() {
				return ModExpression_MissingComma;
			}
			public String rightParenthesisMissingKey(ModExpression expression) {
				return ModExpression_MissingRightParenthesis;
			}
			@Override
			protected String secondExpressionInvalidKey() {
				return ModExpression_InvalidSecondExpression;
			}
			@Override
			protected String secondExpressionMissingKey() {
				return ModExpression_MissingSecondExpression;
			}
		};
	}

	/**
	 * Creates a visitor that collects the {@link NullExpression} if it's been visited.
	 *
	 * @return A new {@link NullExpressionVisitor}
	 */
	protected NullExpressionVisitor buildNullExpressionVisitor() {
		return new NullExpressionVisitor();
	}

	protected AbstractDoubleEncapsulatedExpressionHelper<NullIfExpression> buildNullIfExpressionHelper() {
		return new AbstractDoubleEncapsulatedExpressionHelper<NullIfExpression>(this) {
			@Override
			public String firstExpressionInvalidKey() {
				return NullIfExpression_InvalidFirstExpression;
			}
			@Override
			public String firstExpressionMissingKey() {
				return NullIfExpression_MissingFirstExpression;
			}
			public String leftParenthesisMissingKey(NullIfExpression expression) {
				return NullIfExpression_MissingLeftParenthesis;
			}
			@Override
			public String missingCommaKey() {
				return NullIfExpression_MissingComma;
			}
			public String rightParenthesisMissingKey(NullIfExpression expression) {
				return NullIfExpression_MissingRightParenthesis;
			}
			@Override
			public String secondExpressionInvalidKey() {
				return NullIfExpression_InvalidSecondExpression;
			}
			@Override
			public String secondExpressionMissingKey() {
				return NullIfExpression_MissingSecondExpression;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<ObjectExpression> buildObjectExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<ObjectExpression>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(ObjectExpression expression) {
				return ObjectExpression_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(ObjectExpression expression) {
				return ObjectExpression_MissingExpression;
			}
			@Override
			public boolean isEncapsulatedExpressionValid(ObjectExpression expression) {
				return isValid(expression.getExpression(), IdentificationVariableBNF.ID);
			}
			public String leftParenthesisMissingKey(ObjectExpression expression) {
				return ObjectExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(ObjectExpression expression) {
				return ObjectExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<SizeExpression> buildSizeExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<SizeExpression>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(SizeExpression expression) {
				return SizeExpression_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(SizeExpression expression) {
				return SizeExpression_MissingExpression;
			}
			public String leftParenthesisMissingKey(SizeExpression expression) {
				return SizeExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(SizeExpression expression) {
				return SizeExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<SqrtExpression> buildSqrtExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<SqrtExpression>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(SqrtExpression expression) {
				return SqrtExpression_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(SqrtExpression expression) {
				return SqrtExpression_MissingExpression;
			}
			public String leftParenthesisMissingKey(SqrtExpression expression) {
				return SqrtExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(SqrtExpression expression) {
				return SqrtExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractTripleEncapsulatedExpressionHelper<SubstringExpression> buildSubstringExpressionHelper() {
		return new AbstractTripleEncapsulatedExpressionHelper<SubstringExpression>(this) {
			@Override
			protected String firstCommaMissingKey() {
				return SubstringExpression_MissingFirstComma;
			}
			@Override
			protected String firstExpressionInvalidKey() {
				return SubstringExpression_InvalidFirstExpression;
			}
			@Override
			protected String firstExpressionMissingKey() {
				return SubstringExpression_MissingFirstExpression;
			}
			public String identifier(SubstringExpression expression) {
				return SUBSTRING;
			}
			public String leftParenthesisMissingKey(SubstringExpression expression) {
				return SubstringExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(SubstringExpression expression) {
				return SubstringExpression_MissingRightParenthesis;
			}
			@Override
			protected String secondCommaMissingKey() {
				return SubstringExpression_MissingSecondComma;
			}
			@Override
			protected String secondExpressionInvalidKey() {
				return SubstringExpression_InvalidSecondExpression;
			}
			@Override
			protected String secondExpressionMissingKey() {
				return SubstringExpression_MissingSecondExpression;
			}
			@Override
			protected String thirdExpressionInvalidKey() {
				return SubstringExpression_InvalidThirdExpression;
			}
			@Override
			protected String thirdExpressionMissingKey() {
				return SubstringExpression_MissingThirdExpression;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<SumFunction> buildSumFunctionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<SumFunction>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(SumFunction expression) {
				return SumFunction_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(SumFunction expression) {
				return SumFunction_MissingExpression;
			}
			public String leftParenthesisMissingKey(SumFunction expression) {
				return SumFunction_MissingLeftParenthesis;
			}
			@Override
			public int lengthBeforeEncapsulatedExpression(SumFunction expression) {
				return (expression.hasDistinct() ? 8 /* DISTINCT */ : 0) +
				       (expression.hasSpaceAfterDistinct() ? 1 : 0);
			}
			public String rightParenthesisMissingKey(SumFunction expression) {
				return SumFunction_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<TrimExpression> buildTrimExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<TrimExpression>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(TrimExpression expression) {
				return TrimExpression_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(TrimExpression expression) {
				return TrimExpression_MissingExpression;
			}
			@Override
			public boolean isEncapsulatedExpressionMissing(TrimExpression expression) {
				// Done in visit(TrimExpression)
				return false;
			}
			@Override
			public boolean isEncapsulatedExpressionValid(TrimExpression expression) {
				// Done in visit(TrimExpression)
				return true;
			}
			public String leftParenthesisMissingKey(TrimExpression expression) {
				return TrimExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(TrimExpression expression) {
				return TrimExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<TypeExpression> buildTypeExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<TypeExpression>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(TypeExpression expression) {
				return TypeExpression_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(TypeExpression expression) {
				return TypeExpression_MissingExpression;
			}
			public String leftParenthesisMissingKey(TypeExpression expression) {
				return TypeExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(TypeExpression expression) {
				return TypeExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<UpperExpression> buildUpperExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<UpperExpression>(this) {
			@Override
			public String encapsulatedExpressionInvalidKey(UpperExpression expression) {
				return UpperExpression_InvalidExpression;
			}
			@Override
			public String encapsulatedExpressionMissingKey(UpperExpression expression) {
				return UpperExpression_MissingExpression;
			}
			public String leftParenthesisMissingKey(UpperExpression expression) {
				return UpperExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(UpperExpression expression) {
				return UpperExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<ValueExpression> buildValueExpressionHelper() {
		return new AbstractSingleEncapsulatedExpressionHelper<ValueExpression>(this) {
			@Override
			protected String encapsulatedExpressionInvalidKey(ValueExpression expression) {
				return ValueExpression_InvalidExpression;
			}
			@Override
			protected String encapsulatedExpressionMissingKey(ValueExpression expression) {
				return ValueExpression_MissingExpression;
			}
			@Override
			protected boolean isEncapsulatedExpressionValid(ValueExpression expression) {
				return isValid(expression.getExpression(), IdentificationVariableBNF.ID);
			}
			public String leftParenthesisMissingKey(ValueExpression expression) {
				return ValueExpression_MissingLeftParenthesis;
			}
			public String rightParenthesisMissingKey(ValueExpression expression) {
				return ValueExpression_MissingRightParenthesis;
			}
		};
	}

	protected AbstractSingleEncapsulatedExpressionHelper<CoalesceExpression> coalesceExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<CoalesceExpression> helper = getHelper(COALESCE);
		if (helper == null) {
			helper = buildCoalesceExpressionHelper();
			registerHelper(COALESCE, helper);
		}
		return helper;
	}

	protected CollectionSeparatedByCommaValidator collectionSeparatedByCommaValidator() {
		if (collectionSeparatedByCommaValidator == null) {
			collectionSeparatedByCommaValidator = new CollectionSeparatedByCommaValidator(this);
		}
		return collectionSeparatedByCommaValidator;
	}

	protected CollectionSeparatedBySpaceValidator collectionSeparatedBySpaceValidator() {
		if (collectionSeparatedBySpaceValidator == null) {
			collectionSeparatedBySpaceValidator = new CollectionSeparatedBySpaceValidator(this);
		}
		return collectionSeparatedBySpaceValidator;
	}

	protected ComparisonExpressionVisitor comparisonExpressionVisitor() {
		if (comparisonExpressionVisitor == null) {
			comparisonExpressionVisitor = new ComparisonExpressionVisitor();
		}
		return comparisonExpressionVisitor;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<ConcatExpression> concatExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<ConcatExpression> helper = getHelper(CONCAT);
		if (helper == null) {
			helper = buildConcatExpressionHelper();
			registerHelper(CONCAT, helper);
		}
		return helper;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<CountFunction> countFunctionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<CountFunction> helper = getHelper(COUNT);
		if (helper == null) {
			helper = buildCountFunctionHelper();
			registerHelper(COUNT, helper);
		}
		return helper;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void dispose() {
		inputParameters.clear();
		super.dispose();
	}

	protected AbstractSingleEncapsulatedExpressionHelper<EntryExpression> entryExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<EntryExpression> helper = getHelper(ENTRY);
		if (helper == null) {
			helper = buildEntryExpressionHelper();
			registerHelper(ENTRY, helper);
		}
		return helper;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<ExistsExpression> existsExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<ExistsExpression> helper = getHelper(EXISTS);
		if (helper == null) {
			helper = buildExistsExpressionHelper();
			registerHelper(EXISTS, helper);
		}
		return helper;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<FunctionExpression> functionExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<FunctionExpression> helper = getHelper(FUNCTION);
		if (helper == null) {
			helper = buildFunctionExpressionHelper();
			registerHelper(FUNCTION, 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.expression = null;
		}
	}

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

	protected DateTimeVisitor getDateTimeVisitor() {
		DateTimeVisitor visitor = getHelper(DateTime.CURRENT_TIMESTAMP);
		if (visitor == null) {
			visitor = buildDateTimeVisitor();
			registerHelper(DateTime.CURRENT_TIMESTAMP, visitor);
		}
		return visitor;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected JPQLGrammar getGrammar() {
		return jpqlGrammar;
	}

	/**
	 * Returns the registered helper that was cached with the given id.
	 *
	 * @param id The key used to retrieve the cached helper, if one was cached
	 * @return Either the cached helper or <code>null</code> if no helper was previously cached for
	 * the given id
	 */
	@SuppressWarnings("unchecked")
	protected <T> T getHelper(String id) {
		return (T) helpers.get(id);
	}

	protected AbstractSingleEncapsulatedExpressionHelper<IndexExpression> indexExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<IndexExpression> helper = getHelper(INDEX);
		if (helper == null) {
			helper = buildIndexExpressionHelper();
			registerHelper(INDEX, helper);
		}
		return helper;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void initialize() {
		super.initialize();
		helpers         = new HashMap<String, Object>();
		inputParameters = new ArrayList<InputParameter>();
	}

	protected boolean isChildOfComparisonExpession(AllOrAnyExpression expression) {
		ComparisonExpressionVisitor visitor = comparisonExpressionVisitor();
		BypassParentSubExpressionVisitor bypassVisitor = getBypassParentSubExpressionVisitor();
		try {
			bypassVisitor.visitor = visitor;
			expression.getParent().accept(visitor);
			return visitor.expression != null;
		}
		finally {
			bypassVisitor.visitor = null;
			visitor.expression = null;
		}
	}

	/**
	 * Determines whether the given {@link Expression} is a {@link CollectionExpression}.
	 *
	 * @param expression The {@link Expression} to verify
	 * @return <code>true</code> if the given given {@link Expression} is a {@link CollectionExpression};
	 * <code>false</code> otherwise
	 */
	protected boolean isCollectionExpression(Expression expression) {
		return getCollectionExpression(expression) != null;
	}

	/**
	 * Determines whether the given {@link Expression} represents one of the three date constants or not.
	 *
	 * @param leftExpression The {@link Expression} to visit
	 * @return <code>true</code> if the given {@link Expression} represents one of the tree date
	 * constants; <code>false</code> otherwise
	 */
	protected boolean isDateTimeConstant(Expression leftExpression) {
		DateTimeVisitor visitor = getDateTimeVisitor();
		try {
			leftExpression.accept(visitor);
			return visitor.dateTime;
		}
		finally {
			visitor.dateTime = false;
		}
	}

	protected boolean isInputParameterInValidLocation(InputParameter expression) {
		OwningClauseVisitor visitor = getOwningClauseVisitor();
		try {
			expression.accept(visitor);
			return visitor.whereClause  != null ||
			       visitor.havingClause != null;
		}
		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 JPA version defined by the JPQL grammar is 1.0.
	 *
	 * @return <code>true</code> if the JPQL grammar was defined for JPA 1.0; <code>false</code> if
	 * it was defined for a more recent version
	 */
	protected boolean isJPA1_0() {
		return getJPAVersion() == JPAVersion.VERSION_1_0;
	}

	/**
	 * Determines whether the JPA version defined by the JPQL grammar is 2.0.
	 *
	 * @return <code>true</code> if the JPQL grammar was defined for JPA 2.0; <code>false</code> if
	 * it was defined for a more recent version
	 */
	protected boolean isJPA2_0() {
		return getJPAVersion() == JPAVersion.VERSION_2_0;
	}

	/**
	 * Determines whether the JPA version defined by the JPQL grammar is 2.1.
	 *
	 * @return <code>true</code> if the JPQL grammar was defined for JPA 2.1; <code>false</code> if
	 * it was defined for a more recent version
	 */
	protected boolean isJPA2_1() {
		return getJPAVersion() == JPAVersion.VERSION_2_1;
	}

	/**
	 * Determines whether the given subquery <code><b>SELECT</b></code> clause can return more than
	 * one item or just a single. By default, only one item can be returned.
	 *
	 * @param expression The subquery <code><b>SELECT</b></code> clause
	 * @return <code>true</code> if it can return more than one item; <code>false</code> if it needs
	 * to return only one item
	 */
	protected boolean isMultipleSubquerySelectItemsAllowed(SimpleSelectClause expression) {
		return false;
	}

	/**
	 * Determines whether the JPA version for which the JPQL grammar was defined represents a version
	 * that is newer than the given version.
	 *
	 * @param version The constant to verify if it's representing a version that is older than this one
	 * @return <code>true</code> if this constant represents a newer version and the given constant
	 * represents a version that is older; <code>false</code> if the given constant represents a
	 * newer and this constant represents an older version
	 */
	protected final boolean isNewerThan(JPAVersion version) {
		return getJPAVersion().isNewerThan(version);
	}

	/**
	 * Determines whether the JPA version for which the JPQL grammar was defined represents a version
	 * that is newer than the given version or if it's the same version.
	 *
	 * @param version The constant to verify if it's representing a version that is older than this
	 * one or if it's the same than this one
	 * @return <code>true</code> if this constant represents a newer version and the given constant
	 * represents a version that is older or if it's the same constant; <code>false</code> if the
	 * given constant represents a newer and this constant represents an older version
	 */
	protected final boolean isNewerThanOrEqual(JPAVersion version) {
		return getJPAVersion().isNewerThanOrEqual(version);
	}

	/**
	 * Determines whether the given sequence of characters is a numeric literal or not. There are
	 * two types of numeric literal that is supported:
	 * <ul>
	 *    <li>Decimal literal</li>
	 *    <li>Hexadecimal literal</li>
	 * </ul>
	 *
	 * @param text The sequence of characters to validate
	 * @return <code>true</code> if the given sequence of characters is a valid numeric literal;
	 * <code>false</code> otherwise
	 */
	protected boolean isNumericLiteral(String text) {

		// The ending 'l' or 'L' for a long number has to be removed, Java will not parse it
		if (text.endsWith("l") || text.endsWith("L")) {
			text = text.substring(0, text.length() - 1);
		}

		// Simply try to parse it as a double number (integer and hexadecimal are handled as well)
		try {
			Double.parseDouble(text);
			return true;
		}
		catch (Exception e2) {
			return false;
		}
	}

	/**
	 * Determines whether the JPA version for which the JPQL grammar was defined represents a version
	 * that is older than the given version.
	 *
	 * @param version The constant to verify if it's representing a version that is more recent
	 * than this one
	 * @return <code>true</code> if this constant represents an earlier version and the given
	 * constant represents a version that is more recent; <code>false</code> if the given constant
	 * represents an earlier version and this constant represents a more recent version
	 */
	protected final boolean isOlderThan(JPAVersion version) {
		return getJPAVersion().isOlderThan(version);
	}

	/**
	 * Determines whether the JPA version for which the JPQL grammar was defined represents a version
	 * that is older than the given version or if it's the same version.
	 *
	 * @param version The constant to verify if it's representing a version that is more recent than
	 * this one or if it's the same than this one
	 * @return <code>true</code> if this constant represents an earlier version and the given
	 * constant represents a version that is more recent or if it's the same constant; <code>false</code>
	 * if the given constant represents an earlier version and this constant represents a more recent
	 * version
	 */
	protected final boolean isOlderThanOrEqual(JPAVersion version) {
		return getJPAVersion().isOlderThanOrEqual(version);
	}

	/**
	 * Determines whether the given {@link Expression} is a child of the <b>WHERE</b> or <b>HAVING</b>
	 * clause of the top-level query.
	 *
	 * @param expression The {@link Expression} to visit its parent hierarchy up to the clause
	 * @return <code>true</code> if the first parent being a clause is the <b>WHERE</b> or <b>HAVING</b>
	 * clause; <code>false</code> otherwise
	 */
	protected boolean isOwnedByConditionalClause(Expression expression) {
		OwningClauseVisitor visitor = getOwningClauseVisitor();
		try {
			expression.accept(visitor);
			return visitor.whereClause  != null ||
			       visitor.havingClause != null;
		}
		finally {
			visitor.dispose();
		}
	}

	/**
	 * Determines whether the given {@link Expression} is a child of the <b>FROM</b> clause of the
	 * top-level query.
	 *
	 * @param expression The {@link Expression} to visit its parent hierarchy up to the clause
	 * @return <code>true</code> if the first parent being a clause is the top-level <b>FROM</b>
	 * clause; <code>false</code> otherwise
	 */
	protected boolean isOwnedByFromClause(Expression expression) {
		OwningClauseVisitor visitor = getOwningClauseVisitor();
		try {
			expression.accept(visitor);
			return visitor.fromClause != null;
		}
		finally {
			visitor.dispose();
		}
	}

	/**
	 * Determines whether the given {@link Expression} is a child of the <b>FROM</b> clause of a
	 * subquery.
	 *
	 * @param expression The {@link Expression} to visit its parent hierarchy up to the clause
	 * @return <code>true</code> if the first parent being a clause is the <b>FROM</b> clause of a
	 * subquery; <code>false</code> otherwise
	 */
	protected boolean isOwnedBySubFromClause(Expression expression) {
		OwningClauseVisitor visitor = getOwningClauseVisitor();
		try {
			expression.accept(visitor);
			return visitor.simpleFromClause != null;
		}
		finally {
			visitor.dispose();
		}
	}

	/**
	 * Determines whether a subquery can be used in any clause of the top-level query.
	 *
	 * @return <code>true</code> if a subquery can be defined in any clause; <code>false</code> if
	 * it can only be used within the <code>WHERE</code> and <code>HAVING</code> defined by the JPA
	 * 1.0 and 2.0 specification document
	 */
	protected abstract boolean isSubqueryAllowedAnywhere();

	/**
	 * Determines whether the given variable is a valid Java identifier, which means it follows the
	 * Java specification. The first letter has to be a Java identifier start and the others have to
	 * be Java identifier parts.
	 *
	 * @param variable The variable to validate
	 * @return <code>true</code> if the given variable follows the Java identifier specification;
	 * <code>false</code> otherwise
	 */
	protected boolean isValidJavaIdentifier(String variable) {

		for (int index = 0, count = variable.length(); index < count; index++) {

			int character = variable.charAt(index);

			if ((index == 0) && !Character.isJavaIdentifierStart(character)) {
				return false;
			}

			if ((index > 0) && !Character.isJavaIdentifierPart(character)) {
				return false;
			}
		}

		return true;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<KeyExpression> keyExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<KeyExpression> helper = getHelper(KEY);
		if (helper == null) {
			helper = buildKeyExpressionHelper();
			registerHelper(KEY, helper);
		}
		return helper;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<LengthExpression> lengthExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<LengthExpression> helper = getHelper(LENGTH);
		if (helper == null) {
			helper = buildLengthExpressionHelper();
			registerHelper(LENGTH, helper);
		}
		return helper;
	}

	protected AbstractTripleEncapsulatedExpressionHelper<LocateExpression> locateExpressionHelper() {
		AbstractTripleEncapsulatedExpressionHelper<LocateExpression> helper = getHelper(LOCATE);
		if (helper == null) {
			helper = buildLocateExpressionHelper();
			registerHelper(LOCATE, helper);
		}
		return helper;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<LowerExpression> lowerExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<LowerExpression> helper = getHelper(LOWER);
		if (helper == null) {
			helper = buildLowerExpressionHelper();
			registerHelper(LOWER, helper);
		}
		return helper;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<MaxFunction> maxFunctionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<MaxFunction> helper = getHelper(MAX);
		if (helper == null) {
			helper = buildMaxFunctionHelper();
			registerHelper(MAX, helper);
		}
		return helper;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<MinFunction> minFunctionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<MinFunction> helper = getHelper(MIN);
		if (helper == null) {
			helper = buildMinFunctionHelper();
			registerHelper(MIN, helper);
		}
		return helper;
	}

	protected AbstractDoubleEncapsulatedExpressionHelper<ModExpression> modExpressionHelper() {
		AbstractDoubleEncapsulatedExpressionHelper<ModExpression> helper = getHelper(MOD);
		if (helper == null) {
			helper = buildModExpressionHelper();
			registerHelper(MOD, helper);
		}
		return helper;
	}

	protected AbstractDoubleEncapsulatedExpressionHelper<NullIfExpression> nullIfExpressionHelper() {
		AbstractDoubleEncapsulatedExpressionHelper<NullIfExpression> helper = getHelper(NULLIF);
		if (helper == null) {
			helper = buildNullIfExpressionHelper();
			registerHelper(NULLIF, helper);
		}
		return helper;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<ObjectExpression> objectExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<ObjectExpression> helper = getHelper(OBJECT);
		if (helper == null) {
			helper = buildObjectExpressionHelper();
			registerHelper(OBJECT, helper);
		}
		return helper;
	}

	protected int position(Expression expression, int... extras) {
		int position = position(expression);
		for (int extra : extras) {
			position += extra;
		}
		return position;
	}

	/**
	 * Registers the given helper.
	 *
	 * @param id The key used to cache the given helper
	 * @param helper The helper to cache for future use
	 */
	protected void registerHelper(String id, Object helper) {
		helpers.put(id, helper);
	}

	protected AbstractSingleEncapsulatedExpressionHelper<SizeExpression> sizeExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<SizeExpression> helper = getHelper(SIZE);
		if (helper == null) {
			helper = buildSizeExpressionHelper();
			registerHelper(SIZE, helper);
		}
		return helper;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<SqrtExpression> sqrtExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<SqrtExpression> helper = getHelper(SQRT);
		if (helper == null) {
			helper = buildSqrtExpressionHelper();
			registerHelper(SQRT, helper);
		}
		return helper;
	}

	protected AbstractTripleEncapsulatedExpressionHelper<SubstringExpression> substringExpressionHelper() {
		AbstractTripleEncapsulatedExpressionHelper<SubstringExpression> helper = getHelper(SUBSTRING);
		if (helper == null) {
			helper = buildSubstringExpressionHelper();
			registerHelper(SUBSTRING, helper);
		}
		return helper;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<SumFunction> sumFunctionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<SumFunction> helper = getHelper(SUM);
		if (helper == null) {
			helper = buildSumFunctionHelper();
			registerHelper(SUM, helper);
		}
		return helper;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<TrimExpression> trimExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<TrimExpression> helper = getHelper(TRIM);
		if (helper == null) {
			helper = buildTrimExpressionHelper();
			registerHelper(TRIM, helper);
		}
		return helper;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<TypeExpression> typeExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<TypeExpression> helper = getHelper(TYPE);
		if (helper == null) {
			helper = buildTypeExpressionHelper();
			registerHelper(TYPE, helper);
		}
		return helper;
	}

	protected AbstractSingleEncapsulatedExpressionHelper<UpperExpression> upperExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<UpperExpression> helper = getHelper(UPPER);
		if (helper == null) {
			helper = buildUpperExpressionHelper();
			registerHelper(UPPER, helper);
		}
		return helper;
	}

	protected void validateAbstractConditionalClause(AbstractConditionalClause expression,
	                                                 String missingConditionalExpressionMessageKey,
	                                                 String invalidConditionalExpressionMessageKey) {

		// Missing conditional expression
		if (!expression.hasConditionalExpression()) {

			int startPosition = position(expression);
			int endPosition   = startPosition +
			                    expression.getIdentifier().length() +
			                    (expression.hasSpaceAfterIdentifier() ? 1 : 0);

			addProblem(expression, startPosition, endPosition, missingConditionalExpressionMessageKey);
		}
		else {
			Expression conditionalExpression = expression.getConditionalExpression();

			// Invalid conditional expression
			// Example: "... WHERE foo() = 1", right now it's parsed as 3 expressions
			if (getChildren(conditionalExpression).size() > 1) {
				int startPosition = position(conditionalExpression);
				int endPosition   = startPosition + length(conditionalExpression);
				addProblem(expression, startPosition, endPosition, invalidConditionalExpressionMessageKey);
			}
			else {
				// Invalid conditional expression
				if (!isValid(conditionalExpression, ConditionalExpressionBNF.ID)) {
					int startPosition = position(conditionalExpression);
					int endPosition   = startPosition + length(conditionalExpression);
					addProblem(expression, startPosition, endPosition, invalidConditionalExpressionMessageKey);
				}

				// Visit the conditional expression
				conditionalExpression.accept(this);
			}
		}
	}

	/**
	 * Validates the content of an {@link AbstractDoubleEncapsulatedExpression}, which encapsulates
	 * two expressions separated by a comma.
	 *
	 * @param expression The {@link AbstractDoubleEncapsulatedExpression} to validate
	 * @param helper This helper is used to retrieve specific information related to the {@link
	 * AbstractDoubleEncapsulatedExpression expression} being validated
	 */
	protected <T extends AbstractDoubleEncapsulatedExpression>
	          void validateAbstractDoubleEncapsulatedExpression
	          (T expression, AbstractDoubleEncapsulatedExpressionHelper<T> helper) {

		String identifier = helper.identifier(expression);

		// Missing '('
		if (!helper.hasLeftParenthesis(expression)) {
			int startPosition = position(expression) + identifier.length();
			addProblem(expression, startPosition, helper.leftParenthesisMissingKey(expression));
		}
		else {

			// Missing first expression
			if (!helper.hasFirstExpression(expression)) {

				int startPosition = position(expression) +
				                    identifier.length() +
				                    1 /* '(' */;

				addProblem(expression, startPosition, helper.firstExpressionMissingKey());
			}
			// Invalid first expression
			else if (!helper.isFirstExpressionValid(expression)) {

				int startPosition = position(expression) +
				                    identifier.length() +
				                    1 /* ( */;

				int endPosition = startPosition +
				                  helper.firstExpressionLength(expression);

				addProblem(expression, startPosition, endPosition, helper.firstExpressionInvalidKey());
			}
			else {
				expression.getFirstExpression().accept(this);
			}

			// Missing comma
			if (!helper.hasComma(expression)) {

				int startPosition = position(expression) +
				                    identifier.length() +
				                    1 /* ( */ +
				                    helper.firstExpressionLength(expression);

				addProblem(expression, startPosition, helper.missingCommaKey());
			}
			// After ','
			else {

				// Missing second expression
				if (!helper.hasSecondExpression(expression)) {

					int startPosition = position(expression) +
					                    identifier.length() +
					                    1 /* ( */ +
					                    helper.firstExpressionLength(expression) +
					                    (expression.hasComma() ? 1 : 0) +
					                    (expression.hasSpaceAfterComma() ? 1 : 0);

					addProblem(expression, startPosition, helper.secondExpressionMissingKey());
				}
				// Invalid second expression
				else if (!helper.isSecondExpressionValid(expression)) {

					int startPosition = position(expression) +
					                    identifier.length() +
					                    1 /* ( */ +
					                    helper.firstExpressionLength(expression) +
					                    (expression.hasComma() ? 1 : 0) +
					                    (expression.hasSpaceAfterComma() ? 1 : 0);

					int endPosition = startPosition + helper.secondExpressionLength(expression);

					addProblem(expression, startPosition, endPosition, helper.secondExpressionInvalidKey());
				}
				else {
					expression.getSecondExpression().accept(this);
				}
			}
		}

		// Missing ')'
		if (!helper.hasRightParenthesis(expression)) {

			int startPosition = position(expression) +
			                    identifier.length() +
			                    1 /* ( */ +
			                    helper.firstExpressionLength(expression) +
			                    (expression.hasComma() ? 1 : 0) +
			                    (expression.hasSpaceAfterComma() ? 1 : 0) +
			                    length(expression.getSecondExpression());

			addProblem(expression, startPosition, helper.rightParenthesisMissingKey(expression));
		}
	}

	protected void validateAbstractFromClause(AbstractFromClause expression) {

		// Missing declaration
		if (!expression.hasDeclaration()) {

			int startPosition = position(expression) +
			                    4 /* FROM */ +
			                    (expression.hasSpaceAfterFrom() ? 1 : 0);

			addProblem(expression, startPosition, AbstractFromClause_MissingIdentificationVariableDeclaration);
		}
		else {
			Expression declaration = expression.getDeclaration();

			// Two identification variable declarations have to be separated by a comma and
			// the FROM clause cannot end with a comma
			validateCollectionSeparatedByComma(
				declaration,
				AbstractFromClause_IdentificationVariableDeclarationEndsWithComma,
				AbstractFromClause_IdentificationVariableDeclarationIsMissingComma
			);

			// Validate the declaration
			declaration.accept(this);
		}
	}

	/**
	 * Validates the select expression of the given <code>SELECT</code> clause. The select expression
	 * will only be visited if its JPQL query BNF is part of the select item BNF.
	 *
	 * @param expression The {@link AbstractSelectClause} to validate
	 * @param multipleSelectItemsAllowed Determines whether the <code><b>SELECT</b></code> can have
	 * one or more select expression or not
	 */
	protected void validateAbstractSelectClause(AbstractSelectClause expression,
	                                            boolean multipleSelectItemsAllowed) {

		// Missing select expression
		if (!expression.hasSelectExpression()) {

			int startPosition = position(expression) +
			                    6 /* SELECT */ +
			                    (expression.hasSpaceAfterSelect() ? 1 : 0) +
			                    (expression.hasDistinct() ? 8 : 0) +
			                    (expression.hasSpaceAfterDistinct() ? 1 : 0);

			addProblem(expression, startPosition, AbstractSelectClause_MissingSelectExpression);
		}
		else {
			Expression selectExpression = expression.getSelectExpression();

			// Check for collection expression first
			if (isCollectionExpression(selectExpression)) {

				// The SELECT clause does not support a collection of select expressions
				if (!multipleSelectItemsAllowed) {

					int startPosition = position(expression) +
					                    6 /* SELECT */ +
					                    (expression.hasSpaceAfterSelect() ? 1 : 0) +
					                    (expression.hasDistinct() ? 8 : 0) +
					                    (expression.hasSpaceAfterDistinct() ? 1 : 0);

					int endPosition = startPosition + length(selectExpression);

					addProblem(selectExpression, startPosition, endPosition, SimpleSelectClause_NotSingleExpression);
				}
				// Visit the select expression
				else {
					selectExpression.accept(this);
				}
			}
			// The select expression is not valid
			else if (!isValid(selectExpression, expression.getSelectItemQueryBNFId())) {

				int startPosition = position(expression) +
				                    6 /* SELECT */ +
				                    (expression.hasSpaceAfterSelect() ? 1 : 0) +
				                    (expression.hasDistinct() ? 8 : 0) +
				                    (expression.hasSpaceAfterDistinct() ? 1 : 0);

				int endPosition = startPosition + length(selectExpression);

				addProblem(expression, startPosition, endPosition, AbstractSelectClause_InvalidSelectExpression);
			}
			// Visit the select expression
			else {
				selectExpression.accept(this);
			}
		}
	}

	protected void validateAbstractSelectStatement(AbstractSelectStatement expression) {

		// Does not have a FROM clause
		if (!expression.hasFromClause()) {

			int startPosition = position(expression) +
			                    length(expression.getSelectClause()) +
			                    (expression.hasSpaceAfterSelect() ? 1 : 0);

			addProblem(expression, startPosition, AbstractSelectStatement_FromClauseMissing);
		}
	}

	protected <T extends AbstractSingleEncapsulatedExpression>
	          void validateAbstractSingleEncapsulatedExpression
	          (T expression, AbstractSingleEncapsulatedExpressionHelper<T> helper) {

		String identifier = helper.identifier(expression);

		// Missing '('
		if (!helper.hasLeftParenthesis(expression)) {
			int startPosition = position(expression) + identifier.length();

			addProblem(
				expression,
				startPosition,
				helper.leftParenthesisMissingKey(expression),
				helper.arguments(expression)
			);
		}
		// Missing encapsulated expression
		else if (helper.isEncapsulatedExpressionMissing(expression)) {

			int startPosition = position(expression) +
			                    identifier.length()  +
			                    1 /* '(' */          +
			                    helper.lengthBeforeEncapsulatedExpression(expression);

			addProblem(
				expression,
				startPosition,
				helper.encapsulatedExpressionMissingKey(expression),
				helper.arguments(expression)
			);
		}
		// Validate the encapsulated expression
		else {
			// The encapsulated expression is not valid
			if (!helper.isEncapsulatedExpressionValid(expression)) {
				int startPosition = position(expression) +
				                    identifier.length()  +
				                    1 /* '(' */          +
				                    helper.lengthBeforeEncapsulatedExpression(expression);

				int endPosition = startPosition + helper.encapsulatedExpressionLength(expression);

				addProblem(
					expression,
					startPosition,
					endPosition,
					helper.encapsulatedExpressionInvalidKey(expression),
					helper.arguments(expression)
				);
			}
			// Now visit the encapsulated expression
			else {
				super.visit(expression);
			}
		}

		// Missing ')'
		if (!helper.hasRightParenthesis(expression)) {

			int startPosition = position(expression) + length(expression);

			addProblem(
				expression,
				startPosition,
				helper.rightParenthesisMissingKey(expression),
				helper.arguments(expression)
			);
		}
	}

	protected <T extends AbstractTripleEncapsulatedExpression>
	          void validateAbstractTripleEncapsulatedExpression
	          (T expression, AbstractTripleEncapsulatedExpressionHelper<T> helper) {

		String identifier = helper.identifier(expression);

		// Missing '('
		if (!helper.hasLeftParenthesis(expression)) {
			int startPosition = position(expression) + identifier.length();
			addProblem(expression, startPosition, helper.leftParenthesisMissingKey(expression));
		}
		else {

			// Missing first expression
			if (!helper.hasFirstExpression(expression)) {

				int startPosition = position(expression) +
				                    identifier.length() +
				                    1 /* ( */;

				addProblem(expression, startPosition, helper.firstExpressionMissingKey());
			}
			// Invalid first expression
			else if (!helper.isFirstExpressionValid(expression)) {

				int startPosition = position(expression) +
				                    identifier.length() +
				                    1 /* ( */;

				int endPosition = startPosition + helper.firstExpressionLength(expression);

				addProblem(expression, startPosition, endPosition, helper.firstExpressionInvalidKey());
			}
			else {
				expression.getFirstExpression().accept(this);
			}

			// Missing first comma
			if (helper.hasFirstExpression(expression) &&
			    !expression.hasFirstComma()) {

				int startPosition = position(expression) +
				                    identifier.length() +
				                    1 /* ( */ +
				                    helper.firstExpressionLength(expression);

				addProblem(expression, startPosition, helper.firstCommaMissingKey());
			}

			// Validate second expression
			if (expression.hasFirstComma()) {

				// Missing second expression
				if (!helper.hasSecondExpression(expression)) {

					int startPosition = position(expression) +
					                    identifier.length() +
					                    1 /* ( */ +
					                    helper.firstExpressionLength(expression) +
					                    (expression.hasFirstComma() ? 1 : 0) +
					                    (expression.hasSpaceAfterFirstComma() ? 1 : 0);

					addProblem(expression, startPosition, helper.secondExpressionMissingKey());
				}
				// Invalid second expression
				else if (!helper.isSecondExpressionValid(expression)) {

					int startPosition = position(expression) +
					                    identifier.length() +
					                    1 /* ( */ +
					                    helper.firstExpressionLength(expression) +
					                    (expression.hasFirstComma() ? 1 : 0) +
					                    (expression.hasSpaceAfterFirstComma() ? 1 : 0);

					int endPosition = startPosition + helper.secondExpressionLength(expression);

					addProblem(expression, startPosition, endPosition, helper.secondExpressionInvalidKey());
				}
				else {
					expression.getSecondExpression().accept(this);
				}
			}

			// Missing second comma
			if (helper.hasSecondExpression(expression) &&
			    !expression.hasSecondComma() &&
			    helper.hasThirdExpression(expression)) {

				int startPosition = position(expression) +
				                    identifier.length() +
				                    1 /* ( */ +
				                    helper.firstExpressionLength(expression) +
				                    (expression.hasFirstComma() ? 1 : 0) +
				                    (expression.hasSpaceAfterFirstComma() ? 1 : 0) +
				                    helper.secondExpressionLength(expression);

				addProblem(expression, startPosition, helper.secondCommaMissingKey());
			}

			// Validate third expression
			if (expression.hasSecondComma()) {

				// Missing third expression
				if (!helper.hasThirdExpression(expression)) {

					int startPosition = position(expression) +
					                    identifier.length() +
					                    1 /* ( */ +
					                    helper.firstExpressionLength(expression) +
					                    (expression.hasFirstComma() ? 1 : 0) +
					                    (expression.hasSpaceAfterFirstComma() ? 1 : 0) +
					                    helper.secondExpressionLength(expression) +
					                    (expression.hasSecondComma() ? 1 : 0) +
					                    (expression.hasSpaceAfterSecondComma() ? 1 : 0);

					addProblem(expression, startPosition, helper.thirdExpressionMissingKey());
				}
				// Invalid third expression
				else if (!helper.isThirdExpressionValid(expression)) {

					int startPosition = position(expression) +
					                    identifier.length() +
					                    1 /* ( */ +
					                    helper.firstExpressionLength(expression) +
					                    (expression.hasFirstComma() ? 1 : 0) +
					                    (expression.hasSpaceAfterFirstComma() ? 1 : 0) +
					                    helper.secondExpressionLength(expression) +
					                    (expression.hasSecondComma() ? 1 : 0) +
					                    (expression.hasSpaceAfterSecondComma() ? 1 : 0);

					int endPosition = startPosition + helper.thirdExpressionLength(expression);

					addProblem(expression, startPosition, endPosition, helper.thirdExpressionInvalidKey());
				}
				else {
					expression.getThirdExpression().accept(this);
				}
			}
		}

		// Missing ')'
		if (!helper.hasRightParenthesis(expression)) {

			int startPosition = position(expression) +
			                    identifier.length() +
			                    1 /* ( */ +
			                    helper.firstExpressionLength(expression) +
			                    (expression.hasFirstComma() ? 1 : 0) +
			                    (expression.hasSpaceAfterFirstComma() ? 1 : 0) +
			                    helper.secondExpressionLength(expression) +
			                    (expression.hasSecondComma() ? 1 : 0) +
			                    (expression.hasSpaceAfterSecondComma() ? 1 : 0) +
			                    helper.thirdExpressionLength(expression);

			addProblem(expression, startPosition, helper.rightParenthesisMissingKey(expression));
		}
	}

	protected <T extends AggregateFunction> void validateAggregateFunctionLocation
		(T expression, AbstractSingleEncapsulatedExpressionHelper<T> helper) {

		// Check to see if the aggregate function is in the right clause
		OwningClauseVisitor visitor = getOwningClauseVisitor();
		boolean valid = true;
		try {
			expression.accept(visitor);
			valid = visitor.selectClause       != null ||
			        visitor.simpleSelectClause != null ||
			        visitor.groupByClause      != null ||
			        visitor.orderByClause      != null ||
			        visitor.havingClause       != null;
		}
		finally {
			visitor.dispose();
		}

		// The function is used in an invalid clause
		if (!valid) {

			int startPosition = position(expression);
			int endPosition   = startPosition + length(expression);

			addProblem(
				expression,
				startPosition,
				endPosition,
				AggregateFunction_WrongClause,
				expression.getIdentifier());
		}
		else {
			validateAbstractSingleEncapsulatedExpression(expression, helper);
		}
	}

	protected void validateArithmeticExpression(ArithmeticExpression expression) {
		validateCompoundExpression(
			expression,
			expression.getArithmeticSign(),
			ArithmeticExpression_MissingLeftExpression,
			ArithmeticExpression_InvalidLeftExpression,
			ArithmeticExpression_MissingRightExpression,
			ArithmeticExpression_InvalidRightExpression,
			ArithmeticExpressionBNF.ID,
			ArithmeticTermBNF.ID
		);
	}

	/**
	 * Validates the given {@link Expression} by making sure each child is separated by a comma.
	 *
	 * @param expression The {@link Expression} to validate its children, which should be a series
	 * of {@link Expression} separated by a comma
	 * @param endsWithCommaProblemKey The problem key describing the {@link CollectionExpression} is
	 * ending with a comma
	 * @param missingCommaProblemKey The problem key describing the {@link CollectionExpression} has
	 * two items not separated by a comma
	 */
	protected void validateCollectionSeparatedByComma(Expression expression,
	                                                  String endsWithCommaProblemKey,
	                                                  String missingCommaProblemKey) {

		CollectionSeparatedByCommaValidator validator = collectionSeparatedByCommaValidator();

		try {
			validator.endsWithCommaProblemKey  = endsWithCommaProblemKey;
			validator.wrongSeparatorProblemKey = missingCommaProblemKey;
			expression.accept(validator);
		}
		finally {
			validator.endsWithCommaProblemKey  = null;
			validator.wrongSeparatorProblemKey = null;
		}
	}

	/**
	 * Validates the given {@link Expression} by making sure each child is separated by a whitespace.
	 *
	 * @param expression The {@link Expression} to validate its children, which should be a series
	 * of {@link Expression} separated by a whitespace
	 * @param endsWithCommaProblemKey The problem key describing the {@link CollectionExpression}
	 * is ending with a comma
	 * @param hasCommaProblemKey The problem key describing the {@link CollectionExpression} has two
	 * items separated by a comma
	 */
	protected void validateCollectionSeparatedBySpace(Expression expression,
	                                                  String endsWithCommaProblemKey,
	                                                  String hasCommaProblemKey) {

		CollectionSeparatedBySpaceValidator validator = collectionSeparatedBySpaceValidator();

		try {
			validator.endsWithCommaProblemKey  = endsWithCommaProblemKey;
			validator.wrongSeparatorProblemKey = hasCommaProblemKey;
			expression.accept(validator);
		}
		finally {
			validator.endsWithCommaProblemKey  = null;
			validator.wrongSeparatorProblemKey = null;
		}
	}

	protected void validateCompoundExpression(CompoundExpression expression,
	                                          String identifier,
	                                          String missingLeftExpression,
	                                          String invalidLeftExpression,
	                                          String missingRightExpression,
	                                          String invalidRightExpression,
	                                          String leftExpressionQueryBNF,
	                                          String rightExpressionQueryBNF) {

		// Missing left expression
		if (!expression.hasLeftExpression()) {
			int startPosition = position(expression);
			addProblem(expression, startPosition, missingLeftExpression);
		}
		else {
			Expression leftExpression = expression.getLeftExpression();

			// Invalid left expression
			if (!isValid(leftExpression, leftExpressionQueryBNF)) {

				int startPosition = position(expression);
				int endPosition   = startPosition + length(leftExpression);
				addProblem(expression, startPosition, endPosition, invalidLeftExpression);
			}
			else {
				leftExpression.accept(this);
			}
		}

		// Missing right expression
		if (!expression.hasRightExpression()) {

			int startPosition = position(expression) +
			                    length(expression.getLeftExpression()) +
			                    (expression.hasLeftExpression() ? 1 : 0) +
			                    identifier.length() +
			                    (expression.hasSpaceAfterIdentifier() ? 1 : 0);

			addProblem(expression, startPosition, missingRightExpression);
		}
		else {
			Expression rightExpression = expression.getRightExpression();

			// Invalid right expression
			if (!isValid(rightExpression, rightExpressionQueryBNF)) {

				int startPosition = position(expression) +
				                    length(expression.getLeftExpression()) +
				                    (expression.hasLeftExpression() ? 1 : 0) +
				                    identifier.length() +
				                    (expression.hasSpaceAfterIdentifier() ? 1 : 0);

				int endPosition = startPosition + length(rightExpression);

				addProblem(expression, startPosition, endPosition, invalidRightExpression);
			}
			else {
				rightExpression.accept(this);
			}
		}
	}

	protected void validateIdentificationVariableDeclaration(IdentificationVariableDeclaration expression) {

		// The range variable declaration is missing
		if (!expression.hasRangeVariableDeclaration()) {
			addProblem(expression, position(expression), IdentificationVariableDeclaration_MissingRangeVariableDeclaration);
		}
		else {
			expression.getRangeVariableDeclaration().accept(this);
		}

		validateJoins(expression);
	}

	/**
	 * Validates the given variable name to make sure:
	 * <ul>
	 * <li>It is not a JPQL reserved identifier;</li>
	 * <li>It is a valid Java identifier.</li>
	 * </ul>
	 *
	 * @param expression The expression to validate
	 * @param variableName The text to actually validate
	 * @param variableLength The actual length of the text, which can be longer than the text that is
	 * validated
	 * @param reservedWordProblemKey The problem key used when the variable name is a reserved JPQL
	 * identifier
	 * @param invalidJavaIdentifierProblemKey The problem key used when the variable name is not a
	 * valid Java identifier
	 */
	protected void validateIdentifier(Expression expression,
	                                  String variableName,
	                                  int variableLength,
	                                  String reservedWordProblemKey,
	                                  String invalidJavaIdentifierProblemKey) {

		// Must not be a reserved identifier. An exception is ORDER and GROUP because the actual
		// identifiers are ORDER BY and GROUP BY
		if (getExpressionRegistry().isIdentifier(variableName) &&
		    !"ORDER".equalsIgnoreCase(variableName) &&
		    !"GROUP".equalsIgnoreCase(variableName)) {

			int startPosition = position(expression);
			int endPosition   = startPosition + variableLength;
			addProblem(expression, startPosition, endPosition, reservedWordProblemKey, variableName);
		}
		// The character sequence must begin with a Java identifier start character, and all other
		// characters must be Java identifier part characters. An identifier start character is any
		// character for which the method Character.isJavaIdentifierStart returns true. This includes
		// the underscore (_) character and the dollar sign ($) character. An identifier part
		// character is any character for which the method Character.isJavaIdentifierPart returns
		// true. The question mark (?) character is reserved for use by the Java Persistence query
		// language. An identification variable must not be a reserved identifier or have the same
		// name as any entity in the same persistence unit
		else if (!isValidJavaIdentifier(variableName)) {
			int startPosition = position(expression);
			int endPosition   = startPosition + variableLength;
			addProblem(expression, startPosition, endPosition, invalidJavaIdentifierProblemKey, variableName);
		}
	}

	protected void validateInputParameters(JPQLExpression expression) {

		int positionalCount = 0;
		int namedCount = 0;

		for (InputParameter inputParameter : inputParameters) {
			if (inputParameter.isNamed()) {
				namedCount++;
			}
			else if (inputParameter.isPositional()) {
				positionalCount++;
			}
		}

		if ((positionalCount > 0) && (namedCount > 0)) {
			for (InputParameter parameter : inputParameters) {
				addProblem(parameter, InputParameter_Mixture);
			}
		}
	}

	protected void validateJoins(IdentificationVariableDeclaration expression) {

		// Validate the JOIN expressions
		if (expression.hasJoins()) {

			Expression joins = expression.getJoins();
			List<Expression> children = getChildren(joins);

			// Validate multiple JOIN expression
			if (children.size() > 1) {

				validateCollectionSeparatedBySpace(
					joins,
					IdentificationVariableDeclaration_JoinsEndWithComma,
					IdentificationVariableDeclaration_JoinsHaveComma
				);

				// Make sure each child is a JOIN expression
				for (int index = children.size(); --index >= 0; ) {
					Expression child = children.get(index);
					child.accept(this);
				}
			}
			// Make sure the single expression is a JOIN expression
			// Validate the JOIN expression
			else {
				joins.accept(this);
			}
		}
	}

	protected void validateLikeExpressionEscapeCharacter(LikeExpression expression) {

		Expression escapeCharacter = expression.getEscapeCharacter();

		// Check for a string literal (single quoted character)
		String character = literal(escapeCharacter, LiteralType.STRING_LITERAL);

		// Check for a single character
		if ((character.length() > 0) && ExpressionTools.isQuote(character.charAt(0))) {

			// Unquote the literal first
			character = ExpressionTools.unquote(character);

			// The escape character is not a single character literal
			if (character.length() != 1) {

				int startPosition = position(expression) +
				                    length(expression.getStringExpression()) +
				                    (expression.hasSpaceAfterStringExpression() ? 1 : 0) +
				                    (expression.hasNot() ? 4 : 0) +
				                    4 /* LIKE */ +
				                    (expression.hasSpaceAfterLike() ? 1 : 0) +
				                    length(expression.getPatternValue()) +
				                    (expression.hasSpaceAfterPatternValue() ? 1 : 0) +
				                    6 + /* ESCAPE */ +
				                    (expression.hasSpaceAfterEscape() ? 1 : 0);

				int endPosition = startPosition +  length(escapeCharacter);

				addProblem(
					expression,
					startPosition,
					endPosition,
					LikeExpression_InvalidEscapeCharacter,
					escapeCharacter.toActualText()
				);
			}
		}
		else {
			// Check for an input parameter
			character = literal(escapeCharacter, LiteralType.INPUT_PARAMETER);

			if ((character.length() == 0) &&
			    !isValid(escapeCharacter, LikeExpressionEscapeCharacterBNF.ID)) {

				int startPosition = position(expression) +
				                    length(expression.getStringExpression()) +
				                    4 /* LIKE */ +
				                    (expression.hasSpaceAfterStringExpression() ? 1 : 0) +
				                    (expression.hasNot() ? 1 : 0) +
				                    (expression.hasSpaceAfterLike() ? 1 : 0) +
				                    length(expression.getPatternValue()) +
				                    (expression.hasSpaceAfterPatternValue() ? 1 : 0) +
				                    6 + /* ESCAPE */ +
				                    (expression.hasSpaceAfterEscape() ? 1 : 0);

				int endPosition = startPosition + length(escapeCharacter);

				addProblem(
					expression,
					startPosition,
					endPosition,
					LikeExpression_InvalidEscapeCharacter,
					escapeCharacter.toActualText()
				);
			}
		}
	}

	protected void validateLogicalExpression(LogicalExpression expression,
	                                         String leftExpressionQueryBNF,
	                                         String rightExpressionQueryBNF) {

		validateCompoundExpression(
			expression,
			expression.getIdentifier(),
			LogicalExpression_MissingLeftExpression,
			LogicalExpression_InvalidLeftExpression,
			LogicalExpression_MissingRightExpression,
			LogicalExpression_InvalidRightExpression,
			leftExpressionQueryBNF,
			rightExpressionQueryBNF
		);
	}

	protected void validateOwningClause(InputParameter expression, String parameter) {

		if (!isInputParameterInValidLocation(expression)) {
			int startPosition = position(expression);
			int endPosition   = startPosition + parameter.length();
			addProblem(expression, startPosition, endPosition, InputParameter_WrongClauseDeclaration);
		}
	}

	protected void validatePathExpression(AbstractPathExpression expression) {

		// Missing identification variable
		if (!expression.hasIdentificationVariable() &&
		    !expression.hasVirtualIdentificationVariable()) {

			addProblem(expression, AbstractPathExpression_MissingIdentificationVariable);
		}
		// Validate the identification variable
		else {
			expression.getIdentificationVariable().accept(this);
		}

		// Cannot end with a dot
		if (expression.endsWithDot()) {
			addProblem(expression, AbstractPathExpression_CannotEndWithComma);
		}
	}

	protected void validateSimpleSelectStatement(SimpleSelectStatement expression) {
		validateAbstractSelectStatement(expression);
	}

	protected AbstractSingleEncapsulatedExpressionHelper<ValueExpression> valueExpressionHelper() {
		AbstractSingleEncapsulatedExpressionHelper<ValueExpression> helper = getHelper(VALUE);
		if (helper == null) {
			helper = buildValueExpressionHelper();
			registerHelper(VALUE, helper);
		}
		return helper;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(AbsExpression expression) {
		validateAbstractSingleEncapsulatedExpression(expression, absExpressionHelper());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(AbstractSchemaName expression) {
		// Nothing to validate
		super.visit(expression);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(AdditionExpression expression) {
		validateArithmeticExpression(expression);
	}

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

		validateAbstractSingleEncapsulatedExpression(expression, allOrAnyExpressionHelper());

		// Make sure the expression is part of a comparison expression
		if (!isChildOfComparisonExpession(expression)) {
			addProblem(expression, AllOrAnyExpression_NotPartOfComparisonExpression);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(AndExpression expression) {
		validateLogicalExpression(expression, ConditionalExpressionBNF.ID, ConditionalExpressionBNF.ID);
	}

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

		// Missing expression after +/-
		if (!expression.hasExpression()) {
			int startPosition = position(expression) + 1;
			addProblem(expression, startPosition, ArithmeticFactor_MissingExpression);
		}
		else {
			Expression arithmeticExpression = expression.getExpression();

			if (!isValid(arithmeticExpression, ArithmeticPrimaryBNF.ID)) {
				int startIndex = position(expression) + 1;
				int endIndex   = startIndex;
				addProblem(expression, startIndex, endIndex, ArithmeticFactor_InvalidExpression);
			}
			else {
				arithmeticExpression.accept(this);
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(AvgFunction expression) {
		validateAggregateFunctionLocation(expression, avgFunctionHelper());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(BadExpression expression) {
		int startPosition = position(expression);
		int endPosition   = startPosition + length(expression);
		addProblem(expression, startPosition, endPosition, BadExpression_InvalidExpression);
	}

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

		// Missing expression
		if (!expression.hasExpression()) {
			int startPosition = position(expression);
			addProblem(expression, startPosition, BetweenExpression_MissingExpression);
		}

		// Missing lower bound expression
		if (!expression.hasLowerBoundExpression()) {

			int startPosition = position(expression) +
			                    length(expression.getExpression()) +
			                    (expression.hasExpression() ? 1 : 0) +
			                    (expression.hasNot() ? 11 /* NOT BETWEEN */ : 7 /* BETWEEN */) +
			                    (expression.hasSpaceAfterBetween() ? 1 : 0);

			addProblem(expression, startPosition, BetweenExpression_MissingLowerBoundExpression);
		}
		// Missing 'AND'
		else if (!expression.hasAnd()) {

			int startPosition = position(expression) +
			                    length(expression.getExpression()) +
			                    (expression.hasExpression() ? 1 : 0) +
			                    (expression.hasNot() ? 11 /* NOT BETWEEN */ : 7 /* BETWEEN */) +
			                    (expression.hasSpaceAfterBetween() ? 1 : 0) +
			                    length(expression.getLowerBoundExpression()) +
			                    (expression.hasSpaceAfterLowerBound() ? 1 : 0);

			addProblem(expression, startPosition, BetweenExpression_MissingAnd);
		}
		// Missing upper bound expression
		else if (!expression.hasUpperBoundExpression()) {

			int startPosition = position(expression) +
			                    length(expression.getExpression()) +
			                    (expression.hasExpression() ? 1 : 0) +
			                    (expression.hasNot() ? 11 /* NOT BETWEEN */ : 7 /* BETWEEN */) +
			                    (expression.hasSpaceAfterBetween() ? 1 : 0) +
			                    length(expression.getLowerBoundExpression()) +
			                    (expression.hasSpaceAfterLowerBound() ? 1 : 0) +
			                    3 /* AND */ +
			                    (expression.hasSpaceAfterAnd() ? 1 : 0);

			addProblem(expression, startPosition, BetweenExpression_MissingUpperBoundExpression);
		}

		super.visit(expression);
	}

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

		// JPA 1.0 does not support a CASE expression
		if (isJPA1_0()) {
			addProblem(expression, CaseExpression_InvalidJPAVersion);
		}
		else {
			// WHEN clauses can't be separated by commas
			if (expression.hasWhenClauses()) {
				validateCollectionSeparatedBySpace(
					expression.getWhenClauses(),
					CaseExpression_WhenClausesEndWithComma,
					CaseExpression_WhenClausesHasComma
				);
			}
			// At least one WHEN clause must be specified
			else {

				int startPosition = position(expression) +
				                    4 /* CASE */ +
				                    (expression.hasSpaceAfterCase() ? 1 : 0) +
				                    length(expression.getCaseOperand()) +
				                    (expression.hasSpaceAfterCaseOperand() ? 1 : 0);

				addProblem(expression, startPosition, CaseExpression_MissingWhenClause);
			}

			// Missing ELSE
			if (expression.hasWhenClauses() &&
			   !expression.hasElse()) {

				int startPosition = position(expression) +
				                    4 /* CASE */ +
				                    (expression.hasSpaceAfterCase() ? 1 : 0) +
				                    length(expression.getCaseOperand()) +
				                    (expression.hasSpaceAfterCaseOperand() ? 1 : 0) +
				                    length(expression.getWhenClauses()) +
				                    (expression.hasSpaceAfterWhenClauses() ? 1 : 0);

				addProblem(expression, startPosition, CaseExpression_MissingElseIdentifier);
			}
			// Missing ELSE expression
			else if (expression.hasElse() &&
			        !expression.hasElseExpression()) {

				int startPosition = position(expression) +
				                    4 /* CASE */ +
				                    (expression.hasSpaceAfterCase() ? 1 : 0) +
				                    length(expression.getCaseOperand()) +
				                    (expression.hasSpaceAfterCaseOperand() ? 1 : 0) +
				                    length(expression.getWhenClauses()) +
				                    (expression.hasSpaceAfterWhenClauses() ? 1 : 0) +
				                    4 /* ELSE */ +
				                    (expression.hasSpaceAfterElse() ? 1 : 0);

				addProblem(expression, startPosition, CaseExpression_MissingElseExpression);
			}
			// Missing END
			else if (expression.hasElseExpression() &&
			        !expression.hasEnd()) {

				int startPosition = position(expression) +
				                    4 /* CASE */ +
				                    (expression.hasSpaceAfterCase() ? 1 : 0) +
				                    length(expression.getCaseOperand()) +
				                    (expression.hasSpaceAfterCaseOperand() ? 1 : 0) +
				                    length(expression.getWhenClauses()) +
				                    (expression.hasSpaceAfterWhenClauses() ? 1 : 0) +
				                    4 /* ELSE */ +
				                    (expression.hasSpaceAfterElse() ? 1 : 0) +
				                    length(expression.getElseExpression()) +
				                    (expression.hasSpaceAfterElseExpression() ? 1 : 0);

				addProblem(expression, startPosition, CaseExpression_MissingEndIdentifier);
			}

			super.visit(expression);
		}
	}

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

		// JPA 1.0 does not support a COALESCE expression
		if (isJPA1_0()) {
			addProblem(expression, CoalesceExpression_InvalidJPAVersion);
		}
		else {
			validateAbstractSingleEncapsulatedExpression(expression, coalesceExpressionHelper());
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(CollectionExpression expression) {
		// Nothing to validate, it's done by the parent expression
		// but we want to validate its children
		super.visit(expression);
	}

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

		// FROM => 'IN (x) AS y'
		if (isOwnedByFromClause(expression)) {

			// Missing '('
			if (!expression.hasLeftParenthesis()) {
				int startPosition = position(expression) + 2 /* IN */;
				addProblem(expression, startPosition, CollectionMemberDeclaration_MissingLeftParenthesis);
			}
			// Missing collection valued path expression
			else if (!expression.hasCollectionValuedPathExpression()) {
				int startPosition = position(expression) + 3 /* IN( */;
				addProblem(expression, startPosition, CollectionMemberDeclaration_MissingCollectionValuedPathExpression);
			}
			// Missing right parenthesis
			else if (!expression.hasRightParenthesis()) {

				int startPosition = position(expression) +
				                    2 /* IN */ +
				                    (expression.hasLeftParenthesis() ? 1 : 0) +
				                    (expression.hasSpaceAfterIn() ? 1 : 0) +
				                    length(expression.getCollectionValuedPathExpression());

				addProblem(expression, startPosition, CollectionMemberDeclaration_MissingRightParenthesis);
			}

			// Missing identification variable
			if (expression.hasRightParenthesis() &&
			   !expression.hasIdentificationVariable()) {

				int startPosition = position(expression) +
				                    2 /* IN */ +
				                    (expression.hasLeftParenthesis() ? 1 : 0) +
				                    (expression.hasSpaceAfterIn() ? 1 : 0) +
				                    length(expression.getCollectionValuedPathExpression()) +
				                    1 /* ')' */ +
				                    (expression.hasSpaceAfterRightParenthesis() ? 1 : 0) +
				                    (expression.hasAs() ? 2 : 0) +
				                    (expression.hasSpaceAfterAs() ? 1 : 0);

				addProblem(expression, startPosition, CollectionMemberDeclaration_MissingIdentificationVariable);
			}
		}
		// Subquery FROM => 'IN (x) AS y' or 'IN x'
		else {

			// Missing '('
			if (!expression.hasLeftParenthesis() &&
			     expression.hasRightParenthesis()) {

				int startPosition = position(expression) + 2; // IN
				addProblem(expression, startPosition, CollectionMemberDeclaration_MissingLeftParenthesis);
			}
			// Missing collection valued path expression
			else if (!expression.hasCollectionValuedPathExpression()) {

				int startPosition = position(expression) +
				                    2 /* IN */ +
				                    (expression.hasSpaceAfterIn() ? 1 : 0);

				addProblem(expression, startPosition, CollectionMemberDeclaration_MissingCollectionValuedPathExpression);
			}
			// Missing right parenthesis
			else if (expression.hasLeftParenthesis() &&
			        !expression.hasRightParenthesis()) {

				int startPosition = position(expression) +
				                    2 /* IN */ +
				                    (expression.hasLeftParenthesis() ? 1 : 0) +
				                    (expression.hasSpaceAfterIn() ? 1 : 0) +
				                    length(expression.getCollectionValuedPathExpression());

				addProblem(expression, startPosition, CollectionMemberDeclaration_MissingRightParenthesis);
			}

			// Missing identification variable
			if (expression.hasRightParenthesis() &&
			   !expression.hasIdentificationVariable()) {

				int startPosition = position(expression) +
				                    2 /* IN */ +
				                    (expression.hasLeftParenthesis() ? 1 : 0) +
				                    (expression.hasSpaceAfterIn() ? 1 : 0) +
				                    length(expression.getCollectionValuedPathExpression()) +
				                    1 /* ')' */ +
				                    (expression.hasSpaceAfterRightParenthesis() ? 1 : 0) +
				                    (expression.hasAs() ? 2 : 0) +
				                    (expression.hasSpaceAfterAs() ? 1 : 0);

				addProblem(expression, startPosition, CollectionMemberDeclaration_MissingIdentificationVariable);
			}
		}

		super.visit(expression);
	}

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

		// Missing entity expression
		if (!expression.hasEntityExpression()) {
			int startPosition = position(expression);
			addProblem(expression, startPosition, CollectionMemberExpression_MissingEntityExpression);
		}

		// Missing collection valued path expression
		if (!expression.hasCollectionValuedPathExpression()) {

			int startPosition = position(expression) +
			                    length(expression.getEntityExpression()) +
			                    (expression.hasEntityExpression() ? 1 : 0) +
			                    (expression.hasNot() ? 4 /* NOT + whitespace */ : 0) +
			                    6 /* MEMBER */ +
			                    (expression.hasSpaceAfterMember() ? 1 : 0) +
			                    (expression.hasOf() ? 2 : 0) +
			                    (expression.hasSpaceAfterOf() ? 1 : 0);

			addProblem(expression, startPosition, CollectionMemberExpression_MissingCollectionValuedPathExpression);
		}
		else {
			Expression pathExpression = expression.getCollectionValuedPathExpression();

			// The expression is not a path expression
			if (!isValid(pathExpression, CollectionValuedPathExpressionBNF.ID)) {

				int startPosition = position(expression) +
				                    length(expression.getEntityExpression()) +
				                    (expression.hasEntityExpression() ? 1 : 0) +
				                    (expression.hasNot() ? 4 /* NOT + whitespace */ : 0) +
				                    6 /* MEMBER */ +
				                    (expression.hasSpaceAfterMember() ? 1 : 0) +
				                    (expression.hasOf() ? 2 : 0) +
				                    (expression.hasSpaceAfterOf() ? 1 : 0);

				int endPosition = startPosition + length(pathExpression);

				addProblem(
					expression,
					startPosition,
					endPosition,
					CollectionValuedPathExpression_NotCollectionType,
					expression.toParsedText()
				);
			}
		}

		super.visit(expression);
	}

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

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

		// Missing left expression
		if (!expression.hasLeftExpression()) {
			int startPosition = position(expression);
			addProblem(expression, startPosition, ComparisonExpression_MissingLeftExpression);
		}

		// Missing right expression
		if (!expression.hasRightExpression()) {

			int startPosition = position(expression) +
			                    (expression.hasLeftExpression() ? 1 : 0) +
			                    length(expression.getLeftExpression()) +
			                    expression.getComparisonOperator().length() +
			                    (expression.hasSpaceAfterIdentifier() ? 1 : 0);

			addProblem(expression, startPosition, ComparisonExpression_MissingRightExpression);
		}

		super.visit(expression);
	}

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

		validateAbstractSingleEncapsulatedExpression(expression, concatExpressionHelper());

		if (expression.hasLeftParenthesis() &&
		    expression.hasExpression()) {

			CollectionExpression collectionExpression = getCollectionExpression(expression.getExpression());

			// Single element
			if (collectionExpression == null) {
				addProblem(expression, ConcatExpression_MissingExpression);
			}
			else {
				for (Expression child : collectionExpression.children()) {
					if (!isValid(child, expression.getEncapsulatedExpressionQueryBNFId())) {
						addProblem(child, ConcatExpression_InvalidExpression, child.toParsedText());
					}
				}
			}
		}
	}

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

		String className = expression.getClassName();

		// Missing constructor name
		if (className.length() == 0) {

			int startPosition = position(expression) +
			                    3 /* NEW */ +
			                    (expression.hasSpaceAfterNew() ? 1 : 0);

			addProblem(expression, startPosition, ConstructorExpression_MissingConstructorName);
		}
		// Missing '('
		else if (!expression.hasLeftParenthesis()) {

			int startPosition = position(expression) +
			                    3 /* NEW */ +
			                    (expression.hasSpaceAfterNew() ? 1 : 0) +
			                    className.length();

			addProblem(expression, startPosition, ConstructorExpression_MissingLeftParenthesis);
		}
		else {

			// Missing constructor items
			if (!expression.hasConstructorItems()) {

				int startPosition = position(expression) +
				                    3 /* NEW */ +
				                    (expression.hasSpaceAfterNew() ? 1 : 0) +
				                    className.length() +
				                    1 /* '(' */;

				addProblem(expression, startPosition, ConstructorExpression_MissingConstructorItem);
			}
			else {

				// Validate the constructor items
				validateCollectionSeparatedByComma(
					expression.getConstructorItems(),
					ConstructorExpression_ConstructorItemEndsWithComma,
					ConstructorExpression_ConstructorItemIsMissingComma
				);

				// Missing ')'
				if (expression.hasLeftParenthesis()  &&
				    expression.hasConstructorItems() &&
				   !expression.hasRightParenthesis()) {

					int startPosition = position(expression) +
					                    3 /* NEW */ +
					                    (expression.hasSpaceAfterNew() ? 1 : 0) +
					                    className.length() +
					                    1 /* '(' */ +
					                    length(expression.getConstructorItems());

					addProblem(expression, startPosition, ConstructorExpression_MissingRightParenthesis);
				}
			}

			super.visit(expression);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(CountFunction expression) {
		validateAggregateFunctionLocation(expression, countFunctionHelper());
	}

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

		String dateTime = expression.getText();

		// The JDBC escape syntax
		if (dateTime.startsWith("{")) {
			int length = dateTime.length();

			// Missing opening
			if (!dateTime.startsWith("{d ") &&
			    !dateTime.startsWith("{t ") &&
			    !dateTime.startsWith("{ts ")) {

				int startPosition = position(expression) + 1;
				int endPosition = startPosition;

				for (int index = 1; index < length; index++) {
					if (Character.isWhitespace(dateTime.charAt(index))) {
						break;
					}
					endPosition++;
				}

				addProblem(expression, startPosition, endPosition, DateTime_JDBCEscapeFormat_InvalidSpecification);
			}
			// Missing open quote
			else if (!dateTime.startsWith("{d '") &&
			         !dateTime.startsWith("{t '") &&
			         !dateTime.startsWith("{ts '")) {

				int startPosition = position(expression) + 1;

				for (int index = 1; index < length; index++) {
					startPosition++;

					if (Character.isWhitespace(dateTime.charAt(index))) {
						break;
					}
				}

				addProblem(expression, startPosition, DateTime_JDBCEscapeFormat_MissingOpenQuote);
			}

			// Missing closing '
			if ((length > 1) && (dateTime.charAt(length - (dateTime.endsWith("}") ? 2 : 1)) != '\'')) {
				int startPosition = position(expression) + length;

				if (dateTime.endsWith("}")) {
					startPosition--;
				}

				addProblem(expression, startPosition, DateTime_JDBCEscapeFormat_MissingCloseQuote);
			}
			// Missing closing }
			else if (!dateTime.endsWith("}")) {
				int startPosition = position(expression) +length;
				addProblem(expression, startPosition, DateTime_JDBCEscapeFormat_MissingRightCurlyBrace);
			}
		}
	}

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

		// Missing FROM
		if (!expression.hasFrom()) {

			int startPosition = 6 /* DELETE */ +
			                    (expression.hasSpaceAfterDelete() ? 1 : 0);

			addProblem(expression, startPosition, DeleteClause_FromMissing);
		}
		// Missing range variable declaration
		else if (!expression.hasRangeVariableDeclaration()) {
			// The whitespace is added to the position regardless if it was parsed or not
			int startPosition = 12 /* DELETE FROM + whitespace) */;
			addProblem(expression, startPosition, DeleteClause_RangeVariableDeclarationMissing);
		}

		// Validate range variable declaration
		if (expression.hasRangeVariableDeclaration()) {

			// More than one entity abstract schema type is declared
			CollectionExpression collectionExpression = getCollectionExpression(expression.getRangeVariableDeclaration());

			if (collectionExpression != null) {
				Expression firstChild = collectionExpression.getChild(0);
				int startPosition = position(firstChild) + length(firstChild);
				int endPosition = position(collectionExpression) + length(collectionExpression);
				boolean malformed = false;

				for (int index = collectionExpression.childrenSize() - 1; --index >= 0; ) {
					if (!collectionExpression.hasComma(index)) {
						malformed = true;
					}
				}

				if (collectionExpression.toActualText().endsWith(" ")) {
					endPosition--;
				}

				addProblem(
					expression,
					startPosition,
					endPosition,
					malformed ? DeleteClause_RangeVariableDeclarationMalformed :
					            DeleteClause_MultipleRangeVariableDeclaration
				);
			}
			else {
				super.visit(expression);
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(DeleteStatement expression) {
		// Nothing to validate, done directly by DeleteClause and WhereClause
		super.visit(expression);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(DivisionExpression expression) {
		validateArithmeticExpression(expression);
	}

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

		// Missing collection valued path expression
		if (!expression.hasExpression()) {
			int startPosition = position(expression);
			addProblem(expression, startPosition, EmptyCollectionComparisonExpression_MissingExpression);
		}
		else {
			Expression pathExpression = expression.getExpression();

			// The expression is not a path expression
			if (!isValid(pathExpression, CollectionValuedPathExpressionBNF.ID)) {

				int startPosition = position(expression);
				int endPosition   = startPosition + length(pathExpression);

				addProblem(
					expression,
					startPosition,
					endPosition,
					CollectionValuedPathExpression_NotCollectionType,
					expression.toParsedText()
				);
			}
			else {
				super.visit(expression);
			}
		}
	}

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

		if (isJPA1_0()) {
			addProblem(expression, EntityTypeLiteral_InvalidJPAVersion);
		}
	}

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

		// JPA 1.0 does not support an ENTRY expression
		if (isJPA1_0()) {
			addProblem(expression, EntryExpression_InvalidJPAVersion);
		}
		else {
			validateAbstractSingleEncapsulatedExpression(expression, entryExpressionHelper());
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(ExistsExpression expression) {
		validateAbstractSingleEncapsulatedExpression(expression, existsExpressionHelper());
	}

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

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

		JPQLQueryBNF queryBNF = getQueryBNF(expression.getQueryBNF().getId());
		boolean validFunction = (queryBNF != null) && queryBNF.hasIdentifier(expression.getIdentifier());

		// JPA 1.0/2.0 does not support the function expression
		if (!isJPA2_1() || !validFunction) {
			addProblem(expression, FunctionExpression_InvalidJPAVersion);
		}
		else {
			validateAbstractSingleEncapsulatedExpression(expression, functionExpressionHelper());

			// Missing function name
			if (expression.hasLeftParenthesis()) {
				String functionName = expression.getUnquotedFunctionName();

				if (ExpressionTools.stringIsEmpty(functionName)) {
					int startPosition = position(expression) +
					                    expression.getIdentifier().length() +
					                    (expression.hasLeftParenthesis() ? 1 : 0);

					addProblem(expression, startPosition, FunctionExpression_MissingFunctionName);
				}
			}
		}
	}

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

		// Missing grouping items
		if (!expression.hasGroupByItems()) {

			int startPosition = position(expression) +
			                    8 /* GROUP BY */ +
			                    (expression.hasSpaceAfterGroupBy() ? 1 : 0);

			addProblem(expression, startPosition, GroupByClause_GroupByItemMissing);
		}
		// Validate the separation of multiple ordering items
		else {
			validateCollectionSeparatedByComma(
				expression.getGroupByItems(),
				GroupByClause_GroupByItemEndsWithComma,
				GroupByClause_GroupByItemIsMissingComma
			);

			super.visit(expression);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(HavingClause expression) {
		validateAbstractConditionalClause(
			expression,
			HavingClause_MissingConditionalExpression,
			HavingClause_InvalidConditionalExpression
		);
	}

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

		if (!expression.isVirtual()) {
			String variable = expression.getText();

			validateIdentifier(
				expression,
				variable,
				variable.length(),
				IdentificationVariable_Invalid_ReservedWord,
				IdentificationVariable_Invalid_JavaIdentifier
			);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(IdentificationVariableDeclaration expression) {
		validateIdentificationVariableDeclaration(expression);
	}

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

		// JPA 1.0 does not support an INDEX expression
		if (isJPA1_0()) {
			addProblem(expression, IndexExpression_InvalidJPAVersion);
		}
		else {
			validateAbstractSingleEncapsulatedExpression(expression, indexExpressionHelper());
		}
	}

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

		// Missing expression
		if (!expression.hasExpression()) {
			int startPosition = position(expression);
			addProblem(expression, startPosition, InExpression_MissingExpression);
		}
		else {
			Expression leftExpression = expression.getExpression();
			JPQLQueryBNF queryBNF = getQueryBNF(expression.getExpressionExpressionQueryBNFId());

			// First check for nested array support
			if (isNestedArray(leftExpression)) {
				if (!queryBNF.handlesNestedArray()) {
					int startPosition = position(expression);
					int endPosition   = startPosition + length(leftExpression);
					addProblem(expression, startPosition, endPosition, InExpression_InvalidExpression);
				}
				else {
					leftExpression.accept(this);
				}
			}
			// Validate the expression
			else if (!isValid(leftExpression, queryBNF) || isDateTimeConstant(leftExpression)) {
				int startPosition = position(expression);
				int endPosition   = startPosition + length(leftExpression);
				addProblem(expression, startPosition, endPosition, InExpression_InvalidExpression);
			}
			else {
				leftExpression.accept(this);
			}
		}

		// Check for "IN :input_parameter" defined in JPA 2.0
		boolean singleInputParameter = isNewerThanOrEqual(JPAVersion.VERSION_2_0) &&
		                               expression.isSingleInputParameter();

		// Missing '('
		if (!expression.hasLeftParenthesis() && !singleInputParameter) {

			int startPosition = position(expression) +
			                    length(expression.getExpression()) +
			                    (expression.hasExpression() ? 1 : 0) +
			                    (expression.hasNot() ? 4 /* NOT + whitespace */ : 0) +
			                    2 /* IN */;

			addProblem(expression, startPosition, InExpression_MissingLeftParenthesis);
		}
		// There must be at least one element in the comma separated list that
		// defines the set of values for the IN expression.
		else if (!expression.hasInItems()) {

			int startPosition = position(expression) +
			                    length(expression.getExpression()) +
			                    (expression.hasExpression() ? 1 : 0) +
			                    (expression.hasNot() ? 4 /* NOT + whitespace */ : 0) +
			                    2 /* IN */ +
			                    (expression.hasSpaceAfterIn() ? 1 : 0) +
			                    (expression.hasLeftParenthesis() ? 1 : 0);

			addProblem(expression, startPosition, InExpression_MissingInItems);
		}
		// Make sure the IN items are separated by commas
		else if (!singleInputParameter) {

			Expression inItems = expression.getInItems();
			CollectionExpression collectionExpression = getCollectionExpression(inItems);

			// Validate the collection of items
			if (collectionExpression != null) {

				validateCollectionSeparatedByComma(
					inItems,
					InExpression_ItemEndsWithComma,
					InExpression_ItemIsMissingComma
				);

				// Validate each item
				JPQLQueryBNF queryBNF = getQueryBNF(expression.getExpressionItemQueryBNFId());
				int index = 0;

				for (Expression child : collectionExpression.children()) {

					index++;

					// First check for nested array support
					if (isNestedArray(child)) {
						if (!queryBNF.handlesNestedArray()) {
							addProblem(child, InExpression_ItemInvalidExpression, String.valueOf(index));
						}
						else {
							child.accept(this);
						}
					}
					// Invalid item
					else if (!isValid(child, queryBNF)) {
						addProblem(child, InExpression_ItemInvalidExpression, String.valueOf(index));
					}
					// Validate the item
					else {
						child.accept(this);
					}
				}
			}
			// The single item is invalid
			else if (!isValid(inItems, expression.getExpressionItemQueryBNFId())) {
				addProblem(inItems, InExpression_ItemInvalidExpression);
			}
			// Validate the single item
			else {
				inItems.accept(this);
			}
		}

		// Missing ')'
		if (!singleInputParameter    &&
		     expression.hasInItems() &&
		    !expression.hasRightParenthesis()) {

			int startPosition = position(expression) +
			                    length(expression.getExpression()) +
			                    (expression.hasExpression() ? 1 : 0) +
			                    (expression.hasNot() ? 4 /* NOT + whitespace */ : 0) +
			                    2 /* IN */ +
			                    (expression.hasSpaceAfterIn() ? 1 : 0) +
			                    (expression.hasLeftParenthesis() ? 1 : 0) +
			                    length(expression.getInItems());

			addProblem(expression, startPosition, InExpression_MissingRightParenthesis);
		}
	}

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

		inputParameters.add(expression);
		String parameter = expression.getParameter();

		// No parameter specified
		if (parameter.length() == 1) {
			int startPosition = position(expression);
			addProblem(expression, startPosition, startPosition + 1, InputParameter_MissingParameter);
		}
		// Named parameter: It follows the rules for identifiers defined in Section 4.4.1 of the spec
		else if (expression.isNamed()) {
			if (!isValidJavaIdentifier(parameter.substring(1))) {
				int startPosition = position(expression);
				int endPosition   = startPosition + parameter.length();
				addProblem(expression, startPosition, endPosition, InputParameter_JavaIdentifier);
			}
		}
		// Positional parameter: Designated by the question mark (?) prefix followed by an integer
		else {
			boolean valid = true;

			for (int index = parameter.length(); --index > 0; ) /* Skip ? */ {
				char character = parameter.charAt(index);

				if (!Character.isDigit(character)) {
					int startPosition = position(expression);
					int endPosition   = startPosition + parameter.length();
					addProblem(expression, startPosition, endPosition, InputParameter_NotInteger);
					valid = false;
					break;
				}
			}

			// Input parameters are numbered starting from 1
			if (valid) {
				Integer value = Integer.valueOf(parameter.substring(1));

				if (value < 1) {
					int startPosition = position(expression);
					int endPosition   = startPosition + parameter.length();
					addProblem(expression, startPosition, endPosition, InputParameter_SmallerThanOne);
				}
			}
		}

		// Input parameters can only be used in the WHERE or HAVING clause of a query.
		// Skip the ORDER BY clause because it has its own validation rule. The exception
		// to this rule is in a FUNC expression
		validateOwningClause(expression, parameter);
	}

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

		boolean joinFetch = expression.hasFetch();

		// Validate the JOIN identifier
		String identifier = expression.getIdentifier();

		if (identifier != JOIN             &&
		    identifier != JOIN_FETCH       &&
		    identifier != INNER_JOIN       &&
		    identifier != INNER_JOIN_FETCH &&
		    identifier != LEFT_JOIN        &&
		    identifier != LEFT_JOIN_FETCH  &&
		    identifier != LEFT_OUTER_JOIN  &&
		    identifier != LEFT_OUTER_JOIN_FETCH) {

			int startPosition = position(expression);
			int endPosition   = startPosition + identifier.length();

			addProblem(
				expression,
				startPosition,
				endPosition,
				Join_InvalidIdentifier
			);
		}

		// Missing join association path expression
		if (!expression.hasJoinAssociationPath()) {

			int startPosition = position(expression) +
			                    identifier.length() +
			                    (expression.hasSpaceAfterJoin() ? 1 : 0);

			addProblem(
				expression,
				startPosition,
				joinFetch ? JoinFetch_MissingJoinAssociationPath : Join_MissingJoinAssociationPath
			);
		}
		else {

			Expression joinAssociationPath = expression.getJoinAssociationPath();

			// Invalid join association path
			if (!isValid(joinAssociationPath, JoinAssociationPathExpressionBNF.ID)) {

				int startPosition = position(joinAssociationPath);
				int endPosition   = startPosition + length(joinAssociationPath);
				addProblem(expression, startPosition, endPosition, Join_InvalidJoinAssociationPath);
			}
			// Validate join association path
			else {
				joinAssociationPath.accept(this);
			}
		}

		// Missing identification variable
		// A JOIN expression always needs an identification variable
		// A JOIN FETCH expression does not always require an identification, only if 'AS' is present
		if (expression.hasJoinAssociationPath() &&
		   !expression.hasIdentificationVariable() &&
		    (!joinFetch || expression.hasAs() && isJoinFetchIdentifiable())) {

			int startPosition = position(expression) +
			                    identifier.length() +
			                    (expression.hasSpaceAfterJoin() ? 1 : 0) +
			                    length(expression.getJoinAssociationPath()) +
			                    (expression.hasSpaceAfterJoinAssociation() ? 1 : 0) +
			                    (expression.hasAs() ? 2 : 0) +
			                    (expression.hasSpaceAfterAs() ? 1 : 0);

			addProblem(
				expression,
				startPosition,
				joinFetch ? JoinFetch_MissingIdentificationVariable : Join_MissingIdentificationVariable
			);
		}
		// A JOIN FETCH expression that cannot be identified with an identification variable
		else if (joinFetch &&
		         !isJoinFetchIdentifiable() &&
		         (expression.hasAs() || expression.hasIdentificationVariable())) {

			int startPosition = position(expression) +
			                    identifier.length() +
			                    (expression.hasSpaceAfterJoin() ? 1 : 0) +
			                    length(expression.getJoinAssociationPath()) +
			                    (expression.hasSpaceAfterJoinAssociation() ? 1 : 0);

			int endPosition = startPosition +
			                  (expression.hasAs() ? 2 : 0) +
			                  (expression.hasSpaceAfterAs() ? 1 : 0) +
			                  length(expression.getIdentificationVariable());

			addProblem(expression, startPosition, endPosition, JoinFetch_InvalidIdentification);
		}
		else {
			expression.getIdentificationVariable().accept(this);
		}

		// A JOIN FETCH expression can only be defined in the top-level query
		if (joinFetch && isOwnedBySubFromClause(expression)) {
			int startPosition = position(expression);
			int endPosition = startPosition + length(expression);
			addProblem(expression, startPosition, endPosition, JoinFetch_WrongClauseDeclaration);
		}

		// Traverse the ON clause
		expression.getOnClause().accept(this);
	}

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

		// Validate the statement
		if (expression.hasQueryStatement()) {

			expression.getQueryStatement().accept(this);

			// Now that the entire tree was visited, we can validate the input parameters, which were
			// automatically cached. Positional and named parameters must not be mixed in a single query
			validateInputParameters(expression);

			// Should never have an unknown ending statement
			if (expression.hasUnknownEndingStatement()) {

				String unknownStatement = expression.getUnknownEndingStatement().toActualText();

				// Make sure the unknown ending statement is not a whitespace, one is kept for content assist
				if (ExpressionTools.stringIsNotEmpty(unknownStatement)) {
					int startPosition = length(expression.getQueryStatement());
					int endPosition   = startPosition + length(expression.getUnknownEndingStatement());
					addProblem(expression, startPosition, endPosition, JPQLExpression_UnknownEnding);
				}
				// Empty query
				else if (!expression.hasQueryStatement()) {
					addProblem(expression, 0, length(expression), JPQLExpression_InvalidQuery);
				}
			}
		}
		// Invalid query
		else {
			addProblem(expression, 0, length(expression), JPQLExpression_InvalidQuery);
		}
	}

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

		// JPA 1.0 does not support a KEY expression
		if (isJPA1_0()) {
			addProblem(expression, KeyExpression_InvalidJPAVersion);
		}
		else {
			validateAbstractSingleEncapsulatedExpression(expression, keyExpressionHelper());
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(KeywordExpression expression) {
		// Nothing to validate
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(LengthExpression expression) {
		validateAbstractSingleEncapsulatedExpression(expression, lengthExpressionHelper());
	}

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

		// Missing string expression
		if (!expression.hasStringExpression()) {
			int startPosition = position(expression);
			addProblem(expression, startPosition, LikeExpression_MissingStringExpression);
		}

		// Missing pattern value
		if (!expression.hasPatternValue()) {

			int startPosition = position(expression) +
			                    length(expression.getStringExpression()) +
			                    4 /* LIKE */ +
			                    (expression.hasSpaceAfterStringExpression() ? 1 : 0) +
			                    (expression.hasNot() ? 1 : 0) +
			                    (expression.hasSpaceAfterLike() ? 1 : 0);

			addProblem(expression, startPosition, LikeExpression_MissingPatternValue);
		}

		// Validate the escape character
		if (expression.hasEscape()) {

			// Missing escape character
			if (!expression.hasEscapeCharacter()) {

				int startPosition = position(expression) +
				                    length(expression.getStringExpression()) +
				                    4 /* LIKE */ +
				                    (expression.hasSpaceAfterStringExpression() ? 1 : 0) +
				                    (expression.hasNot() ? 1 : 0) +
				                    (expression.hasSpaceAfterLike() ? 1 : 0) +
				                    length(expression.getPatternValue()) +
				                    (expression.hasSpaceAfterPatternValue() ? 1 : 0) +
				                    6 + /* ESCAPE */ +
				                    (expression.hasSpaceAfterEscape() ? 1 : 0);

				addProblem(expression, startPosition, LikeExpression_MissingEscapeCharacter);
			}
			else {
		   	validateLikeExpressionEscapeCharacter(expression);
		   }
		}

		super.visit(expression);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(LocateExpression expression) {
		validateAbstractTripleEncapsulatedExpression(expression, locateExpressionHelper());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(LowerExpression expression) {
		validateAbstractSingleEncapsulatedExpression(expression, lowerExpressionHelper());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(MaxFunction expression) {
		validateAggregateFunctionLocation(expression, maxFunctionHelper());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(MinFunction expression) {
		validateAggregateFunctionLocation(expression, minFunctionHelper());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(ModExpression expression) {
		validateAbstractDoubleEncapsulatedExpression(expression, modExpressionHelper());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(MultiplicationExpression expression) {
		validateArithmeticExpression(expression);
	}

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

		// Missing expression
		if (!expression.hasExpression()) {

			int startPosition = position(expression) +
			                    3 /* NOT */ +
			                    (expression.hasSpaceAfterNot() ? 1 : 0);

			addProblem(expression, startPosition, NotExpression_MissingExpression);
		}
		else {
			super.visit(expression);
		}
	}

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

		// Missing expression
		if (!expression.hasExpression()) {
			int startPosition = position(expression);
			addProblem(expression, startPosition,  NullComparisonExpression_MissingExpression);
		}
		else {
			super.visit(expression);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(NullExpression expression) {
		// Nothing to validate
	}

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

		// JPA 1.0 does not support a NULLIF expression
		if (isJPA1_0()) {
			addProblem(expression, NullIfExpression_InvalidJPAVersion);
		}
		else {
			validateAbstractDoubleEncapsulatedExpression(expression, nullIfExpressionHelper());
		}
	}

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

		String text = expression.getText();

		// - Exact numeric literals support the use of Java integer literal syntax as well as SQL
		//   exact numeric literal syntax
		// - Approximate literals support the use Java floating point literal syntax as well as SQL
		//   approximate numeric literal syntax
		// - Appropriate suffixes can be used to indicate the specific type of a numeric literal in
		//   accordance with the Java Language Specification
		if (!isNumericLiteral(text)) {
			int startPosition = position(expression);
			int endPosition   = startPosition + text.length();
			addProblem(expression, startPosition, endPosition, NumericLiteral_Invalid, text);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(ObjectExpression expression) {
		validateAbstractSingleEncapsulatedExpression(expression, objectExpressionHelper());
	}

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

		// Missing conditional expression
		if (!expression.hasConditionalExpression()) {

			int startPosition = position(expression) +
			                    2 /* ON */ +
			                    (expression.hasSpaceAfterIdentifier() ? 1 : 0);

			addProblem(expression, startPosition, OnClause_MissingConditionalExpression);
		}
		else {
			Expression conditionalExpression = expression.getConditionalExpression();

			// Invalid conditional expression
			if (!isValid(conditionalExpression, ConditionalExpressionBNF.ID)) {

				int startPosition = position(expression) +
				                    2 /* ON */ +
				                    (expression.hasSpaceAfterIdentifier() ? 1 : 0);

				int endPosition = startPosition + length(conditionalExpression);

				addProblem(expression, startPosition, endPosition, OnClause_InvalidConditionalExpression);
			}
			// Validate the conditional expression
			else {
				conditionalExpression.accept(this);
			}
		}
	}

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

		if (!expression.hasOrderByItems()) {
			int startPosition = position(expression.getOrderByItems());
			addProblem(expression, startPosition, OrderByClause_OrderByItemMissing);
		}
		// Validate the separation of multiple grouping items
		else {
			validateCollectionSeparatedByComma(
				expression.getOrderByItems(),
				OrderByClause_OrderByItemEndsWithComma,
				OrderByClause_OrderByItemIsMissingComma
			);
		}

		super.visit(expression);
	}

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

		// Missing ordering item
		if (!expression.hasExpression()) {
			int startPosition = position(expression);
			addProblem(expression, startPosition, OrderByItem_MissingExpression);
		}
		else {
			Expression item = expression.getExpression();

			// Invalid order by item
			if (!isValid(item, InternalOrderByItemBNF.ID)) {
				int startPosition = position(item);
				int endPosition   = startPosition + length(item);
				addProblem(item, startPosition, endPosition, OrderByItem_InvalidExpression);
			}
			else {
				super.visit(expression);
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(OrExpression expression) {
		validateLogicalExpression(
			expression,
			ConditionalExpressionBNF.ID,
			ConditionalExpressionBNF.ID
		);
	}

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

		// Missing abstract schema name
		if (!expression.hasRootObject()) {
			int startPosition = position(expression);
			addProblem(expression, startPosition, RangeVariableDeclaration_MissingRootObject);
		}
		else {

			Expression rootObject = expression.getRootObject();

			if (!isValid(rootObject, RangeDeclarationBNF.ID)) {

				int startPosition = position(rootObject);
				int endPosition   = startPosition + length(rootObject);

				addProblem(expression, startPosition, endPosition, RangeVariableDeclaration_InvalidRootObject);
			}
			else {
				rootObject.accept(this);
			}
		}

		// Missing identification variable
		if (!expression.hasIdentificationVariable() &&
		    !expression.hasVirtualIdentificationVariable()) {

			int startPosition = position(expression) +
			                    length(expression.getRootObject()) +
			                    (expression.hasSpaceAfterRootObject() ? 1 : 0) +
			                    (expression.hasAs() ? 2 : 0) +
			                    (expression.hasSpaceAfterAs() ? 1 : 0);

			addProblem(expression, startPosition, RangeVariableDeclaration_MissingIdentificationVariable);
		}
		else {
			expression.getIdentificationVariable().accept(this);
		}
	}

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

		// JPA 1.0 does not support a result variable expression
		if (isJPA1_0()) {
			int startPosition = position(expression) +
			                    length(expression.getSelectExpression()) +
			                    (expression.hasSelectExpression() ? 1 : 0);

			int endPosition = startPosition +
			                  (expression.hasAs() ? 2 : 0) +
			                  (expression.hasSpaceAfterAs() ? 1 : 0) +
			                  length(expression.getResultVariable());

			addProblem(expression, startPosition, endPosition, ResultVariable_InvalidJPAVersion);
		}
		else {

			// Missing select expression
			if (!expression.hasSelectExpression()) {
				int startPosition = position(expression);
				addProblem(expression, startPosition, ResultVariable_MissingSelectExpression);
			}
			// Validate the select expression
			else {
				expression.getSelectExpression().accept(this);
			}

			// Missing result variable
			if (!expression.hasResultVariable()) {

				int startPosition = position(expression) +
				                    length(expression.getSelectExpression()) +
				                    (expression.hasSelectExpression() ? 1 : 0) +
				                    (expression.hasAs() ? 2 : 0) +
				                    (expression.hasSpaceAfterAs() ? 1 : 0);

				addProblem(expression, startPosition, ResultVariable_MissingResultVariable);
			}
			// Validate the result variable
			else {
				expression.getResultVariable().accept(this);
			}
		}
	}

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

		validateAbstractSelectClause(expression, true);

		// Make sure the select expression are separated by a comma
		if (expression.hasSelectExpression()) {
			validateCollectionSeparatedByComma(
				expression.getSelectExpression(),
				AbstractSelectClause_SelectExpressionEndsWithComma,
				AbstractSelectClause_SelectExpressionIsMissingComma
			);
		}
	}

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

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(SimpleSelectClause expression) {
		validateAbstractSelectClause(expression, isMultipleSubquerySelectItemsAllowed(expression));
	}

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(SizeExpression expression) {
		validateAbstractSingleEncapsulatedExpression(expression, sizeExpressionHelper());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(SqrtExpression expression) {
		validateAbstractSingleEncapsulatedExpression(expression, sqrtExpressionHelper());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(StateFieldPathExpression expression) {
		validatePathExpression(expression);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(StringLiteral expression) {
		if (!expression.hasCloseQuote()) {
			addProblem(expression, StringLiteral_MissingClosingQuote);
		}
	}

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

		// Missing sub-expression
		if (!expression.hasExpression()) {
			int startPosition = position(expression) + 1;
			addProblem(expression, startPosition, SubExpression_MissingExpression);
		}
		else {

			// Missing right parenthesis
			if (!expression.hasRightParenthesis()) {

				int startPosition = position(expression) +
				                    1 /* ( */ +
				                    length(expression.getExpression());

				addProblem(expression, startPosition, SubExpression_MissingRightParenthesis);
			}

			super.visit(expression);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(SubstringExpression expression) {
		validateAbstractTripleEncapsulatedExpression(expression, substringExpressionHelper());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(SubtractionExpression expression) {
		validateArithmeticExpression(expression);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(SumFunction expression) {
		validateAggregateFunctionLocation(expression, sumFunctionHelper());
	}

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

		// EclipseLink 1.0 does not support TREAT expression
		if (isJPA1_0()) {
			addProblem(expression, TreatExpression_InvalidJPAPlatform);
		}
		else  {
			// TODO
			super.visit(expression);
		}
	}

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

		validateAbstractSingleEncapsulatedExpression(expression, trimExpressionHelper());

		// Missing string primary
		if (!expression.hasExpression()) {

			int startPosition = position(expression) +
			                    4 /* TRIM */ +
			                    (expression.hasLeftParenthesis() ? 1 : 0) +
			                    expression.getSpecification().getValue().length() +
			                    (expression.hasSpaceAfterSpecification() ? 1 : 0) +
			                    length(expression.getTrimCharacter()) +
			                    (expression.hasSpaceAfterTrimCharacter() ? 1 : 0) +
			                    (expression.hasFrom() ? 4 : 0) +
			                    (expression.hasSpaceAfterFrom() ? 1 : 0);

			addProblem(expression, startPosition, TrimExpression_MissingExpression);
		}
		// Invalid string primary
		else if (!isValid(expression.getExpression(), expression.getEncapsulatedExpressionQueryBNFId())) {

			int startPosition = position(expression) +
			                    4 /* TRIM */ +
			                    (expression.hasLeftParenthesis() ? 1 : 0) +
			                    expression.getSpecification().getValue().length() +
			                    (expression.hasSpaceAfterSpecification() ? 1 : 0) +
			                    length(expression.getTrimCharacter()) +
			                    (expression.hasSpaceAfterTrimCharacter() ? 1 : 0) +
			                    (expression.hasFrom() ? 4 : 0) +
			                    (expression.hasSpaceAfterFrom() ? 1 : 0);

			int endPosition = startPosition + length(expression.getExpression());
			addProblem(expression, startPosition, endPosition, TrimExpression_InvalidExpression);
		}

		// Invalid trim character
		if (expression.hasTrimCharacter()) {

			Expression trimCharacter = expression.getTrimCharacter();

			// Make sure it's not an input parameter
			String inputParameter = literal(trimCharacter, LiteralType.INPUT_PARAMETER);

			if (ExpressionTools.stringIsEmpty(inputParameter)) {

				String stringLiteral = literal(trimCharacter, LiteralType.STRING_LITERAL);

				int startPosition = position(expression) +
				                    4 /* TRIM */ +
				                    (expression.hasLeftParenthesis() ? 1 : 0) +
				                    expression.getSpecification().getValue().length() +
				                    (expression.hasSpaceAfterSpecification() ? 1 : 0);

				int endPosition = startPosition + length(trimCharacter);

				if (ExpressionTools.stringIsEmpty(stringLiteral)) {
					addProblem(trimCharacter, startPosition, endPosition, TrimExpression_InvalidTrimCharacter);
				}
				else {
					stringLiteral = stringLiteral.substring(1, stringLiteral.length() - (stringLiteral.endsWith("'") ? 1 : 0));

					if (stringLiteral.length() != 1) {
						addProblem(trimCharacter, startPosition, endPosition, TrimExpression_NotSingleStringLiteral);
					}
				}
			}
		}
	}

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

		// JPA 1.0 does not support a TYPE expression
		if (isJPA1_0()) {
			addProblem(expression, TypeExpression_InvalidJPAVersion);
		}
		else {
			validateAbstractSingleEncapsulatedExpression(expression, typeExpressionHelper());
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(UnknownExpression expression) {
		// Nothing to validate and we don't want to validate its encapsulated expression
	}

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

		// Missing range variable declaration
		if (!expression.hasRangeVariableDeclaration()) {

			int startPosition = position(expression) +
			                    6 /* UPDATE */ +
			                    (expression.hasSpaceAfterUpdate() ? 1 : 0);

			addProblem(expression, startPosition, UpdateClause_MissingRangeVariableDeclaration);
		}
		// Missing 'SET'
		else if (!expression.hasSet()) {

			int startPosition = position(expression) +
			                    6 /* UPDATE */ +
			                    (expression.hasSpaceAfterUpdate() ? 1 : 0) +
			                    length(expression.getRangeVariableDeclaration()) +
			                    (expression.hasSpaceAfterRangeVariableDeclaration() ? 1 : 0);

			addProblem(expression, startPosition, UpdateClause_MissingSet);
		}
		// Missing update items
		else if (!expression.hasUpdateItems()) {

			int startPosition = position(expression) +
			                    6 /* UPDATE */ +
			                    (expression.hasSpaceAfterUpdate() ? 1 : 0) +
			                    length(expression.getRangeVariableDeclaration()) +
			                    (expression.hasSpaceAfterRangeVariableDeclaration() ? 1 : 0) +
			                    3 /* 'SET' */ +
			                    (expression.hasSpaceAfterSet() ? 1 : 0);

			addProblem(expression, startPosition, UpdateClause_MissingUpdateItems);
		}
		// Make sure the update items are separated by commas
		else {
			validateCollectionSeparatedByComma(
				expression.getUpdateItems(),
				UpdateClause_UpdateItemEndsWithComma,
				UpdateClause_UpdateItemIsMissingComma
			);
		}

		super.visit(expression);
	}

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

		// Missing state field path expression
		if (!expression.hasStateFieldPathExpression()) {
			int startPosition = position(expression);
			addProblem(expression, startPosition, UpdateItem_MissingStateFieldPathExpression);
		}

		// Missing '='
		if (expression.hasStateFieldPathExpression() &&
		   !expression.hasEqualSign()) {

			int startPosition = position(expression) +
			                    length(expression.getStateFieldPathExpression()) +
			                    (expression.hasSpaceAfterStateFieldPathExpression() ? 1 : 0);

			addProblem(expression, startPosition, UpdateItem_MissingEqualSign);
		}
		// After '='
		else if (expression.hasEqualSign()) {

			// Missing new value
			if (!expression.hasNewValue()) {

				int startPosition = position(expression) +
				                    length(expression.getStateFieldPathExpression()) +
				                    (expression.hasSpaceAfterStateFieldPathExpression() ? 1 : 0) +
				                    1 /* '=' */ +
				                    (expression.hasSpaceAfterEqualSign() ? 1 : 0);

				addProblem(expression, startPosition, UpdateItem_MissingNewValue);
			}
			// Invalid new value
			else {
				// TODO: Anything to validate?
			}
		}

		super.visit(expression);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(UpdateStatement expression) {
		// Done directly by UpdateClause and WhereClause
		super.visit(expression);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(UpperExpression expression) {
		validateAbstractSingleEncapsulatedExpression(expression, upperExpressionHelper());
	}

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

		// JPA 1.0 does not support a VALUE expression
		if (isJPA1_0()) {
			addProblem(expression, ValueExpression_InvalidJPAVersion);
		}
		else {
			validateAbstractSingleEncapsulatedExpression(expression, valueExpressionHelper());
		}
	}

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

		// WHEN expression is missing
		if (!expression.hasWhenExpression()) {

			int startPosition = position(expression) +
			                    4 /* WHEN */ +
			                    (expression.hasSpaceAfterWhen() ? 1 : 0);

			addProblem(expression, startPosition, WhenClause_MissingWhenExpression);
		}

		// THEN identifier is missing
		if (expression.hasWhenExpression() &&
		   !expression.hasThen()) {

			int startPosition = position(expression) +
			                    4 /* WHEN */ +
			                    (expression.hasSpaceAfterWhen() ? 1 : 0) +
			                    length(expression.getWhenExpression()) +
			                    (expression.hasSpaceAfterWhenExpression() ? 1 : 0);

			addProblem(expression, startPosition, WhenClause_MissingThenIdentifier);
		}

		// THEN expression is missing
		if (expression.hasThen() &&
		   !expression.hasThenExpression()) {

			int startPosition = position(expression) +
			                    4 /* WHEN */ +
			                    (expression.hasSpaceAfterWhen() ? 1 : 0) +
			                    length(expression.getWhenExpression()) +
			                    (expression.hasSpaceAfterWhenExpression() ? 1 : 0) +
			                    4 /* THEN */ +
			                    (expression.hasSpaceAfterThen() ? 1 : 0);

			addProblem(expression, startPosition, WhenClause_MissingThenExpression);
		}

		super.visit(expression);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(WhereClause expression) {
		validateAbstractConditionalClause(
			expression,
			WhereClause_MissingConditionalExpression,
			WhereClause_InvalidConditionalExpression
		);
	}

    // Made static for performance reasons.
	private abstract static class AbstractValidator {

        protected final AbstractGrammarValidator validator;

        protected AbstractValidator(AbstractGrammarValidator validator) {
            this.validator = validator;
        }
	}

    // Made static for performance reasons.
	/**
	 * This validate is responsible to validate the collection of {@link Expression Expressions}:
	 * <ul>
	 * <li>Making sure they are all separated by a comma or by a space (depending on which one is
	 * required);</li>
	 * <li>Making sure it does not end with a comma;</li>
	 * <li>There is no empty expression between two commas.</li>
	 * </ul>
	 */
	protected abstract static class AbstractCollectionValidator extends AbstractExpressionVisitor {

		String endsWithCommaProblemKey;
		boolean validateOnly;
		String wrongSeparatorProblemKey;
		private final AbstractGrammarValidator validator;

		protected AbstractCollectionValidator(AbstractGrammarValidator validator) {
		    this.validator = validator;
		}

		protected void validateEndsWithComma(CollectionExpression expression) {

			if (expression.endsWithComma()) {
				int lastIndex = expression.childrenSize() - 1;
				int length = expression.toParsedText(lastIndex).length();
				int startPosition = validator.position(expression) + length - 1;

				if (expression.endsWithSpace()) {
					startPosition--;
				}

				int endPosition = startPosition + 1;

				if (!validateOnly) {
				    validator.addProblem(expression, startPosition, endPosition, endsWithCommaProblemKey);
				}
			}
		}

		protected void validateSeparation(CollectionExpression expression) {

			for (int index = 0, count = expression.childrenSize(); index + 1 < count; index++) {

				Expression expression1 = expression.getChild(index);

				if (validator.length(expression1) == 0) {
					int startPosition = validator.position(expression1);
					int endPosition   = startPosition;

					validator.addProblem(
						expression,
						startPosition,
						endPosition,
						CollectionExpression_MissingExpression,
						String.valueOf(index + 1)
					);
				}

				if (!validateSeparator(expression, index)) {
					Expression expression2 = expression.getChild(index + 1);

					int startPosition = validator.position(expression1) + validator.length(expression1);
					int endPosition   = validator.position(expression2);

					// The space is part of the child expression, move backward
					if (!expression.hasSpace(index)) {
						startPosition--;
					}

					if (!validateOnly) {
					    validator.addProblem(
							expression,
							startPosition,
							endPosition,
							wrongSeparatorProblemKey,
							expression1.toParsedText(),
							expression2.toParsedText()
						);
					}
				}
			}
		}

		/**
		 * Validates
		 *
		 * @param expression
		 * @param index
		 * @return
		 */
		abstract boolean validateSeparator(CollectionExpression expression, int index);

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

    // Made static for performance reasons.
	protected static abstract class AbstractDoubleEncapsulatedExpressionHelper<T extends AbstractDoubleEncapsulatedExpression>
	        extends AbstractValidator implements AbstractEncapsulatedExpressionHelper<T> {

        protected AbstractDoubleEncapsulatedExpressionHelper(AbstractGrammarValidator validator) {
            super(validator);
        }

		/**
		 * {@inheritDoc}
		 */
		public String[] arguments(T expression) {
			return ExpressionTools.EMPTY_STRING_ARRAY;
		}

		protected abstract String firstExpressionInvalidKey();

		protected int firstExpressionLength(T expression) {
			return validator.length(expression.getFirstExpression());
		}

		protected abstract String firstExpressionMissingKey();

		protected boolean hasComma(T expression) {
			return !hasFirstExpression(expression) ||
			       expression.hasComma();
		}

		protected boolean hasFirstExpression(T expression) {
			return expression.hasFirstExpression();
		}

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

		/**
		 * {@inheritDoc}
		 */
		public boolean hasRightParenthesis(T expression) {
			// If the second encapsulated expression is missing, then no need
			// to add a problem for a missing ')' because the second encapsulated
			// expression needs to be specified first
			return !expression.hasSecondExpression() ||
			        expression.hasRightParenthesis();
		}

		protected boolean hasSecondExpression(T expression) {
			return !expression.hasComma() ||
			        expression.hasSecondExpression();
		}

		/**
		 * {@inheritDoc}
		 */
		public String identifier(T expression) {
			return expression.getIdentifier();
		}

		protected final boolean isFirstExpressionValid(T expression) {
			return validator.isValid(expression.getFirstExpression(), expression.parameterExpressionBNF(0));
		}

		protected final boolean isSecondExpressionValid(T expression) {
			return validator.isValid(expression.getSecondExpression(), expression.parameterExpressionBNF(1));
		}

		protected abstract String missingCommaKey();

		protected abstract String secondExpressionInvalidKey();

		protected int secondExpressionLength(T expression) {
			return validator.length(expression.getSecondExpression());
		}

		protected abstract String secondExpressionMissingKey();
	}

    // Made static for performance reasons.
	/**
	 * The root helper that validates any {@link AbstractEncapsulatedExpression}.
	 *
	 * @see AbstractDoubleEncapsulatedExpressionHelper
	 * @see AbstractSingleEncapsulatedExpressionHelper
	 * @see AbstractTripleEncapsulatedExpressionHelper
	 */
	protected static interface AbstractEncapsulatedExpressionHelper<T extends AbstractEncapsulatedExpression> {

		/**
		 * Returns the arguments that can help to format the localized problem.
		 *
		 * @param expression The {@link AbstractEncapsulatedExpression} being validated
		 * @return The list of arguments used to complete the localized problem
		 */
		String[] arguments(T expression);

		/**
		 * Determines whether the given {@link AbstractEncapsulatedExpression} has the left parenthesis.
		 *
		 * @param expression The {@link AbstractEncapsulatedExpression} being validated
		 * @return <code>true</code> if the left parenthesis was parsed
		 */
		boolean hasLeftParenthesis(T expression);

		/**
		 * Determines whether the given {@link AbstractEncapsulatedExpression} has the right parenthesis.
		 *
		 * @param expression The {@link AbstractEncapsulatedExpression} being validated
		 * @return <code>true</code> if the right parenthesis was parsed
		 */
		boolean hasRightParenthesis(T expression);

		/**
		 * Returns the JPQL identifier of the given {@link AbstractEncapsulatedExpression}.
		 *
		 * @param expression The {@link AbstractEncapsulatedExpression} being validated
		 * @return The JPQL identifier of the given {@link AbstractEncapsulatedExpression}
		 */
		String identifier(T expression);

		/**
		 * Returns the message key for the problem describing that the left parenthesis is missing.
		 *
		 * @param expression The {@link AbstractEncapsulatedExpression} being validated
		 * @return The key used to retrieve the localized message
		 */
		String leftParenthesisMissingKey(T expression);

		/**
		 * Returns the message key for the problem describing that the right parenthesis is missing.
		 *
		 * @param expression The {@link AbstractEncapsulatedExpression} being validated
		 * @return The key used to retrieve the localized message
		 */
		String rightParenthesisMissingKey(T expression);
	}

    // Made static for performance reasons.
	/**
	 * The abstract implementation of {@link AbstractSingleEncapsulatedExpressionHelper} which
	 * implements some of the methods since the behavior is the same for all subclasses of
	 * {@link AbstractSingleEncapsulatedExpression}.
	 */
	protected static abstract class AbstractSingleEncapsulatedExpressionHelper<T extends AbstractSingleEncapsulatedExpression>
	        extends AbstractValidator implements AbstractEncapsulatedExpressionHelper<T> {

	    protected AbstractSingleEncapsulatedExpressionHelper(AbstractGrammarValidator validator) {
	        super(validator);
	    }

	    /**
		 * {@inheritDoc}
		 */
		public String[] arguments(T expression) {
			return ExpressionTools.EMPTY_STRING_ARRAY;
		}

		/**
		 * Returns the message key for the problem describing that the encapsulated expression is invalid.
		 *
		 * @param expression The {@link AbstractSingleEncapsulatedExpression} being validated
		 * @return The key used to retrieve the localized message
		 */
		protected abstract String encapsulatedExpressionInvalidKey(T expression);

		/**
		 * Returns the length of the encapsulated expression.
		 *
		 * @param expression {@link AbstractSingleEncapsulatedExpression} being validated
		 * @return The length of the encapsulated expression
		 */
		protected int encapsulatedExpressionLength(T expression) {
			return validator.length(expression.getExpression());
		}

		/**
		 * Returns the message key for the problem describing that the encapsulated expression is missing.
		 *
		 * @param expression The {@link AbstractSingleEncapsulatedExpression} being validated
		 * @return The key used to retrieve the localized message
		 */
		protected abstract String encapsulatedExpressionMissingKey(T expression);

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

		/**
		 * {@inheritDoc}
		 */
		public boolean hasRightParenthesis(T expression) {
			// If the encapsulated expression is missing, then no need to
			// add a problem for a missing ')' because the encapsulated
			// expression needs to be specified first
			return !expression.hasEncapsulatedExpression() ||
			        expression.hasRightParenthesis();
		}

		/**
		 * {@inheritDoc}
		 */
		public final String identifier(T expression) {
			return expression.getIdentifier();
		}

		/**
		 * Determines whether there is an encapsulated expression or not.
		 *
		 * @param expression The {@link AbstractSingleEncapsulatedExpression} being validated
		 * @return <code>true</code> if the given {@link AbstractSingleEncapsulatedExpression} has an
		 * encapsulated expression; <code>false</code> otherwise
		 */
		protected boolean isEncapsulatedExpressionMissing(T expression) {
			return !expression.hasExpression();
		}

		/**
		 * Determines whether the encapsulated expression is valid.
		 *
		 * @param expression The {@link AbstractSingleEncapsulatedExpression} being validated
		 * @return <code>true</code> if the encapsulated expression is valid; <code>false</code>
		 * otherwise
		 */
		protected boolean isEncapsulatedExpressionValid(T expression) {
			return validator.isValid(expression.getExpression(), expression.getEncapsulatedExpressionQueryBNFId());
		}

		/**
		 * Returns the length after the left parenthesis and before the encapsulated expression starts.
		 * <p>
		 * By default, there is no text after the left parenthesis and the encapsulated expression
		 * but there are exceptions, such as the functions (AVG, COUNT, MIN, MAX, SUM).
		 *
		 * @param expression The {@link AbstractSingleEncapsulatedExpression} being validated
		 * @return The length after the left parenthesis and before the encapsulated expression starts
		 */
		protected int lengthBeforeEncapsulatedExpression(T expression) {
			return 0;
		}
	}

    // Made static for performance reasons.
	protected abstract static class AbstractTripleEncapsulatedExpressionHelper<T extends AbstractTripleEncapsulatedExpression>
	        extends AbstractValidator implements AbstractEncapsulatedExpressionHelper<T> {

        protected AbstractTripleEncapsulatedExpressionHelper(AbstractGrammarValidator validator) {
            super(validator);
        }

        /**
		 * {@inheritDoc}
		 */
		public String[] arguments(T expression) {
			return ExpressionTools.EMPTY_STRING_ARRAY;
		}

		protected abstract String firstCommaMissingKey();

		protected abstract String firstExpressionInvalidKey();

		protected int firstExpressionLength(T expression) {
			return validator.length(expression.getFirstExpression());
		}

		protected abstract String firstExpressionMissingKey();

		protected boolean hasFirstExpression(T expression) {
			return expression.hasFirstExpression();
		}

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

		/**
		 * {@inheritDoc}
		 */
		public boolean hasRightParenthesis(T expression) {
			return !isRightParenthesisMissing(expression);
		}

		protected boolean hasSecondExpression(T expression) {
			return expression.hasSecondExpression();
		}

		protected boolean hasThirdExpression(T expression) {
			return expression.hasThirdExpression();
		}

		protected boolean isFirstExpressionValid(T expression) {
			return validator.isValid(expression.getFirstExpression(), expression.getParameterQueryBNFId(0));
		}

		/**
		 * Determines whether the right parenthesis is missing from the given expression.
		 *
		 * @param expression The {@link Expression} to verify for the existence of the right parenthesis
		 * by determining if the encapsulated information has been parsed or not
		 * @return <code>true</code> if the encapsulated information was parsed and the right parenthesis
		 * is missing; <code>false</code> in any other case
		 */
		protected boolean isRightParenthesisMissing(T expression) {

			if (!expression.hasLeftParenthesis() ||
			    !expression.hasFirstExpression() ||
			     expression.hasRightParenthesis()) {

				return false;
			}

			if (expression.hasFirstExpression()  &&
			   !expression.hasFirstComma()       &&
			   !expression.hasSecondExpression() &&
			   !expression.hasSecondComma()      &&
			   !expression.hasThirdExpression()) {

				return false;
			}

			if (expression.hasFirstComma()       &&
			   !expression.hasSecondExpression() &&
			   !expression.hasSecondComma()      &&
			   !expression.hasThirdExpression()) {

				return false;
			}

			if (expression.hasSecondExpression() &&
			    expression.hasSecondComma()      &&
			   !expression.hasThirdExpression()) {

				return false;
			}

			return true;
		}

		protected boolean isSecondExpressionValid(T expression) {
			return validator.isValid(expression.getSecondExpression(), expression.getParameterQueryBNFId(1));
		}

		protected boolean isThirdExpressionValid(T expression) {
			return validator.isValid(expression.getThirdExpression(), expression.getParameterQueryBNFId(2));
		}

		protected abstract String secondCommaMissingKey();

		protected abstract String secondExpressionInvalidKey();

		protected int secondExpressionLength(T expression) {
			return validator.length(expression.getSecondExpression());
		}

		protected abstract String secondExpressionMissingKey();

		protected abstract String thirdExpressionInvalidKey();

		protected int thirdExpressionLength(T expression) {
			return validator.length(expression.getThirdExpression());
		}

		protected abstract String thirdExpressionMissingKey();
	}

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

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

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

    // Made static final for performance reasons.
	/**
	 * This validator validates a {@link CollectionExpression} by making sure each item is separated
	 * by a comma.
	 */
	protected static final class CollectionSeparatedByCommaValidator extends AbstractCollectionValidator {

        protected CollectionSeparatedByCommaValidator(AbstractGrammarValidator validator) {
            super(validator);
        }

        /**
		 * {@inheritDoc}
		 */
		@Override
		boolean validateSeparator(CollectionExpression expression, int index) {
			return expression.hasComma(index);
		}
	}

    // Made static final for performance reasons.
	/**
	 * This validator validates a {@link CollectionExpression} by making sure each item is not
	 * separated by a comma.
	 */
	protected static final class CollectionSeparatedBySpaceValidator extends AbstractCollectionValidator {

	    protected CollectionSeparatedBySpaceValidator(AbstractGrammarValidator validator) {
            super(validator);
        }

		/**
		 * {@inheritDoc}
		 */
		@Override
		boolean validateSeparator(CollectionExpression expression, int index) {
			return !expression.hasComma(index);
		}
	}

    // Made static final for performance reasons.
	protected static final class ComparisonExpressionVisitor extends AbstractExpressionVisitor {

		/**
		 * The {@link ComparisonExpression} if it is the {@link Expression} that was visited.
		 */
		ComparisonExpression expression;

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

    // Made static final for performance reasons.
	protected static final class DateTimeVisitor extends AbstractExpressionVisitor {

		/**
		 * Determines whether the visited {@link Expression} is {@link DateTime} or not.
		 */
		public boolean dateTime;

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

    // Made static final for performance reasons.
	/**
	 * This visitor checks to see if the visited expression is {@link NullExpression}.
	 */
	protected static final class NullExpressionVisitor extends AbstractExpressionVisitor {

		/**
		 * The {@link NullExpression} if it is the {@link Expression} that was visited.
		 */
		protected NullExpression expression;

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

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