/*******************************************************************************
 * Copyright (c) 2012, 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 java.util.ArrayList;
import java.util.List;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.parser.AbstractExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractPathExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSchemaName;
import org.eclipse.persistence.jpa.jpql.parser.AbstractTraverseChildrenVisitor;
import org.eclipse.persistence.jpa.jpql.parser.CollectionValuedPathExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConstructorExpression;
import org.eclipse.persistence.jpa.jpql.parser.EntityTypeLiteral;
import org.eclipse.persistence.jpa.jpql.parser.Expression;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable;
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.ResultVariable;
import org.eclipse.persistence.jpa.jpql.parser.SelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression;
import org.eclipse.persistence.jpa.jpql.tools.resolver.Resolver;
import org.eclipse.persistence.jpa.jpql.tools.resolver.StateFieldResolver;
import org.eclipse.persistence.jpa.jpql.tools.spi.IManagedTypeProvider;
import org.eclipse.persistence.jpa.jpql.tools.spi.IMapping;
import org.eclipse.persistence.jpa.jpql.tools.spi.IQuery;
import org.eclipse.persistence.jpa.jpql.tools.spi.IType;

/**
 * The abstract implementation providing refactoring support for JPQL queries. This version does not
 * change the {@link org.eclipse.persistence.jpa.jpql.tools.model.query.JPQLQueryStateObject query} but
 * rather gather the changes in {@link RefactoringDelta} and it is the responsibility of the invoker
 * to the actual change.
 * <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 DefaultBasicRefactoringTool
 * @see EclipseLinkBasicRefactoringTool
 *
 * @version 2.6
 * @since 2.4
 * @author Pascal Filion
 */
@SuppressWarnings("nls")
public abstract class BasicRefactoringTool extends AbstractRefactoringTool {

	/**
	 * Keeps track of the changes made to the JPQL query.
	 */
	private DefaultRefactoringDelta delta;

	/**
	 * The parsed tree representation of the JPQL query.
	 */
	private JPQLExpression jpqlExpression;

	/**
	 * The {@link JPQLGrammar} that was used to parse the JPQL query or JPQL fragments.
	 */
	private JPQLGrammar jpqlGrammar;

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

	/**
	 * Creates a new <code>BasicRefactoringTool</code>.
	 *
	 * @param jpqlQuery The JPQL query to manipulate
	 * @param jpqlGrammar The {@link JPQLGrammar} that was used to parse the JPQL query
	 * @param managedTypeProvider The external form of a provider that gives access to the JPA metadata
	 */
	protected BasicRefactoringTool(CharSequence jpqlQuery,
	                                JPQLGrammar jpqlGrammar,
	                                IManagedTypeProvider managedTypeProvider) {

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

	/**
	 * Creates a new <code>BasicRefactoringTool</code>.
	 *
	 * @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 jpqlGrammar The {@link JPQLGrammar} that was used to parse the JPQL fragment
	 * @param managedTypeProvider The external form of a provider that gives access to the JPA metadata
	 * @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 BasicRefactoringTool(CharSequence jpqlFragment,
	                               JPQLGrammar jpqlGrammar,
	                               IManagedTypeProvider managedTypeProvider,
	                               String jpqlQueryBNFId) {

		super(jpqlFragment, managedTypeProvider, jpqlQueryBNFId);
		this.jpqlGrammar = jpqlGrammar;
		this.delta       = new DefaultRefactoringDelta(jpqlFragment);
	}

	/**
	 * Creates the visitor that will traverse the {@link } 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 oldAttributeName The current name of the attribute to rename
	 * @param newAttributeName The new name of the attribute
	 * @return A new {@link AttributeNameRenamer}
	 */
	protected AttributeNameRenamer buildAttributeNameRenamer(String typeName,
	                                                         String oldAttributeName,
	                                                         String newAttributeName) {

		return new AttributeNameRenamer(getQueryContext(), typeName, oldAttributeName, newAttributeName);
	}

	/**
	 * Creates the visitor that will traverse the {@link } 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 } 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 } 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 a new {@link JPQLQueryContext} that can retrieve information from the declaration
	 * portion of the JPQL query.
	 *
	 * @return A new concrete instance of {@link JPQLQueryContext}
	 */
	protected abstract JPQLQueryContext buildJPQLQueryContext();

	/**
	 * Creates the visitor that will traverse the {@link } 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 visitor that will traverse the {@link } 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 delta of the changes made to the JPQL query.
	 *
	 * @return An object containing the refactoring events
	 */
	public RefactoringDelta getDelta() {
		return delta;
	}

	/**
	 * Returns the parsed tree representation of the JPQL query.
	 *
	 * @return The root of the parsed tree
	 */
	public JPQLExpression getExpression() {
		if (jpqlExpression == null) {
			jpqlExpression = new JPQLExpression(getJPQLFragment(), jpqlGrammar, isTolerant());
		}
		return jpqlExpression;
	}

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

	/**
	 * Returns the {@link JPQLQueryContext} that is used by this visitor.
	 *
	 * @return The {@link JPQLQueryContext} holding onto the JPQL query and the cached information
	 */
	public JPQLQueryContext getQueryContext() {
		if (queryContext == null) {
			queryContext = buildJPQLQueryContext();
			queryContext.setJPQLExpression(getExpression());
			queryContext.setQuery(new JavaQuery(getManagedTypeProvider(), getJPQLFragment()));
		}
		return queryContext;
	}

	/**
	 * Determines whether some refactoring operations found changes to be made in the JPQL query.
	 *
	 * @return <code>true</code> if there is at least one {@link TextEdit}; <code>false</code> otherwise
	 */
	public boolean hasChanges() {
		return delta.hasTextEdits();
	}

	/**
	 * Renames the attribute (persistent field or persistent property) from the given type.
	 *
	 * @param type The Java class from which the change originate
	 * @param oldAttributeName The current name of the attribute to rename
	 * @param newAttributeName The new name of the attribute
	 */
	public void renameAttribute(Class<?> type, String oldAttributeName, String newAttributeName) {
		renameAttribute(type.getName(), oldAttributeName, newAttributeName);
	}

	/**
	 * Renames the attribute (persistent field or persistent property) from the given type.
	 *
	 * @param type The {@link IType} from which the change originate
	 * @param oldAttributeName The current name of the attribute to rename
	 * @param newAttributeName The new name of the attribute
	 */
	public void renameAttribute(IType type, String oldAttributeName, String newAttributeName) {
		renameAttribute(type.getName(), oldAttributeName, newAttributeName);
	}

	/**
	 * Renames the attribute (persistent field or persistent property) from the given type.
	 *
	 * @param typeName The fully qualified name of the type that got one of its attributes renamed
	 * @param oldAttributeName The current name of the attribute to rename
	 * @param newAttributeName The new name of the attribute
	 */
	public void renameAttribute(String typeName, String oldAttributeName, String newAttributeName) {
		AbstractRenamer renamer = buildAttributeNameRenamer(typeName, oldAttributeName, newAttributeName);
		getExpression().accept(renamer);
		delta.addTextEdits(renamer.textEdits);
	}

	/**
	 * 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);
		getExpression().accept(renamer);
		delta.addTextEdits(renamer.textEdits);
	}

	/**
	 * 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) {
		AbstractRenamer renamer = buildEntityNameRenamer(oldEntityName, newEntityName);
		getExpression().accept(renamer);
		delta.addTextEdits(renamer.textEdits);
	}

	/**
	 * 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) {
		AbstractRenamer renamer = buildEnumConstantRenamer(oldEnumConstant, newEnumConstant);
		getExpression().accept(renamer);
		delta.addTextEdits(renamer.textEdits);
	}

	/**
	 * 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) {
		AbstractRenamer renamer = buildResultVariableNameRenamer(oldVariableName, newVariableName);
		getExpression().accept(renamer);
		delta.addTextEdits(renamer.textEdits);
	}

	/**
	 * 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) {
		AbstractRenamer renamer = buildVariableNameRenamer(oldVariableName, newVariableName);
		getExpression().accept(renamer);
		delta.addTextEdits(renamer.textEdits);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toActualText() {
		return getDelta().applyChanges();
	}

	/**
	 * The abstract class that all refactoring classes should extend, it automatically provides the
     * <a href="http://help.eclipse.org/indigo/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Ftext%2Fedits%2FMultiTextEdit.html">MultiTextEdit</a>
     * that will hold the {@link TextEdit} objects that are related to the same refactoring event.
	 */
	protected abstract class AbstractRenamer extends AbstractTraverseChildrenVisitor {

		/**
		 * The list of {@link TextEdit} objects that were created for each refactoring operation.
		 */
		protected List<TextEdit> textEdits;

		/**
		 * Creates a new <code>AbstractRenamer</code>.
		 */
		protected AbstractRenamer() {
			super();
			textEdits = new ArrayList<TextEdit>();
		}

		/**
		 * Adds a new {@link TextEdit} with the given information.
		 *
		 * @param expression The {@link Expression} which should be refactored, it will be used to
		 * retrieve the offset of the change
		 * @param extraOffset Additional offset that will be added to the given {@link Expression}'s
		 * offset, which is the length of the string representation of what is before it
		 * @param oldValue The old value to change to the new one
		 * @param newValue The new value
		 */
		protected void addTextEdit(Expression expression, int extraOffset, String oldValue, String newValue) {
			TextEdit textEdit = buildTextEdit(
				reposition(expression.getOffset() + extraOffset),
				oldValue,
				newValue
			);
			textEdits.add(textEdit);
		}

		/**
		 * Adds a new {@link TextEdit} with the given information.
		 *
		 * @param expression The {@link Expression} which should be refactored, it will be used to
		 * retrieve the offset of the change
		 * @param oldValue The old value to change to the new one
		 * @param newValue The new value
		 */
		protected void addTextEdit(Expression expression, String oldValue, String newValue) {
			addTextEdit(expression, 0, oldValue, newValue);
		}

		/**
		 * Creates a new {@link TextEdit} for the given refactoring information.
		 *
		 * @param offset The position where the change should be made within the actual JPQL fragment
		 * @param oldValue The old value to change to the new one
		 * @param newValue The new value
		 * @return A new {@link TextEdit}
		 */
		protected TextEdit buildTextEdit(int offset, String oldValue, String newValue) {
			return new DefaultTextEdit(offset, oldValue, newValue);
		}

		/**
		 * Repositions the given position that is based on the generated JPQL query to be the position
		 * from the JPQL fragment that was parsed.
		 *
		 * @param offset The position within the string generated by {@link Expression#toActualText()}
		 * @return The position within the JPQL fragment that was passed to the refactoring tool
		 */
		protected int reposition(int offset) {
			return ExpressionTools.repositionCursor(
				getExpression().toActualText(),
				offset,
				getJPQLFragment()
			);
		}
	}

	/**
	 * This visitor renames any segment of a path expression.
	 */
	protected class AttributeNameRenamer extends AbstractRenamer {

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

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

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

		/**
		 * 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 queryContext The context used to query information about the JPQL query
		 * @param typeName The fully qualified name of the type that got one of its attributes renamed
		 * @param oldAttributeName The current name of the attribute to rename
		 * @param newAttributeName The new name of the attribute
		 */
		public AttributeNameRenamer(JPQLQueryContext queryContext,
		                            String typeName,
		                            String oldAttributeName,
		                            String newAttributeName) {

			super();
			this.typeName         = typeName;
			this.queryContext     = queryContext;
			this.oldAttributeName = oldAttributeName;
			this.newAttributeName = newAttributeName;
		}

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

			if (!expression.hasIdentificationVariable() ||
			     expression.startsWithDot()) {

				return;
			}

			Resolver resolver = queryContext.getResolver(expression.getIdentificationVariable());

			// Can't continue to resolve the path expression
			if (resolver == null) {
				return;
			}

			// Now traverse the path expression after the identification variable
			for (int index = expression.hasVirtualIdentificationVariable() ? 0 : 1, count = expression.pathSize(); index < count; index++) {

				// Retrieve the mapping for the path at the current position
				String path = expression.getPath(index);
				Resolver childResolver = resolver.getChild(path);

				if (childResolver == null) {
					childResolver = new StateFieldResolver(resolver, path);
					resolver.addChild(path, childResolver);
				}

				IMapping mapping = childResolver.getMapping();

				// Invalid path expression
				if (mapping == null) {
					break;
				}

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

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

					if (parentTypeName.equals(typeName)) {
						int extraOffset = expression.toParsedText(0, index).length() + 1 /* '.' */;
						addTextEdit(expression, extraOffset, oldAttributeName, newAttributeName);
						break;
					}
				}

				resolver = childResolver;
			}
		}

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

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

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

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

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

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

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(CollectionValuedPathExpression expression) {
			// Test for a fully qualified class name in a range variable declaration
			if (!expression.startsWithDot()) {
				visit(expression, expression.toActualText(), 0);
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(ConstructorExpression expression) {
			visit(expression, expression.getClassName(), 4); // 4 = "NEW "
		}

		/**
		 * Visits the given {@link } 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
		 * org.eclipse.persistence.jpa.jpql.tools.RefactoringTool.StateObjectUpdater StateObjectUpdater}
         * will be notified to replace the value.
		 *
		 * @param expression The {@link } that is being visited
		 * @param extraOffset Additional offset that will be added to the given {@link Expression}'s
		 * offset, which is the length of the string representation of what is before it
		 * @param value The value to check if it's the old class name
		 */
		protected void visit(Expression expression, String value, int extraOffset) {

			if (oldClassName.equals(value)) {
				addTextEdit(expression, extraOffset, oldClassName, 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)) {

					// Retrieve the fragment from the beginning to the current dot
					String fragment = value.substring(0, index);

					if (oldClassName.equals(fragment)) {
						addTextEdit(expression, extraOffset, oldClassName, newClassName);
						break;
					}
					// No need to continue the search
					else if (fragment.length() < oldClassName.length()) {
						break;
					}
				}
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(StateFieldPathExpression expression) {
			// A fully qualified enum constant is parsed as a state field path expression
			if (!expression.startsWithDot()) {
				visit(expression, expression.toActualText(), 0);
			}
		}
	}

	/**
	 * This visitor renames an entity name. There are three possible {@link
     * org.eclipse.persistence.jpa.jpql.tools.model.query.StateObject StateObjects}
	 * that can represent an entity name:
	 * <ul>
	 * <li>
	 * {@link org.eclipse.persistence.jpa.jpql.tools.model.query.AbstractSchemaNameStateObject AbstractSchemaNameStateObject}:
	 * <i>Employee</i> in<br><br>
	 * <code><b>SELECT</b> e<br>
	 * <b>FROM</b> Employee e</code><br><br>
	 * </li>
	 * <li>
	 * {@link org.eclipse.persistence.jpa.jpql.tools.model.query.EntityTypeLiteralStateObject EntityTypeLiteralStateObject}:
	 * <i>Exempt</i> in<br><br>
	 * <pre><code><b> SELECT CASE TYPE</b>(e) <b>WHEN</b> Exempt <b>THEN</b> 'Exempt'
	 * &nbsp;&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'
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b>END</b>
	 * <b>FROM</b> Employee e</code></pre>
	 * </li>
	 * <li>
	 * {@link org.eclipse.persistence.jpa.jpql.tools.model.query.IdentificationVariableStateObject IdentificationVariableStateObject}:
	 * <i>Exempt</i> in<br>
	 * <pre><code> <b>SELECT</b> e
	 * <b>FROM</b> Employee e
	 * <b>WHERE TYPE</b>(e) {@literal <>} Exempt</code></pre>
	 * </li>
	 * </ul>
	 */
	protected class EntityNameRenamer extends AbstractRenamer {

		/**
		 * 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(AbstractSchemaName expression) {
			if (oldEntityName.equals(expression.getText())) {
				addTextEdit(expression, oldEntityName, newEntityName);
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(EntityTypeLiteral expression) {
			if (oldEntityName.equals(expression.getEntityTypeName())) {
				addTextEdit(expression, oldEntityName, newEntityName);
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(IdentificationVariable expression) {
			if (oldEntityName.equals(expression.getText())) {
				addTextEdit(expression, oldEntityName, newEntityName);
			}
		}
	}

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

		/**
		 * 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(AbstractPathExpression expression) {

			String path = expression.toString();

			if (path.equals(oldEnumConstant)) {

				// Check to see if the path is actually a fully qualified enum constant
				IType type = managedTypeProvider.getTypeRepository().getEnumType(path);

				// If it is not null, then it's a fully qualified enum constant
				if (type != null) {
					addTextEdit(expression, oldEnumConstant, newEnumConstant);
				}
			}
		}

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

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

	/**
	 * A simple implementation of {@link IQuery}.
	 */
	protected static class JavaQuery implements IQuery {

		/**
		 * The string representation of the JPQL query.
		 */
		private String jpqlQuery;

		/**
		 * The provider of JPA managed types.
		 */
		private IManagedTypeProvider provider;

		/**
		 * Creates a new <code>JavaQuery</code>.
		 *
		 * @param provider The provider of JPA managed types
		 * @param jpqlQuery The string representation of the JPQL query
		 */
		public JavaQuery(IManagedTypeProvider provider, CharSequence jpqlQuery) {
			super();
			this.provider = provider;
			setExpression(jpqlQuery);
		}

		/**
		 * {@inheritDoc}
		 */
		public String getExpression() {
			return jpqlQuery;
		}

		/**
		 * {@inheritDoc}
		 */
		public IManagedTypeProvider getProvider() {
			return provider;
		}

		/**
		 * Sets the string representation of the JPQL query.
		 *
		 * @param jpqlQuery The string representation of the JPQL query
		 */
		public void setExpression(CharSequence jpqlQuery) {
			this.jpqlQuery = (jpqlQuery != null) ? jpqlQuery.toString() : ExpressionTools.EMPTY_STRING;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append("JPQL query=[");
			sb.append(jpqlQuery);
			sb.append("]");
			return sb.toString();
		}
	}

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

		/**
		 * 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(IdentificationVariable expression) {

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

				addTextEdit(expression, oldVariableName, newVariableName);
			}
		}

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

			if (expression.hasResultVariable()) {
				renameIdentificationVariable = true;
				try {
					expression.getResultVariable().accept(this);
				}
				finally {
					renameIdentificationVariable = false;
				}
			}
		}

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

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

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

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

		/**
		 * 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(IdentificationVariable expression) {
			if (oldVariableName.equalsIgnoreCase(expression.getText())) {
				addTextEdit(expression, oldVariableName, newVariableName);
			}
		}
	}
}