blob: 4604d7a9d4d9bae86f429668534e00a5d20f51d4 [file] [log] [blame]
/*
* Copyright (c) 2011, 2021 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;
}
@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>() {
@Override
public void update(ConstructorExpressionStateObject stateObject, CharSequence newValue) {
stateObject.setClassName(newValue);
}
};
}
protected StateObjectUpdater<StateFieldPathExpressionStateObject> buildPathExpressionStateObjectUpdater() {
return new StateObjectUpdater<StateFieldPathExpressionStateObject>() {
@Override
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;
}
@Override
public void visit(ConstructorExpressionStateObject stateObject) {
visit(stateObject, stateObject.getClassName(), constructorUpdater());
}
@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;
}
@Override
public void visit(AbstractSchemaNameStateObject stateObject) {
if (oldEntityName.equals(stateObject.getText())) {
stateObject.setText(newEntityName);
}
}
@Override
public void visit(EntityTypeLiteralStateObject stateObject) {
if (oldEntityName.equals(stateObject.getText())) {
stateObject.setText(newEntityName);
}
}
@Override
public void visit(IdentificationVariableStateObject stateObject) {
if (oldEntityName.equals(stateObject.getText())) {
stateObject.setText(newEntityName);
}
}
@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);
}
}
}
@Override
public void visit(CollectionValuedPathExpressionStateObject stateObject) {
renameEnumConstant(stateObject);
}
@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;
}
}
}
}
@Override
public void visit(CollectionValuedPathExpressionStateObject stateObject) {
rename(stateObject);
}
@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;
}
@Override
public void visit(IdentificationVariableStateObject stateObject) {
if (renameIdentificationVariable &&
oldVariableName.equalsIgnoreCase(stateObject.getText())) {
stateObject.setText(newVariableName);
}
}
@Override
public void visit(JPQLQueryStateObject stateObject) {
if (stateObject.hasQueryStatement()) {
stateObject.getQueryStatement().accept(this);
}
}
@Override
public void visit(OrderByItemStateObject stateObject) {
if (stateObject.hasStateObject()) {
stateObject.getStateObject().accept(this);
}
}
@Override
public void visit(ResultVariableStateObject stateObject) {
if (oldVariableName.equalsIgnoreCase(stateObject.getResultVariable())) {
stateObject.setResultVariable(newVariableName);
}
}
@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 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;
}
@Override
public void visit(CollectionValuedPathExpressionStateObject stateObject) {
// Make sure the collection-valued path expression's identification variable was created
stateObject.getIdentificationVariable();
super.visit(stateObject);
}
@Override
public void visit(IdentificationVariableStateObject stateObject) {
if (oldVariableName.equalsIgnoreCase(stateObject.getText())) {
stateObject.setText(newVariableName);
}
}
@Override
public void visit(StateFieldPathExpressionStateObject stateObject) {
// Make sure the collection-valued path expression's identification variable was created
stateObject.getIdentificationVariable();
super.visit(stateObject);
}
}
}