Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RenameBean Scanning Recipe Refactor #630

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
270 changes: 100 additions & 170 deletions src/main/java/org/openrewrite/java/spring/RenameBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,19 @@
import org.openrewrite.java.search.DeclaresType;
import org.openrewrite.java.search.FindAnnotations;
import org.openrewrite.java.search.UsesType;
import org.openrewrite.java.tree.*;
import org.openrewrite.java.service.AnnotationService;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.TypeUtils;

import java.util.*;

import static org.openrewrite.java.MethodMatcher.methodPattern;

@EqualsAndHashCode(callSuper = false)
@Value
public class RenameBean extends Recipe {
public class RenameBean extends ScanningRecipe<List<TreeVisitor<?, ExecutionContext>>> {

@Option(required = false, example = "foo.MyType")
@Nullable
Expand Down Expand Up @@ -74,90 +78,66 @@ public String getDescription() {
return "Renames a Spring bean, both declaration and references.";
}

/**
* @param methodDeclaration, which may or may not declare a bean
* @param newName, for the potential bean
* @return a recipe for this methodDeclaration if it declares a bean, or null if it does not declare a bean
*/
public static @Nullable RenameBean fromDeclaration(J.MethodDeclaration methodDeclaration, String newName) {
return methodDeclaration.getMethodType() == null ? null :
fromDeclaration(methodDeclaration, newName, methodDeclaration.getMethodType().getReturnType().toString());
@Override
public List<TreeVisitor<?, ExecutionContext>> getInitialValue(ExecutionContext ctx) {
return new ArrayList<>();
}

/**
* @param methodDeclaration, which may or may not declare a bean
* @param newName, for the potential bean
* @param type, to override the type field on the returned RenameBean instance
* @return a recipe for this methodDeclaration if it declares a bean, or null if it does not declare a bean
*/
public static @Nullable RenameBean fromDeclaration(J.MethodDeclaration methodDeclaration, String newName, @Nullable String type) {
BeanSearchResult beanSearchResult = isBean(methodDeclaration.getAllAnnotations(), BEAN_METHOD_ANNOTATIONS);
if (!beanSearchResult.isBean || methodDeclaration.getMethodType() == null) {
return null;
}
String beanName =
beanSearchResult.beanName != null ? beanSearchResult.beanName : methodDeclaration.getSimpleName();
return beanName.equals(newName) ? null : new RenameBean(type, beanName, newName);
}
@Override
public TreeVisitor<?, ExecutionContext> getScanner(List<TreeVisitor<?, ExecutionContext>> acc) {

/**
* @param classDeclaration, which may or may not declare a bean
* @param newName, for the potential bean
* @return a recipe for this classDeclaration if it declares a bean, or null if it does not declare a bean
*/
public static @Nullable RenameBean fromDeclaration(J.ClassDeclaration classDeclaration, String newName) {
return classDeclaration.getType() == null ? null :
fromDeclaration(classDeclaration, newName, classDeclaration.getType().toString());
}
return Preconditions.check(precondition(), new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
J.MethodDeclaration m = super.visitMethodDeclaration(method, ctx);

/**
* @param classDeclaration, which may or may not declare a bean
* @param newName, for the potential bean
* @param type, to override the type field on the returned RenameBean instance
* @return a recipe for this classDeclaration if it declares a bean, or null if it does not declare a bean
*/
public static @Nullable RenameBean fromDeclaration(J.ClassDeclaration classDeclaration, String newName, @Nullable String type) {
BeanSearchResult beanSearchResult = isBean(classDeclaration.getAllAnnotations(), BEAN_TYPE_ANNOTATIONS);
if (!beanSearchResult.isBean || classDeclaration.getType() == null) {
return null;
}
String beanName =
beanSearchResult.beanName != null ? beanSearchResult.beanName : classDeclaration.getSimpleName();
return beanName.equals(newName) ? null : new RenameBean(type, beanName, newName);
// handle beans named via methods
List<J.Annotation> allAnnotations = service(AnnotationService.class).getAllAnnotations(getCursor());
Expression beanNameExpression = getBeanNameExpression(allAnnotations, BEAN_METHOD_ANNOTATIONS);
if (beanNameExpression == null && isRelevantType(m.getMethodType().getReturnType()) && m.getSimpleName().equals(oldName)) {
acc.add(new ChangeMethodName(methodPattern(m), newName, true, false).getVisitor());
}

// handle annotation renames
acc.add(renameBeanAnnotations(BEAN_METHOD_ANNOTATIONS));
return m;
}

@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);

List<J.Annotation> allAnnotations = service(AnnotationService.class).getAllAnnotations(getCursor());
Expression beanNameExpression = getBeanNameExpression(allAnnotations, BEAN_TYPE_ANNOTATIONS);

// handle bean named via class name
if (beanNameExpression == null && isRelevantType(cd.getType()) && StringUtils.uncapitalize(cd.getSimpleName()).equals(oldName)) {
String newFullyQualifiedTypeName = cd.getType().getFullyQualifiedName()
.replaceAll("^((.+\\.)*)[^.]+$", "$1" + StringUtils.capitalize(newName));
acc.add(new ChangeType(cd.getType().getFullyQualifiedName(), newFullyQualifiedTypeName, false).getVisitor());
acc.add(new ChangeType(cd.getType().getFullyQualifiedName() + "Test", newFullyQualifiedTypeName + "Test", false).getVisitor());
}

// handle annotation renames
acc.add(renameBeanAnnotations(BEAN_TYPE_ANNOTATIONS));

return cd;
}
});
}

private static BeanSearchResult isBean(Collection<J.Annotation> annotations, Set<String> types) {
for (J.Annotation annotation : annotations) {
if (anyAnnotationMatches(annotation, types)) {
if (annotation.getArguments() != null && !annotation.getArguments().isEmpty()) {
for (Expression expr : annotation.getArguments()) {
if (expr instanceof J.Literal) {
return new BeanSearchResult(true, (String) ((J.Literal) expr).getValue());
}
J.Assignment beanNameAssignment = asBeanNameAssignment(expr);
if (beanNameAssignment != null) {
Expression assignmentExpr = beanNameAssignment.getAssignment();
if (assignmentExpr instanceof J.Literal) {
return new BeanSearchResult(true, (String) ((J.Literal) assignmentExpr).getValue());
} else if (assignmentExpr instanceof J.NewArray) {
List<Expression> initializers = ((J.NewArray) assignmentExpr).getInitializer();
if (initializers != null) {
for (Expression initExpr : initializers) {
// if multiple aliases, just take the first one
if (initExpr instanceof J.Literal) {
return new BeanSearchResult(true,
(String) ((J.Literal) initExpr).getValue());
}
}
}
}
}
}
@Override
public TreeVisitor<?, ExecutionContext> getVisitor(List<TreeVisitor<?, ExecutionContext>> acc) {
return new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) {
J.CompilationUnit newCu = super.visitCompilationUnit(cu, ctx);
for (TreeVisitor<?, ExecutionContext> visitor : acc) {
newCu = (J.CompilationUnit) visitor.visit(newCu, ctx);
}
return new BeanSearchResult(true, null);
return newCu;
}
}
return new BeanSearchResult(false, null);
};
}

private static boolean anyAnnotationMatches(J.Annotation type, Set<String> types) {
Expand Down Expand Up @@ -190,111 +170,61 @@ private TreeVisitor<?, ExecutionContext> precondition() {
Preconditions.or(new UsesType<>(type, false), new DeclaresType<>(type));
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(precondition(), new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method,
ExecutionContext ctx) {
J.MethodDeclaration m = super.visitMethodDeclaration(method, ctx);

// handle bean declarations
if (m.getMethodType() != null && isRelevantType(m.getMethodType().getReturnType())) {
boolean maybeRenameMethodDeclaration = maybeRenameBean(m.getAllAnnotations(),
BEAN_METHOD_ANNOTATIONS);
if (maybeRenameMethodDeclaration && m.getSimpleName().equals(oldName)) {
doAfterVisit(new ChangeMethodName(methodPattern(m), newName, false, false).getVisitor());
private @Nullable Expression getBeanNameExpression(Collection<J.Annotation> annotations, Set<String> types) {
for (J.Annotation annotation : annotations) {
if (anyAnnotationMatches(annotation, types)) {
if (annotation.getArguments() != null && !annotation.getArguments().isEmpty()) {
for (Expression expr : annotation.getArguments()) {
if (expr instanceof J.Literal) {
return expr;
}
J.Assignment beanNameAssignment = asBeanNameAssignment(expr);
if (beanNameAssignment != null) {
return beanNameAssignment;
}
}
}

// handle bean references (method params)
for (Statement statement : m.getParameters()) {
renameMatchingQualifierAnnotations(statement);
}

return m;
}
}
return null;
}

@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl,
ExecutionContext ctx) {
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);

// handle bean declarations
if (cd.getType() != null && isRelevantType(cd.getType())) {
boolean maybeRenameClass = maybeRenameBean(cd.getAllAnnotations(), BEAN_TYPE_ANNOTATIONS);
if (maybeRenameClass && StringUtils.uncapitalize(cd.getSimpleName()).equals(oldName)) {
String newFullyQualifiedTypeName = cd.getType().getFullyQualifiedName()
.replaceAll("^((.+\\.)*)[^.]+$", "$1" + StringUtils.capitalize(newName));
doAfterVisit(new ChangeType(cd.getType().getFullyQualifiedName(), newFullyQualifiedTypeName, false).getVisitor());
private TreeVisitor<J, ExecutionContext> renameBeanAnnotations(Set<String> types) {
return new JavaIsoVisitor<ExecutionContext>() {
private boolean annotationParentMatchesBeanType() {
if (getCursor().getParent() != null) {
Object annotationParent = getCursor().getParent().getValue();

if (annotationParent instanceof J.MethodDeclaration) {
return isRelevantType(((J.MethodDeclaration) annotationParent).getMethodType().getReturnType());
} else if (annotationParent instanceof J.ClassDeclaration) {
return isRelevantType(((J.ClassDeclaration) annotationParent).getType());
} else if (annotationParent instanceof J.VariableDeclarations) {
return isRelevantType(((J.VariableDeclarations) annotationParent).getType());
}
}

// handle bean references (fields)
for (Statement statement : cd.getBody().getStatements()) {
renameMatchingQualifierAnnotations(statement);
}

return cd;
return false;
}

private void renameMatchingQualifierAnnotations(Statement statement) {
if (statement instanceof J.VariableDeclarations) {
J.VariableDeclarations varDecls = (J.VariableDeclarations) statement;
for (J.VariableDeclarations.NamedVariable namedVar : varDecls.getVariables()) {
if (isRelevantType(namedVar.getType())) {
maybeRenameBean(varDecls.getAllAnnotations(), JUST_QUALIFIER);
break;
}
}
}
}
@Override
public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) {
Expression beanNameExpression = getBeanNameExpression(Collections.singleton(annotation), types);

/**
* Checks for presence of a bean-like annotation in the list of annotations,
* and queues up a visitor to change that annotation's arguments if they match the oldName.
*
* @return true in the specific case where there are bean-like annotations,
* but they don't determine the name of the bean, and therefore the J element itself should be renamed
*/
private boolean maybeRenameBean(Collection<J.Annotation> annotations, Set<String> types) {
J.Annotation beanAnnotation = null;
J.Literal literalBeanName = null;
J.Assignment beanNameAssignment = null;
outer:
for (J.Annotation annotation : annotations) {
if (anyAnnotationMatches(annotation, types)) {
beanAnnotation = annotation;
if (beanAnnotation.getArguments() != null && !beanAnnotation.getArguments().isEmpty()) {
for (Expression expr : beanAnnotation.getArguments()) {
if (expr instanceof J.Literal) {
literalBeanName = (J.Literal) expr;
break outer;
}
beanNameAssignment = asBeanNameAssignment(expr);
if (beanNameAssignment != null) {
break outer;
}
}
}
}
}
if (beanAnnotation != null) {
if (literalBeanName != null) {
if (oldName.equals(literalBeanName.getValue())) {
doAfterVisit(renameBeanAnnotationValue(beanAnnotation));
if (beanNameExpression != null && annotationParentMatchesBeanType()) {
if (beanNameExpression instanceof J.Literal) {
if (oldName.equals(((J.Literal) beanNameExpression).getValue())) {
doAfterVisit(renameBeanAnnotationValue(annotation));
}
} else if (beanNameAssignment != null) {
} else if (beanNameExpression instanceof J.Assignment) {
J.Assignment beanNameAssignment = (J.Assignment) beanNameExpression;
if (contains(beanNameAssignment.getAssignment(), oldName)) {
doAfterVisit(renameBeanAnnotationValue(beanAnnotation, beanNameAssignment));
doAfterVisit(renameBeanAnnotationValue(annotation, beanNameAssignment));
}
} else {
return true;
}
}
return false;
return super.visitAnnotation(annotation, ctx);
}
});
};
}

private static J.@Nullable Assignment asBeanNameAssignment(Expression argumentExpression) {
Expand All @@ -310,8 +240,8 @@ private boolean maybeRenameBean(Collection<J.Annotation> annotations, Set<String
return null;
}

private TreeVisitor<J, ExecutionContext> renameBeanAnnotationValue(J.Annotation beanAnnotation,
J.Assignment beanNameAssignment) {
private TreeVisitor<J, ExecutionContext> renameBeanAnnotationValue(
J.Annotation beanAnnotation, J.Assignment beanNameAssignment) {
return new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) {
Expand Down
Loading