/*******************************************************************************
 * Copyright (c) 2006, 2013 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.tests.jpql.parser;

import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.JPAVersion;
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.AbstractFromClause;
import org.eclipse.persistence.jpa.jpql.parser.AbstractOrderByClause;
import org.eclipse.persistence.jpa.jpql.parser.AbstractPathExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSchemaName;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSelectClause;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSingleEncapsulatedExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractTripleEncapsulatedExpression;
import org.eclipse.persistence.jpa.jpql.parser.AdditionExpression;
import org.eclipse.persistence.jpa.jpql.parser.AllOrAnyExpression;
import org.eclipse.persistence.jpa.jpql.parser.AndExpression;
import org.eclipse.persistence.jpa.jpql.parser.ArithmeticFactor;
import org.eclipse.persistence.jpa.jpql.parser.AsOfClause;
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.CastExpression;
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.ComparisonExpression;
import org.eclipse.persistence.jpa.jpql.parser.CompoundExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConcatExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConnectByClause;
import org.eclipse.persistence.jpa.jpql.parser.ConstructorExpression;
import org.eclipse.persistence.jpa.jpql.parser.CountFunction;
import org.eclipse.persistence.jpa.jpql.parser.DatabaseType;
import org.eclipse.persistence.jpa.jpql.parser.DateTime;
import org.eclipse.persistence.jpa.jpql.parser.DefaultEclipseLinkJPQLGrammar;
import org.eclipse.persistence.jpa.jpql.parser.DefaultJPQLGrammar;
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.ExtractExpression;
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.HierarchicalQueryClause;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable;
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.JPQLExpression;
import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar;
import org.eclipse.persistence.jpa.jpql.parser.JPQLStatementBNF;
import org.eclipse.persistence.jpa.jpql.parser.Join;
import org.eclipse.persistence.jpa.jpql.parser.KeywordExpression;
import org.eclipse.persistence.jpa.jpql.parser.LengthExpression;
import org.eclipse.persistence.jpa.jpql.parser.LikeExpression;
import org.eclipse.persistence.jpa.jpql.parser.LocateExpression;
import org.eclipse.persistence.jpa.jpql.parser.LogicalExpression;
import org.eclipse.persistence.jpa.jpql.parser.LowerExpression;
import org.eclipse.persistence.jpa.jpql.parser.MaxFunction;
import org.eclipse.persistence.jpa.jpql.parser.MinFunction;
import org.eclipse.persistence.jpa.jpql.parser.ModExpression;
import org.eclipse.persistence.jpa.jpql.parser.MultiplicationExpression;
import org.eclipse.persistence.jpa.jpql.parser.NotExpression;
import org.eclipse.persistence.jpa.jpql.parser.NullComparisonExpression;
import org.eclipse.persistence.jpa.jpql.parser.NullExpression;
import org.eclipse.persistence.jpa.jpql.parser.NullIfExpression;
import org.eclipse.persistence.jpa.jpql.parser.NumericLiteral;
import org.eclipse.persistence.jpa.jpql.parser.ObjectExpression;
import org.eclipse.persistence.jpa.jpql.parser.OnClause;
import org.eclipse.persistence.jpa.jpql.parser.OrExpression;
import org.eclipse.persistence.jpa.jpql.parser.OrderByClause;
import org.eclipse.persistence.jpa.jpql.parser.OrderByItem;
import org.eclipse.persistence.jpa.jpql.parser.OrderByItem.NullOrdering;
import org.eclipse.persistence.jpa.jpql.parser.OrderByItem.Ordering;
import org.eclipse.persistence.jpa.jpql.parser.OrderSiblingsByClause;
import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.RegexpExpression;
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.StartWithClause;
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.TableExpression;
import org.eclipse.persistence.jpa.jpql.parser.TableVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.TreatExpression;
import org.eclipse.persistence.jpa.jpql.parser.TrimExpression;
import org.eclipse.persistence.jpa.jpql.parser.TrimExpression.Specification;
import org.eclipse.persistence.jpa.jpql.parser.TypeExpression;
import org.eclipse.persistence.jpa.jpql.parser.UnionClause;
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.WhenClause;
import org.eclipse.persistence.jpa.jpql.parser.WhereClause;
import org.eclipse.persistence.jpa.tests.jpql.JPQLBasicTest;
import static org.eclipse.persistence.jpa.jpql.parser.AbstractExpression.*;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.*;
import static org.eclipse.persistence.jpa.tests.jpql.parser.JPQLParserTester.*;
import static org.eclipse.persistence.jpa.tests.jpql.parser.JPQLQueryBuilder.*;
import static org.junit.Assert.*;

/**
 * This abstract class provides the functionality to test the parsed tree representation of a JPQL
 * query by traversing the tree and comparing each node.
 * <p>
 * Note: This class provides the {@link ExpressionTester} for all JPQL grammars (1.0, 2.0 and 2.1),
 * as well as for EclipseLink (all versions).
 *
 * @version 2.6
 * @since 2.3
 * @author Pascal Filion
 */
@SuppressWarnings({"nls", "unused" /* For the extra import statement, see bug 330740 */ })
public abstract class JPQLParserTest extends JPQLBasicTest {

	@JPQLGrammarTestHelper
	private JPQLGrammar jpqlGrammar;

	protected JPQLGrammar getGrammar() {
		return jpqlGrammar;
	}

	protected final boolean isEclipseLinkProvider() {
		return DefaultEclipseLinkJPQLGrammar.PROVIDER_NAME == jpqlGrammar.getProvider();
	}

	protected final boolean isGenericProvider() {
		return DefaultJPQLGrammar.PROVIDER_NAME == jpqlGrammar.getProvider();
	}

	protected final boolean isJPA1_0() {
		return jpqlGrammar.getJPAVersion() == JPAVersion.VERSION_1_0;
	}

	protected final boolean isJPA2_0() {
		return jpqlGrammar.getJPAVersion() == JPAVersion.VERSION_2_0;
	}

	protected final boolean isJPA2_1() {
		return jpqlGrammar.getJPAVersion() == JPAVersion.VERSION_2_1;
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 * <p>
	 * The parsing system will have the tolerance turned on.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 */
	protected void testInvalidQuery(String jpqlQuery, ExpressionTester expressionTester) {
		testQuery(jpqlQuery, expressionTester, JPQLStatementBNF.ID, JPQLQueryStringFormatter.DEFAULT, true);
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 * <p>
	 * The parsing system will have the tolerance turned on.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 * @param jpqlGrammar The {@link JPQLGrammar} used to determine how to parse the JPQL query
	 * @param formatter This formatter is used to personalized the formatting of the JPQL query
	 * before it is used to test the generated string
	 */
	protected void testInvalidQuery(String jpqlQuery,
	                                ExpressionTester expressionTester,
	                                JPQLGrammar jpqlGrammar) {

		testQuery(jpqlQuery, expressionTester, jpqlGrammar, JPQLStatementBNF.ID, JPQLQueryStringFormatter.DEFAULT, true);
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 * <p>
	 * The parsing system will have the tolerance turned on.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 * @param formatter This formatter is used to personalized the formatting of the JPQL query
	 * before it is used to test the generated string
	 */
	protected void testInvalidQuery(String jpqlQuery,
	                                ExpressionTester expressionTester,
	                                JPQLQueryStringFormatter formatter) {

		testQuery(jpqlQuery, expressionTester, JPQLStatementBNF.ID, formatter, true);
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 * <p>
	 * The parsing system will have the tolerance turned on.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 * @param queryBNFId The unique identifier of the {@link org.eclipse.persistence.jpa.jpql.parser.
	 * JPQLQueryBNF JPQLQueryBNF}
	 */
	protected void testInvalidQuery(String jpqlQuery,
	                                ExpressionTester expressionTester,
	                                String jpqlQueryBNFId) {

		testQuery(jpqlQuery, expressionTester, jpqlQueryBNFId, JPQLQueryStringFormatter.DEFAULT, true);
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 * <p>
	 * The parsing system will have the tolerance turned on.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 * @param queryBNFId The unique identifier of the {@link org.eclipse.persistence.jpa.jpql.parser.
	 * JPQLQueryBNF JPQLQueryBNF}
	 * @param formatter This formatter is used to personalized the formatting of the JPQL query
	 * before it is used to test the generated string
	 */
	protected void testInvalidQuery(String jpqlQuery,
	                                ExpressionTester expressionTester,
	                                String jpqlQueryBNFId,
	                                JPQLQueryStringFormatter formatter) {

		testQuery(jpqlQuery, expressionTester, jpqlQueryBNFId, formatter, true);
	}

	/**
	 * Tests parsing the given JPQL query by comparing the parsed tree ({@link JPQLExpression}) with
	 * the given tester, which is an equivalent representation of the parsed tree.
	 * <p>
	 * This method test both parsing modes: tolerant and non-tolerant. When the tolerant mode is
	 * turned on, it means it will parse valid/complete and invalid/incomplete JPQL queries. When the
	 * tolerant mode is turned off, it means the JPQL query has to be complete and has to be
	 * grammatically valid.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 */
	protected void testQuery(String jpqlQuery, ExpressionTester expressionTester) {
		testQuery(jpqlQuery, expressionTester, JPQLStatementBNF.ID, JPQLQueryStringFormatter.DEFAULT, true);
		testQuery(jpqlQuery, expressionTester, JPQLStatementBNF.ID, JPQLQueryStringFormatter.DEFAULT, false);
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 * <p>
	 * This method test both parsing modes: tolerant and non-tolerant. When the tolerant mode is
	 * turned on, it means it will parse valid/complete and invalid/incomplete JPQL queries. When the
	 * tolerant mode is turned off, it means the JPQL query has to be complete and has to be
	 * grammatically valid.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 * @param jpqlGrammar The {@link JPQLGrammar} used to determine how to parse the JPQL query
	 * @param formatter This formatter is used to personalized the formatting of the JPQL query
	 * before it is used to test the generated string
	 */
	protected void testQuery(String jpqlQuery,
	                         ExpressionTester expressionTester,
	                         JPQLGrammar jpqlGrammar) {

		testQuery(jpqlQuery, expressionTester, jpqlGrammar, JPQLStatementBNF.ID, JPQLQueryStringFormatter.DEFAULT, true);
		testQuery(jpqlQuery, expressionTester, jpqlGrammar, JPQLStatementBNF.ID, JPQLQueryStringFormatter.DEFAULT, false);
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 * <p>
	 * This method test both parsing modes: tolerant and non-tolerant. When the tolerant mode is
	 * turned on, it means it will parse valid/complete and invalid/incomplete JPQL queries. When the
	 * tolerant mode is turned off, it means the JPQL query has to be complete and has to be
	 * grammatically valid.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 * @param jpqlGrammar The {@link JPQLGrammar} used to determine how to parse the JPQL query
	 * @param formatter This formatter is used to personalized the formatting of the JPQL query
	 * before it is used to test the generated string
	 */
	protected void testQuery(String jpqlQuery,
	                         ExpressionTester expressionTester,
	                         JPQLGrammar jpqlGrammar,
	                         JPQLQueryStringFormatter formatter) {

		testQuery(jpqlQuery, expressionTester, jpqlGrammar, JPQLStatementBNF.ID, formatter, true);
		testQuery(jpqlQuery, expressionTester, jpqlGrammar, JPQLStatementBNF.ID, formatter, false);
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 * <p>
	 * This method test both parsing modes: tolerant and non-tolerant. When the tolerant mode is
	 * turned on, it means it will parse valid/complete and invalid/incomplete JPQL queries. When the
	 * tolerant mode is turned off, it means the JPQL query has to be complete and has to be
	 * grammatically valid.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 * @param queryBNFId The unique identifier of the {@link org.eclipse.persistence.jpa.jpql.parser.
	 * JPQLQueryBNF JPQLQueryBNF}
	 * @param formatter This formatter is used to personalized the formatting of the JPQL query
	 * before it is used to test the generated string
	 */
	protected void testQuery(String jpqlQuery,
	                         ExpressionTester expressionTester,
	                         JPQLGrammar jpqlGrammar,
	                         String jpqlQueryBNFId,
	                         JPQLQueryStringFormatter formatter) {

		testQuery(jpqlQuery, expressionTester, jpqlGrammar, jpqlQueryBNFId, formatter, true);
		testQuery(jpqlQuery, expressionTester, jpqlGrammar, jpqlQueryBNFId, formatter, false);
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 * @param jpqlGrammar The {@link JPQLGrammar} used to determine how to parse the JPQL query
	 * @param queryBNFId The unique identifier of the {@link org.eclipse.persistence.jpa.jpql.parser.
	 * JPQLQueryBNF JPQLQueryBNF}
	 * @param formatter This formatter is used to personalized the formatting of the JPQL query
	 * before it is used to test the generated string
	 * @param tolerant Determines if the parsing system should be tolerant, meaning if it should try
	 * to parse grammatically invalid or incomplete queries
	 */
	protected void testQuery(String jpqlQuery,
	                         ExpressionTester expressionTester,
	                         JPQLGrammar jpqlGrammar,
	                         String jpqlQueryBNFId,
	                         JPQLQueryStringFormatter formatter,
	                         boolean tolerant) {

		JPQLExpression jpqlExpression = buildQuery(jpqlQuery, jpqlGrammar, jpqlQueryBNFId, formatter, tolerant);

		if (expressionTester.getClass() != JPQLExpressionTester.class) {
			expressionTester = jpqlExpression(expressionTester);
		}

		expressionTester.test(jpqlExpression);
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 * <p>
	 * This method test both parsing modes: tolerant and non-tolerant. When the tolerant mode is
	 * turned on, it means it will parse valid/complete and invalid/incomplete JPQL queries. When the
	 * tolerant mode is turned off, it means the JPQL query has to be complete and has to be
	 * grammatically valid.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 * @param formatter This formatter is used to personalized the formatting of the JPQL query
	 * before it is used to test the generated string
	 */
	protected void testQuery(String jpqlQuery,
	                         ExpressionTester expressionTester,
	                         JPQLQueryStringFormatter formatter) {

		testQuery(jpqlQuery, expressionTester, JPQLStatementBNF.ID, formatter, true);
		testQuery(jpqlQuery, expressionTester, JPQLStatementBNF.ID, formatter, false);
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 * <p>
	 * This method test both parsing modes: tolerant and non-tolerant. When the tolerant mode is
	 * turned on, it means it will parse valid/complete and invalid/incomplete JPQL queries. When the
	 * tolerant mode is turned off, it means the JPQL query has to be complete and has to be
	 * grammatically valid.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 * @param queryBNFId The unique identifier of the {@link org.eclipse.persistence.jpa.jpql.parser.
	 * JPQLQueryBNF JPQLQueryBNF}
	 */
	protected void testQuery(String jpqlQuery,
	                         ExpressionTester expressionTester,
	                         String jpqlQueryBNFId) {

		testQuery(jpqlQuery, expressionTester, jpqlQueryBNFId, JPQLQueryStringFormatter.DEFAULT, true);
		testQuery(jpqlQuery, expressionTester, jpqlQueryBNFId, JPQLQueryStringFormatter.DEFAULT, false);
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 * <p>
	 * This method test both parsing modes: tolerant and non-tolerant. When the tolerant mode is
	 * turned on, it means it will parse valid/complete and invalid/incomplete JPQL queries. When the
	 * tolerant mode is turned off, it means the JPQL query has to be complete and has to be
	 * grammatically valid.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 * @param queryBNFId The unique identifier of the {@link org.eclipse.persistence.jpa.jpql.parser.
	 * JPQLQueryBNF JPQLQueryBNF}
	 * @param formatter This formatter is used to personalized the formatting of the JPQL query
	 * before it is used to test the generated string
	 */
	protected void testQuery(String jpqlQuery,
	                         ExpressionTester expressionTester,
	                         String jpqlQueryBNFId,
	                         JPQLQueryStringFormatter formatter) {

		testQuery(jpqlQuery, expressionTester, jpqlQueryBNFId, formatter, true);
		testQuery(jpqlQuery, expressionTester, jpqlQueryBNFId, formatter, false);
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 * @param queryBNFId The unique identifier of the {@link org.eclipse.persistence.jpa.jpql.parser.
	 * JPQLQueryBNF JPQLQueryBNF}
	 * @param formatter This formatter is used to personalized the formatting of the JPQL query
	 * before it is used to test the generated string
	 * @param tolerant Determines if the parsing system should be tolerant, meaning if it should try
	 * to parse grammatically invalid or incomplete queries
	 */
	protected void testQuery(String jpqlQuery,
	                         ExpressionTester expressionTester,
	                         String jpqlQueryBNFId,
	                         JPQLQueryStringFormatter formatter,
	                         boolean tolerant) {

		testQuery(jpqlQuery, expressionTester, jpqlGrammar, jpqlQueryBNFId, formatter, tolerant);
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 * <p>
	 * The parsing system will have the tolerance turned off.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 */
	protected void testValidQuery(String jpqlQuery, ExpressionTester expressionTester) {
		testQuery(jpqlQuery, expressionTester, JPQLStatementBNF.ID, JPQLQueryStringFormatter.DEFAULT, false);
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 * <p>
	 * The parsing system will have the tolerance turned off.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 * @param formatter This formatter is used to personalized the formatting of the JPQL query
	 * before it is used to test the generated string
	 */
	protected void testValidQuery(String jpqlQuery,
	                              ExpressionTester expressionTester,
	                              JPQLQueryStringFormatter formatter) {

		testQuery(jpqlQuery, expressionTester, JPQLStatementBNF.ID, formatter, false);
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 * <p>
	 * The parsing system will have the tolerance turned off.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 * @param queryBNFId The unique identifier of the {@link org.eclipse.persistence.jpa.jpql.parser.
	 * JPQLQueryBNF JPQLQueryBNF}
	 */
	protected void testValidQuery(String jpqlQuery,
	                              ExpressionTester expressionTester,
	                              String jpqlQueryBNFId) {

		testQuery(jpqlQuery, expressionTester, jpqlQueryBNFId, JPQLQueryStringFormatter.DEFAULT, false);
	}

	/**
	 * Tests the parsing of the given JPQL query by comparing the parsed tree ({@link JPQLExpression})
	 * with the given tester, which is an equivalent representation of the parsed tree.
	 * <p>
	 * The parsing system will have the tolerance turned off.
	 *
	 * @param jpqlQuery The JPQL query to parse and to test the parsed tree representation
	 * @param expressionTester The tester used to verify the parsed tree is correctly representing the
	 * JPQL query
	 * @param queryBNFId The unique identifier of the {@link org.eclipse.persistence.jpa.jpql.parser.
	 * JPQLQueryBNF JPQLQueryBNF}
	 * @param formatter This formatter is used to personalized the formatting of the JPQL query
	 * before it is used to test the generated string
	 */
	protected void testValidQuery(String jpqlQuery,
	                              ExpressionTester expressionTester,
	                              String jpqlQueryBNFId,
	                              JPQLQueryStringFormatter formatter) {

		testQuery(jpqlQuery, expressionTester, jpqlQueryBNFId, formatter, false);
	}

	public static final class AbsExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		protected AbsExpressionTester(ExpressionTester expression) {
			super(expression);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return AbsExpression.class;
		}

		@Override
		protected String identifier() {
			return ABS;
		}
	}

	public static abstract class AbstractConditionalClauseTester extends AbstractExpressionTester {

		private ExpressionTester conditionalExpression;
		public boolean hasSpaceAfterIdentifier;

		protected AbstractConditionalClauseTester(ExpressionTester conditionalExpression) {
			super();
			this.conditionalExpression   = conditionalExpression;
			this.hasSpaceAfterIdentifier = !conditionalExpression.isNull();
		}

		protected abstract Class<? extends AbstractConditionalClause> expressionType();

		protected abstract String identifier();

		public void test(Expression expression) {

			assertInstance(expression, expressionType());

			AbstractConditionalClause conditionalClause = (AbstractConditionalClause) expression;
			assertEquals(toString(),                      conditionalClause.toParsedText());
			assertEquals(!conditionalExpression.isNull(), conditionalClause.hasConditionalExpression());
			assertEquals(hasSpaceAfterIdentifier,         conditionalClause.hasSpaceAfterIdentifier());

			conditionalExpression.test(conditionalClause.getConditionalExpression());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(identifier());
			if (hasSpaceAfterIdentifier) {
				sb.append(SPACE);
			}
			sb.append(conditionalExpression);
			return sb.toString();
		}
	}

	public static abstract class AbstractDoubleEncapsulatedExpressionTester extends AbstractEncapsulatedExpressionTester {

		private ExpressionTester firstExpression;
		public boolean hasComma;
		public boolean hasSpaceAfterComma;
		private ExpressionTester secondExpression;

		protected AbstractDoubleEncapsulatedExpressionTester(ExpressionTester firstExpression,
		                                                     ExpressionTester secondExpression) {

			super();
			this.hasComma           = true;
			this.hasSpaceAfterComma = true;
			this.firstExpression    = firstExpression;
			this.secondExpression   = secondExpression;
		}

		@Override
		protected abstract Class<? extends AbstractDoubleEncapsulatedExpression> expressionType();

		@Override
		protected boolean hasEncapsulatedExpression() {
			return !firstExpression.isNull() || hasComma || !secondExpression.isNull();
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);

			AbstractDoubleEncapsulatedExpression abstractDoubleEncapsulatedExpression = (AbstractDoubleEncapsulatedExpression) expression;
			assertEquals(!firstExpression.isNull(),  abstractDoubleEncapsulatedExpression.hasFirstExpression());
			assertEquals(!secondExpression.isNull(), abstractDoubleEncapsulatedExpression.hasSecondExpression());
			assertEquals(hasComma,                   abstractDoubleEncapsulatedExpression.hasComma());
			assertEquals(hasSpaceAfterComma,         abstractDoubleEncapsulatedExpression.hasSpaceAfterComma());

			firstExpression .test(abstractDoubleEncapsulatedExpression.getFirstExpression());
			secondExpression.test(abstractDoubleEncapsulatedExpression.getSecondExpression());
		}

		@Override
		protected void toStringEncapsulatedExpression(StringBuilder sb) {
			sb.append(firstExpression);
			if (hasComma) {
				sb.append(COMMA);
			}
			if (hasSpaceAfterComma) {
				sb.append(SPACE);
			}
			sb.append(secondExpression);
		}
	}

	public static abstract class AbstractEncapsulatedExpressionTester extends AbstractExpressionTester {

		public boolean hasLeftParenthesis;
		public boolean hasRightParenthesis;

		protected AbstractEncapsulatedExpressionTester() {
			super();
			this.hasLeftParenthesis  = true;
			this.hasRightParenthesis = true;
		}

		protected abstract Class<? extends AbstractEncapsulatedExpression> expressionType();

		protected abstract boolean hasEncapsulatedExpression();

		protected abstract String identifier();

		public void test(Expression expression) {
			assertInstance(expression, expressionType());

			AbstractEncapsulatedExpression abstractEncapsulatedExpression = (AbstractEncapsulatedExpression) expression;
			assertEquals(toString(),                  abstractEncapsulatedExpression.toParsedText());
			assertEquals(hasLeftParenthesis,          abstractEncapsulatedExpression.hasLeftParenthesis());
			assertEquals(hasRightParenthesis,         abstractEncapsulatedExpression.hasRightParenthesis());
			assertEquals(hasEncapsulatedExpression(), abstractEncapsulatedExpression.hasEncapsulatedExpression());
		}

		@Override
		public final String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(identifier());
			if (hasLeftParenthesis) {
				sb.append(LEFT_PARENTHESIS);
			}
			else if (hasEncapsulatedExpression()) {
				sb.append(SPACE);
			}
			toStringEncapsulatedExpression(sb);
			if (hasRightParenthesis) {
				sb.append(RIGHT_PARENTHESIS);
			}
			return sb.toString();
		}

		protected abstract void toStringEncapsulatedExpression(StringBuilder sb);
	}

	/**
	 * The abstract definition of an {@link ExpressionTester}.
	 */
	public static abstract class AbstractExpressionTester implements ExpressionTester {

		public final AdditionExpressionTester add(ExpressionTester expression) {
			return JPQLParserTester.add(this, expression);
		}

		public final AndExpressionTester and(ExpressionTester expression) {
			return JPQLParserTester.and(this, expression);
		}

		protected final void assertInstance(Expression expression,
		                                    Class<? extends Expression> expressionType) {

			Class<? extends Expression> expressionClass = expression.getClass();

			if (expressionClass != expressionType &&
			   !expressionType.isAssignableFrom(expressionClass)) {

				StringBuilder sb = new StringBuilder();
				sb.append("Expecting ");
				sb.append(expressionType.getSimpleName());
				sb.append(" but was ");
				sb.append(expressionClass.getSimpleName());
				sb.append(" for [");
				sb.append(expression.toParsedText());
				sb.append("]");

				fail(sb.toString());
			}
		}

		public final BetweenExpressionTester between(ExpressionTester lowerBoundExpression,
		                                             ExpressionTester upperBoundExpression) {

			return JPQLParserTester.between(this, lowerBoundExpression, upperBoundExpression);
		}

		public final ComparisonExpressionTester different(ExpressionTester expression) {
			return JPQLParserTester.different(this, expression);
		}

		public final DivisionExpressionTester divide(ExpressionTester expression) {
			return JPQLParserTester.division(this, expression);
		}

		public final ComparisonExpressionTester equal(ExpressionTester expression) {
			return JPQLParserTester.equal(this, expression);
		}

		public final ComparisonExpressionTester greaterThan(ExpressionTester expression) {
			return JPQLParserTester.greaterThan(this, expression);
		}

		public final ComparisonExpressionTester greaterThanOrEqual(ExpressionTester expression) {
			return JPQLParserTester.greaterThanOrEqual(this, expression);
		}

		public final InExpressionTester in(ExpressionTester... inItems) {
			if (inItems.length == 1) {
				return JPQLParserTester.in(this, inItems[0]);
			}
			return JPQLParserTester.in(this, inItems);
		}

		public final InExpressionTester in(String inputParameter) {
			return JPQLParserTester.in(this, inputParameter);
		}

		public final EmptyCollectionComparisonExpressionTester isEmpty() {
			return JPQLParserTester.isEmpty(this);
		}

		public final EmptyCollectionComparisonExpressionTester isNotEmpty() {
			return JPQLParserTester.isNotEmpty(this);
		}

		public boolean isNull() {
			return false;
		}

		public final LikeExpressionTester like(ExpressionTester patternValue) {
			return JPQLParserTester.like(this, patternValue);
		}

		public final LikeExpressionTester like(ExpressionTester patternValue,
		                                       ExpressionTester escapeCharacter) {

			return JPQLParserTester.like(this, patternValue, escapeCharacter);
		}

		public final ComparisonExpressionTester lowerThan(ExpressionTester expression) {
			return JPQLParserTester.lowerThan(this, expression);
		}

		public final ComparisonExpressionTester lowerThanOrEqual(ExpressionTester expression) {
			return JPQLParserTester.lowerThanOrEqual(this, expression);
		}

		public final CollectionMemberExpressionTester member(ExpressionTester collectionPath) {
			return JPQLParserTester.member(this, collectionPath);
		}

		public final CollectionMemberExpressionTester memberOf(ExpressionTester collectionPath) {
			return JPQLParserTester.memberOf(this, collectionPath);
		}

		public final MultiplicationExpressionTester multiply(ExpressionTester expression) {
			return JPQLParserTester.multiplication(this, expression);
		}

		public final BetweenExpressionTester notBetween(ExpressionTester lowerBoundExpression,
		                                                ExpressionTester upperBoundExpression) {

			return JPQLParserTester.notBetween(this, lowerBoundExpression, upperBoundExpression);
		}

		public final ComparisonExpressionTester notEqual(ExpressionTester expression) {
			return JPQLParserTester.notEqual(this, expression);
		}

		public final InExpressionTester notIn(ExpressionTester... inItems) {
			if (inItems.length == 1) {
				return JPQLParserTester.notIn(this, inItems[0]);
			}
			return JPQLParserTester.notIn(this, inItems);
		}

		public final InExpressionTester notIn(String inputParameter) {
			return JPQLParserTester.notIn(this, inputParameter);
		}

		public final LikeExpressionTester notLike(ExpressionTester expression) {
			return JPQLParserTester.notLike(this, expression);
		}

		public final LikeExpressionTester notLike(ExpressionTester expression,
		                                          ExpressionTester escapeCharacter) {

			return JPQLParserTester.notLike(this, expression, escapeCharacter);
		}

		public final ExpressionTester notMember(ExpressionTester collectionPath) {
			return JPQLParserTester.notMember(this, collectionPath);
		}

		public final ExpressionTester notMemberOf(ExpressionTester collectionPath) {
			return JPQLParserTester.notMemberOf(this, collectionPath);
		}

		public final OrExpressionTester or(ExpressionTester expression) {
			return JPQLParserTester.or(this, expression);
		}

		public final RegexpExpressionTester regexp(StringLiteralTester patternValue) {
			return JPQLParserTester.regexp(this, patternValue);
		}

		public final SubtractionExpressionTester subtract(ExpressionTester expression) {
			return JPQLParserTester.subtract(this, expression);
		}
	}

	public static abstract class AbstractFromClauseTester extends AbstractExpressionTester {

		private ExpressionTester asOfClause;
		private ExpressionTester declaration;
		private boolean hasSpaceAfterDeclaration;
		public boolean hasSpaceAfterFrom;
		private boolean hasSpaceAfterHierarchicalQueryClause;
		private ExpressionTester hierarchicalQueryClause;

		protected AbstractFromClauseTester(ExpressionTester declaration,
		                                   ExpressionTester hierarchicalQueryClause,
		                                   ExpressionTester asOfClause) {
			super();
			this.hasSpaceAfterFrom                    = true;
			this.asOfClause                           = asOfClause;
			this.declaration                          = declaration;
			this.hierarchicalQueryClause              = hierarchicalQueryClause;
			this.hasSpaceAfterDeclaration             = !declaration.isNull() && (!hierarchicalQueryClause.isNull() || !asOfClause.isNull());
			this.hasSpaceAfterHierarchicalQueryClause = !hierarchicalQueryClause.isNull() && !asOfClause.isNull();
		}

		public void test(Expression expression) {
			assertInstance(expression, AbstractFromClause.class);

			AbstractFromClause fromClause = (AbstractFromClause) expression;
			assertEquals(toString(),                           fromClause.toParsedText());
			assertEquals(hasSpaceAfterFrom,                    fromClause.hasSpaceAfterFrom());
			assertEquals(!declaration.isNull(),                fromClause.hasDeclaration());
			assertEquals(hasSpaceAfterDeclaration,             fromClause.hasSpaceAfterDeclaration());
			assertEquals(!hierarchicalQueryClause.isNull(),    fromClause.hasHierarchicalQueryClause());
			assertEquals(hasSpaceAfterHierarchicalQueryClause, fromClause.hasSpaceAfterHierarchicalQueryClause());
			assertEquals(!asOfClause.isNull(),                 fromClause.hasAsOfClause());

			declaration.test(fromClause.getDeclaration());
			hierarchicalQueryClause.test(fromClause.getHierarchicalQueryClause());
			asOfClause.test(fromClause.getAsOfClause());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(FROM);
			if (hasSpaceAfterFrom) {
				sb.append(SPACE);
			}
			sb.append(declaration);
			if (hasSpaceAfterDeclaration) {
				sb.append(SPACE);
			}
			sb.append(hierarchicalQueryClause);
			if (hasSpaceAfterHierarchicalQueryClause) {
				sb.append(SPACE);
			}
			sb.append(asOfClause);
			return sb.toString();
		}
	}

	public static abstract class AbstractOrderByClauseTester extends AbstractExpressionTester {

		public boolean hasSpaceAfterIdentifier;
		private ExpressionTester orderByItems;

		protected AbstractOrderByClauseTester(ExpressionTester orderByItems) {
			super();
			this.orderByItems            = orderByItems;
			this.hasSpaceAfterIdentifier = !orderByItems.isNull();
		}

		protected abstract String identifier();

		public void test(Expression expression) {
			assertInstance(expression, AbstractOrderByClause.class);

			AbstractOrderByClause orderByClause = (AbstractOrderByClause) expression;
			assertEquals(toString(),              orderByClause.toParsedText());
			assertEquals(!orderByItems.isNull(),  orderByClause.hasOrderByItems());
			assertEquals(hasSpaceAfterIdentifier, orderByClause.hasSpaceAfterIdentifier());

			orderByItems.test(orderByClause.getOrderByItems());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(identifier());
			if (hasSpaceAfterIdentifier) {
				sb.append(SPACE);
			}
			sb.append(orderByItems);
			return sb.toString();
		}
	}

	public static abstract class AbstractPathExpressionTester extends AbstractExpressionTester {

		private boolean endsWithDot;
		private ExpressionTester identificationVariable;
		private boolean startsWithDot;
		private String value;

		protected AbstractPathExpressionTester(ExpressionTester identificationVariable, String value) {

			super();

			this.value                  = value;
			this.identificationVariable = identificationVariable;
			this.startsWithDot          = (identificationVariable.toString().length() == 0) && (value.indexOf(DOT) > -1);

			if (value.length() > 1) {
				endsWithDot = value.charAt(value.length() - 1) == DOT;
			}
		}

		public void test(Expression expression) {

			assertInstance(expression, AbstractPathExpression.class);

			AbstractPathExpression abstractPathExpression = (AbstractPathExpression) expression;
			assertEquals(toString(),                       abstractPathExpression.toParsedText());
			assertEquals(!identificationVariable.isNull(), abstractPathExpression.hasIdentificationVariable());
			assertEquals(endsWithDot,                      abstractPathExpression.endsWithDot());
			assertEquals(startsWithDot,                    abstractPathExpression.startsWithDot());

			identificationVariable.test(abstractPathExpression.getIdentificationVariable());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(identificationVariable);
			if (startsWithDot || (sb.length() > 0)) {
				sb.append(DOT);
			}
			sb.append(value);
			return sb.toString();
		}
	}

	public static final class AbstractSchemaNameTester extends AbstractExpressionTester {

		private String abstractSchemaName;

		protected AbstractSchemaNameTester(String abstractSchemaName) {
			super();
			this.abstractSchemaName = abstractSchemaName;
		}

		public void test(Expression expression) {
			assertInstance(expression, AbstractSchemaName.class);

			AbstractSchemaName abstractSchemaName = (AbstractSchemaName) expression;
			assertEquals(toString(), abstractSchemaName.toParsedText());
		}

		@Override
		public String toString() {
			return abstractSchemaName;
		}
	}

	public static abstract class AbstractSelectClauseTester extends AbstractExpressionTester {

		private boolean hasDistinct;
		public boolean hasSpaceAfterDistinct;
		public boolean hasSpaceAfterSelect;
		private ExpressionTester selectExpression;

		protected AbstractSelectClauseTester(ExpressionTester selectExpression, boolean hasDistinct) {
			super();
			this.hasDistinct           = hasDistinct;
			this.hasSpaceAfterDistinct = hasDistinct;
			this.hasSpaceAfterSelect   = hasDistinct || !selectExpression.isNull();
			this.selectExpression      = selectExpression;
		}

		public void test(Expression expression) {

			assertInstance(expression, AbstractSelectClause.class);
			AbstractSelectClause selectClause = (AbstractSelectClause) expression;

			assertEquals(toString(),                 selectClause.toParsedText());
			assertEquals(hasSpaceAfterSelect,        selectClause.hasSpaceAfterSelect());
			assertEquals(hasDistinct,                selectClause.hasDistinct());
			assertEquals(hasSpaceAfterDistinct,      selectClause.hasSpaceAfterDistinct());
			assertEquals(!selectExpression.isNull(), selectClause.hasSelectExpression());

			selectExpression.test(selectClause.getSelectExpression());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(SELECT);
			if (hasSpaceAfterSelect) {
				sb.append(SPACE);
			}
			if (hasDistinct) {
				sb.append(DISTINCT);
			}
			if (hasSpaceAfterDistinct) {
				sb.append(SPACE);
			}
			sb.append(selectExpression);
			return sb.toString();
		}
	}

	public static abstract class AbstractSelectStatementTester extends AbstractExpressionTester {

		private ExpressionTester fromClause;
		private ExpressionTester groupByClause;
		public boolean hasSpaceAfterFrom;
		public boolean hasSpaceAfterGroupBy;
		public boolean hasSpaceAfterSelect;
		public boolean hasSpaceAfterWhere;
		private ExpressionTester havingClause;
		private ExpressionTester selectClause;
		private ExpressionTester whereClause;

		protected AbstractSelectStatementTester(ExpressionTester selectClause,
		                                        ExpressionTester fromClause,
		                                        ExpressionTester whereClause,
		                                        ExpressionTester groupByClause,
		                                        ExpressionTester havingClause) {

			super();

			this.selectClause            = selectClause;
			this.fromClause              = fromClause;
			this.whereClause             = whereClause;
			this.groupByClause           = groupByClause;
			this.havingClause            = havingClause;

			hasSpaceAfterSelect  = !fromClause.isNull();
			hasSpaceAfterFrom    = !fromClause.isNull() && (!whereClause.isNull() || !groupByClause.isNull() || !havingClause.isNull());
			hasSpaceAfterWhere   = !whereClause.isNull() && (!groupByClause.isNull() || !havingClause.isNull());
			hasSpaceAfterGroupBy = !groupByClause.isNull() && !havingClause.isNull();
		}

		protected abstract Class<? extends AbstractSelectStatement> expressionType();

		public void test(Expression expression) {
			assertInstance(expression, expressionType());

			AbstractSelectStatement selectStatement = (AbstractSelectStatement) expression;
			assertEquals(toString(),                        selectStatement.toParsedText());
			assertEquals(!selectClause .isNull(), selectStatement.hasSelectClause());
			assertEquals(!fromClause   .isNull(), selectStatement.hasFromClause());
			assertEquals(!whereClause  .isNull(), selectStatement.hasWhereClause());
			assertEquals(!groupByClause.isNull(), selectStatement.hasGroupByClause());
			assertEquals(!havingClause .isNull(), selectStatement.hasHavingClause());

			selectClause .test(selectStatement.getSelectClause());
			fromClause   .test(selectStatement.getFromClause());
			whereClause  .test(selectStatement.getWhereClause());
			groupByClause.test(selectStatement.getGroupByClause());
			havingClause .test(selectStatement.getHavingClause());
		}

		@Override
		public String toString() {

			StringBuilder sb = new StringBuilder();

			// SELECT
			sb.append(selectClause);

			if (hasSpaceAfterSelect && (sb.length() > 0) && (sb.charAt(sb.length() - 1) != ' ')) {
				sb.append(SPACE);
			}

			// FROM
			sb.append(fromClause);

			if (hasSpaceAfterFrom && sb.charAt(sb.length() - 1) != ' ') {
				sb.append(SPACE);
			}

			// WHERE
			sb.append(whereClause);

			if (hasSpaceAfterWhere && sb.charAt(sb.length() - 1) != ' ') {
				sb.append(SPACE);
			}

			// GROUP BY
			sb.append(groupByClause);

			if (hasSpaceAfterGroupBy && sb.charAt(sb.length() - 1) != ' ') {
				sb.append(SPACE);
			}

			// HAVING
			sb.append(havingClause);

			return sb.toString();
		}
	}

	public static abstract class AbstractSingleEncapsulatedExpressionTester extends AbstractEncapsulatedExpressionTester {

		private ExpressionTester expression;

		protected AbstractSingleEncapsulatedExpressionTester(ExpressionTester expression) {
			super();
			this.expression = expression;
		}

		@Override
		protected abstract Class<? extends AbstractSingleEncapsulatedExpression> expressionType();

		@Override
		protected boolean hasEncapsulatedExpression() {
			return !expression.isNull();
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);

			AbstractSingleEncapsulatedExpression encapsulatedExpression = (AbstractSingleEncapsulatedExpression) expression;
			this.expression.test(encapsulatedExpression.getExpression());
		}

		@Override
		protected void toStringEncapsulatedExpression(StringBuilder sb) {
			sb.append(expression);
		}
	}

	public static abstract class AbstractTripleEncapsulatedExpressionTester extends AbstractEncapsulatedExpressionTester {

		private ExpressionTester firstExpression;
		public boolean hasFirstComma;
		public boolean hasSecondComma;
		public boolean hasSpaceAfterFirstComma;
		public boolean hasSpaceAfterSecondComma;
		private ExpressionTester secondExpression;
		private ExpressionTester thirdExpression;

		protected AbstractTripleEncapsulatedExpressionTester(ExpressionTester firstExpression,
		                                                     ExpressionTester secondExpression,
		                                                     ExpressionTester thirdExpression) {

			super();

			this.firstExpression  = firstExpression;
			this.secondExpression = secondExpression;
			this.thirdExpression  = thirdExpression;

			hasFirstComma  = !secondExpression.isNull();
			hasSecondComma = !thirdExpression.isNull();

			hasSpaceAfterFirstComma  = hasFirstComma;
			hasSpaceAfterSecondComma = hasSecondComma;
		}

		@Override
		protected abstract Class<? extends AbstractTripleEncapsulatedExpression> expressionType();

		@Override
		protected boolean hasEncapsulatedExpression() {
			return !firstExpression.isNull() || hasFirstComma || !secondExpression.isNull() || hasSecondComma || !thirdExpression.isNull();
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);
			assertInstance(expression, AbstractTripleEncapsulatedExpression.class);

			AbstractTripleEncapsulatedExpression tripleExpression = (AbstractTripleEncapsulatedExpression) expression;
			assertEquals(toString(), tripleExpression.toParsedText());

			assertEquals(hasFirstComma,  tripleExpression.hasFirstComma());
			assertEquals(hasSecondComma, tripleExpression.hasSecondComma());

			assertEquals(hasSpaceAfterFirstComma,  tripleExpression.hasSpaceAfterFirstComma());
			assertEquals(hasSpaceAfterSecondComma, tripleExpression.hasSpaceAfterSecondComma());

			assertEquals(!firstExpression.isNull(),  tripleExpression.hasFirstExpression());
			assertEquals(!secondExpression.isNull(), tripleExpression.hasSecondExpression());
			assertEquals(!thirdExpression.isNull(),  tripleExpression.hasThirdExpression());

			firstExpression.test(tripleExpression.getFirstExpression());
			secondExpression.test(tripleExpression.getSecondExpression());
			thirdExpression.test(tripleExpression.getThirdExpression());
		}

		@Override
		protected void toStringEncapsulatedExpression(StringBuilder sb) {
			sb.append(firstExpression);
			if (hasFirstComma) {
				sb.append(COMMA);
			}
			if (hasSpaceAfterFirstComma) {
				sb.append(SPACE);
			}
			sb.append(secondExpression);
			if (hasSecondComma) {
				sb.append(COMMA);
			}
			if (hasSpaceAfterSecondComma) {
				sb.append(SPACE);
			}
			sb.append(thirdExpression);
		}
	}

	public static final class AdditionExpressionTester extends CompoundExpressionTester {

		protected AdditionExpressionTester(ExpressionTester leftExpression,
		                                   ExpressionTester rightExpression) {

			super(leftExpression, rightExpression);
		}

		@Override
		protected Class<? extends CompoundExpression> expressionType() {
			return AdditionExpression.class;
		}

		@Override
		protected String identifier() {
			return PLUS;
		}
	}

	public static abstract class AggregateFunctionTester extends AbstractSingleEncapsulatedExpressionTester {

		public boolean hasDistinct;
		public boolean hasSpaceAfterDistinct;

		protected AggregateFunctionTester(ExpressionTester expression, boolean hasDistinct) {
			super(expression);
			this.hasDistinct = hasDistinct;
			this.hasSpaceAfterDistinct = hasDistinct;
		}

		@Override
		protected void toStringEncapsulatedExpression(StringBuilder sb) {

			if (hasDistinct) {
				sb.append(DISTINCT);
			}

			if (hasSpaceAfterDistinct) {
				sb.append(SPACE);
			}

			super.toStringEncapsulatedExpression(sb);
		}
	}

	public static final class AllOrAnyExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		private String identifier;

		protected AllOrAnyExpressionTester(String identifier, ExpressionTester expression) {
			super(expression);
			this.identifier = identifier;
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return AllOrAnyExpression.class;
		}

		@Override
		protected String identifier() {
			return identifier;
		}
	}

	public static final class AndExpressionTester extends LogicalExpressionTester {

		protected AndExpressionTester(ExpressionTester leftExpression,
		                              ExpressionTester rightExpression) {

			super(leftExpression, rightExpression);
		}

		@Override
		protected Class<? extends CompoundExpression> expressionType() {
			return AndExpression.class;
		}

		@Override
		protected String identifier() {
			return AND;
		}
	}

	public static final class ArithmeticFactorTester extends AbstractExpressionTester {

		private ExpressionTester expression;
		private String sign;

		protected ArithmeticFactorTester(String sign, ExpressionTester expression) {
			super();
			this.sign = sign;
			this.expression = expression;
		}

		public void test(Expression expression) {
			assertInstance(expression, ArithmeticFactor.class);

			ArithmeticFactor factor = (ArithmeticFactor) expression;
			assertEquals(toString(),    factor.toParsedText());
			assertEquals(sign == MINUS, factor.isNegative());
			assertEquals(sign == PLUS,  factor.isPositive());

			this.expression.test(factor.getExpression());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(sign);
			sb.append(expression);
			return sb.toString();
		}
	}

	protected final static class AsOfClauseTester extends AbstractExpressionTester {

		private String category;
		private ExpressionTester expression;
		public boolean hasSpaceAfterCategory;
		public boolean hasSpaceAfterIdentifier;

		protected AsOfClauseTester(String category, ExpressionTester expression) {
			super();
			this.category                = category;
			this.expression              = expression;
			this.hasSpaceAfterIdentifier = (category != null) || !expression.isNull();
			this.hasSpaceAfterCategory   = (category != null) && !expression.isNull();
		}

		public void test(Expression expression) {
			assertInstance(expression, AsOfClause.class);

			AsOfClause asOfClause = (AsOfClause) expression;
			assertEquals(toString(),                asOfClause.toParsedText());
			assertEquals(!this.expression.isNull(), asOfClause.hasExpression());
			assertEquals(hasSpaceAfterIdentifier,   asOfClause.hasSpaceAfterIdentifier());
			assertEquals(hasSpaceAfterCategory,     asOfClause.hasSpaceAfterCategory());

			this.expression.test(asOfClause.getExpression());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(AS_OF);
			if (hasSpaceAfterIdentifier) {
				sb.append(SPACE);
			}
			if (category != null) {
				sb.append(category);
			}
			if (hasSpaceAfterCategory) {
				sb.append(SPACE);
			}
			sb.append(expression);
			return sb.toString();
		}
	}

	public static final class AvgFunctionTester extends AggregateFunctionTester {

		protected AvgFunctionTester(ExpressionTester expression, boolean hasDistinct) {
			super(expression, hasDistinct);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return AvgFunction.class;
		}

		@Override
		protected String identifier() {
			return AVG;
		}
	}

	public static final class BadExpressionTester extends AbstractExpressionTester {

		private ExpressionTester expression;

		protected BadExpressionTester(ExpressionTester expression) {
			super();
			this.expression = expression;
		}

		public void test(Expression expression) {
			assertInstance(expression, BadExpression.class);

			BadExpression badExpression = (BadExpression) expression;
			assertEquals(toString(), badExpression.toParsedText());

			this.expression.test(badExpression.getExpression());
		}

		@Override
		public String toString() {
			return expression.toString();
		}
	}

	public static final class BetweenExpressionTester extends AbstractExpressionTester {

		private ExpressionTester expression;
		public boolean hasAnd;
		public boolean hasBetween;
		private boolean hasNot;
		public boolean hasSpaceAfterAnd;
		public boolean hasSpaceAfterBetween;
		public boolean hasSpaceAfterLowerBound;
		private ExpressionTester lowerBoundExpression;
		private ExpressionTester upperBoundExpression;

		protected BetweenExpressionTester(ExpressionTester expression,
		                                  boolean hasNot,
		                                  ExpressionTester lowerBoundExpression,
		                                  ExpressionTester upperBoundExpression) {

			super();
			this.hasNot                  = hasNot;
			this.hasAnd                  = true;
			this.expression              = expression;
			this.hasSpaceAfterAnd        = true;
			this.hasSpaceAfterLowerBound = true;
			this.hasSpaceAfterBetween    = true;
			this.lowerBoundExpression    = lowerBoundExpression;
			this.upperBoundExpression    = upperBoundExpression;
		}

		public void test(Expression expression) {
			assertInstance(expression, BetweenExpression.class);

			BetweenExpression betweenExpression = (BetweenExpression) expression;
			assertEquals(toString(),                     betweenExpression.toParsedText());
			assertEquals(!this.expression.isNull(),      betweenExpression.hasExpression());
			assertEquals(hasNot,                         betweenExpression.hasNot());
			assertEquals(hasAnd,                         betweenExpression.hasAnd());
			assertEquals(!lowerBoundExpression.isNull(), betweenExpression.hasLowerBoundExpression());
			assertEquals(hasSpaceAfterAnd,               betweenExpression.hasSpaceAfterAnd());
			assertEquals(hasSpaceAfterBetween,           betweenExpression.hasSpaceAfterBetween());
			assertEquals(hasSpaceAfterLowerBound,        betweenExpression.hasSpaceAfterLowerBound());
			assertEquals(!upperBoundExpression.isNull(), betweenExpression.hasUpperBoundExpression());

			this.expression.test(betweenExpression.getExpression());
			lowerBoundExpression.test(betweenExpression.getLowerBoundExpression());
			upperBoundExpression.test(betweenExpression.getUpperBoundExpression());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(expression);
			if (!expression.isNull()) {
				sb.append(SPACE);
			}
			if (hasNot) {
				sb.append(NOT);
				sb.append(SPACE);
			}
			sb.append(BETWEEN);
			if (hasSpaceAfterBetween) {
				sb.append(SPACE);
			}
			sb.append(lowerBoundExpression);
			if (hasSpaceAfterLowerBound) {
				sb.append(SPACE);
			}
			if (hasAnd) {
				sb.append(AND);
			}
			if (hasSpaceAfterAnd) {
				sb.append(SPACE);
			}
			sb.append(upperBoundExpression);
			return sb.toString();
		}
	}

	public static final class CaseExpressionTester extends AbstractExpressionTester {

		private ExpressionTester caseOperand;
		private ExpressionTester elseExpression;
		public boolean hasElse;
		public boolean hasEnd;
		public boolean hasSpaceAfterCase;
		public boolean hasSpaceAfterCaseOperand;
		public boolean hasSpaceAfterElse;
		public boolean hasSpaceAfterElseExpression;
		public boolean hasSpaceAfterWhenClauses;
		private ExpressionTester whenClauses;

		protected CaseExpressionTester(ExpressionTester caseOperand,
		                               ExpressionTester whenClauses,
		                               ExpressionTester elseExpression) {

			super();
			this.whenClauses                 = whenClauses;
			this.caseOperand                 = caseOperand;
			this.elseExpression              = elseExpression;
			this.hasElse                     = !elseExpression.isNull();
			this.hasEnd                      = true;
			this.hasSpaceAfterCase           = true;
			this.hasSpaceAfterElse           = hasElse;
			this.hasSpaceAfterElseExpression = hasElse;
			this.hasSpaceAfterCaseOperand    = !caseOperand.isNull();
			this.hasSpaceAfterWhenClauses    = !whenClauses.isNull();
		}

		public void test(Expression expression) {

			assertInstance(expression, CaseExpression.class);

			CaseExpression caseExpression = (CaseExpression) expression;
			assertEquals(toString(),                  caseExpression.toParsedText());
			assertEquals(!caseOperand.isNull(),       caseExpression.hasCaseOperand());
			assertEquals(hasElse,                     caseExpression.hasElse());
			assertEquals(hasEnd,                      caseExpression.hasEnd());
			assertEquals(hasSpaceAfterCase,           caseExpression.hasSpaceAfterCase());
			assertEquals(hasSpaceAfterCaseOperand,    caseExpression.hasSpaceAfterCaseOperand());
			assertEquals(hasSpaceAfterElse,           caseExpression.hasSpaceAfterElse());
			assertEquals(hasSpaceAfterElseExpression, caseExpression.hasSpaceAfterElseExpression());
			assertEquals(hasSpaceAfterWhenClauses,    caseExpression.hasSpaceAfterWhenClauses());
			assertEquals(!whenClauses.isNull(),       caseExpression.hasWhenClauses());

			caseOperand.test(caseExpression.getCaseOperand());
			whenClauses.test(caseExpression.getWhenClauses());
			elseExpression.test(caseExpression.getElseExpression());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(CASE);
			if (hasSpaceAfterCase) {
				sb.append(SPACE);
			}
			sb.append(caseOperand);
			if (hasSpaceAfterCaseOperand) {
				sb.append(SPACE);
			}
			sb.append(whenClauses);
			if (hasSpaceAfterWhenClauses) {
				sb.append(SPACE);
			}
			if (hasElse) {
				sb.append(ELSE);
			}
			if (hasSpaceAfterElse) {
				sb.append(SPACE);
			}
			sb.append(elseExpression);
			if (hasSpaceAfterElseExpression) {
				sb.append(SPACE);
			}
			if (hasEnd) {
				sb.append(END);
			}
			return sb.toString();
		}
	}

	public static final class CastExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		private ExpressionTester databaseType;
		private boolean hasAs;
		public boolean hasSpaceAfterAs;
		public boolean hasSpaceAfterExpression;

		protected CastExpressionTester(ExpressionTester expression,
		                               boolean hasAs,
		                               ExpressionTester databaseType) {

			super(expression);
			this.hasAs                   = hasAs;
			this.databaseType            = databaseType;
			this.hasSpaceAfterAs         = hasAs && !databaseType.isNull();
			this.hasSpaceAfterExpression = !expression.isNull() && (hasAs || !databaseType.isNull());
		}

		@Override
		protected Class<CastExpression> expressionType() {
			return CastExpression.class;
		}

		@Override
		protected boolean hasEncapsulatedExpression() {
			return super.hasEncapsulatedExpression() || hasAs || !databaseType.isNull();
		}

		@Override
		protected String identifier() {
			return CAST;
		}

		@Override
		protected void toStringEncapsulatedExpression(StringBuilder sb) {
			super.toStringEncapsulatedExpression(sb);
			if (hasSpaceAfterExpression) {
				sb.append(SPACE);
			}
			if (hasAs) {
				sb.append(AS);
			}
			if (hasSpaceAfterAs) {
				sb.append(SPACE);
			}
			sb.append(databaseType);
		}
	}

	public static final class CoalesceExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		protected CoalesceExpressionTester(ExpressionTester expression) {
			super(expression);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return CoalesceExpression.class;
		}

		@Override
		protected String identifier(){
			return COALESCE;
		}
	}

	public static final class CollectionExpressionTester extends AbstractExpressionTester {

		public Boolean[] commas;
		private ExpressionTester[] expressionTesters;
		public Boolean[] spaces;

		protected CollectionExpressionTester(ExpressionTester[] expressionTesters,
		                                     Boolean[] commas,
		                                     Boolean[] spaces) {

			super();
			this.expressionTesters = expressionTesters;
			this.spaces            = spaces;
			this.commas            = commas;
		}

		public void test(Expression expression) {
			assertInstance(expression, CollectionExpression.class);

			CollectionExpression collectionExpression = (CollectionExpression) expression;
			assertEquals(toString(), collectionExpression.toParsedText());
			assertEquals(spaces.length, collectionExpression.childrenSize());
			assertEquals(commas.length, collectionExpression.childrenSize());
			assertEquals(expressionTesters.length, collectionExpression.childrenSize());

			// Expressions
			for (int index = expressionTesters.length; --index >= 0; ) {
				expressionTesters[index].test(collectionExpression.getChild(index));
			}

			// Spaces
			for (int index = 0, count = spaces.length; index < count; index++) {
				assertEquals(
					"The flag for a space at " + index + " does not match",
					spaces[index],
					collectionExpression.hasSpace(index)
				);
			}

			// Commas
			for (int index = 0, count = commas.length; index < count; index++) {
				assertEquals(
					"The flag for a comma at " + index + " does not match",
					commas[index],
					collectionExpression.hasComma(index)
				);
			}
		}

		@Override
		public String toString() {

			StringBuilder sb = new StringBuilder();

			for (int index = 0, count = expressionTesters.length; index < count; index++) {
				sb.append(expressionTesters[index]);

				if ((index < commas.length) && commas[index]) {
					sb.append(COMMA);
				}

				if ((index < spaces.length) && spaces[index]) {
					sb.append(SPACE);
				}
			}

			return sb.toString();
		}
	}

	public static final class CollectionMemberDeclarationTester extends AbstractExpressionTester {

		private ExpressionTester collectionValuedPath;
		public boolean hasAs;
		public boolean hasLeftParenthesis;
		public boolean hasRightParenthesis;
		public boolean hasSpaceAfterAs;
		public boolean hasSpaceAfterIn;
		public boolean hasSpaceAfterRightParenthesis;
		private ExpressionTester identificationVariable;

		protected CollectionMemberDeclarationTester(ExpressionTester collectionValuedPath,
		                                            boolean hasAs,
		                                            ExpressionTester identificationVariable) {

			super();
			this.hasAs                         = hasAs;
			this.hasSpaceAfterAs               = hasAs;
			this.hasLeftParenthesis            = true;
			this.hasRightParenthesis           = true;
			this.collectionValuedPath          = collectionValuedPath;
			this.identificationVariable        = identificationVariable;
			this.hasSpaceAfterRightParenthesis = hasAs || !identificationVariable.isNull();
		}

		public void test(Expression expression) {
			assertInstance(expression, CollectionMemberDeclaration.class);

			CollectionMemberDeclaration collectionMemberDeclaration = (CollectionMemberDeclaration) expression;
			assertEquals(toString(),                       collectionMemberDeclaration.toParsedText());
			assertEquals(hasAs,                            collectionMemberDeclaration.hasAs());
			assertEquals(!collectionValuedPath.isNull(),   collectionMemberDeclaration.hasCollectionValuedPathExpression());
			assertEquals(!identificationVariable.isNull(), collectionMemberDeclaration.hasIdentificationVariable());
			assertEquals(hasLeftParenthesis,               collectionMemberDeclaration.hasLeftParenthesis());
			assertEquals(hasRightParenthesis,              collectionMemberDeclaration.hasRightParenthesis());
			assertEquals(hasSpaceAfterAs,                  collectionMemberDeclaration.hasSpaceAfterAs());
			assertEquals(hasSpaceAfterIn,                  collectionMemberDeclaration.hasSpaceAfterIn());
			assertEquals(hasSpaceAfterRightParenthesis,    collectionMemberDeclaration.hasSpaceAfterRightParenthesis());

			collectionValuedPath.test(collectionMemberDeclaration.getCollectionValuedPathExpression());
			identificationVariable.test(collectionMemberDeclaration.getIdentificationVariable());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(IN);
			if (hasSpaceAfterIn) {
				sb.append(SPACE);
			}
			else if (hasLeftParenthesis) {
				sb.append(LEFT_PARENTHESIS);
			}
			sb.append(collectionValuedPath);
			if (hasRightParenthesis) {
				sb.append(RIGHT_PARENTHESIS);
			}
			if (hasSpaceAfterRightParenthesis) {
				sb.append(SPACE);
			}
			if (hasAs) {
				sb.append(AS);
			}
			if (hasSpaceAfterAs) {
				sb.append(SPACE);
			}
			sb.append(identificationVariable);
			return sb.toString();
		}
	}

	public static final class CollectionMemberExpressionTester extends AbstractExpressionTester {

		private ExpressionTester collectionPath;
		private ExpressionTester entityExpression;
		private boolean hasNot;
		private boolean hasOf;
		public boolean hasSpaceAfterMember;
		public boolean hasSpaceAfterOf;

		protected CollectionMemberExpressionTester(ExpressionTester entityExpression,
		                                           boolean hasNot,
		                                           boolean hasOf,
		                                           ExpressionTester collectionPath) {

			super();
			this.hasNot                         = hasNot;
			this.hasOf                          = hasOf;
			this.hasSpaceAfterMember            = true;
			this.hasSpaceAfterOf                = hasOf;
			this.entityExpression               = entityExpression;
			this.collectionPath = collectionPath;
		}

		public void test(Expression expression) {
			assertInstance(expression, CollectionMemberExpression.class);

			CollectionMemberExpression collectionMemberExpression = (CollectionMemberExpression) expression;
			assertEquals(toString(),                               collectionMemberExpression.toParsedText());
			assertEquals(!collectionPath.isNull(), collectionMemberExpression.hasCollectionValuedPathExpression());
			assertEquals(!entityExpression.isNull(),               collectionMemberExpression.hasEntityExpression());
			assertEquals(hasNot,                                   collectionMemberExpression.hasNot());
			assertEquals(hasSpaceAfterMember,                      collectionMemberExpression.hasSpaceAfterMember());
			assertEquals(hasOf,                                    collectionMemberExpression.hasOf());
			assertEquals(hasSpaceAfterOf,                          collectionMemberExpression.hasSpaceAfterOf());

			entityExpression.test(collectionMemberExpression.getEntityExpression());
			collectionPath.test(collectionMemberExpression.getCollectionValuedPathExpression());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(entityExpression);
			if (!entityExpression.isNull()) {
				sb.append(SPACE);
			}
			if (hasNot) {
				sb.append(NOT);
				sb.append(SPACE);
			}
			if (hasOf) {
				sb.append(MEMBER_OF);
				if (hasSpaceAfterOf) {
					sb.append(SPACE);
				}
			}
			else {
				sb.append(MEMBER);
				if (hasSpaceAfterMember) {
					sb.append(SPACE);
				}
			}
			sb.append(collectionPath);
			return sb.toString();
		}
	}

	public static final class CollectionValuedPathExpressionTester extends AbstractPathExpressionTester {

		protected CollectionValuedPathExpressionTester(ExpressionTester identificationVariable,
		                                               String value) {

			super(identificationVariable, value);
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);
			assertInstance(expression, CollectionValuedPathExpression.class);
		}
	}

	public static final class ComparisonExpressionTester extends AbstractExpressionTester {

		private String comparator;
		public boolean hasSpaceAfterIdentifier;
		private ExpressionTester leftExpression;
		private ExpressionTester rightExpression;

		protected ComparisonExpressionTester(String comparator,
		                                     ExpressionTester leftExpression,
		                                     ExpressionTester rightExpression) {

			super();
			this.comparator              = comparator;
			this.leftExpression          = leftExpression;
			this.rightExpression         = rightExpression;
			this.hasSpaceAfterIdentifier = true;
		}

		public void test(Expression expression) {
			assertInstance(expression, ComparisonExpression.class);

			ComparisonExpression comparisonExpression = (ComparisonExpression) expression;
			assertEquals(toString(),                comparisonExpression.toParsedText());
			assertEquals(!leftExpression.isNull(),  comparisonExpression.hasLeftExpression());
			assertEquals(!rightExpression.isNull(), comparisonExpression.hasRightExpression());
			assertEquals(hasSpaceAfterIdentifier,   comparisonExpression.hasSpaceAfterIdentifier());

			leftExpression .test(comparisonExpression.getLeftExpression());
			rightExpression.test(comparisonExpression.getRightExpression());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(leftExpression);
			if (!leftExpression.isNull()) {
				sb.append(SPACE);
			}
			sb.append(comparator);
			if (hasSpaceAfterIdentifier) {
				sb.append(SPACE);
			}
			sb.append(rightExpression);
			return sb.toString();
		}
	}

	public static abstract class CompoundExpressionTester extends AbstractExpressionTester {

		public boolean hasSpaceAfterIdentifier;
		private ExpressionTester leftExpression;
		private ExpressionTester rightExpression;

		protected CompoundExpressionTester(ExpressionTester leftExpression,
		                                   ExpressionTester rightExpression) {

			super();
			this.leftExpression          = leftExpression;
			this.rightExpression         = rightExpression;
			this.hasSpaceAfterIdentifier = !rightExpression.isNull();
		}

		protected abstract Class<? extends CompoundExpression> expressionType();

		protected abstract String identifier();

		public void test(Expression expression) {
			assertInstance(expression, expressionType());

			CompoundExpression compoundExpression = (CompoundExpression) expression;
			assertEquals(toString(),                compoundExpression.toParsedText());
			assertEquals(!leftExpression.isNull(),  compoundExpression.hasLeftExpression());
			assertEquals(!rightExpression.isNull(), compoundExpression.hasRightExpression());
			assertEquals(hasSpaceAfterIdentifier,   compoundExpression.hasSpaceAfterIdentifier());

			leftExpression .test(compoundExpression.getLeftExpression());
			rightExpression.test(compoundExpression.getRightExpression());
		}

		@Override
		public String toString() {

			StringBuilder sb = new StringBuilder();

			if (!leftExpression.isNull()) {
				sb.append(leftExpression);
				if (sb.charAt(sb.length() - 1) != SPACE) {
					sb.append(SPACE);
				}
			}

			sb.append(identifier());

			if (hasSpaceAfterIdentifier) {
				sb.append(SPACE);
			}

			sb.append(rightExpression);
			return sb.toString();
		}
	}

	public static final class ConcatExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		protected ConcatExpressionTester(ExpressionTester expression) {
			super(expression);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return ConcatExpression.class;
		}

		@Override
		protected String identifier() {
			return CONCAT;
		}
	}

	public static final class ConnectByClauseTester extends AbstractExpressionTester {

		private ExpressionTester expression;
		public boolean hasSpaceAfterConnectBy;

		protected ConnectByClauseTester(ExpressionTester expression) {
			super();
			this.expression             = expression;
			this.hasSpaceAfterConnectBy = !expression.isNull();
		}

		public void test(Expression expression) {
			assertInstance(expression, ConnectByClause.class);

			ConnectByClause priorExpression = (ConnectByClause) expression;
			assertEquals(toString(),                priorExpression.toParsedText());
			assertEquals(hasSpaceAfterConnectBy,    priorExpression.hasSpaceAfterConnectBy());
			assertEquals(!this.expression.isNull(), priorExpression.hasExpression());

			this.expression.test(priorExpression.getExpression());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(CONNECT_BY);
			if (hasSpaceAfterConnectBy) {
				sb.append(SPACE);
			}
			sb.append(expression);
			return sb.toString();
		}
	}

	public static final class ConstructorExpressionTester extends AbstractExpressionTester {

		private String className;
		private ExpressionTester constructorItems;
		public boolean hasLeftParenthesis;
		public boolean hasRightParenthesis;
		public boolean hasSpaceAfterNew;

		protected ConstructorExpressionTester(String className, ExpressionTester constructorItems) {
			super();
			this.className           = className;
			this.constructorItems    = constructorItems;
			this.hasSpaceAfterNew    = true;
			this.hasLeftParenthesis  = true;
			this.hasRightParenthesis = true;
		}

		public void test(Expression expression) {
			assertInstance(expression, ConstructorExpression.class);

			ConstructorExpression constructorExpression = (ConstructorExpression) expression;
			assertEquals(toString(),                 constructorExpression.toParsedText());
			assertEquals(!constructorItems.isNull(), constructorExpression.hasConstructorItems());
			assertEquals(hasSpaceAfterNew,           constructorExpression.hasSpaceAfterNew());
			assertEquals(hasLeftParenthesis,         constructorExpression.hasLeftParenthesis());
			assertEquals(hasRightParenthesis,        constructorExpression.hasRightParenthesis());

			constructorItems.test(constructorExpression.getConstructorItems());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(NEW);
			if (hasSpaceAfterNew) {
				sb.append(SPACE);
			}
			sb.append(className);
			if (hasLeftParenthesis) {
				sb.append(LEFT_PARENTHESIS);
			}
			sb.append(constructorItems);
			if (hasRightParenthesis) {
				sb.append(RIGHT_PARENTHESIS);
			}
			return sb.toString();
		}
	}

	public static final class CountFunctionTester extends AggregateFunctionTester {

		protected CountFunctionTester(ExpressionTester expression, boolean hasDistinct) {
			super(expression, hasDistinct);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return CountFunction.class;
		}

		@Override
		protected String identifier() {
			return COUNT;
		}
	}

	public static final class DatabaseTypeTester extends AbstractDoubleEncapsulatedExpressionTester {

		private String databaseType;

		protected DatabaseTypeTester(String databaseType,
		                             ExpressionTester size,
		                             ExpressionTester precision) {

			super(size, precision);
			this.databaseType = databaseType;
		}

		@Override
		protected Class<DatabaseType> expressionType() {
			return DatabaseType.class;
		}

		@Override
		protected String identifier() {
			return databaseType;
		}
	}

	public static final class DateTimeTester extends AbstractExpressionTester {

		private String dateTime;

		protected DateTimeTester(String dateTime) {
			super();
			this.dateTime = dateTime;
		}

		public void test(Expression expression) {
			assertInstance(expression, DateTime.class);

			DateTime dateTime = (DateTime) expression;
			assertEquals(toString(), dateTime.toParsedText());
		}

		@Override
		public String toString() {
			return dateTime;
		}
	}

	/**
	 * Tester for {@link DeleteClause}.
	 */
	public static final class DeleteClauseTester extends AbstractExpressionTester {

		public boolean hasFrom;
		public boolean hasSpaceAfterDelete;
		public boolean hasSpaceAfterFrom;
		private ExpressionTester rangeVariableDeclaration;

		protected DeleteClauseTester(ExpressionTester rangeVariableDeclaration) {
			super();
			this.hasFrom                  = true;
			this.hasSpaceAfterFrom        = true;
			this.hasSpaceAfterDelete      = true;
			this.rangeVariableDeclaration = rangeVariableDeclaration;
		}

		public void test(Expression expression) {

			assertInstance(expression, DeleteClause.class);

			DeleteClause deleteClause = (DeleteClause) expression;
			assertEquals(toString(),                         deleteClause.toParsedText());
			assertEquals(hasSpaceAfterDelete,                deleteClause.hasSpaceAfterDelete());
			assertEquals(hasFrom,                            deleteClause.hasFrom());
			assertEquals(hasSpaceAfterFrom,                  deleteClause.hasSpaceAfterFrom());
			assertEquals(!rangeVariableDeclaration.isNull(), deleteClause.hasRangeVariableDeclaration());

			rangeVariableDeclaration.test(deleteClause.getRangeVariableDeclaration());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(DELETE);
			if (hasSpaceAfterDelete) {
				sb.append(SPACE);
			}
			if (hasFrom) {
				sb.append(FROM);
			}
			if (hasSpaceAfterFrom) {
				sb.append(SPACE);
			}
			sb.append(rangeVariableDeclaration);
			return sb.toString();
		}
	}

	public static final class DeleteStatementTester extends AbstractExpressionTester {

		private ExpressionTester deleteClause;
		public boolean hasSpaceAfterDeleteClause;
		private ExpressionTester whereClause;

		protected DeleteStatementTester(ExpressionTester deleteClause, ExpressionTester whereClause) {
			super();
			this.deleteClause              = deleteClause;
			this.whereClause               = whereClause;
			this.hasSpaceAfterDeleteClause = !whereClause.isNull();
		}

		public void test(Expression expression) {

			assertInstance(expression, DeleteStatement.class);

			DeleteStatement deleteStatement = (DeleteStatement) expression;
			assertEquals(toString(),                deleteStatement.toParsedText());
			assertEquals(hasSpaceAfterDeleteClause, deleteStatement.hasSpaceAfterDeleteClause());
			assertEquals(!whereClause.isNull(),     deleteStatement.hasWhereClause());

			deleteClause.test(deleteStatement.getDeleteClause());
			whereClause .test(deleteStatement.getWhereClause());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(deleteClause);
			if (hasSpaceAfterDeleteClause) {
				sb.append(SPACE);
			}
			sb.append(whereClause);
			return sb.toString();
		}
	}

	public static final class DivisionExpressionTester extends CompoundExpressionTester {

		protected DivisionExpressionTester(ExpressionTester leftExpression,
		                                   ExpressionTester rightExpression) {

			super(leftExpression, rightExpression);
		}

		@Override
		protected Class<? extends CompoundExpression> expressionType() {
			return DivisionExpression.class;
		}

		@Override
		protected String identifier() {
			return DIVISION;
		}
	}

	public static final class EmptyCollectionComparisonExpressionTester extends AbstractExpressionTester {

		private ExpressionTester collectionPath;
		private boolean hasNot;
		public boolean hasSpaceAfterIs;

		protected EmptyCollectionComparisonExpressionTester(ExpressionTester collectionPath,
		                                                    boolean hasNot) {

			super();
			this.hasNot = hasNot;
			this.hasSpaceAfterIs = true;
			this.collectionPath = collectionPath;
		}

		public void test(Expression expression) {
			assertInstance(expression, EmptyCollectionComparisonExpression.class);

			EmptyCollectionComparisonExpression emptyCollection = (EmptyCollectionComparisonExpression) expression;
			assertEquals(toString(),                               emptyCollection.toParsedText());
			assertEquals(!collectionPath.isNull(), emptyCollection.hasExpression());
			assertEquals(hasNot,                                   emptyCollection.hasNot());
			assertEquals(hasSpaceAfterIs,                          emptyCollection.hasSpaceAfterIs());

			collectionPath.test(emptyCollection.getExpression());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(collectionPath);
			if (!collectionPath.isNull()) {
				sb.append(SPACE);
			}
			sb.append(IS);
			if (hasSpaceAfterIs) {
				sb.append(SPACE);
			}
			if (hasNot) {
				sb.append(NOT);
				sb.append(SPACE);
			}
			sb.append(EMPTY);
			return sb.toString();
		}
	}

	public static final class EntityTypeLiteralTester extends AbstractExpressionTester {

		private String entityType;

		protected EntityTypeLiteralTester(String entityType) {
			super();
			this.entityType = entityType;
		}

		public void test(Expression expression) {
			assertInstance(expression, EntityTypeLiteral.class);

			EntityTypeLiteral entityTypeLiteral = (EntityTypeLiteral) expression;
			assertEquals(toString(), entityTypeLiteral.toParsedText());
		}

		@Override
		public String toString() {
			return entityType;
		}
	}

	public static final class EntryExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		protected EntryExpressionTester(ExpressionTester identificationVariable) {
			super(identificationVariable);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return EntryExpression.class;
		}

		@Override
		protected String identifier() {
			return ENTRY;
		}
	}

	public static final class ExistsExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		private boolean hasNot;

		protected ExistsExpressionTester(ExpressionTester subquery, boolean hasNot) {
			super(subquery);
			this.hasNot = hasNot;
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return ExistsExpression.class;
		}

		@Override
		protected String identifier() {
			return hasNot ? NOT_EXISTS : EXISTS;
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);
			assertInstance(expression, ExistsExpression.class);

			ExistsExpression existsExpression = (ExistsExpression) expression;
			assertEquals(hasNot, existsExpression.hasNot());
		}
	}

	/**
	 * This tester tests an {@link Expression} information to make sure it correctly parsed a section
	 * of the query. This interface also adds helper method for easily creating a parsed tree
	 * representation of the actual query parsed tree.
	 */
	public static interface ExpressionTester {

		AdditionExpressionTester add(ExpressionTester expression);
		AndExpressionTester and(ExpressionTester expression);
		BetweenExpressionTester between(ExpressionTester lowerBoundExpression, ExpressionTester upperBoundExpression);
		ComparisonExpressionTester different(ExpressionTester expression);
		DivisionExpressionTester divide(ExpressionTester expression);
		ComparisonExpressionTester equal(ExpressionTester expression);
		ComparisonExpressionTester greaterThan(ExpressionTester expression);
		ComparisonExpressionTester greaterThanOrEqual(ExpressionTester expression);
		InExpressionTester in(ExpressionTester... inItems);
		InExpressionTester in(String inputParameter);
		EmptyCollectionComparisonExpressionTester isEmpty();
		EmptyCollectionComparisonExpressionTester isNotEmpty();

		/**
		 * Determines whether this tester represents the {@link NullExpression}.
		 *
		 * @return <code>true</code> if this tester represents a <code>null</code> object;
		 * <code>false</code> otherwise
		 */
		boolean isNull();

		LikeExpressionTester like(ExpressionTester patternValue);
		LikeExpressionTester like(ExpressionTester patternValue, ExpressionTester escapeCharacter);
		ComparisonExpressionTester lowerThan(ExpressionTester expression);
		ComparisonExpressionTester lowerThanOrEqual(ExpressionTester expression);
		ExpressionTester member(ExpressionTester collectionPath);
		ExpressionTester memberOf(ExpressionTester collectionPath);
		MultiplicationExpressionTester multiply(ExpressionTester expression);
		BetweenExpressionTester notBetween(ExpressionTester lowerBoundExpression, ExpressionTester upperBoundExpression);
		ComparisonExpressionTester notEqual(ExpressionTester expression);
		InExpressionTester notIn(ExpressionTester... inItems);
		InExpressionTester notIn(String inputParameter);
		LikeExpressionTester notLike(ExpressionTester expression);
		LikeExpressionTester notLike(ExpressionTester expression, ExpressionTester escapeCharacter);
		ExpressionTester notMember(ExpressionTester collectionPath);
		ExpressionTester notMemberOf(ExpressionTester collectionPath);
		OrExpressionTester or(ExpressionTester expression);
		RegexpExpressionTester regexp(StringLiteralTester patternValue);
		SubtractionExpressionTester subtract(ExpressionTester expression);

		/**
		 * Tests the given {@link Expression} internal data.
		 */
		void test(Expression expression);
	}

	public static final class ExtractExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		private boolean hasFrom;
		public boolean hasSpaceAfterFrom;
		public boolean hasSpaceAfterPart;
		private String part;

		protected ExtractExpressionTester(String part, boolean hasFom, ExpressionTester expression) {
			super(expression);
			this.hasFrom           = hasFom;
			this.part              = (part != null) ? part : ExpressionTools.EMPTY_STRING;
			this.hasSpaceAfterPart = (part != null) && (hasFrom || !expression.isNull());
			this.hasSpaceAfterFrom = hasFom && !expression.isNull();
		}

		@Override
		protected Class<ExtractExpression> expressionType() {
			return ExtractExpression.class;
		}

		@Override
		protected boolean hasEncapsulatedExpression() {
			return super.hasEncapsulatedExpression() || hasFrom || ExpressionTools.stringIsNotEmpty(part);
		}

		@Override
		protected String identifier() {
			return EXTRACT;
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);

			ExtractExpression extractExpression = (ExtractExpression) expression;
			assertEquals(part,              extractExpression.getDatePart());
			assertEquals(hasFrom,           extractExpression.hasFrom());
			assertSame  (hasSpaceAfterFrom, extractExpression.hasSpaceAfterFrom());
			assertSame  (hasSpaceAfterPart, extractExpression.hasSpaceAfterDatePart());
		}

		@Override
		protected void toStringEncapsulatedExpression(StringBuilder sb) {

			sb.append(part);

			if (hasSpaceAfterPart) {
				sb.append(SPACE);
			}

			if (hasFrom) {
				sb.append(FROM);
			}

			if (hasSpaceAfterFrom) {
				sb.append(SPACE);
			}

			super.toStringEncapsulatedExpression(sb);
		}
	}

	public static final class FromClauseTester extends AbstractFromClauseTester {

		protected FromClauseTester(ExpressionTester declarations,
		                           ExpressionTester hierarchicalQueryClause,
		                           ExpressionTester asOfClause) {

			super(declarations, hierarchicalQueryClause, asOfClause);
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);
			assertInstance(expression, FromClause.class);
		}
	}

	public static final class FunctionExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		private String functionName;
		public boolean hasComma;
		public boolean hasSpaceAfterComma;
		private String identifier;

		protected FunctionExpressionTester(String identifier,
		                                   String functionName,
		                                   ExpressionTester funcItems) {

			super(funcItems);
			this.identifier = identifier;
			this.functionName = functionName;
			this.hasSpaceAfterComma = !funcItems.isNull();
			this.hasComma = !funcItems.isNull();
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return FunctionExpression.class;
		}

		@Override
		protected boolean hasEncapsulatedExpression() {
			return functionName.length() > 0 ||
			       hasComma                  ||
			       hasSpaceAfterComma        ||
			       super.hasEncapsulatedExpression();
		}

		@Override
		protected String identifier() {
			return identifier;
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);

			FunctionExpression funcExpression = (FunctionExpression) expression;
			assertEquals(identifier,         funcExpression.getIdentifier());
			assertEquals(functionName,       funcExpression.getFunctionName());
			assertEquals(hasComma,           funcExpression.hasComma());
			assertEquals(hasSpaceAfterComma, funcExpression.hasSpaceAfterComma());
		}

		@Override
		protected void toStringEncapsulatedExpression(StringBuilder sb) {
			if (functionName != null) {
				sb.append(functionName);
			}
			if (hasComma) {
				sb.append(COMMA);
			}
			if (hasSpaceAfterComma) {
				sb.append(SPACE);
			}
			super.toStringEncapsulatedExpression(sb);
		}
	}

	public static final class GroupByClauseTester extends AbstractExpressionTester {

		private ExpressionTester groupByItems;
		public boolean hasSpaceAfterGroupBy;

		protected GroupByClauseTester(ExpressionTester groupByItems) {
			super();
			this.groupByItems         = groupByItems;
			this.hasSpaceAfterGroupBy = !groupByItems.isNull();
		}

		public void test(Expression expression) {

			assertInstance(expression, GroupByClause.class);

			GroupByClause groupByClause = (GroupByClause) expression;
			assertEquals(toString(),             groupByClause.toParsedText());
			assertEquals(!groupByItems.isNull(), groupByClause.hasGroupByItems());
			assertEquals(hasSpaceAfterGroupBy,   groupByClause.hasSpaceAfterGroupBy());

			groupByItems.test(groupByClause.getGroupByItems());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(GROUP_BY);
			if (hasSpaceAfterGroupBy) {
				sb.append(SPACE);
			}
			sb.append(groupByItems);
			return sb.toString();
		}
	}

	public static final class HavingClauseTester extends AbstractConditionalClauseTester {

		protected HavingClauseTester(ExpressionTester conditionalExpression) {
			super(conditionalExpression);
		}

		@Override
		protected Class<? extends AbstractConditionalClause> expressionType() {
			return HavingClause.class;
		}

		@Override
		protected String identifier() {
			return HAVING;
		}
	}

	public static final class HierarchicalQueryClauseTester extends AbstractExpressionTester {

		private ExpressionTester connectByClause;
		public boolean hasSpaceAfterConnectByClause;
		public boolean hasSpaceAfterStartWithClause;
		private ExpressionTester orderSiblingsByClause;
		private ExpressionTester startWithClause;

		protected HierarchicalQueryClauseTester(ExpressionTester startWithClause,
		                                        ExpressionTester connectByClause,
		                                        ExpressionTester orderSiblingsByClause) {
			super();
			this.startWithClause              = startWithClause;
			this.connectByClause              = connectByClause;
			this.orderSiblingsByClause        = orderSiblingsByClause;
			this.hasSpaceAfterStartWithClause = !startWithClause.isNull() && !connectByClause.isNull();
			this.hasSpaceAfterConnectByClause = !connectByClause.isNull() && !orderSiblingsByClause.isNull();
		}

		public void test(Expression expression) {
			assertInstance(expression, HierarchicalQueryClause.class);

			HierarchicalQueryClause clause = (HierarchicalQueryClause) expression;
			assertEquals(toString(),                      clause.toString());
			assertEquals(!startWithClause.isNull(),       clause.hasStartWithClause());
			assertEquals(!connectByClause.isNull(),       clause.hasConnectByClause());
			assertEquals(!orderSiblingsByClause.isNull(), clause.hasOrderSiblingsByClause());
			assertEquals(hasSpaceAfterStartWithClause,    clause.hasSpaceAfterStartWithClause());
			assertEquals(hasSpaceAfterConnectByClause,    clause.hasSpaceAfterConnectByClause());

			startWithClause.test(clause.getStartWithClause());
			connectByClause.test(clause.getConnectByClause());
			orderSiblingsByClause.test(clause.getOrderSiblingsByClause());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(startWithClause);
			if (hasSpaceAfterStartWithClause) {
				sb.append(SPACE);
			}
			sb.append(connectByClause);
			if (hasSpaceAfterConnectByClause) {
				sb.append(SPACE);
			}
			sb.append(orderSiblingsByClause);
			return sb.toString();
		}
	}

	public static final class IdentificationVariableDeclarationTester extends AbstractExpressionTester {

		public boolean hasSpace;
		private ExpressionTester joins;
		private ExpressionTester rangeVariableDeclaration;

		protected IdentificationVariableDeclarationTester(ExpressionTester rangeVariableDeclaration,
		                                                  ExpressionTester joins) {

			super();
			this.rangeVariableDeclaration = rangeVariableDeclaration;
			this.hasSpace                 = !joins.isNull();
			this.joins                    = joins;
		}

		public void test(Expression expression) {

			assertInstance(expression, IdentificationVariableDeclaration.class);

			IdentificationVariableDeclaration identificationVariableDeclaration = (IdentificationVariableDeclaration) expression;
			assertEquals(toString(),                         identificationVariableDeclaration.toParsedText());
			assertEquals(!rangeVariableDeclaration.isNull(), identificationVariableDeclaration.hasRangeVariableDeclaration());
			assertEquals(hasSpace,                           identificationVariableDeclaration.hasSpace());
			assertEquals(!joins.isNull(),                    identificationVariableDeclaration.hasJoins());

			rangeVariableDeclaration.test(identificationVariableDeclaration.getRangeVariableDeclaration());
			joins.test(identificationVariableDeclaration.getJoins());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(rangeVariableDeclaration);
			if (hasSpace) {
				sb.append(SPACE);
			}
			sb.append(joins);
			return sb.toString();
		}
	}

	public static final class IdentificationVariableTester extends AbstractExpressionTester {

		private String identificationVariable;
		private boolean virtual;
		private ExpressionTester virtualPathExpression;

		protected IdentificationVariableTester(String identificationVariable,
		                                       boolean virtual,
		                                       ExpressionTester virtualPathExpression) {
			super();
			this.virtual                = virtual;
			this.identificationVariable = identificationVariable;
			this.virtualPathExpression  = virtualPathExpression;
		}

		@Override
		public boolean isNull() {
			return virtual && virtualPathExpression.isNull();
		}

		public void test(Expression expression) {
			assertInstance(expression, IdentificationVariable.class);

			IdentificationVariable identificationVariable = (IdentificationVariable) expression;
			assertEquals(this.identificationVariable, identificationVariable.toParsedText());
			assertEquals(virtual,                     identificationVariable.isVirtual());

			if (virtual) {
				StateFieldPathExpression pathExpression = identificationVariable.getStateFieldPathExpression();
				assertEquals(virtualPathExpression.isNull(), pathExpression == null);
				if (pathExpression != null) {
					virtualPathExpression.test(pathExpression);
				}
			}
		}

		@Override
		public String toString() {
			return virtual ? virtualPathExpression.toString() : identificationVariable;
		}
	}

	public static final class IndexExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		protected IndexExpressionTester(ExpressionTester identificationVariable) {
			super(identificationVariable);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return IndexExpression.class;
		}

		@Override
		protected String identifier() {
			return INDEX;
		}
	}

	public static final class InExpressionTester extends AbstractExpressionTester {

		public boolean hasLeftParenthesis;
		private boolean hasNot;
		public boolean hasRightParenthesis;
		public boolean hasSpaceAfterIn;
		private ExpressionTester inItems;
		private ExpressionTester stateFieldPathExpression;

		protected InExpressionTester(ExpressionTester stateFieldPathExpression,
		                             boolean hasNot,
		                             ExpressionTester inItems) {

			super();
			this.stateFieldPathExpression = stateFieldPathExpression;
			this.hasLeftParenthesis       = !inItems.isNull();
			this.hasRightParenthesis      = !inItems.isNull();
			this.hasNot                   = hasNot;
			this.inItems                  = inItems;
			this.hasSpaceAfterIn          = !hasLeftParenthesis && !inItems.isNull();
		}

		public void test(Expression expression) {
			assertInstance(expression, InExpression.class);

			InExpression inExpression = (InExpression) expression;
			assertEquals(toString(),                         inExpression.toParsedText());
			assertEquals(hasLeftParenthesis,                 inExpression.hasLeftParenthesis());
			assertEquals(hasRightParenthesis,                inExpression.hasRightParenthesis());
			assertEquals(hasNot,                             inExpression.hasNot());
			assertEquals(!inItems.isNull(),                  inExpression.hasInItems());
			assertEquals(!stateFieldPathExpression.isNull(), inExpression.hasExpression());

			stateFieldPathExpression.test(inExpression.getExpression());
			inItems.test(inExpression.getInItems());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(stateFieldPathExpression);
			if (!stateFieldPathExpression.isNull()) {
				sb.append(SPACE);
			}
			sb.append(hasNot ? NOT_IN : IN);
			if (hasSpaceAfterIn) {
				sb.append(SPACE);
			}
			if (hasLeftParenthesis) {
				sb.append(LEFT_PARENTHESIS);
			}
			sb.append(inItems);
			if (hasRightParenthesis) {
				sb.append(RIGHT_PARENTHESIS);
			}
			return sb.toString();
		}
	}

	public static final class InputParameterTester extends AbstractExpressionTester {

		private String inputParameter;

		protected InputParameterTester(String inputParameter) {
			super();
			this.inputParameter = inputParameter;
		}

		public void test(Expression expression) {
			assertInstance(expression, InputParameter.class);

			InputParameter inputParameter = (InputParameter) expression;
			assertEquals(toString(),                           inputParameter.toParsedText());
			assertEquals(this.inputParameter.charAt(0) == '?', inputParameter.isPositional());
			assertEquals(this.inputParameter.charAt(0) == ':', inputParameter.isNamed());
			assertEquals(this.inputParameter.substring(1),     inputParameter.getParameterName());
		}

		@Override
		public String toString() {
			return inputParameter;
		}
	}

	public static final class JoinTester extends AbstractExpressionTester {

		private boolean hasAs;
		public boolean hasSpaceAfterAs;
		public boolean hasSpaceAfterIdentificationVariable;
		public boolean hasSpaceAfterJoin;
		public boolean hasSpaceAfterJoinAssociation;
		private ExpressionTester identificationVariable;
		private ExpressionTester joinAssociationPath;
		private ExpressionTester joinCondition;
		private String joinType;

		protected JoinTester(String joinType,
		                     ExpressionTester joinAssociationPath,
		                     boolean hasAs,
		                     ExpressionTester identificationVariable,
		                     ExpressionTester joinCondition) {

			super();
			this.joinType                            = joinType;
			this.hasSpaceAfterJoin                   = true;
			this.joinAssociationPath                 = joinAssociationPath;
			this.hasAs                               = hasAs;
			this.hasSpaceAfterAs                     = hasAs;
			this.identificationVariable              = identificationVariable;
			this.hasSpaceAfterJoinAssociation        = !joinAssociationPath.isNull();
			this.hasSpaceAfterIdentificationVariable = !identificationVariable.isNull() && !joinCondition.isNull();
			this.joinCondition                       = joinCondition;
		}

		public void test(Expression expression) {
			assertInstance(expression, Join.class);

			Join join = (Join) expression;
			assertEquals(toString(), join.toParsedText());
			assertEquals(joinType, join.getIdentifier());

			assertEquals(hasSpaceAfterJoin,                   join.hasSpaceAfterJoin());
			assertEquals(!joinAssociationPath.isNull(),       join.hasJoinAssociationPath());
			assertEquals(hasAs,                               join.hasAs());
			assertEquals(hasSpaceAfterAs,                     join.hasSpaceAfterAs());
			assertEquals(hasSpaceAfterJoinAssociation,        join.hasSpaceAfterJoinAssociation());
			assertEquals(!identificationVariable.isNull(),    join.hasIdentificationVariable());
			assertEquals(hasSpaceAfterIdentificationVariable, join.hasSpaceAfterIdentificationVariable());

			joinAssociationPath.test(join.getJoinAssociationPath());
			identificationVariable.test(join.getIdentificationVariable());
			joinCondition.test(join.getOnClause());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(joinType);
			if (hasSpaceAfterJoin) {
				sb.append(SPACE);
			}
			sb.append(joinAssociationPath);
			if (hasSpaceAfterJoinAssociation) {
				sb.append(SPACE);
			}
			if (hasAs) {
				sb.append(AS);
			}
			if (hasSpaceAfterAs) {
				sb.append(SPACE);
			}
			sb.append(identificationVariable);
			if (hasSpaceAfterIdentificationVariable) {
				sb.append(SPACE);
			}
			sb.append(joinCondition);
			return sb.toString();
		}
	}

	public static final class JPQLExpressionTester extends AbstractExpressionTester {

		private ExpressionTester queryStatement;
		private ExpressionTester unknownExpression;

		protected JPQLExpressionTester(ExpressionTester queryStatement,
		                               ExpressionTester unknownExpression) {

			super();
			this.queryStatement    = queryStatement;
			this.unknownExpression = unknownExpression;
		}

		public void test(Expression expression) {

			JPQLExpression jpqlExpression = (JPQLExpression) expression;

			assertEquals(toString(),                  jpqlExpression.toParsedText());
			assertEquals(!queryStatement.isNull(),    jpqlExpression.hasQueryStatement());
			assertEquals(!unknownExpression.isNull(), jpqlExpression.hasUnknownEndingStatement());

			queryStatement   .test(jpqlExpression.getQueryStatement());
			unknownExpression.test(jpqlExpression.getUnknownEndingStatement());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(queryStatement);
			sb.append(unknownExpression);
			return sb.toString();
		}
	}

	public static final class KeywordExpressionTester extends AbstractExpressionTester {

		private String keyword;

		protected KeywordExpressionTester(String keyword) {
			super();
			this.keyword = keyword;
		}

		public void test(Expression expression) {
			assertInstance(expression, KeywordExpression.class);

			KeywordExpression keywordExpression = (KeywordExpression) expression;
			assertEquals(toString(), keywordExpression.toParsedText());
		}

		@Override
		public String toString() {
			return keyword;
		}
	}

	public static final class LengthExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		protected LengthExpressionTester(ExpressionTester expression) {
			super(expression);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return LengthExpression.class;
		}

		@Override
		protected String identifier() {
			return LENGTH;
		}
	}

	public static final class LikeExpressionTester extends AbstractExpressionTester {

		private ExpressionTester escapeCharacter;
		public boolean hasEscape;
		private boolean hasNot;
		public boolean hasSpaceAfterEscape;
		public boolean hasSpaceAfterLike;
		public boolean hasSpaceAfterPatternValue;
		private ExpressionTester patternValue;
		private ExpressionTester stringExpression;

		protected LikeExpressionTester(ExpressionTester stringExpression,
		                               boolean hasNot,
		                               ExpressionTester patternValue,
		                               ExpressionTester escapeCharacter) {

			super();
			this.hasNot              = hasNot;
			this.hasSpaceAfterLike   = true;
			this.stringExpression    = stringExpression;
			this.patternValue        = patternValue;
			this.escapeCharacter     = escapeCharacter;
			this.hasEscape           = !escapeCharacter.isNull();
			this.hasSpaceAfterEscape = hasEscape;
			this.hasSpaceAfterPatternValue = !patternValue.isNull() && hasEscape;
		}

		public void test(Expression expression) {
			assertInstance(expression, LikeExpression.class);

			LikeExpression likeExpression = (LikeExpression) expression;
			assertEquals(toString(),                likeExpression.toParsedText());
			assertEquals(hasNot,                    likeExpression.hasNot());
			assertEquals(hasSpaceAfterLike,         likeExpression.hasSpaceAfterLike());
			assertEquals(hasEscape,                 likeExpression.hasEscape());
			assertEquals(!escapeCharacter.isNull(), likeExpression.hasEscapeCharacter());
			assertEquals(hasSpaceAfterEscape,       likeExpression.hasSpaceAfterEscape());
			assertEquals(hasSpaceAfterPatternValue, likeExpression.hasSpaceAfterPatternValue());
			assertEquals(!patternValue.isNull(),    likeExpression.hasPatternValue());

			stringExpression.test(likeExpression.getStringExpression());
			patternValue.test(likeExpression.getPatternValue());
			escapeCharacter.test(likeExpression.getEscapeCharacter());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(stringExpression);
			if (!stringExpression.isNull()) {
				sb.append(SPACE);
			}
			sb.append(hasNot ? NOT_LIKE : LIKE);
			if (hasSpaceAfterLike) {
				sb.append(SPACE);
			}
			sb.append(patternValue);
			if (hasSpaceAfterPatternValue) {
				sb.append(SPACE);
			}
			if (hasEscape) {
				sb.append(ESCAPE);
			}
			if (hasSpaceAfterEscape) {
				sb.append(SPACE);
			}
			sb.append(escapeCharacter);
			return sb.toString();
		}
	}

	public static final class LocateExpressionTester extends AbstractTripleEncapsulatedExpressionTester {

		protected LocateExpressionTester(ExpressionTester firstExpression,
		                                 ExpressionTester secondExpression,
		                                 ExpressionTester thirdExpression) {

			super(firstExpression, secondExpression, thirdExpression);
		}

		@Override
		protected Class<? extends AbstractTripleEncapsulatedExpression> expressionType() {
			return LocateExpression.class;
		}

		@Override
		protected String identifier() {
			return LOCATE;
		}
	}

	public static abstract class LogicalExpressionTester extends CompoundExpressionTester {

		protected LogicalExpressionTester(ExpressionTester leftExpression,
		                                  ExpressionTester rightExpression) {

			super(leftExpression, rightExpression);
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);
			assertInstance(expression, LogicalExpression.class);
		}
	}

	public static final class LowerExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		protected LowerExpressionTester(ExpressionTester expression) {
			super(expression);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return LowerExpression.class;
		}

		@Override
		protected String identifier() {
			return LOWER;
		}
	}

	public static final class MaxFunctionTester extends AggregateFunctionTester {

		protected MaxFunctionTester(ExpressionTester expression, boolean hasDistinct) {
			super(expression, hasDistinct);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return MaxFunction.class;
		}

		@Override
		protected String identifier() {
			return MAX;
		}
	}

	public static final class MinFunctionTester extends AggregateFunctionTester {

		protected MinFunctionTester(ExpressionTester expression, boolean hasDistinct) {
			super(expression, hasDistinct);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return MinFunction.class;
		}

		@Override
		protected String identifier() {
			return MIN;
		}
	}

	public static final class ModExpressionTester extends AbstractDoubleEncapsulatedExpressionTester {

		protected ModExpressionTester(ExpressionTester firstExpression, ExpressionTester secondExpression) {
			super(firstExpression, secondExpression);
		}

		@Override
		protected Class<? extends AbstractDoubleEncapsulatedExpression> expressionType() {
			return ModExpression.class;
		}

		@Override
		protected String identifier() {
			return MOD;
		}
	}

	public static final class MultiplicationExpressionTester extends CompoundExpressionTester {

		protected MultiplicationExpressionTester(ExpressionTester leftExpression,
		                                         ExpressionTester rightExpression) {

			super(leftExpression, rightExpression);
		}

		@Override
		protected Class<? extends CompoundExpression> expressionType() {
			return MultiplicationExpression.class;
		}

		@Override
		protected String identifier() {
			return MULTIPLICATION;
		}
	}

	public static final class NotExpressionTester extends AbstractExpressionTester {

		private ExpressionTester expression;
		public boolean hasSpaceAfterNot;

		protected NotExpressionTester(ExpressionTester expression) {
			super();
			this.expression       = expression;
			this.hasSpaceAfterNot = true;
		}

		public void test(Expression expression) {
			assertInstance(expression, NotExpression.class);

			NotExpression notExpression = (NotExpression) expression;
			assertEquals(toString(),                notExpression.toParsedText());
			assertEquals(hasSpaceAfterNot,          notExpression.hasSpaceAfterNot());
			assertEquals(!this.expression.isNull(), notExpression.hasExpression());

			this.expression.test(notExpression.getExpression());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(NOT);
			if (hasSpaceAfterNot) {
				sb.append(SPACE);
			}
			sb.append(expression);
			return sb.toString();
		}
	}

	public static final class NullComparisonExpressionTester extends AbstractExpressionTester {

		private ExpressionTester expression;
		private boolean hasNot;

		protected NullComparisonExpressionTester(ExpressionTester expression, boolean hasNot) {
			super();
			this.hasNot     = hasNot;
			this.expression = expression;
		}

		public void test(Expression expression) {
			assertInstance(expression, NullComparisonExpression.class);

			NullComparisonExpression nullComparisonExpression = (NullComparisonExpression) expression;
			assertEquals(toString(),                nullComparisonExpression.toParsedText());
			assertEquals(hasNot,                    nullComparisonExpression.hasNot());
			assertEquals(!this.expression.isNull(), nullComparisonExpression.hasExpression());

			this.expression.test(nullComparisonExpression.getExpression());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(expression);
			if (!expression.isNull()) {
				sb.append(SPACE);
			}
			sb.append(hasNot ? IS_NOT_NULL : IS_NULL);
			return sb.toString();
		}
	}

	public static final class NullExpressionTester extends AbstractExpressionTester {

		@Override
		public boolean isNull() {
			return true;
		}

		public void test(Expression expression) {
			assertInstance(expression, NullExpression.class);
		}

		@Override
		public String toString() {
			return ExpressionTools.EMPTY_STRING;
		}
	}

	public static final class NullIfExpressionTester extends AbstractDoubleEncapsulatedExpressionTester {

		protected NullIfExpressionTester(ExpressionTester firstExpression,
		                                 ExpressionTester secondExpression) {

			super(firstExpression, secondExpression);
      }

		@Override
		protected Class<? extends AbstractDoubleEncapsulatedExpression> expressionType() {
			return NullIfExpression.class;
		}

		@Override
		protected String identifier() {
			return NULLIF;
		}
	}

	public static final class NumericLiteralTester extends AbstractExpressionTester {

		private String number;

		protected NumericLiteralTester(String number) {
			super();
			this.number = number;
		}

		public void test(Expression expression) {
			assertInstance(expression, NumericLiteral.class);

			assertEquals(toString(), expression.toParsedText());
		}

		@Override
		public String toString() {
			return number;
		}
	}

	public static final class ObjectExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		protected ObjectExpressionTester(ExpressionTester identificationVariable) {
			super(identificationVariable);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return ObjectExpression.class;
		}

		@Override
		protected String identifier() {
			return OBJECT;
		}
	}

	public static final class OnClauseTester extends AbstractConditionalClauseTester {

		protected OnClauseTester(ExpressionTester conditionalExpression) {
			super(conditionalExpression);
		}

		@Override
		protected Class<? extends AbstractConditionalClause> expressionType() {
			return OnClause.class;
		}

		@Override
		protected String identifier() {
			return ON;
		}
	}

	public static final class OrderByClauseTester extends AbstractOrderByClauseTester {

		protected OrderByClauseTester(ExpressionTester orderByItems) {
			super(orderByItems);
		}

		@Override
		protected String identifier() {
			return ORDER_BY;
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);
			assertInstance(expression, OrderByClause.class);
		}
	}

	public static final class OrderByItemTester extends AbstractExpressionTester {

		public boolean hasSpaceAfterExpression;
		public boolean hasSpaceAfterNulls;
		public boolean hasSpaceAfterOrdering;
		private NullOrdering nullOrdering;
		public String nulls;
		private ExpressionTester orderByItem;
		private Ordering ordering;

		protected OrderByItemTester(ExpressionTester orderByItem,
		                            Ordering ordering,
		                            NullOrdering nullOrdering) {

			super();
			this.ordering     = ordering;
			this.orderByItem  = orderByItem;
			this.nullOrdering = nullOrdering;
			this.hasSpaceAfterExpression = ordering != Ordering.DEFAULT ||
			                               nullOrdering != NullOrdering.DEFAULT;
			this.hasSpaceAfterOrdering = ordering != Ordering.DEFAULT &&
			                             nullOrdering != NullOrdering.DEFAULT;
		}

		public void test(Expression expression) {
			assertInstance(expression, OrderByItem.class);

			OrderByItem orderByItem = (OrderByItem) expression;
			assertEquals(toString(), orderByItem.toParsedText());
			assertEquals(!this.orderByItem.isNull(), orderByItem.hasExpression());
			assertEquals(hasSpaceAfterExpression,    orderByItem.hasSpaceAfterExpression());
			assertEquals(hasSpaceAfterOrdering,      orderByItem.hasSpaceAfterOrdering());
			assertSame  (ordering, orderByItem.getOrdering());

			if (nulls != null) {
				assertEquals(nulls, orderByItem.getActualNullOrdering().toUpperCase());
			}
			else {
				assertSame(nullOrdering, orderByItem.getNullOrdering());
			}

			this.orderByItem.test(orderByItem.getExpression());
		}

		@Override
		public String toString() {

			StringBuilder sb = new StringBuilder();
			sb.append(orderByItem);

			if (hasSpaceAfterExpression) {
				sb.append(SPACE);
			}

			if (ordering != Ordering.DEFAULT) {
				sb.append(ordering.name());
			}

			if (hasSpaceAfterOrdering) {
				sb.append(SPACE);
			}

			if (nulls != null) {
				sb.append(nulls);
				if (hasSpaceAfterNulls) {
					sb.append(SPACE);
				}
			}
			else if (nullOrdering != NullOrdering.DEFAULT) {
				sb.append(nullOrdering.getIdentifier());
			}

			return sb.toString();
		}
	}

	public static final class OrderSiblingsByClauseTester extends AbstractOrderByClauseTester {

		protected OrderSiblingsByClauseTester(ExpressionTester orderByItems) {
			super(orderByItems);
		}

		@Override
		protected String identifier() {
			return ORDER_SIBLINGS_BY;
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);
			assertInstance(expression, OrderSiblingsByClause.class);
		}
	}

	public static final class OrExpressionTester extends LogicalExpressionTester {

		protected OrExpressionTester(ExpressionTester leftExpression,
		                             ExpressionTester rightExpression) {

			super(leftExpression, rightExpression);
		}

		@Override
		protected Class<? extends CompoundExpression> expressionType() {
			return OrExpression.class;
		}

		@Override
		protected String identifier() {
			return OR;
		}
	}

	public static final class RangeVariableDeclarationTester extends AbstractExpressionTester {

		private ExpressionTester abstractSchemaName;
		private boolean hasAs;
		public boolean hasSpaceAfterAbstractSchemaName;
		public boolean hasSpaceAfterAs;
		private ExpressionTester identificationVariable;

		protected RangeVariableDeclarationTester(ExpressionTester abstractSchemaName,
		                                         boolean hasAs,
		                                         ExpressionTester identificationVariable) {

			super();
			this.hasAs                           = hasAs;
			this.abstractSchemaName              = abstractSchemaName;
			this.identificationVariable          = identificationVariable;
			this.hasSpaceAfterAbstractSchemaName = hasAs || !identificationVariable.isNull();
			this.hasSpaceAfterAs                 = hasAs;

			if (identificationVariable instanceof IdentificationVariableTester &&
			    identificationVariable.isNull()) {

				this.hasSpaceAfterAbstractSchemaName = true;
			}
		}

		public void test(Expression expression) {
			assertInstance(expression, RangeVariableDeclaration.class);

			RangeVariableDeclaration rangeVariableDeclaration = (RangeVariableDeclaration) expression;
			assertEquals(toString(),                       rangeVariableDeclaration.toParsedText());
			assertEquals(hasAs,                            rangeVariableDeclaration.hasAs());
			assertEquals(hasSpaceAfterAs,                  rangeVariableDeclaration.hasSpaceAfterAs());
			assertEquals(!identificationVariable.isNull(), rangeVariableDeclaration.hasIdentificationVariable());
			assertEquals(hasSpaceAfterAbstractSchemaName,  rangeVariableDeclaration.hasSpaceAfterRootObject());

			abstractSchemaName.test(rangeVariableDeclaration.getRootObject());
			identificationVariable.test(rangeVariableDeclaration.getIdentificationVariable());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(abstractSchemaName);
			if (hasSpaceAfterAbstractSchemaName) {
				sb.append(SPACE);
			}
			if (hasAs) {
				sb.append(AS);
			}
			if (hasSpaceAfterAs) {
				sb.append(SPACE);
			}
			sb.append(identificationVariable);
			return sb.toString();
		}
	}

	public static final class RegexpExpressionTester extends AbstractExpressionTester {

		public boolean hasSpaceAfterIdentifier;
		private ExpressionTester patternValue;
		private ExpressionTester stringExpression;

		protected RegexpExpressionTester(ExpressionTester stringExpression,
		                                 ExpressionTester patternValue) {

			super();
			this.stringExpression        = stringExpression;
			this.patternValue            = patternValue;
			this.hasSpaceAfterIdentifier = !patternValue.isNull();
		}

		public void test(Expression expression) {
			assertInstance(expression, RegexpExpression.class);

			RegexpExpression regexpExpression = (RegexpExpression) expression;
			assertEquals(toString(),                 regexpExpression.toParsedText());
			assertEquals(!patternValue.isNull(),     regexpExpression.hasPatternValue());
			assertEquals(hasSpaceAfterIdentifier,    regexpExpression.hasSpaceAfterIdentifier());
			assertEquals(!stringExpression.isNull(), regexpExpression.hasSpaceAfterStringExpression());
			assertEquals(!stringExpression.isNull(), regexpExpression.hasStringExpression());

			stringExpression.test(regexpExpression.getStringExpression());
			patternValue    .test(regexpExpression.getPatternValue());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(stringExpression);
			if (!stringExpression.isNull()) {
				sb.append(SPACE);
			}
			sb.append(REGEXP);
			if (hasSpaceAfterIdentifier) {
				sb.append(SPACE);
			}
			sb.append(patternValue);
			return sb.toString();
		}
	}

	public static final class ResultVariableTester extends AbstractExpressionTester {

		private boolean hasAs;
		public boolean hasSpaceAfterAs;
		private ExpressionTester resultVariable;
		private ExpressionTester selectExpression;

		protected ResultVariableTester(ExpressionTester selectExpression,
		                               boolean hasAs,
		                               ExpressionTester resultVariable) {

			super();
			this.hasAs            = hasAs;
			this.hasSpaceAfterAs  = hasAs;
			this.selectExpression = selectExpression;
			this.resultVariable   = resultVariable;
		}

		public void test(Expression expression) {

			assertInstance(expression, ResultVariable.class);
			ResultVariable resultVariable = (ResultVariable) expression;

			assertEquals(toString(),                    resultVariable.toParsedText());
			assertEquals(hasAs,                         resultVariable.hasAs());
			assertEquals(hasSpaceAfterAs,               resultVariable.hasSpaceAfterAs());
			assertEquals(!this.resultVariable.isNull(), resultVariable.hasResultVariable());
			assertEquals(!selectExpression.isNull(),    resultVariable.hasSelectExpression());

			this.selectExpression.test(resultVariable.getSelectExpression());
			this.resultVariable  .test(resultVariable.getResultVariable());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(selectExpression);
			if (!selectExpression.isNull()) {
				sb.append(SPACE);
			}
			if (hasAs) {
				sb.append(AS);
			}
			if (hasSpaceAfterAs) {
				sb.append(SPACE);
			}
			sb.append(resultVariable);
			return sb.toString();
		}
	}

	public static final class SelectClauseTester extends AbstractSelectClauseTester {

		protected SelectClauseTester(ExpressionTester selectExpressions, boolean hasDistinct) {
			super(selectExpressions, hasDistinct);
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);
			assertInstance(expression, SelectClause.class);
		}
	}

	public static final class SelectStatementTester extends AbstractSelectStatementTester {

		public boolean hasSpaceBeforeOrderByClause;
		public boolean hasSpaceBeforeUnionClauses;
		private ExpressionTester orderByClause;
		private ExpressionTester unionClauses;

		protected SelectStatementTester(ExpressionTester selectClause,
		                                ExpressionTester fromClause,
		                                ExpressionTester whereClause,
		                                ExpressionTester groupByClause,
		                                ExpressionTester havingClause,
		                                ExpressionTester orderByClause,
		                                ExpressionTester unionClauses) {

			super(selectClause,
			      fromClause,
			      whereClause,
			      groupByClause,
			      havingClause);

			this.orderByClause = orderByClause;
			this.unionClauses  = unionClauses;

			this.hasSpaceBeforeOrderByClause = !orderByClause.isNull();
			this.hasSpaceBeforeUnionClauses  = !unionClauses .isNull();

//			hasSpaceAfterFrom                    |= (!fromClause   .isNull() && (!whereClause.isNull() || !groupByClause.isNull() &&  havingClause .isNull() && !orderByClause.isNull() && !unionClauses.isNull());
//			hasSpaceAfterWhere                   |= (!whereClause  .isNull() && groupByClause.isNull() && !havingClause .isNull() && !orderByClause.isNull() && !unionClauses .isNull());
//			hasSpaceAfterHierarchicalQueryClause |= (!whereClause  .isNull() && groupByClause.isNull() && !havingClause .isNull() && !orderByClause.isNull() && !unionClauses .isNull());
//			hasSpaceAfterGroupBy                 |= (!groupByClause.isNull() && havingClause .isNull() && !orderByClause.isNull() && !unionClauses.isNull());
		}

		@Override
		protected Class<? extends AbstractSelectStatement> expressionType() {
			return SelectStatement.class;
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);

			SelectStatement selectStatement = (SelectStatement) expression;
			assertEquals(!orderByClause.isNull(), selectStatement.hasOrderByClause());
//			assertEquals(hasSpaceBeforeOrderByClause, selectStatement.hasSpaceBeforeOrderBy());

			orderByClause.test(selectStatement.getOrderByClause());
			unionClauses .test(selectStatement.getUnionClauses());
		}

		@Override
		public String toString() {

			StringBuilder sb = new StringBuilder();
			sb.append(super.toString());
			if (hasSpaceBeforeOrderByClause && sb.charAt(sb.length() - 1) != ' ') {
				sb.append(SPACE);
			}
			sb.append(orderByClause);
			if (hasSpaceBeforeUnionClauses && sb.charAt(sb.length() - 1) != ' ') {
				sb.append(SPACE);
			}
			sb.append(unionClauses);
			return sb.toString();
		}
	}

	public static final class SimpleFromClauseTester extends AbstractFromClauseTester {

		protected SimpleFromClauseTester(ExpressionTester declaration,
				                           ExpressionTester hierarchicalQueryClause,
				                           ExpressionTester asOfClause) {

			super(declaration, hierarchicalQueryClause, asOfClause);
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);
			assertInstance(expression, SimpleFromClause.class);
		}
	}

	public static final class SimpleSelectClauseTester extends AbstractSelectClauseTester {

		protected SimpleSelectClauseTester(ExpressionTester selectExpressions, boolean hasDistinct) {
			super(selectExpressions, hasDistinct);
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);
			assertInstance(expression, SimpleSelectClause.class);
		}
	}

	public static final class SimpleSelectStatementTester extends AbstractSelectStatementTester {

		protected SimpleSelectStatementTester(ExpressionTester selectClause,
		                                      ExpressionTester fromClause,
		                                      ExpressionTester whereClause,
		                                      ExpressionTester groupByClause,
		                                      ExpressionTester havingClause) {

			super(selectClause, fromClause, whereClause, groupByClause, havingClause);
		}

		@Override
		protected Class<? extends AbstractSelectStatement> expressionType() {
			return SimpleSelectStatement.class;
		}
	}

	public static final class SizeExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		protected SizeExpressionTester(ExpressionTester collectionPath) {
			super(collectionPath);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return SizeExpression.class;
		}

		@Override
		protected String identifier() {
			return SIZE;
		}
	}

	public static final class SqrtExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		protected SqrtExpressionTester(ExpressionTester expression) {
			super(expression);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return SqrtExpression.class;
		}

		@Override
		protected String identifier() {
			return SQRT;
		}
	}

	public static final class StartWithClauseTester extends AbstractConditionalClauseTester {

		protected StartWithClauseTester(ExpressionTester expression) {
			super(expression);
		}

		@Override
		protected Class<StartWithClause> expressionType() {
			return StartWithClause.class;
		}

		@Override
		protected String identifier() {
			return START_WITH;
		}
	}

	public static final class StateFieldPathExpressionTester extends AbstractPathExpressionTester {

		protected StateFieldPathExpressionTester(ExpressionTester identificationVariable,
                                               String value) {

			super(identificationVariable, value);
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);
			assertInstance(expression, StateFieldPathExpression.class);
		}
	}

	public static final class StringLiteralTester extends AbstractExpressionTester {

		private boolean hasCloseQuote;
		private String literal;

		protected StringLiteralTester(String literal) {

			super();
			this.literal = literal;

			if (literal.length() > 1) {
				char lastChar = literal.charAt(literal.length() - 1);
				hasCloseQuote = (lastChar == SINGLE_QUOTE) || (lastChar == DOUBLE_QUOTE);
			}
		}

		public void test(Expression expression) {
			assertInstance(expression, StringLiteral.class);

			StringLiteral stringLiteral = (StringLiteral) expression;
			assertEquals(toString(),    stringLiteral.toString());
			assertEquals(hasCloseQuote, stringLiteral.hasCloseQuote());
		}

		@Override
		public String toString() {
			return literal;
		}
	}

	public static final class SubExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		protected SubExpressionTester(ExpressionTester expression) {
			super(expression);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return SubExpression.class;
		}

		@Override
		protected String identifier() {
			return ExpressionTools.EMPTY_STRING;
		}
	}

	public static final class SubstringExpressionTester extends AbstractTripleEncapsulatedExpressionTester {

		protected SubstringExpressionTester(ExpressionTester firstExpression,
		                                    ExpressionTester firstArithmeticExpression,
		                                    ExpressionTester secondArithmeticExpression) {

			super(firstExpression, firstArithmeticExpression, secondArithmeticExpression);
		}

		@Override
		protected Class<? extends AbstractTripleEncapsulatedExpression> expressionType() {
			return SubstringExpression.class;
		}

		@Override
		protected String identifier() {
			return SubstringExpression.SUBSTRING;
		}
	}

	public static final class SubtractionExpressionTester extends CompoundExpressionTester {

		protected SubtractionExpressionTester(ExpressionTester leftExpression,
		                                      ExpressionTester rightExpression) {

			super(leftExpression, rightExpression);
		}

		@Override
		protected Class<? extends CompoundExpression> expressionType() {
			return SubtractionExpression.class;
		}

		@Override
		protected String identifier() {
			return MINUS;
		}
	}

	public static final class SumFunctionTester extends AggregateFunctionTester {

		protected SumFunctionTester(ExpressionTester expression, boolean hasDistinct) {
			super(expression, hasDistinct);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return SumFunction.class;
		}

		@Override
		protected String identifier() {
			return SUM;
		}
	}

	public static final class TableExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		protected TableExpressionTester(ExpressionTester expression) {
			super(expression);
		}

		@Override
		protected Class<TableExpression> expressionType() {
			return TableExpression.class;
		}

		@Override
		protected String identifier() {
			return TABLE;
		}
	}

	public static final class TableVariableDeclarationTester extends AbstractExpressionTester {


		private boolean hasAs;
		public boolean hasSpaceAfterAs;
		public boolean hasSpaceAfterTableExpression;
		private ExpressionTester identificationVariable;
		private TableExpressionTester tableExpression;

		protected TableVariableDeclarationTester(TableExpressionTester tableExpression,
		                                         boolean hasAs,
		                                         ExpressionTester identificationVariable) {
			super();
			this.tableExpression              = tableExpression;
			this.hasAs                        = hasAs;
			this.identificationVariable       = identificationVariable;
			this.hasSpaceAfterTableExpression = hasAs || !identificationVariable.isNull();
			this.hasSpaceAfterAs              = hasAs && !identificationVariable.isNull();
		}

		public void test(Expression expression) {
			assertInstance(expression, Expression.class);

			TableVariableDeclaration tableVariableDeclaration = (TableVariableDeclaration) expression;
			assertEquals(toString(), tableVariableDeclaration.toParsedText());
			assertEquals(!identificationVariable.isNull(), tableVariableDeclaration.hasIdentificationVariable());
			assertEquals(hasAs,                            tableVariableDeclaration.hasAs());
			assertEquals(hasSpaceAfterAs,                  tableVariableDeclaration.hasSpaceAfterAs());
			assertEquals(hasSpaceAfterTableExpression,     tableVariableDeclaration.hasSpaceAfterTableExpression());

			tableExpression       .test(tableVariableDeclaration.getTableExpression());
			identificationVariable.test(tableVariableDeclaration.getIdentificationVariable());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(tableExpression);
			if (hasSpaceAfterTableExpression) {
				sb.append(SPACE);
			}
			if (hasAs) {
				sb.append(AS);
			}
			if (hasSpaceAfterAs) {
				sb.append(SPACE);
			}
			sb.append(identificationVariable);
			return sb.toString();
		}
	}

	public static final class TreatExpressionTester extends AbstractEncapsulatedExpressionTester {

		private ExpressionTester collectionValuedPathExpression;
		private ExpressionTester entityTypeName;
		private boolean hasAs;
		public boolean hasSpaceAfterAs;
		public boolean hasSpaceAfterCollectionValuedPathExpression;

		protected TreatExpressionTester(ExpressionTester collectionValuedPathExpression,
		                                boolean hasAs,
		                                ExpressionTester entityTypeName) {
			super();
			this.hasAs           = hasAs;
			this.entityTypeName  = entityTypeName;
			this.hasSpaceAfterAs = hasAs;
			this.hasSpaceAfterCollectionValuedPathExpression = true;
			this.collectionValuedPathExpression              = collectionValuedPathExpression;
		}

		@Override
		protected Class<? extends AbstractEncapsulatedExpression> expressionType() {
			return TreatExpression.class;
		}

		@Override
		protected boolean hasEncapsulatedExpression() {
			return !collectionValuedPathExpression.isNull() || !entityTypeName.isNull() || hasAs;
		}

		@Override
		protected String identifier() {
			return TREAT;
		}

		@Override
		protected void toStringEncapsulatedExpression(StringBuilder sb) {
			sb.append(collectionValuedPathExpression);
			if (hasSpaceAfterCollectionValuedPathExpression) {
				sb.append(SPACE);
			}
			if (hasAs) {
				sb.append(AS);
			}
			if (hasSpaceAfterAs) {
				sb.append(SPACE);
			}
			sb.append(entityTypeName);
		}
	}

	public static final class TrimExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		private boolean hasFrom;
		public boolean hasSpaceAfterFrom;
		public boolean hasSpaceAfterSpecification;
		public boolean hasSpaceAfterTrimCharacter;
		private Specification specification;
		private ExpressionTester trimCharacter;

		protected TrimExpressionTester(Specification specification,
		                               ExpressionTester stringPrimary,
		                               ExpressionTester trimCharacter,
		                               boolean hasFrom) {

			super(stringPrimary);
			this.specification              = specification;
			this.trimCharacter              = trimCharacter;
			this.hasFrom                    = hasFrom;
			this.hasSpaceAfterFrom          = hasFrom;
			this.hasSpaceAfterTrimCharacter = !trimCharacter.isNull();
			this.hasSpaceAfterSpecification = specification != Specification.DEFAULT;
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return TrimExpression.class;
		}

		@Override
		protected String identifier() {
			return TRIM;
		}

		@Override
		public void test(Expression expression) {
			super.test(expression);

			TrimExpression trimExpression = (TrimExpression) expression;
			assertEquals(hasFrom,                                trimExpression.hasFrom());
			assertEquals(hasSpaceAfterFrom,                      trimExpression.hasSpaceAfterFrom());
			assertEquals(hasSpaceAfterTrimCharacter,             trimExpression.hasSpaceAfterTrimCharacter());
			assertEquals(!trimCharacter.isNull(),                trimExpression.hasTrimCharacter());
			assertEquals(specification != Specification.DEFAULT, trimExpression.hasSpecification());
			assertEquals(hasSpaceAfterSpecification,             trimExpression.hasSpaceAfterSpecification());

			trimCharacter.test(trimExpression.getTrimCharacter());
		}

		@Override
		protected void toStringEncapsulatedExpression(StringBuilder sb) {
			if (specification != Specification.DEFAULT) {
				sb.append(specification);
			}
			if (hasSpaceAfterSpecification) {
				sb.append(SPACE);
			}
			sb.append(trimCharacter);
			if (hasSpaceAfterTrimCharacter) {
				sb.append(SPACE);
			}
			if (hasFrom) {
				sb.append(FROM);
			}
			if (hasSpaceAfterFrom) {
				sb.append(SPACE);
			}
			super.toStringEncapsulatedExpression(sb);
		}
	}

	public static final class TypeExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		protected TypeExpressionTester(ExpressionTester identificationVariable) {
			super(identificationVariable);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return TypeExpression.class;
		}

		@Override
		protected String identifier() {
			return TYPE;
		}
	}

	public static final class UnionClauseTester extends AbstractExpressionTester {

		private boolean hasAll;
		public boolean hasSpaceAfterAll;
		public boolean hasSpaceAfterIdentifier;
		private String identifier;
		private ExpressionTester subquery;

		protected UnionClauseTester(String identifier, boolean hasAll, ExpressionTester subquery) {
			super();
			this.identifier = identifier;
			this.hasAll     = hasAll;
			this.subquery   = subquery;
			this.hasSpaceAfterIdentifier = hasAll || !subquery.isNull();
			this.hasSpaceAfterAll        = hasAll && !subquery.isNull();
		}

		public void test(Expression expression) {
			assertInstance(expression, UnionClause.class);

			UnionClause unionClause = (UnionClause) expression;
			assertEquals(toString(),              unionClause.toParsedText());
			assertEquals(identifier,              unionClause.getIdentifier());
			assertEquals(hasSpaceAfterIdentifier, unionClause.hasSpaceAfterIdentifier());
			assertEquals(hasAll,                  unionClause.hasAll());
			assertEquals(!subquery.isNull(),      unionClause.hasQuery());
			assertEquals(hasSpaceAfterAll,        unionClause.hasSpaceAfterAll());

			subquery.test(unionClause.getQuery());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(identifier);
			if (hasSpaceAfterIdentifier) {
				sb.append(SPACE);
			}
			if (hasAll) {
				sb.append(ALL);
			}
			if (hasSpaceAfterAll) {
				sb.append(SPACE);
			}
			sb.append(subquery);
			return sb.toString();
		}
	}

	public static final class UnknownExpressionTester extends AbstractExpressionTester {

		private final String unknownText;

		protected UnknownExpressionTester(String unknownText) {
			super();
			this.unknownText = unknownText;
		}

		public void test(Expression expression) {
			assertInstance(expression, UnknownExpression.class);

			UnknownExpression unknownExpression = (UnknownExpression) expression;
			assertEquals(toString(), unknownExpression.toParsedText());
		}

		@Override
		public String toString() {
			return unknownText;
		}
	}

	public static final class UpdateClauseTester extends AbstractExpressionTester {

		public boolean hasSet;
		public boolean hasSpaceAfterRangeVariableDeclaration;
		public boolean hasSpaceAfterSet;
		public boolean hasSpaceAfterUpdate;
		private ExpressionTester rangeVariableDeclaration;
		private ExpressionTester updateItems;

		protected UpdateClauseTester(ExpressionTester rangeVariableDeclaration,
		                             ExpressionTester updateItems) {

			super();
			this.hasSet                                = true;
			this.hasSpaceAfterSet                      = true;
			this.hasSpaceAfterUpdate                   = true;
			this.updateItems                           = updateItems;
			this.rangeVariableDeclaration              = rangeVariableDeclaration;
			this.hasSpaceAfterRangeVariableDeclaration = !rangeVariableDeclaration.isNull();
		}

		public void test(Expression expression) {
			assertInstance(expression, UpdateClause.class);

			UpdateClause updateClause = (UpdateClause) expression;
			assertEquals(toString(),                            updateClause.toParsedText());
			assertEquals(!rangeVariableDeclaration.isNull(),    updateClause.hasRangeVariableDeclaration());
			assertEquals(hasSet,                                updateClause.hasSet());
			assertEquals(hasSpaceAfterSet,                      updateClause.hasSpaceAfterSet());
			assertEquals(hasSpaceAfterUpdate,                   updateClause.hasSpaceAfterUpdate());
			assertEquals(!updateItems.isNull(),                 updateClause.hasUpdateItems());
			assertEquals(hasSpaceAfterRangeVariableDeclaration, updateClause.hasSpaceAfterRangeVariableDeclaration());

			rangeVariableDeclaration.test(updateClause.getRangeVariableDeclaration());
			updateItems.test(updateClause.getUpdateItems());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(UPDATE);
			if (hasSpaceAfterUpdate) {
				sb.append(SPACE);
			}
			sb.append(rangeVariableDeclaration);
			if (hasSpaceAfterRangeVariableDeclaration) {
				sb.append(SPACE);
			}
			if (hasSet) {
				sb.append(SET);
			}
			if (hasSpaceAfterSet) {
				sb.append(SPACE);
			}
			sb.append(updateItems);
			return sb.toString();
		}
	}

	public static final class UpdateItemTester extends AbstractExpressionTester {

		public boolean hasEqualSign;
		public boolean hasSpaceAfterEqualSign;
		public boolean hasSpaceAfterStateFieldPathExpression;
		private ExpressionTester newValue;
		private ExpressionTester stateFieldPathExpression;

		protected UpdateItemTester(ExpressionTester stateFieldPathExpression,
		                           ExpressionTester newValue) {

			super();
			this.stateFieldPathExpression = stateFieldPathExpression;
			this.newValue = newValue;
			this.hasSpaceAfterStateFieldPathExpression = true;
			this.hasSpaceAfterEqualSign = true;
			this.hasEqualSign = true;
		}

		public void test(Expression expression) {
			assertInstance(expression, UpdateItem.class);

			UpdateItem updateItem = (UpdateItem) expression;
			assertEquals(toString(),                            updateItem.toParsedText());
			assertEquals(hasEqualSign,                          updateItem.hasEqualSign());
			assertEquals(!newValue.isNull(),                    updateItem.hasNewValue());
			assertEquals(hasSpaceAfterEqualSign,                updateItem.hasSpaceAfterEqualSign());
			assertEquals(hasSpaceAfterStateFieldPathExpression, updateItem.hasSpaceAfterStateFieldPathExpression());
			assertEquals(!stateFieldPathExpression.isNull(),    updateItem.hasStateFieldPathExpression());

			stateFieldPathExpression.test(updateItem.getStateFieldPathExpression());
			newValue.test(updateItem.getNewValue());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(stateFieldPathExpression);
			if (hasSpaceAfterStateFieldPathExpression) {
				sb.append(SPACE);
			}
			if (hasEqualSign) {
				sb.append(EQUAL);
			}
			if (hasSpaceAfterEqualSign) {
				sb.append(SPACE);
			}
			sb.append(newValue);
			return sb.toString();
		}
	}

	public static final class UpdateStatementTester extends AbstractExpressionTester {

		public boolean hasSpaceAfterUpdateClause;
		private ExpressionTester updateClause;
		private ExpressionTester whereClause;

		protected UpdateStatementTester(ExpressionTester updateClause, ExpressionTester whereClause) {
			super();
			this.updateClause = updateClause;
			this.whereClause  = whereClause;
			this.hasSpaceAfterUpdateClause = !whereClause.isNull();
		}

		public void test(Expression expression) {
			assertInstance(expression, UpdateStatement.class);

			UpdateStatement updateStatement = (UpdateStatement) expression;
			assertEquals(toString(), updateStatement.toParsedText());
			assertEquals(hasSpaceAfterUpdateClause, updateStatement.hasSpaceAfterUpdateClause());
			assertEquals(!whereClause.isNull(),     updateStatement.hasWhereClause());

			updateClause.test(updateStatement.getUpdateClause());
			whereClause .test(updateStatement.getWhereClause());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(updateClause);

			if (hasSpaceAfterUpdateClause) {
				sb.append(SPACE);
			}

			sb.append(whereClause);
			return sb.toString();
		}
	}

	public static final class UpperExpressionTester extends AbstractSingleEncapsulatedExpressionTester {

		protected UpperExpressionTester(ExpressionTester expression) {
			super(expression);
		}

		@Override
		protected Class<? extends AbstractSingleEncapsulatedExpression> expressionType() {
			return UpperExpression.class;
		}

		@Override
		protected String identifier() {
			return UPPER;
		}
	}

	public static final class WhenClauseTester extends AbstractExpressionTester {

		public boolean hasSpaceAfterThen;
		public boolean hasSpaceAfterWhen;
		public boolean hasSpaceAfterWhenExpression;
		public boolean hasThen;
		private ExpressionTester thenExpression;
		private ExpressionTester whenExpression;

		protected WhenClauseTester(ExpressionTester whenExpression, ExpressionTester thenExpression) {
			super();
			this.hasSpaceAfterWhenExpression = true;
			this.hasSpaceAfterThen           = true;
			this.hasSpaceAfterWhen           = true;
			this.hasThen                     = true;
			this.thenExpression              = thenExpression;
			this.whenExpression              = whenExpression;
		}

		public void test(Expression expression) {
			assertInstance(expression, WhenClause.class);

			WhenClause whenClause = (WhenClause) expression;
			assertEquals(toString(),                  whenClause.toParsedText());
			assertEquals(!whenExpression.isNull(),    whenClause.hasWhenExpression());
			assertEquals(hasSpaceAfterWhenExpression, whenClause.hasSpaceAfterWhenExpression());
			assertEquals(hasSpaceAfterThen,           whenClause.hasSpaceAfterThen());
			assertEquals(hasSpaceAfterWhen,           whenClause.hasSpaceAfterWhen());
			assertEquals(hasThen,                     whenClause.hasThen());
			assertEquals(!thenExpression.isNull(),    whenClause.hasThenExpression());

			whenExpression.test(whenClause.getWhenExpression());
			thenExpression.test(whenClause.getThenExpression());
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(WHEN);
			if (hasSpaceAfterWhen) {
				sb.append(SPACE);
			}
			sb.append(whenExpression);
			if (hasSpaceAfterWhenExpression) {
				sb.append(SPACE);
			}
			if (hasThen) {
				sb.append(THEN);
			}
			if (hasSpaceAfterWhen) {
				sb.append(SPACE);
			}
			sb.append(thenExpression);
			return sb.toString();
		}
	}

	public static final class WhereClauseTester extends AbstractConditionalClauseTester {

		protected WhereClauseTester(ExpressionTester conditionalExpression) {
			super(conditionalExpression);
		}

		@Override
		protected Class<? extends AbstractConditionalClause> expressionType() {
			return WhereClause.class;
		}

		@Override
		protected String identifier() {
			return WHERE;
		}
	}
}