From 98d7a63ff22828b2cc9d65c7b5cb619127f66db7 Mon Sep 17 00:00:00 2001 From: aboyko Date: Thu, 23 Feb 2023 23:14:43 -0500 Subject: [PATCH] Recipe for Adding Config annotation --- ...ConfigurationAnnotationIfBeansPresent.java | 142 ++++++++++++++++++ .../rewrite/java/AnnotationHierarchies.java | 10 ++ ...ConfigurationIfBeansPresentCodeAction.java | 4 +- 3 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/AddConfigurationAnnotationIfBeansPresent.java diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/AddConfigurationAnnotationIfBeansPresent.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/AddConfigurationAnnotationIfBeansPresent.java new file mode 100644 index 000000000..e746df33c --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/AddConfigurationAnnotationIfBeansPresent.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * Copyright (c) 2023 VMware, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VMware, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.rewrite.java; + +import java.util.Comparator; + +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Statement; +import org.openrewrite.java.tree.TypeUtils; + +public class AddConfigurationAnnotationIfBeansPresent extends Recipe { + + private static final String CONFIGURATION_PACKAGE = "org.springframework.context.annotation"; + + private static final String CONFIGURATION_SIMPLE_NAME = "Configuration"; + + private static final String FQN_CONFIGURATION = CONFIGURATION_PACKAGE + "." + CONFIGURATION_SIMPLE_NAME; + + private static final String FQN_BEAN = "org.springframework.context.annotation.Bean"; + + @Override + public String getDisplayName() { + return "Add missing '@Configuration' annotation"; + } + + @Override + public String getDescription() { + return "Class having `@Bean' annotation over any methods but missing '@Configuration' annotation over the declaring class would have '@Configuration' annotation added."; + } + + @Override + protected TreeVisitor getApplicableTest() { + return new UsesType<>(FQN_BEAN); + } + + @Override + protected TreeVisitor getVisitor() { + return new JavaIsoVisitor() { + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext p) { + J.ClassDeclaration c = super.visitClassDeclaration(classDecl, p); + if (isApplicableClass(c, getCursor())) { + c = addConfigurationAnnotation(c); + } + return c; + } + + private J.ClassDeclaration addConfigurationAnnotation(J.ClassDeclaration c) { + maybeAddImport(FQN_CONFIGURATION); + JavaTemplate template = JavaTemplate.builder(() -> getCursor(), "@" + CONFIGURATION_SIMPLE_NAME) + .imports( + FQN_CONFIGURATION) + .javaParser(() -> JavaParser.fromJavaVersion().dependsOn("package " + CONFIGURATION_PACKAGE + + "; public @interface " + CONFIGURATION_SIMPLE_NAME + " {}").build()) + .build(); + return c.withTemplate(template, + c.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName))); + } + + }; + } + + public static boolean isApplicableClass(J.ClassDeclaration classDecl, Cursor cursor) { + if (classDecl.getKind() == J.ClassDeclaration.Kind.Type.Class) { + boolean isStatic = false; + for (J.Modifier m : classDecl.getModifiers()) { + if (m.getType() == J.Modifier.Type.Abstract) { + return false; + } else if (m.getType() == J.Modifier.Type.Static) { + isStatic = true; + } + } + + if (!isStatic) { + // no static keyword? check if it is top level class in the CU + J.CompilationUnit cu = cursor.dropParentUntil(J.CompilationUnit.class::isInstance).getValue(); + if (!cu.getClasses().contains(classDecl)) { + return false; + } + } + + // check if '@Configuration' is already over the class + AnnotationMatcher matcher = new AnnotationMatcher("@" + FQN_CONFIGURATION, true); + + for (J.Annotation a : classDecl.getLeadingAnnotations()) { + JavaType.FullyQualified aType = TypeUtils.asFullyQualified(a.getType()); + if (aType != null && matcher.matchesAnnotationOrMetaAnnotation(aType)) { + // Found '@Configuration' annotation + return false; + } + } + // No '@Configuration' present. Check if any methods have '@Bean' annotation + for (Statement s : classDecl.getBody().getStatements()) { + if (s instanceof J.MethodDeclaration) { + if (isBeanMethod((J.MethodDeclaration) s)) { + return true; + } + } + } + + } + return false; + } + + private static boolean isBeanMethod(J.MethodDeclaration methodDecl) { + for (J.Modifier m : methodDecl.getModifiers()) { + if (m.getType() == J.Modifier.Type.Abstract) { + return false; + } else if (m.getType() == J.Modifier.Type.Static) { + return false; + } + } + AnnotationMatcher beanAnnotationMatcher = new AnnotationMatcher("@" + FQN_BEAN, true); + for (J.Annotation a : methodDecl.getLeadingAnnotations()) { + JavaType.FullyQualified aType = TypeUtils.asFullyQualified(a.getType()); + if (aType != null && beanAnnotationMatcher.matchesAnnotationOrMetaAnnotation(aType)) { + return true; + } + } + return false; + } + +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/AnnotationHierarchies.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/AnnotationHierarchies.java index 638545605..29ea1149e 100644 --- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/AnnotationHierarchies.java +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/AnnotationHierarchies.java @@ -1,3 +1,13 @@ +/******************************************************************************* + * Copyright (c) 2022 VMware, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VMware, Inc. - initial API and implementation + *******************************************************************************/ package org.springframework.ide.vscode.commons.rewrite.java; import java.util.Collection; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/AddConfigurationIfBeansPresentCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/AddConfigurationIfBeansPresentCodeAction.java index 5254e0816..d6e244fa5 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/AddConfigurationIfBeansPresentCodeAction.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/AddConfigurationIfBeansPresentCodeAction.java @@ -16,7 +16,6 @@ import org.openrewrite.ExecutionContext; import org.openrewrite.SourceFile; import org.openrewrite.Tree; import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.spring.boot2.AddConfigurationAnnotationIfBeansPresent; import org.openrewrite.java.tree.J.ClassDeclaration; import org.openrewrite.java.tree.J.MethodDeclaration; import org.openrewrite.java.tree.J.VariableDeclarations; @@ -28,13 +27,14 @@ import org.springframework.ide.vscode.commons.java.Version; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; +import org.springframework.ide.vscode.commons.rewrite.java.AddConfigurationAnnotationIfBeansPresent; import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker; import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor; import org.springframework.ide.vscode.commons.rewrite.java.JavaMarkerVisitor; public class AddConfigurationIfBeansPresentCodeAction implements RecipeCodeActionDescriptor { - private static final String ID = "org.openrewrite.java.spring.boot2.AddConfigurationAnnotationIfBeansPresent"; + private static final String ID = "org.springframework.ide.vscode.commons.rewrite.java.AddConfigurationAnnotationIfBeansPresent"; private static final String PROBLEM_LABEL = "'@Configuration' is missing on a class defining Spring Beans";