/*
 * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation
//
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);
        }
    }
}
