diff --git a/src/main/java/org/openrewrite/java/migrate/javax/AddScopeToInjectedClass.java b/src/main/java/org/openrewrite/java/migrate/javax/AddScopeToInjectedClass.java new file mode 100644 index 0000000000..e7fa7164d0 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/javax/AddScopeToInjectedClass.java @@ -0,0 +1,97 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.migrate.javax; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.ScanningRecipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +public class AddScopeToInjectedClass extends ScanningRecipe> { + private static final String JAVAX_INJECT_INJECT = "javax.inject.Inject"; + private static final String JAVAX_ENTERPRISE_CONTEXT_DEPENDENT = "javax.enterprise.context.Dependent"; + private static final Collection TYPES_PROMPTING_SCOPE_ADDITION = Arrays.asList(JAVAX_INJECT_INJECT); + + @Override + public String getDisplayName() { + return "Add scope annotation to injected classes"; + } + + @Override + public String getDescription() { + return "Finds member variables annotated with `@Inject' and applies `@Dependent` scope annotation to the variable's type."; + } + + @Override + public Set getInitialValue(ExecutionContext ctx) { + return new HashSet<>(); + } + + @Override + public TreeVisitor getScanner(Set injectedTypes) { + return new JavaIsoVisitor() { + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) { + J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, executionContext); + for (JavaType.Variable variable : cd.getType().getMembers()) { + if (variableTypeRequiresScope(variable)) { + injectedTypes.add(((JavaType.FullyQualified) variable.getType()).getFullyQualifiedName()); + } + } + return cd; + } + + private boolean variableTypeRequiresScope(@Nullable JavaType.Variable memberVariable) { + if (memberVariable == null) { + return false; + } + AnnotationMatcher matcher = new AnnotationMatcher(JAVAX_INJECT_INJECT); + for (JavaType.FullyQualified fullYQualifiedAnnotation : memberVariable.getAnnotations()) { + if(memberVariable.getAnnotations().stream().anyMatch(matcher::matchesAnnotationOrMetaAnnotation)) { + return true; + } + } + return false; + } + }; + } + + @Override + public TreeVisitor getVisitor(Set injectedTypes) { + return new JavaIsoVisitor() { + @Override + public J.CompilationUnit visitCompilationUnit(J.CompilationUnit compilationUnit, ExecutionContext executionContext) { + J.CompilationUnit cu = super.visitCompilationUnit(compilationUnit, executionContext); + for (J.ClassDeclaration aClass : cu.getClasses()) { + if (injectedTypes.contains(aClass.getType().getFullyQualifiedName())) { + return new AnnotateTypesVisitor(JAVAX_ENTERPRISE_CONTEXT_DEPENDENT) + .visitCompilationUnit(cu, injectedTypes); + } + } + return cu; + } + }; + } +} diff --git a/src/main/java/org/openrewrite/java/migrate/javax/AnnotateTypesVisitor.java b/src/main/java/org/openrewrite/java/migrate/javax/AnnotateTypesVisitor.java new file mode 100644 index 0000000000..3ac8386980 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/javax/AnnotateTypesVisitor.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.migrate.javax; + +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeUtils; + +import java.util.Comparator; +import java.util.Set; + +public class AnnotateTypesVisitor extends JavaIsoVisitor> { + private final String annotationToBeAdded; + private final AnnotationMatcher annotationMatcher; + private final JavaTemplate template; + + public AnnotateTypesVisitor(String annotationToBeAdded) { + this.annotationToBeAdded = annotationToBeAdded; + String[] split = this.annotationToBeAdded.split("\\."); + String className = split[split.length - 1]; + String packageName = this.annotationToBeAdded.substring(0, this.annotationToBeAdded.lastIndexOf(".")); + this.annotationMatcher = new AnnotationMatcher("@" + this.annotationToBeAdded); + String interfaceAsString = String.format("package %s; public @interface %s {}", packageName, className); + this.template = JavaTemplate.builder("@" + className) + .imports(this.annotationToBeAdded) + .javaParser(JavaParser.fromJavaVersion().dependsOn(interfaceAsString)) + .build(); + } + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Set injectedTypes) { + J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, injectedTypes); + if (injectedTypes.contains(TypeUtils.asFullyQualified(cd.getType()).getFullyQualifiedName()) + && cd.getLeadingAnnotations().stream().noneMatch(annotationMatcher::matches)) { + maybeAddImport(annotationToBeAdded); + return template.apply(getCursor(), cd.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName))); + } + return cd; + } +} diff --git a/src/test/java/org/openrewrite/java/migrate/javax/AddScopeToInjectedClassTest.java b/src/test/java/org/openrewrite/java/migrate/javax/AddScopeToInjectedClassTest.java new file mode 100644 index 0000000000..e2c58c18cc --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/javax/AddScopeToInjectedClassTest.java @@ -0,0 +1,164 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.migrate.javax; + +import org.openrewrite.DocumentExample; +import org.openrewrite.java.migrate.javax.AddScopeToInjectedClass; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.java.JavaParser; +import org.junit.jupiter.api.Test; + +import static org.openrewrite.java.Assertions.java; + +class AddScopeToInjectedClassTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + + spec.recipe(new AddScopeToInjectedClass()); + spec.parser(JavaParser.fromJavaVersion().dependsOn(""" + package javax.enterprise.context; + @Target({ElementType.Type}) + @Retention(RetentionPolicy.RUNTIME) + public @interface Dependent { + } + """, + """ + package javax.inject; + @Target({ElementType.Type}) + @Retention(RetentionPolicy.RUNTIME) + public @interface Inject { + } + """)); + } + + @DocumentExample + @Test + void scopeRequired() { + rewriteRun(spec -> + java(""" + package com.sample.service; + + public class Bar {} + + """, + """ + package com.sample.service; + + import javax.enterprise.context.Dependent; + + @Dependent + public class Bar {} + + """), + + java(""" + package com.sample; + + import javax.inject.Inject; + import com.sample.service.Bar; + + public class Foo{ + + @javax.inject.Inject + Bar service; + } + """)); + } + + + @Test + void noMemberVariableAnnotation() { + rewriteRun(spec -> + java(""" + package com.sample.service; + + public class Bar {} + + """), + + java(""" + package com.sample; + + import com.sample.service.Bar; + + public class Foo{ + + Bar service; + } + """)); + } + + @Test + void nonInjectAnnotation() { + rewriteRun(spec -> + java(""" + package com.sample.service; + + public class Bar {} + + """), + + java(""" + package com.sample; + + import com.sample.service.Bar; + import javax.inject.NotInject; + + public class Foo{ + @NotInject + Bar service; + } + """), + java(""" + package javax.inject; + @Target({ElementType.Type}) + @Retention(RetentionPolicy.RUNTIME) + public @interface NotInject { + } + """)); + } + + + @Test + void scopeAnnotationAlreadyExists() { + rewriteRun(spec -> + java(""" + package com.sample.service; + + import javax.enterprise.context.Dependent; + + @Dependent + public class Bar {} + + """), + + java(""" + package com.sample; + + import javax.inject.Inject; + import com.sample.service.Bar; + + public class Foo{ + + @javax.inject.Inject + Bar service; + } + """)); + } + +} \ No newline at end of file