/*******************************************************************************
 * Copyright (c) 2011, 2014 Oracle and/or its affiliates. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
 * which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation
 *
 ******************************************************************************/
package org.eclipse.persistence.jpa.jpql.tools;

import org.eclipse.persistence.jpa.jpql.parser.AbstractExpression;
import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar;
import org.eclipse.persistence.jpa.jpql.parser.JPQLStatementBNF;
import org.eclipse.persistence.jpa.jpql.tools.model.IJPQLQueryBuilder;
import org.eclipse.persistence.jpa.jpql.tools.model.IJPQLQueryFormatter;
import org.eclipse.persistence.jpa.jpql.tools.model.query.AbstractPathExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.AbstractSchemaNameStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.AbstractTraverseChildrenVisitor;
import org.eclipse.persistence.jpa.jpql.tools.model.query.CollectionValuedPathExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.ConstructorExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.EntityTypeLiteralStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.IdentificationVariableStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.JPQLQueryStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.OrderByItemStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.ResultVariableStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.SelectStatementStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.StateFieldPathExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.StateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.TreatExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.spi.IManagedTypeProvider;
import org.eclipse.persistence.jpa.jpql.tools.spi.IMapping;
import org.eclipse.persistence.jpa.jpql.tools.spi.IType;

/**
 * The abstract implementation providing refactoring support for JPQL queries.
 * <p>
 * Provisional API: This interface is part of an interim API that is still under development and
 * expected to change significantly before reaching stability. It is available at this early stage
 * to solicit feedback from pioneering adopters on the understanding that any code that uses this
 * API will almost certainly be broken (repeatedly) as the API evolves.
 *
 * @see DefaultRefactoringTool
 * @see EclipseLinkRefactoringTool
 *
 * @version 2.4
 * @since 2.4
 * @author Pascal Filion
 */
public abstract class RefactoringTool extends AbstractRefactoringTool {

	/**
	 * The builder that creates the {@link StateObject} representation of the JPQL query.
	 */
	private IJPQLQueryBuilder jpqlQueryBuilder;

	/**
	 * The formatter that converts a {@link StateObject} representation of the JPQL query into a
	 * string representation.
	 */
	private IJPQLQueryFormatter jpqlQueryFormatter;

	/**
	 * The editable representation of the JPQL query.
	 */
	private JPQLQueryStateObject stateObject;

	/**
	 * Creates a new <code>RefactoringTool</code>.
	 *
	 * @param managedTypeProvider The external form of a provider that gives access to the JPA metadata
	 * @param jpqlQueryBuilder The builder that creates the {@link StateObject} representation of the
	 * JPQL query
	 * @param jpqlQuery The JPQL query to manipulate
	 */
	protected RefactoringTool(IManagedTypeProvider managedTypeProvider,
	                          IJPQLQueryBuilder jpqlQueryBuilder,
	                          CharSequence jpqlQuery) {

		this(managedTypeProvider, jpqlQueryBuilder, jpqlQuery, JPQLStatementBNF.ID);
	}

	/**
	 * Creates a new <code>RefactoringTool</code>.
	 *
	 * @param managedTypeProvider The external form of a provider that gives access to the JPA metadata
	 * @param jpqlQueryBuilder The builder that creates the {@link StateObject} representation of the
	 * JPQL query
	 * @param jpqlFragment The JPQL query to manipulate or a single JPQL fragment, which is parsed
	 * using the JPQL query BNF identifier by the given ID
	 * @param jpqlQueryBNFId The unique identifier of the {@link
     * org.eclipse.persistence.jpa.jpql.parser.JPQLQueryBNF JPQLQueryBNF} that determines how to parse the JPQL fragment
	 */
	protected RefactoringTool(IManagedTypeProvider managedTypeProvider,
	                          IJPQLQueryBuilder jpqlQueryBuilder,
	                          CharSequence jpqlFragment,
	                          String jpqlQueryBNFId) {

		super(jpqlFragment, managedTypeProvider, jpqlQueryBNFId);
		this.jpqlQueryBuilder = jpqlQueryBuilder;
	}

	/**
	 * Creates the visitor that will traverse the {@link StateObject} representation of the JPQL
	 * query and will rename the fully qualified class name.
	 *
	 * @param oldClassName The current name of the class to rename
	 * @param newClassName The new name of the class
	 * @return A new {@link ClassNameRenamer}
	 */
	protected ClassNameRenamer buildClassNameRenamer(String oldClassName, String newClassName) {
		return new ClassNameRenamer(oldClassName, newClassName);
	}

	/**
	 * Creates the visitor that will traverse the {@link StateObject} representation of the JPQL
	 * query and will rename the entity name.
	 *
	 * @param oldEntityName The current name of the entity to rename
	 * @param newEntityName The new name of the entity
	 * @return A new {@link EntityNameRenamer}
	 */
	protected EntityNameRenamer buildEntityNameRenamer(String oldEntityName, String newEntityName) {
		return new EntityNameRenamer(oldEntityName, newEntityName);
	}

	/**
	 * Creates the visitor that will traverse the {@link StateObject} representation of the JPQL
	 * query and will rename the enum constant.
	 *
	 * @param oldClassName The new name of the enum constant
	 * @param newClassName The current name of the enum constant to rename
	 * @return A new {@link EnumConstantRenamer}
	 */
	protected EnumConstantRenamer buildEnumConstantRenamer(String oldClassName, String newClassName) {
		return new EnumConstantRenamer(getManagedTypeProvider(), oldClassName, newClassName);
	}

	/**
	 * Creates the visitor that will traverse the {@link StateObject} representation of the JPQL
	 * query and will rename a type's attribute name.
	 *
	 * @param typeName The fully qualified name of the type that got one of its attributes renamed
	 * @param oldFieldName The current name of the attribute to rename
	 * @param newFieldName The new name of the attribute
	 * @return A new {@link org.eclipse.persistence.jpa.jpql.tools.BasicRefactoringTool.AttributeNameRenamer
     * AttributeNameRenamer}
	 */
	protected FieldNameRenamer buildFieldNameRenamer(String typeName,
	                                                 String oldFieldName,
	                                                 String newFieldName) {

		return new FieldNameRenamer(getManagedTypeProvider(), typeName, oldFieldName, newFieldName);
	}

	/**
	 * Creates a new formatter that will convert the {@link StateObject} representation of the JPQL
	 * query, once the refactoring occurred into a string.
	 *
	 * @return A new concrete instance of {@link IJPQLQueryFormatter}
	 */
	protected abstract IJPQLQueryFormatter buildFormatter();

	/**
	 * Creates a new {@link JPQLQueryContext} that can retrieve information from the declaration
	 * portion of the JPQL query.
	 * <p>
	 * <b>NOTE:</b> This is temporary because the {@link StateObject} API does not have that
	 * knowledge yet.
	 *
	 * @return A new concrete instance of {@link JPQLQueryContext}
	 */
	protected abstract JPQLQueryContext buildJPQLQueryContext();

	/**
	 * Creates the visitor that will traverse the {@link StateObject} representation of the JPQL
	 * query and will rename a result variable.
	 *
	 * @param oldVariableName The current result variable name
	 * @param newVariableName The new name of the result variable
	 * @return A new {@link ResultVariableNameRenamer}
	 */
	protected ResultVariableNameRenamer buildResultVariableNameRenamer(String oldVariableName,
	                                                                   String newVariableName) {

		return new ResultVariableNameRenamer(oldVariableName, newVariableName);
	}

	/**
	 * Creates the {@link StateObject} representation of the JPQL fragment to manipulate.
	 *
	 * @return The parsed and editable JPQL query
	 * @see #getStateObject()
	 */
	protected JPQLQueryStateObject buildStateObject() {
		return jpqlQueryBuilder.buildStateObject(
			getManagedTypeProvider(),
			getJPQLFragment(),
			getJPQLQueryBNFId(),
			isTolerant()
		);
	}

	/**
	 * Creates the visitor that will traverse the {@link StateObject} representation of the JPQL
	 * query and will rename an identification variable.
	 *
	 * @param oldVariableName The current identification variable name
	 * @param newVariableName The new name of the identification variable
	 * @return A new {@link VariableNameRenamer}
	 */
	protected VariableNameRenamer buildVariableNameRenamer(String oldVariableName,
	                                                       String newVariableName) {

		return new VariableNameRenamer(oldVariableName, newVariableName);
	}

	/**
	 * Returns the {@link IJPQLQueryFormatter} that creates an accurate representation of the {@link
	 * StateObject}, i.e. that output the JPQL query with the case used for the JPQL identifier.
	 *
	 * @return An instance of {@link IJPQLQueryFormatter}
	 */
	public IJPQLQueryFormatter getFormatter() {
		if (jpqlQueryFormatter == null) {
			jpqlQueryFormatter = buildFormatter();
		}
		return jpqlQueryFormatter;
	}

	/**
	 * Returns the {@link JPQLGrammar} that is associated with this builder.
	 *
	 * @return The {@link JPQLGrammar} that was used to parse the JPQL query or JPQL fragments
	 */
	public JPQLGrammar getGrammar() {
		return jpqlQueryBuilder.getGrammar();
	}

	/**
	 * Returns the builder that creates the {@link StateObject} representation of the JPQL query.
	 *
	 * @return The builder of the {@link StateObject} to be manipulated
	 */
	public IJPQLQueryBuilder getJPQLQueryBuilder() {
		return jpqlQueryBuilder;
	}

	/**
	 * Returns the {@link StateObject} representation of the JPQL query or JPQL fragment that was parsed.
	 *
	 * @return The editable state model
	 */
	public JPQLQueryStateObject getStateObject() {
		if (stateObject == null) {
			stateObject = buildStateObject();
		}
		return stateObject;
	}

	/**
	 * Renames a fully qualified class name.
	 *
	 * @param oldClassName The current fully qualified class name of the class to rename
	 * @param newClassName The new fully qualified class name
	 */
	public void renameClassName(String oldClassName, String newClassName) {
		ClassNameRenamer renamer = buildClassNameRenamer(oldClassName, newClassName);
		getStateObject().accept(renamer);
	}

	/**
	 * Renames a given entity name.
	 *
	 * @param oldEntityName The current name of the entity to rename
	 * @param newEntityName The new name of the entity
	 */
	public void renameEntityName(String oldEntityName, String newEntityName) {
		EntityNameRenamer renamer = buildEntityNameRenamer(oldEntityName, newEntityName);
		getStateObject().accept(renamer);
	}

	/**
	 * Renames an enum constant, which has to be fully qualified.
	 *
	 * @param oldEnumConstant The current fully qualified name of the enum constant to rename
	 * @param newEnumConstant The new fully qualified name of the enum constant
	 */
	public void renameEnumConstant(String oldEnumConstant, String newEnumConstant) {
		EnumConstantRenamer renamer = buildEnumConstantRenamer(oldEnumConstant, newEnumConstant);
		getStateObject().accept(renamer);
	}

	/**
	 * Renames a field from the given type.
	 *
	 * @param type The Java class from which the change originate
	 * @param oldFieldName The current name of the attribute to rename
	 * @param newFieldName The new name of the attribute
	 */
	public void renameField(Class<?> type, String oldFieldName, String newFieldName) {
		renameField(type.getName(), oldFieldName, newFieldName);
	}

	/**
	 * Renames a field from the given type.
	 *
	 * @param type The {@link IType} from which the change originate
	 * @param oldFieldName The current name of the attribute to rename
	 * @param newFieldName The new name of the attribute
	 */
	public void renameField(IType type, String oldFieldName, String newFieldName) {
		renameField(type.getName(), oldFieldName, newFieldName);
	}

	/**
	 * Renames a field from the given type.
	 *
	 * @param typeName The fully qualified name of the type that got one of its attributes renamed
	 * @param oldFieldName The current name of the attribute to rename
	 * @param newFieldName The new name of the attribute
	 */
	public void renameField(String typeName, String oldFieldName, String newFieldName) {
		FieldNameRenamer renamer = buildFieldNameRenamer(typeName, oldFieldName, newFieldName);
		getStateObject().accept(renamer);
	}

	/**
	 * Renames a result variable name.
	 *
	 * @param oldVariableName The current identification variable name
	 * @param newVariableName The new name of the identification variable
	 */
	public void renameResultVariable(String oldVariableName, String newVariableName) {
		ResultVariableNameRenamer renamer = buildResultVariableNameRenamer(oldVariableName, newVariableName);
		getStateObject().accept(renamer);
	}

	/**
	 * Renames a variable name.
	 *
	 * @param oldVariableName The current identification variable name
	 * @param newVariableName The new name of the identification variable
	 */
	public void renameVariable(String oldVariableName, String newVariableName) {
		VariableNameRenamer renamer = buildVariableNameRenamer(oldVariableName, newVariableName);
		getStateObject().accept(renamer);
	}

	/**
	 * Sets the {@link IJPQLQueryFormatter} that creates an accurate representation of the {@link
	 * StateObject}, i.e. that output the JPQL query with the case used for the JPQL identifier.
	 *
	 * @param jpqlQueryFormatter This formatter converts a {@link StateObject} representation of the
	 * JPQL query into a string representation
	 */
	public void setFormatter(IJPQLQueryFormatter jpqlQueryFormatter) {
		this.jpqlQueryFormatter = jpqlQueryFormatter;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toActualText() {
		return getFormatter().toString(getStateObject());
	}

	/**
	 * This visitor renames a fully qualified class name.
	 */
	protected static class ClassNameRenamer extends AbstractTraverseChildrenVisitor {

		/**
		 * The {@link StateObjectUpdater} that updates the class name when notified.
		 */
		protected StateObjectUpdater<ConstructorExpressionStateObject> constructorUpdater;

		/**
		 * The current name of the class to rename.
		 */
		protected final String newClassName;

		/**
		 * The new name of the class.
		 */
		protected final String oldClassName;

		/**
		 * The {@link StateObjectUpdater} that updates the state field path expression when notified.
		 */
		protected StateObjectUpdater<StateFieldPathExpressionStateObject> pathExpressionUpdater;

		/**
		 * Creates a new <code>ClassNameRenamer</code>.
		 *
		 * @param oldClassName The current name of the class to rename
		 * @param newClassName The new name of the class
		 */
		public ClassNameRenamer(String oldClassName, String newClassName) {
			super();
			this.oldClassName = oldClassName;
			this.newClassName = newClassName;
		}

		protected StateObjectUpdater<ConstructorExpressionStateObject> buildConstructorUpdater() {
			return new StateObjectUpdater<ConstructorExpressionStateObject>() {
				public void update(ConstructorExpressionStateObject stateObject, CharSequence newValue) {
					stateObject.setClassName(newValue);
				}
			};
		}

		protected StateObjectUpdater<StateFieldPathExpressionStateObject> buildPathExpressionStateObjectUpdater() {
			return new StateObjectUpdater<StateFieldPathExpressionStateObject>() {
				public void update(StateFieldPathExpressionStateObject stateObject, CharSequence newValue) {
					stateObject.setPath(newValue);
				}
			};
		}

		protected StateObjectUpdater<ConstructorExpressionStateObject> constructorUpdater() {
			if (constructorUpdater == null ){
				constructorUpdater = buildConstructorUpdater();
			}
			return constructorUpdater;
		}

		protected StateObjectUpdater<StateFieldPathExpressionStateObject> pathExpressionUpdater() {
			if (pathExpressionUpdater == null ){
				pathExpressionUpdater = buildPathExpressionStateObjectUpdater();
			}
			return pathExpressionUpdater;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(ConstructorExpressionStateObject stateObject) {
			visit(stateObject, stateObject.getClassName(), constructorUpdater());
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(StateFieldPathExpressionStateObject stateObject) {
			visit(stateObject, stateObject.getPath(), pathExpressionUpdater());
		}

		/**
		 * Visits the given {@link StateObject} and if its value is the same as the old class name or
		 * if the value represents an inner class of that old class name, then the given {@link
		 * StateObjectUpdater} will be notified to replace the value.
		 *
		 * @param stateObject The {@link StateObject} that is being visited
		 * @param value The value to check if it's the old class name
		 * @param updater The {@link StateObjectUpdater} is notified when to replace the value
		 */
		protected <T extends StateObject> void visit(T stateObject,
		                                             String value,
		                                             StateObjectUpdater<T> updater) {

			if (oldClassName.equals(value)) {
				updater.update(stateObject, newClassName);
			}
			else {
				int index = value.lastIndexOf(AbstractExpression.DOT);

				// Traverse the value by retrieving a fragment up to the last dot (based on the index)
				for (; index > -1; index = value.lastIndexOf(AbstractExpression.DOT, index - 1)) {
					String fragment = value.substring(0, index);

					if (oldClassName.equals(fragment)) {
						StringBuilder newValue = new StringBuilder(newClassName);
						newValue.append(AbstractExpression.DOT);

						// The path does not end with '.'
						if (index + 1 < value.length()) {
							newValue.append(value.substring(index + 1));
						}

						updater.update(stateObject, newValue);
						break;
					}
					// No need to continue the search
					else if (fragment.length() < oldClassName.length()) {
						break;
					}
				}
			}
		}
	}

	/**
	 * This visitor renames an entity name. There are three possible {@link StateObject StateObjects}
	 * that can represent an entity name:
	 * <ul>
	 * <li>
	 * {@link AbstractSchemaNameStateObject}:
	 * <i>Employee</i> in<br><br>
	 * <code><b>SELECT</b> e<br>
	 * <b>FROM</b> Employee e</code><br><br>
	 * </li>
	 * <li>
	 * {@link EntityTypeLiteralStateObject}:
	 * <i>Exempt</i> in<br><br>
	 * <code><b>SELECT CASE TYPE</b>(e) <b>WHEN</b> Exempt <b>THEN</b> 'Exempt'</code><p>
	 * <code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
	 * <b>ELSE</b> 'NONE'</code><p>
	 * <code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
	 * <b>END</b></code><p>
	 * <code><b>FROM</b> Employee e</code><br><br>
	 * </li>
	 * <li>
	 * {@link IdentificationVariableStateObject}:
	 * <i>Exempt</i> in<br><br>
	 * <code><b>SELECT</b> e<br>
	 * <b>FROM</b> Employee e</code><br>
	 * <b>WHERE TYPE</b>(e) {@literal <>} Exempt
	 * </li>
	 * </ul>
	 */
	protected static class EntityNameRenamer extends AbstractTraverseChildrenVisitor {

		/**
		 * The current name of the entity to rename.
		 */
		protected final String newEntityName;

		/**
		 * The new name of the entity.
		 */
		protected final String oldEntityName;

		/**
		 * Creates a new <code>EntityNameRenamer</code>.
		 *
		 * @param oldEntityName The current name of the entity to rename
		 * @param newEntityName The new name of the entity
		 */
		public EntityNameRenamer(String oldEntityName, String newEntityName) {
			super();
			this.oldEntityName = oldEntityName;
			this.newEntityName = newEntityName;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(AbstractSchemaNameStateObject stateObject) {
			if (oldEntityName.equals(stateObject.getText())) {
				stateObject.setText(newEntityName);
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(EntityTypeLiteralStateObject stateObject) {
			if (oldEntityName.equals(stateObject.getText())) {
				stateObject.setText(newEntityName);
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(IdentificationVariableStateObject stateObject) {
			if (oldEntityName.equals(stateObject.getText())) {
				stateObject.setText(newEntityName);
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(TreatExpressionStateObject stateObject) {
			if (oldEntityName.equals(stateObject.getEntityTypeName())) {
				stateObject.setEntityTypeName(newEntityName);
			}
		}
	}

	/**
	 * This visitor renames an enum constant. An enum constant is represented by a path expression.
	 */
	protected static class EnumConstantRenamer extends AbstractTraverseChildrenVisitor {

		/**
		 * The external form of a provider that gives access to the JPA metadata.
		 */
		protected final IManagedTypeProvider managedTypeProvider;

		/**
		 * The current name of the enum constant to rename.
		 */
		protected final String newEnumConstant;

		/**
		 * The new name of the enum constant.
		 */
		protected final String oldEnumConstant;

		/**
		 * Creates a new <code>ClassNameRenamer</code>.
		 *
		 * @param managedTypeProvider The provider of managed types
		 * @param oldEnumConstant The new name of the enum constant
		 * @param newEnumConstant The current name of the enum constant to rename
		 */
		public EnumConstantRenamer(IManagedTypeProvider managedTypeProvider,
		                           String oldEnumConstant,
		                           String newEnumConstant) {

			super();
			this.oldEnumConstant     = oldEnumConstant;
			this.newEnumConstant     = newEnumConstant;
			this.managedTypeProvider = managedTypeProvider;
		}

		protected void renameEnumConstant(AbstractPathExpressionStateObject stateObject) {

			String path = stateObject.toString();

			if (path.equals(oldEnumConstant)) {

				IType type = managedTypeProvider.getTypeRepository().getEnumType(path);

				if (type != null) {
					stateObject.setPath(newEnumConstant);
				}
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(CollectionValuedPathExpressionStateObject stateObject) {
			renameEnumConstant(stateObject);
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(StateFieldPathExpressionStateObject stateObject) {
			renameEnumConstant(stateObject);
		}
	}

	/**
	 * This visitor renames any segment of a path expression.
	 */
	protected static class FieldNameRenamer extends AbstractTraverseChildrenVisitor {

		/**
		 * The external form of a provider that gives access to the JPA metadata.
		 */
		protected final IManagedTypeProvider managedTypeProvider;

		/**
		 * The new name of the attribute.
		 */
		protected final String newFieldName;

		/**
		 * The current name of the attribute to rename.
		 */
		protected final String oldFieldName;

		/**
		 * The fully qualified name of the type that got one of its attributes renamed.
		 */
		protected final String typeName;

		/**
		 * Creates a new <code>AttributeNameRenamer</code>.
		 *
		 * @param typeName The fully qualified name of the type that got one of its attributes renamed
		 * @param oldFieldName The current name of the attribute to rename
		 * @param newFieldName The new name of the attribute
		 */
		public FieldNameRenamer(IManagedTypeProvider managedTypeProvider,
		                        String typeName,
		                        String oldFieldName,
		                        String newFieldName) {

			super();
			this.typeName            = typeName;
			this.oldFieldName        = oldFieldName;
			this.newFieldName        = newFieldName;
			this.managedTypeProvider = managedTypeProvider;
		}

		/**
		 * Performs the rename on the path expression.
		 *
		 * @param stateObject The {@link AbstractPathExpressionStateObject} being visited, which may
		 * have to have its path renamed
		 */
		protected void rename(AbstractPathExpressionStateObject stateObject) {

			// Now traverse the path expression after the identification variable
			for (int index = 1, count = stateObject.itemsSize(); index < count; index++) {

				// Retrieve the mapping for the path at the current position
				IMapping mapping = stateObject.getMapping(index);

				if (mapping == null) {
					break;
				}

				// The name matches
				if (mapping.getName().equals(oldFieldName)) {

					// Make sure the field name is from the right type
					String parentTypeName = mapping.getParent().getType().getName();

					if (parentTypeName.equals(typeName)) {
						stateObject.setPath(index, newFieldName);
						break;
					}
				}
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(CollectionValuedPathExpressionStateObject stateObject) {
			rename(stateObject);
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(StateFieldPathExpressionStateObject stateObject) {
			rename(stateObject);
		}
	}

	/**
	 * This visitor renames all the result variables found in the JPQL query.
	 */
	protected static class ResultVariableNameRenamer extends AbstractTraverseChildrenVisitor {

		/**
		 * The new name of the result variable.
		 */
		protected final String newVariableName;

		/**
		 * The current result variable name.
		 */
		protected final String oldVariableName;

		/**
		 * Makes sure an identification variable is renamed only when it's used by an order by item.
		 */
		protected boolean renameIdentificationVariable;

		/**
		 * Creates a new <code>ResultVariableNameRenamer</code>.
		 *
		 * @param oldVariableName The current result variable name
		 * @param newVariableName The new name of the result variable
		 */
		public ResultVariableNameRenamer(String oldVariableName, String newVariableName) {
			super();
			this.oldVariableName = oldVariableName;
			this.newVariableName = newVariableName;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(IdentificationVariableStateObject stateObject) {

			if (renameIdentificationVariable &&
			    oldVariableName.equalsIgnoreCase(stateObject.getText())) {

				stateObject.setText(newVariableName);
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(JPQLQueryStateObject stateObject) {
			if (stateObject.hasQueryStatement()) {
				stateObject.getQueryStatement().accept(this);
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(OrderByItemStateObject stateObject) {
			if (stateObject.hasStateObject()) {
				stateObject.getStateObject().accept(this);
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(ResultVariableStateObject stateObject) {
			if (oldVariableName.equalsIgnoreCase(stateObject.getResultVariable())) {
				stateObject.setResultVariable(newVariableName);
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(SelectStatementStateObject stateObject) {

			// Result variables defined in the SELECT clause
			stateObject.getSelectClause().accept(this);

			// Result variables used in the ORDER BY clause
			if (stateObject.hasOrderByClause()) {
				renameIdentificationVariable = true;
				try {
					stateObject.getOrderByClause().accept(this);
				}
				finally {
					renameIdentificationVariable = false;
				}
			}
		}
	}

	/**
	 * This interface is used to transparently push the new value into the {@link StateObject}.
	 */
	protected static interface StateObjectUpdater<T extends StateObject> {

		/**
		 * Updates the given {@link StateObject} by updating its state with the given new value.
		 *
		 * @param stateObject The {@link StateObject} to update
		 * @param newValue The new value to push into the object
		 */
		void update(T stateObject, CharSequence newValue);
	}

	/**
	 * This visitor renames all the identification variables found in the JPQL query.
	 */
	protected static class VariableNameRenamer extends AbstractTraverseChildrenVisitor {

		/**
		 * The new name of the identification variable.
		 */
		protected final String newVariableName;

		/**
		 * The current identification variable name.
		 */
		protected final String oldVariableName;

		/**
		 * Creates a new <code>VariableNameRenamer</code>.
		 *
		 * @param oldVariableName The current identification variable name
		 * @param newVariableName The new name of the identification variable
		 */
		public VariableNameRenamer(String oldVariableName, String newVariableName) {
			super();
			this.oldVariableName = oldVariableName;
			this.newVariableName = newVariableName;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(CollectionValuedPathExpressionStateObject stateObject) {

			// Make sure the collection-valued path expression's identification variable was created
			stateObject.getIdentificationVariable();

			super.visit(stateObject);
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(IdentificationVariableStateObject stateObject) {
			if (oldVariableName.equalsIgnoreCase(stateObject.getText())) {
				stateObject.setText(newVariableName);
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(StateFieldPathExpressionStateObject stateObject) {

			// Make sure the collection-valued path expression's identification variable was created
			stateObject.getIdentificationVariable();

			super.visit(stateObject);
		}
	}
}