From dde93c43420a1d982e1f28bb787bd1c0f861e5cb Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Mon, 18 Nov 2024 11:51:16 -0800 Subject: [PATCH 01/13] Add relevant context to description of AddSpringDependencyManagementPlugin --- .../gradle/spring/AddSpringDependencyManagementPlugin.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/gradle/spring/AddSpringDependencyManagementPlugin.java b/src/main/java/org/openrewrite/gradle/spring/AddSpringDependencyManagementPlugin.java index fcde83843..ff5d4d78b 100644 --- a/src/main/java/org/openrewrite/gradle/spring/AddSpringDependencyManagementPlugin.java +++ b/src/main/java/org/openrewrite/gradle/spring/AddSpringDependencyManagementPlugin.java @@ -39,7 +39,9 @@ public String getDisplayName() { @Override public String getDescription() { - return "Add `io.spring.dependency-management` plugin, if in use."; + return "Prior to Spring Boot 2.0 the dependency management plugin was applied automatically as part of the overall spring boot plugin. " + + "Afterwards the dependency-management plugin must be applied explicitly, or Gradle's `platform()` feature may be used instead. " + + "This recipe makes usage of io-spring.dependency-management explicit in anticipation of upgrade to Spring Boot 2.0 or later."; } @Override @@ -55,7 +57,7 @@ public TreeVisitor getVisitor() { private static class UsesSpringDependencyManagement extends JavaIsoVisitor { @Override - public J visit(@Nullable Tree tree, ExecutionContext ctx) { + public @Nullable J visit(@Nullable Tree tree, ExecutionContext ctx) { if (tree instanceof JavaSourceFile) { JavaSourceFile cu = (JavaSourceFile) tree; Optional maybeGp = cu.getMarkers().findFirst(GradleProject.class); From f22e69ba77ca11c79aa79a983ab58a7f52170927 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 18 Nov 2024 21:29:29 +0100 Subject: [PATCH 02/13] Adopt RemoveMethodInvocations from rewrite-java --- src/main/resources/META-INF/rewrite/spring-framework-61.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/META-INF/rewrite/spring-framework-61.yml b/src/main/resources/META-INF/rewrite/spring-framework-61.yml index 0c2b06657..a13d933cb 100644 --- a/src/main/resources/META-INF/rewrite/spring-framework-61.yml +++ b/src/main/resources/META-INF/rewrite/spring-framework-61.yml @@ -27,5 +27,5 @@ recipeList: groupId: org.springframework artifactId: "*" newVersion: 6.1.x - - org.openrewrite.java.migrate.RemoveMethodInvocation: + - org.openrewrite.java.RemoveMethodInvocations: methodPattern: org.springframework.http.client.SimpleClientHttpRequestFactory setOutputStreaming(boolean) From 9214bb8f3e9227506f20fd4ccbef07c9e967e3a1 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Mon, 18 Nov 2024 14:18:43 -0800 Subject: [PATCH 03/13] Remove now-defunct rewrite-templating dependency --- build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0f4418822..f36685ab1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -135,7 +135,6 @@ dependencies { implementation("org.openrewrite:rewrite-yaml") implementation("org.openrewrite:rewrite-gradle") implementation("org.openrewrite:rewrite-maven") - implementation("org.openrewrite:rewrite-templating:$rewriteVersion") implementation("org.openrewrite.recipe:rewrite-java-dependencies:${rewriteVersion}") implementation("org.openrewrite.recipe:rewrite-static-analysis:${rewriteVersion}") implementation("org.openrewrite.gradle.tooling:model:${rewriteVersion}") From 1c195ec9b0733c7708e9ad83891857d5bfd1b8b9 Mon Sep 17 00:00:00 2001 From: Shannon Pamperl Date: Wed, 20 Nov 2024 21:39:56 +0000 Subject: [PATCH 04/13] refactor: Update Gradle wrapper Use this link to re-run the recipe: https://app.moderne.io/recipes/org.openrewrite.gradle.UpdateGradleWrapper?organizationId=T3BlblJld3JpdGU%3D#defaults=W3sibmFtZSI6ImFkZElmTWlzc2luZyIsInZhbHVlIjoiRmFsc2UifV0= Co-authored-by: Moderne --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c79dc6b8a..9751024bf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,8 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionSha256Sum=57dafb5c2622c6cc08b993c85b7c06956a2f53536432a30ead46166dbca0f1e9 +distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6 From 09b6109a140f75eab1a8d27f1c09573458e84531 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Thu, 21 Nov 2024 01:10:01 +0100 Subject: [PATCH 05/13] Fix method type following openrewrite/rewrite#4688 --- .../http/SimplifyWebTestClientCalls.java | 19 ++++++++++++------- .../http/SimplifyWebTestClientCallsTest.java | 2 -- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/openrewrite/java/spring/http/SimplifyWebTestClientCalls.java b/src/main/java/org/openrewrite/java/spring/http/SimplifyWebTestClientCalls.java index bfdf8cf8e..52234e394 100644 --- a/src/main/java/org/openrewrite/java/spring/http/SimplifyWebTestClientCalls.java +++ b/src/main/java/org/openrewrite/java/spring/http/SimplifyWebTestClientCalls.java @@ -23,8 +23,10 @@ import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.search.UsesMethod; -import org.openrewrite.java.tree.J.Literal; -import org.openrewrite.java.tree.J.MethodInvocation; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +import static java.util.Collections.emptyList; public class SimplifyWebTestClientCalls extends Recipe { @@ -44,10 +46,10 @@ public String getDescription() { public TreeVisitor getVisitor() { return Preconditions.check(new UsesMethod<>(IS_EQUAL_TO_INT_MATCHER), new JavaIsoVisitor() { @Override - public MethodInvocation visitMethodInvocation(MethodInvocation method, ExecutionContext ctx) { - MethodInvocation m = super.visitMethodInvocation(method, ctx); + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = super.visitMethodInvocation(method, ctx); if (IS_EQUAL_TO_INT_MATCHER.matches(m.getMethodType())) { - int statusCode = (int) ((Literal) m.getArguments().get(0)).getValue(); + int statusCode = (int) ((J.Literal) m.getArguments().get(0)).getValue(); switch (statusCode) { case 200: return replaceMethod(m, "isOk()"); @@ -80,9 +82,12 @@ public MethodInvocation visitMethodInvocation(MethodInvocation method, Execution return m; } - private MethodInvocation replaceMethod(MethodInvocation method, String methodName) { + private J.MethodInvocation replaceMethod(J.MethodInvocation method, String methodName) { JavaTemplate template = JavaTemplate.builder(methodName).build(); - return template.apply(getCursor(), method.getCoordinates().replaceMethod()); + J.MethodInvocation methodInvocation = template.apply(getCursor(), method.getCoordinates().replaceMethod()); + JavaType.Method type = methodInvocation.getMethodType().withParameterNames(emptyList()).withParameterTypes(emptyList()); + return methodInvocation.withArguments(emptyList()).withMethodType(type).withName(methodInvocation.getName().withType(type)); + } }); } diff --git a/src/test/java/org/openrewrite/java/spring/http/SimplifyWebTestClientCallsTest.java b/src/test/java/org/openrewrite/java/spring/http/SimplifyWebTestClientCallsTest.java index cb71aebf8..dca05c62d 100644 --- a/src/test/java/org/openrewrite/java/spring/http/SimplifyWebTestClientCallsTest.java +++ b/src/test/java/org/openrewrite/java/spring/http/SimplifyWebTestClientCallsTest.java @@ -159,7 +159,6 @@ void someMethod() { } @Test - @Disabled("Yet to be implemented") void doesNotUseIsOkForHttpStatus300() { rewriteRun( //language=java @@ -182,5 +181,4 @@ void someMethod() { ) ); } - } From a29fb1145a588891357e28e3c689b736aa27e1ac Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Thu, 21 Nov 2024 13:18:31 +0100 Subject: [PATCH 06/13] Update method type as well when removing arguments --- .../spring/boot2/ConvertToSecurityDslVisitor.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/java/spring/boot2/ConvertToSecurityDslVisitor.java b/src/main/java/org/openrewrite/java/spring/boot2/ConvertToSecurityDslVisitor.java index f992dabf6..f372db40c 100644 --- a/src/main/java/org/openrewrite/java/spring/boot2/ConvertToSecurityDslVisitor.java +++ b/src/main/java/org/openrewrite/java/spring/boot2/ConvertToSecurityDslVisitor.java @@ -127,7 +127,7 @@ private J.Lambda createLambdaParam(String paramName, JavaType paramType, List(param, Space.EMPTY, Markers.EMPTY))), - Space.build(" ", Collections.emptyList()), + Space.build(" ", emptyList()), body, JavaType.Primitive.Void ); @@ -145,9 +145,19 @@ private J.MethodInvocation unfoldMethodInvocationChain(J.Identifier core, List computeAndMarkChain() { } } if (cursor == null || chain.isEmpty()) { - return Collections.emptyList(); + return emptyList(); } if (!(cursor.getValue() instanceof J.MethodInvocation)) { // top invocation is at the end of the chain - mark it. We'd need to strip off prefix from this invocation later From 40aad1752c96f554136140a7223859d0c53fb130 Mon Sep 17 00:00:00 2001 From: Adrien Loison <7086917+adrilo@users.noreply.github.com> Date: Thu, 21 Nov 2024 14:19:38 +0100 Subject: [PATCH 07/13] Fix incorrect path computation for Spring API endpoints (#632) * Fix incorrect path computation for Spring API endpoints The current logic did not cover the case where a `RequestMapping` with a path was defined on the Controller class, and no paths were defined in the `*Mapping` at the method level. * Do not disable the FindApiEndpointsTest * Simplify FindApiEndpoints and SpringRequestMapping * Add for now failing unit test * AnnotationMatcher does not support wildcards --------- Co-authored-by: Tim te Beek --- .../java/spring/search/FindApiEndpoints.java | 12 +-- .../spring/trait/SpringRequestMapping.java | 3 +- .../spring/search/FindApiEndpointsTest.java | 90 +++++++++++++++++-- 3 files changed, 86 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/openrewrite/java/spring/search/FindApiEndpoints.java b/src/main/java/org/openrewrite/java/spring/search/FindApiEndpoints.java index 1fa3cf6d6..bad16e4c7 100644 --- a/src/main/java/org/openrewrite/java/spring/search/FindApiEndpoints.java +++ b/src/main/java/org/openrewrite/java/spring/search/FindApiEndpoints.java @@ -20,25 +20,17 @@ 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.service.AnnotationService; import org.openrewrite.java.spring.table.ApiEndpoints; import org.openrewrite.java.spring.trait.SpringRequestMapping; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.marker.SearchResult; -import java.util.List; -import java.util.stream.Stream; - -import static java.util.stream.Collectors.toList; - @Value @EqualsAndHashCode(callSuper = false) public class FindApiEndpoints extends Recipe { - private static final List REST_ENDPOINTS = Stream.of("Request", "Get", "Post", "Put", "Delete", "Patch") - .map(method -> new AnnotationMatcher("@org.springframework.web.bind.annotation." + method + "Mapping")) - .collect(toList()); transient ApiEndpoints apis = new ApiEndpoints(this); @@ -63,7 +55,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex J.MethodDeclaration m = super.visitMethodDeclaration(method, ctx); SpringRequestMapping.Matcher matcher = new SpringRequestMapping.Matcher(); - for (J.Annotation annotation : m.getAllAnnotations()) { + for (J.Annotation annotation : service(AnnotationService.class).getAllAnnotations(getCursor())) { m = matcher.get(annotation, getCursor()).map(requestMapping -> { String path = requestMapping.getPath(); String httpMethod = requestMapping.getHttpMethod(); diff --git a/src/main/java/org/openrewrite/java/spring/trait/SpringRequestMapping.java b/src/main/java/org/openrewrite/java/spring/trait/SpringRequestMapping.java index 26f8ffbee..523cbaba5 100644 --- a/src/main/java/org/openrewrite/java/spring/trait/SpringRequestMapping.java +++ b/src/main/java/org/openrewrite/java/spring/trait/SpringRequestMapping.java @@ -35,6 +35,7 @@ @Value public class SpringRequestMapping implements Trait { + private static final List REST_ENDPOINTS = Stream.of("Request", "Get", "Post", "Put", "Delete", "Patch") .map(method -> new AnnotationMatcher("@org.springframework.web.bind.annotation." + method + "Mapping")) .collect(toList()); @@ -65,7 +66,7 @@ public String getPath() { List pathEndings = new Annotated(cursor) .getDefaultAttribute(null) .map(Literal::getStrings) - .orElse(Collections.emptyList()); + .orElse(Collections.singletonList("")); StringBuilder result = new StringBuilder(); for (int j = 0; j < pathPrefixes.size(); j++) { diff --git a/src/testWithSpringBoot_3_0/java/org/openrewrite/java/spring/search/FindApiEndpointsTest.java b/src/testWithSpringBoot_3_0/java/org/openrewrite/java/spring/search/FindApiEndpointsTest.java index 19db372ae..75b5bf814 100644 --- a/src/testWithSpringBoot_3_0/java/org/openrewrite/java/spring/search/FindApiEndpointsTest.java +++ b/src/testWithSpringBoot_3_0/java/org/openrewrite/java/spring/search/FindApiEndpointsTest.java @@ -15,7 +15,6 @@ */ package org.openrewrite.java.spring.search; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.java.JavaParser; @@ -24,7 +23,6 @@ import static org.openrewrite.java.Assertions.java; -@Disabled class FindApiEndpointsTest implements RewriteTest { @Override @@ -42,7 +40,7 @@ void withinController() { """ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; - + @Controller class PersonController { @GetMapping("/count") @@ -54,7 +52,7 @@ int count() { """ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; - + @Controller class PersonController { /*~~(GET /count)~~>*/@GetMapping("/count") @@ -67,6 +65,47 @@ int count() { ); } + @Test + void withResponseBody() { + rewriteRun( + //language=java + java( + """ + import org.springframework.stereotype.Controller; + import org.springframework.web.bind.annotation.*; + + @Controller + class PersonController { + @GetMapping("/person") + @ResponseBody Person person() { + return new Person(); + } + } + + class Person { + int age = 42; + } + """, + """ + import org.springframework.stereotype.Controller; + import org.springframework.web.bind.annotation.*; + + @Controller + class PersonController { + /*~~(GET /person)~~>*/@GetMapping("/person") + @ResponseBody Person person() { + return new Person(); + } + } + + class Person { + int age = 42; + } + """ + ) + ); + } + @Test @DocumentExample void webClient() { @@ -75,7 +114,7 @@ void webClient() { java( """ import org.springframework.web.bind.annotation.*; - + @RequestMapping("/person") class PersonController { @GetMapping("/count") @@ -86,7 +125,7 @@ int count() { """, """ import org.springframework.web.bind.annotation.*; - + @RequestMapping("/person") class PersonController { /*~~(GET /person/count)~~>*/@GetMapping("/count") @@ -106,7 +145,7 @@ void multiplePathsOneMethod() { java( """ import org.springframework.web.bind.annotation.*; - + @RequestMapping({"/person", "/people"}) class PersonController { @GetMapping({"/count", "/length"}) @@ -117,7 +156,7 @@ int count() { """, """ import org.springframework.web.bind.annotation.*; - + @RequestMapping({"/person", "/people"}) class PersonController { /*~~(GET /person/count, /person/length, /people/count, /people/length)~~>*/@GetMapping({"/count", "/length"}) @@ -129,4 +168,39 @@ int count() { ) ); } + + @Test + void pathDefinedOnlyOnController() { + rewriteRun( + //language=java + java( + """ + import org.springframework.stereotype.Controller; + import org.springframework.web.bind.annotation.*; + + @Controller + @RequestMapping("/count") + class PersonController { + @GetMapping + int count() { + return 42; + } + } + """, + """ + import org.springframework.stereotype.Controller; + import org.springframework.web.bind.annotation.*; + + @Controller + @RequestMapping("/count") + class PersonController { + /*~~(GET /count)~~>*/@GetMapping + int count() { + return 42; + } + } + """ + ) + ); + } } From 9f2dc3a391ab7c1201b3ec01f8637a5fd05f7b48 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Thu, 21 Nov 2024 12:52:01 -0800 Subject: [PATCH 08/13] Add commons codec dependency upgrade --- src/main/resources/META-INF/rewrite/spring-boot-32.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/resources/META-INF/rewrite/spring-boot-32.yml b/src/main/resources/META-INF/rewrite/spring-boot-32.yml index 2154aae94..3becdbc30 100644 --- a/src/main/resources/META-INF/rewrite/spring-boot-32.yml +++ b/src/main/resources/META-INF/rewrite/spring-boot-32.yml @@ -47,6 +47,11 @@ recipeList: - org.openrewrite.gradle.plugins.UpgradePluginVersion: pluginIdPattern: org.springframework.boot newVersion: 3.2.x + - org.openrewrite.java.dependencies.AddDependency: + groupId: commons-codec + artifactId: commons-codec + version: 1.7.x + onlyIfUsing: org.apache.commons.codec..* - org.openrewrite.java.spring.security6.UpgradeSpringSecurity_6_2 - org.openrewrite.java.spring.boot3.SpringBootProperties_3_2 - org.openrewrite.java.spring.boot3.EnableVirtualThreads From 5324de6f0c20d66a13e169bab417b0f8af55d7ac Mon Sep 17 00:00:00 2001 From: Nick McKinney Date: Mon, 25 Nov 2024 13:46:25 -0500 Subject: [PATCH 09/13] fixing SpringFoxToSpringDoc to use 1.x-appropriate springdoc artifact helps with #638 --- src/main/resources/META-INF/rewrite/springdoc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/META-INF/rewrite/springdoc.yml b/src/main/resources/META-INF/rewrite/springdoc.yml index 5c3e1b524..ad73cea7f 100644 --- a/src/main/resources/META-INF/rewrite/springdoc.yml +++ b/src/main/resources/META-INF/rewrite/springdoc.yml @@ -67,7 +67,7 @@ recipeList: artifactId: "*" - org.openrewrite.java.dependencies.AddDependency: groupId: org.springdoc - artifactId: springdoc-openapi-starter-webmvc-ui + artifactId: springdoc-openapi-ui version: 1.5.x # aligns with spring boot 2.6 --- From aa5e3da7be3902876650d837912c304d9af812ba Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Tue, 26 Nov 2024 12:13:14 +0100 Subject: [PATCH 10/13] ChangeSpringPropertyKey does not yet fully support glob --- .../org/openrewrite/java/spring/ChangeSpringPropertyKey.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/openrewrite/java/spring/ChangeSpringPropertyKey.java b/src/main/java/org/openrewrite/java/spring/ChangeSpringPropertyKey.java index 79a6d46f3..12cfc5e30 100644 --- a/src/main/java/org/openrewrite/java/spring/ChangeSpringPropertyKey.java +++ b/src/main/java/org/openrewrite/java/spring/ChangeSpringPropertyKey.java @@ -46,7 +46,7 @@ public String getDescription() { } @Option(displayName = "Old property key", - description = "The property key to rename. Supports glob", + description = "The property key to rename.", example = "management.metrics.binders.*.enabled") String oldPropertyKey; From 9adfca4b22ab2d7f69ed831fad461e280fad5128 Mon Sep 17 00:00:00 2001 From: Ryan Hudson <43145457+ryan-hudson@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:39:53 -0500 Subject: [PATCH 11/13] RenameBean Scanning Recipe Refactor (#630) * Refactored the RenameBean recipe into a scanning recipe to allow renaming usages that occur outside the file where the bean is defined. * Add missing language hints * Remove unused `fromDeclaration` methods --------- Co-authored-by: Hudson, Ryan Co-authored-by: Tim te Beek --- .../openrewrite/java/spring/RenameBean.java | 270 ++-- .../java/spring/RenameBeanTest.java | 1273 ++++++++++------- 2 files changed, 822 insertions(+), 721 deletions(-) diff --git a/src/main/java/org/openrewrite/java/spring/RenameBean.java b/src/main/java/org/openrewrite/java/spring/RenameBean.java index 84ed34f0a..69a4b59ac 100644 --- a/src/main/java/org/openrewrite/java/spring/RenameBean.java +++ b/src/main/java/org/openrewrite/java/spring/RenameBean.java @@ -27,7 +27,11 @@ 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.*; @@ -35,7 +39,7 @@ @EqualsAndHashCode(callSuper = false) @Value -public class RenameBean extends Recipe { +public class RenameBean extends ScanningRecipe>> { @Option(required = false, example = "foo.MyType") @Nullable @@ -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> 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 getScanner(List> 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() { + @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 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 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 annotations, Set 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 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 getVisitor(List> acc) { + return new JavaIsoVisitor() { + @Override + public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { + J.CompilationUnit newCu = super.visitCompilationUnit(cu, ctx); + for (TreeVisitor 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 types) { @@ -190,111 +170,61 @@ private TreeVisitor precondition() { Preconditions.or(new UsesType<>(type, false), new DeclaresType<>(type)); } - @Override - public TreeVisitor getVisitor() { - return Preconditions.check(precondition(), new JavaIsoVisitor() { - @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 annotations, Set 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 renameBeanAnnotations(Set types) { + return new JavaIsoVisitor() { + 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 annotations, Set 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) { @@ -310,8 +240,8 @@ private boolean maybeRenameBean(Collection annotations, Set renameBeanAnnotationValue(J.Annotation beanAnnotation, - J.Assignment beanNameAssignment) { + private TreeVisitor renameBeanAnnotationValue( + J.Annotation beanAnnotation, J.Assignment beanNameAssignment) { return new JavaIsoVisitor() { @Override public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) { diff --git a/src/test/java/org/openrewrite/java/spring/RenameBeanTest.java b/src/test/java/org/openrewrite/java/spring/RenameBeanTest.java index 0024c1422..c423df152 100644 --- a/src/test/java/org/openrewrite/java/spring/RenameBeanTest.java +++ b/src/test/java/org/openrewrite/java/spring/RenameBeanTest.java @@ -33,15 +33,17 @@ public void defaults(RecipeSpec spec) { .parser( JavaParser.fromJavaVersion() .logCompilationWarningsAndErrors(true) + //language=java .dependsOn( """ - package sample; - class MyType {} - """, """ - package sample; - @interface MyAnnotation { - String value() default ""; - } + package sample; + class MyType {} + """, + """ + package sample; + @interface MyAnnotation { + String value() default ""; + } """ ) .classpathFromResources(new InMemoryExecutionContext(), "spring-context-6.+", "spring-beans-6.+") @@ -57,32 +59,34 @@ class BeanMethods { @Test void impliedName() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean - public MyType foo() { - return new MyType(); - } - } - """, """ - package sample; - - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean - public MyType bar() { - return new MyType(); - } - } + package sample; + + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean + public MyType foo() { + return new MyType(); + } + } + """, """ + package sample; + + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean + public MyType bar() { + return new MyType(); + } + } + """ ) ); } @@ -91,32 +95,34 @@ public MyType bar() { void impliedNameNullType() { rewriteRun( spec -> spec.recipe(new RenameBean(null, "foo", "bar")), + //language=java java( """ - package sample; - - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean - public MyType foo() { - return new MyType(); - } - } - """, """ - package sample; - - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean - public MyType bar() { - return new MyType(); - } - } + package sample; + + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean + public MyType foo() { + return new MyType(); + } + } + """, """ + package sample; + + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean + public MyType bar() { + return new MyType(); + } + } + """ ) ); } @@ -124,32 +130,34 @@ public MyType bar() { @Test void explicitNameValueParam() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean(value = "foo") - public MyType beanMethod() { - return new MyType(); - } - } - """, """ - package sample; - - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean(value = "bar") - public MyType beanMethod() { - return new MyType(); - } - } + package sample; + + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean(value = "foo") + public MyType beanMethod() { + return new MyType(); + } + } + """, """ + package sample; + + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean(value = "bar") + public MyType beanMethod() { + return new MyType(); + } + } + """ ) ); } @@ -157,32 +165,34 @@ public MyType beanMethod() { @Test void explicitName() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean("foo") - public MyType beanMethod() { - return new MyType(); - } - } - """, """ - package sample; - - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean("bar") - public MyType beanMethod() { - return new MyType(); - } - } + package sample; + + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean("foo") + public MyType beanMethod() { + return new MyType(); + } + } + """, """ + package sample; + + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean("bar") + public MyType beanMethod() { + return new MyType(); + } + } + """ ) ); } @@ -190,32 +200,34 @@ public MyType beanMethod() { @Test void explicitNameNameParam() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean(name = "foo") - public MyType beanMethod() { - return new MyType(); - } - } - """, """ - package sample; - - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean(name = "bar") - public MyType beanMethod() { - return new MyType(); - } - } + package sample; + + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean(name = "foo") + public MyType beanMethod() { + return new MyType(); + } + } + """, """ + package sample; + + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean(name = "bar") + public MyType beanMethod() { + return new MyType(); + } + } + """ ) ); } @@ -223,32 +235,34 @@ public MyType beanMethod() { @Test void explicitNameMultiNameParam() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean(name = { "foo", "somethingElse" }) - public MyType beanMethod() { - return new MyType(); - } - } - """, """ - package sample; - - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean(name = { "bar", "somethingElse" }) - public MyType beanMethod() { - return new MyType(); - } - } + package sample; + + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean(name = { "foo", "somethingElse" }) + public MyType beanMethod() { + return new MyType(); + } + } + """, """ + package sample; + + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean(name = { "bar", "somethingElse" }) + public MyType beanMethod() { + return new MyType(); + } + } + """ ) ); } @@ -256,36 +270,38 @@ public MyType beanMethod() { @Test void qualifierName() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean - @Qualifier("foo") - public MyType myType() { - return new MyType(); - } - } - """, """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean - @Qualifier("bar") - public MyType myType() { - return new MyType(); - } - } + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean + @Qualifier("foo") + public MyType myType() { + return new MyType(); + } + } + """, """ + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean + @Qualifier("bar") + public MyType myType() { + return new MyType(); + } + } + """ ) ); } @@ -294,36 +310,38 @@ public MyType myType() { void qualifierNameNullType() { rewriteRun( spec -> spec.recipe(new RenameBean(null, "foo", "bar")), + //language=java java( """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean - @Qualifier("foo") - public MyType myType() { - return new MyType(); - } - } - """, """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean - @Qualifier("bar") - public MyType myType() { - return new MyType(); - } - } + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean + @Qualifier("foo") + public MyType myType() { + return new MyType(); + } + } + """, """ + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean + @Qualifier("bar") + public MyType myType() { + return new MyType(); + } + } + """ ) ); } @@ -331,22 +349,23 @@ public MyType myType() { @Test void wrongQualifierName() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean - @Qualifier("fooz") - public MyType myType() { - return new MyType(); - } - } - """ + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean + @Qualifier("fooz") + public MyType myType() { + return new MyType(); + } + } + """ ) ); } @@ -354,20 +373,21 @@ public MyType myType() { @Test void wrongBeanName() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean({ "fooz" }) - public MyType myType() { - return new MyType(); - } - } - """ + package sample; + + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean({ "fooz" }) + public MyType myType() { + return new MyType(); + } + } + """ ) ); } @@ -375,20 +395,21 @@ public MyType myType() { @Test void wrongImpliedName() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean - public MyType myType() { - return new MyType(); - } - } - """ + package sample; + + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean + public MyType myType() { + return new MyType(); + } + } + """ ) ); } @@ -396,20 +417,21 @@ public MyType myType() { @Test void wrongType() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean - public String foo() { - return ""; - } - } - """ + package sample; + + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean + public String foo() { + return ""; + } + } + """ ) ); } @@ -417,20 +439,21 @@ public String foo() { @Test void wrongTypeWithExplicitName() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.context.annotation.Bean; - import sample.MyType; - - class A { - @Bean("foo") - public String myBean() { - return ""; - } - } - """ + package sample; + + import org.springframework.context.annotation.Bean; + import sample.MyType; + + class A { + @Bean("foo") + public String myBean() { + return ""; + } + } + """ ) ); } @@ -438,20 +461,21 @@ public String myBean() { @Test void wrongAnnotation() { rewriteRun( + //language=java java( """ - package sample; - - import sample.MyAnnotation; - import sample.MyType; - - class A { - @MyAnnotation - public String foo() { - return ""; - } - } - """ + package sample; + + import sample.MyAnnotation; + import sample.MyType; + + class A { + @MyAnnotation + public String foo() { + return ""; + } + } + """ ) ); } @@ -465,26 +489,28 @@ class BeanClasses { void impliedName() { rewriteRun( spec -> spec.recipe(new RenameBean("sample.Foo", "foo", "bar")), + //language=java java( """ - package sample; - - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - class Foo { - } - """, """ - package sample; - - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - class Bar { - } + package sample; + + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + class Foo { + } + """, """ + package sample; + + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + class Bar { + } + """ ) ); } @@ -493,26 +519,28 @@ class Bar { void impliedNameNullType() { rewriteRun( spec -> spec.recipe(new RenameBean(null, "foo", "bar")), + //language=java java( """ - package sample; - - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - class Foo { - } - """, """ - package sample; - - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - class Bar { - } + package sample; + + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + class Foo { + } + """, """ + package sample; + + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + class Bar { + } + """ ) ); } @@ -521,26 +549,28 @@ class Bar { void explicitName() { rewriteRun( spec -> spec.recipe(new RenameBean("sample.Foo", "foo", "bar")), + //language=java java( """ - package sample; - - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration("foo") - class Foo { - } - """, """ - package sample; - - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration("bar") - class Foo { - } + package sample; + + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration("foo") + class Foo { + } + """, """ + package sample; + + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration("bar") + class Foo { + } + """ ) ); } @@ -549,26 +579,28 @@ class Foo { void explicitNameNullType() { rewriteRun( spec -> spec.recipe(new RenameBean(null, "foo", "bar")), + //language=java java( """ - package sample; - - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration("foo") - class Foo { - } - """, """ - package sample; - - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration("bar") - class Foo { - } + package sample; + + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration("foo") + class Foo { + } + """, """ + package sample; + + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration("bar") + class Foo { + } + """ ) ); } @@ -577,30 +609,32 @@ class Foo { void qualifierName() { rewriteRun( spec -> spec.recipe(new RenameBean("sample.Foo", "fooz", "barz")), + //language=java java( """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - @Qualifier("fooz") - class Foo { - } - """, """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - @Qualifier("barz") - class Foo { - } + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + @Qualifier("fooz") + class Foo { + } + """, """ + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + @Qualifier("barz") + class Foo { + } + """ ) ); } @@ -609,19 +643,20 @@ class Foo { void wrongQualifierName() { rewriteRun( spec -> spec.recipe(new RenameBean("sample.Foo", "foo", "bar")), + //language=java java( """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - @Qualifier("fooz") - class Foo { - } - """ + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + @Qualifier("fooz") + class Foo { + } + """ ) ); } @@ -630,18 +665,19 @@ class Foo { void wrongType() { rewriteRun( spec -> spec.recipe(new RenameBean("sample.Foo", "foo", "bar")), + //language=java java( """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration("foo") - class A { - } - """ + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration("foo") + class A { + } + """ ) ); } @@ -650,18 +686,19 @@ class A { void wrongAnnotation() { rewriteRun( spec -> spec.recipe(new RenameBean("sample.Foo", "foo", "bar")), + //language=java java( """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import sample.MyAnnotation; - import sample.MyType; - - @MyAnnotation("foo") - class A { - } - """ + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import sample.MyAnnotation; + import sample.MyType; + + @MyAnnotation("foo") + class A { + } + """ ) ); } @@ -675,38 +712,40 @@ class Usages { @Test void parameterUsage() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Bean; - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - class A { - @Bean - public String myBean(@Qualifier("foo") MyType myType) { - return ""; - } - } - """, """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Bean; - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - class A { - @Bean - public String myBean(@Qualifier("bar") MyType myType) { - return ""; - } - } + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + class A { + @Bean + public String myBean(@Qualifier("foo") MyType myType) { + return ""; + } + } + """, """ + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + class A { + @Bean + public String myBean(@Qualifier("bar") MyType myType) { + return ""; + } + } + """ ) ); } @@ -715,38 +754,40 @@ public String myBean(@Qualifier("bar") MyType myType) { void parameterUsageNullType() { rewriteRun( spec -> spec.recipe(new RenameBean(null, "foo", "bar")), + //language=java java( """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Bean; - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - class A { - @Bean - public String myBean(@Qualifier("foo") MyType myType) { - return ""; - } - } - """, """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Bean; - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - class A { - @Bean - public String myBean(@Qualifier("bar") MyType myType) { - return ""; - } - } + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + class A { + @Bean + public String myBean(@Qualifier("foo") MyType myType) { + return ""; + } + } + """, """ + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + class A { + @Bean + public String myBean(@Qualifier("bar") MyType myType) { + return ""; + } + } + """ ) ); } @@ -754,23 +795,24 @@ public String myBean(@Qualifier("bar") MyType myType) { @Test void parameterWrongType() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Bean; - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - class A { - @Bean - public String myBean(@Qualifier("foo") String myString) { - return ""; - } - } - """ + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + class A { + @Bean + public String myBean(@Qualifier("foo") String myString) { + return ""; + } + } + """ ) ); } @@ -778,23 +820,24 @@ public String myBean(@Qualifier("foo") String myString) { @Test void parameterWrongName() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Bean; - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - class A { - @Bean - public String myBean(@Qualifier("fooz") MyType myType) { - return ""; - } - } - """ + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + class A { + @Bean + public String myBean(@Qualifier("fooz") MyType myType) { + return ""; + } + } + """ ) ); } @@ -802,22 +845,23 @@ public String myBean(@Qualifier("fooz") MyType myType) { @Test void parameterNoName() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.context.annotation.Bean; - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - class A { - @Bean - public String myBean(MyType myType) { - return ""; - } - } - """ + package sample; + + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + class A { + @Bean + public String myBean(MyType myType) { + return ""; + } + } + """ ) ); } @@ -825,32 +869,34 @@ public String myBean(MyType myType) { @Test void fieldUsage() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - class A { - @Qualifier("foo") - private MyType myType; - } - """, """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - class A { - @Qualifier("bar") - private MyType myType; - } + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + class A { + @Qualifier("foo") + private MyType myType; + } + """, """ + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + class A { + @Qualifier("bar") + private MyType myType; + } + """ ) ); } @@ -858,20 +904,21 @@ class A { @Test void fieldWrongType() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - class A { - @Qualifier("foo") - private String myString; - } - """ + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + class A { + @Qualifier("foo") + private String myString; + } + """ ) ); } @@ -879,20 +926,21 @@ class A { @Test void fieldWrongName() { rewriteRun( + //language=java java( """ - package sample; - - import org.springframework.beans.factory.annotation.Qualifier; - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - class A { - @Qualifier("fooz") - private MyType myTye; - } - """ + package sample; + + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + class A { + @Qualifier("fooz") + private MyType myTye; + } + """ ) ); } @@ -900,18 +948,141 @@ class A { @Test void fieldNoName() { rewriteRun( + //language=java + java( + """ + package sample; + + import org.springframework.context.annotation.Configuration; + import sample.MyType; + + @Configuration + class A { + private MyType myTye; + } + """ + ) + ); + } + + @Test + void renamesMethodDeclarationAndUsageInOtherFile() { + rewriteRun( + spec -> spec.recipe(new RenameBean(null, "beanMethod", "newBeanMethod")), + //language=java + java( + """ + package sample; + + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + public class TestConfiguration { + + @Bean + public void beanMethod() { + } + } + """, + """ + package sample; + + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + public class TestConfiguration { + + @Bean + public void newBeanMethod() { + } + } + """ + ), + //language=java + java( + """ + package sample; + + public class TestClass { + + TestConfiguration testConfig; + + void testMethod() { + testConfig.beanMethod(); + } + } + """, + """ + package sample; + + public class TestClass { + + TestConfiguration testConfig; + + void testMethod() { + testConfig.newBeanMethod(); + } + } + """ + ) + ); + } + + @Test + void renamesClassBeanUsagesInOtherFiles() { + rewriteRun( + spec -> spec.recipe(new RenameBean(null, "testConfiguration", "newTestConfiguration")), + //language=java java( """ - package sample; - - import org.springframework.context.annotation.Configuration; - import sample.MyType; - - @Configuration - class A { - private MyType myTye; - } + package sample; + + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + public class TestConfiguration { + } + """, """ + package sample; + + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + public class NewTestConfiguration { + } + """ + ), + //language=java + java( + """ + package sample; + + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.stereotype.Service; + + @Service + public class MyClass { + @Autowired + TestConfiguration testConfig; + } + """, + """ + package sample; + + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.stereotype.Service; + + @Service + public class MyClass { + @Autowired + NewTestConfiguration testConfig; + } + """ ) ); } From 4d67107e8ef650326881f959a2dcb2b0d8bcf143 Mon Sep 17 00:00:00 2001 From: SiBorea <108953913+SiBorea@users.noreply.github.com> Date: Wed, 27 Nov 2024 01:47:12 +0800 Subject: [PATCH 12/13] Add MigrateSpringdocCommon recipe (#633) * Add MigrateSpringdocCommon recipe * Apply suggestions from bot Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Fix syntax messed up by bot * Add MigrateSpringdocCommon recipe * Apply suggestions from bot Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Fix syntax messed up by bot * Fix build fail on SpringBoot_1_5 * Fix rebase error * Move to Spring Boot 2.6 tests, to match inclusion in `spring-boot-26.yml` --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Tim te Beek --- build.gradle.kts | 3 + .../resources/META-INF/rewrite/springdoc.yml | 20 +++++ .../springdoc/MigrateSpringdocCommonTest.java | 90 +++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 src/testWithSpringBoot_2_6/java/org/openrewrite/java/springdoc/MigrateSpringdocCommonTest.java diff --git a/build.gradle.kts b/build.gradle.kts index f36685ab1..22942c42d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -219,6 +219,9 @@ dependencies { "testWithSpringBoot_2_5RuntimeOnly"("org.springframework.boot:spring-boot-actuator:2.5.+") "testWithSpringBoot_2_5RuntimeOnly"("org.springframework:spring-web:5.3.+") + "testWithSpringBoot_2_6RuntimeOnly"("org.springdoc:springdoc-openapi-common:1.+") + "testWithSpringBoot_2_6RuntimeOnly"("io.swagger.core.v3:swagger-models:2.+") + "testWithSpringBoot_2_7RuntimeOnly"("org.springframework:spring-context:5.3.+") "testWithSpringBoot_2_7RuntimeOnly"("org.springframework.boot:spring-boot-starter:2.7.+") "testWithSpringBoot_2_7RuntimeOnly"("org.springframework.boot:spring-boot:2.7.+") diff --git a/src/main/resources/META-INF/rewrite/springdoc.yml b/src/main/resources/META-INF/rewrite/springdoc.yml index ad73cea7f..cb0df6d0c 100644 --- a/src/main/resources/META-INF/rewrite/springdoc.yml +++ b/src/main/resources/META-INF/rewrite/springdoc.yml @@ -27,6 +27,7 @@ tags: recipeList: - org.openrewrite.java.springdoc.SwaggerToSpringDoc - org.openrewrite.java.springdoc.ReplaceSpringFoxDependencies + - org.openrewrite.java.springdoc.MigrateSpringdocCommon --- type: specs.openrewrite.org/v1beta/recipe @@ -151,3 +152,22 @@ recipeList: groupId: org.springdoc artifactId: "*" newVersion: 2.1.x + +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.springdoc.MigrateSpringdocCommon +displayName: Migrate from springdoc-openapi-common to springdoc-openapi-starter-common +description: Migrate from springdoc-openapi-common to springdoc-openapi-starter-common. +tags: + - springdoc + - openapi +recipeList: + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: org.springdoc.core.customizers.OpenApiCustomiser + newFullyQualifiedTypeName: org.springdoc.core.customizers.OpenApiCustomizer + - org.openrewrite.java.ChangeMethodName: + methodPattern: org.springdoc.core.GroupedOpenApi.Builder addOpenApiCustomiser(..) + newMethodName: addOpenApiCustomizer + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: org.springdoc.core.GroupedOpenApi + newFullyQualifiedTypeName: org.springdoc.core.models.GroupedOpenApi diff --git a/src/testWithSpringBoot_2_6/java/org/openrewrite/java/springdoc/MigrateSpringdocCommonTest.java b/src/testWithSpringBoot_2_6/java/org/openrewrite/java/springdoc/MigrateSpringdocCommonTest.java new file mode 100644 index 000000000..01427a623 --- /dev/null +++ b/src/testWithSpringBoot_2_6/java/org/openrewrite/java/springdoc/MigrateSpringdocCommonTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2024 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.springdoc; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; +import static org.openrewrite.java.Assertions.java; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; + +class MigrateSpringdocCommonTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipeFromResources("org.openrewrite.java.springdoc.MigrateSpringdocCommon") + .parser(JavaParser.fromJavaVersion().classpath( + "springdoc-openapi-common-1.+", + "swagger-models-2.+" + )); + } + + @Test + @DocumentExample + void fixCustomiserAndGroupedOpenApi() { + // language=java + rewriteRun( + java( + """ + import io.swagger.v3.oas.models.OpenAPI; + import org.springdoc.core.GroupedOpenApi; + import org.springdoc.core.customizers.OpenApiCustomiser; + + public class OpenApiConfiguration { + + public static void groupedOpenApi() { + GroupedOpenApi.builder() + .group("group") + .pathsToMatch("/api/**") + .addOpenApiCustomiser(new FoobarOpenApiCustomiser()) + .build(); + } + + public static class FoobarOpenApiCustomiser implements OpenApiCustomiser { + @Override + public void customise(OpenAPI openApi) { + } + } + } + """, """ + import io.swagger.v3.oas.models.OpenAPI; + import org.springdoc.core.customizers.OpenApiCustomizer; + import org.springdoc.core.models.GroupedOpenApi; + + public class OpenApiConfiguration { + + public static void groupedOpenApi() { + GroupedOpenApi.builder() + .group("group") + .pathsToMatch("/api/**") + .addOpenApiCustomizer(new FoobarOpenApiCustomiser()) + .build(); + } + + public static class FoobarOpenApiCustomiser implements OpenApiCustomizer { + @Override + public void customise(OpenAPI openApi) { + } + } + } + """ + ) + ); + } +} From 6bf850270c34ea4f8fe9981d965ae79e5e0d60ae Mon Sep 17 00:00:00 2001 From: Curtis Date: Wed, 27 Nov 2024 02:17:20 +0800 Subject: [PATCH 13/13] Add ChangeMethodParameter for modify parameters in Spring Batch method declaration (#631) * Add ChangeMethodParameter for modify parameters in method declaration Signed-off-by: Kun Chang * Minor polish * Add correct year * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Add UpgradeSkipPolicyParameterType for Spring Batch Signed-off-by: Kun Chang * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Fix tests * Also show ability to change interface methods --------- Signed-off-by: Kun Chang Co-authored-by: Tim te Beek Co-authored-by: Tim te Beek Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../java/spring/ChangeMethodParameter.java | 244 ++++++++++ .../META-INF/rewrite/spring-batch-5.0.yml | 11 + .../spring/ChangeMethodParameterTest.java | 445 ++++++++++++++++++ .../UpgradeSkipPolicyParameterTypeTest.java | 74 +++ 4 files changed, 774 insertions(+) create mode 100644 src/main/java/org/openrewrite/java/spring/ChangeMethodParameter.java create mode 100644 src/test/java/org/openrewrite/java/spring/ChangeMethodParameterTest.java create mode 100644 src/testWithSpringBoot_3_0/java/org/openrewrite/java/spring/batch/UpgradeSkipPolicyParameterTypeTest.java diff --git a/src/main/java/org/openrewrite/java/spring/ChangeMethodParameter.java b/src/main/java/org/openrewrite/java/spring/ChangeMethodParameter.java new file mode 100644 index 000000000..b1cfdac56 --- /dev/null +++ b/src/main/java/org/openrewrite/java/spring/ChangeMethodParameter.java @@ -0,0 +1,244 @@ +/* + * Copyright 2024 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.spring; + +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; +import lombok.Value; +import org.openrewrite.*; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.search.DeclaresMethod; +import org.openrewrite.java.service.ImportService; +import org.openrewrite.java.tree.*; +import org.openrewrite.marker.Markers; + +import java.util.ArrayList; +import java.util.List; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.openrewrite.Tree.randomId; + +/** + * A recipe to change parameter type for a method declaration. + *

+ * NOTE: This recipe is usually used for initial modification of parameter changes. + * After modifying method parameters using this recipe, you may also need to modify + * the method definition as needed to avoid compilation errors. + */ +@Value +@EqualsAndHashCode(callSuper = false) +public class ChangeMethodParameter extends Recipe { + + /** + * A method pattern that is used to find matching method declarations. + * See {@link MethodMatcher} for details on the expression's syntax. + */ + @Option(displayName = "Method pattern", + description = "A method pattern that is used to find the method declarations to modify.", + example = "com.yourorg.A foo(int, int)") + String methodPattern; + + @Option(displayName = "Parameter type", + description = "The new type of the parameter that gets updated.", + example = "java.lang.String") + String parameterType; + + @Option(displayName = "Parameter index", + description = "A zero-based index that indicates the position at which the parameter will be added.", + example = "0") + Integer parameterIndex; + + @Override + public String getInstanceNameSuffix() { + return String.format("`%s` in methods `%s`", parameterType, methodPattern); + } + + @Override + public String getDisplayName() { + return "Change parameter type for a method declaration"; + } + + @Override + public String getDescription() { + return "Change parameter type for a method declaration, identified by a method pattern."; + } + + @Override + public TreeVisitor getVisitor() { + MethodMatcher methodMatcher = new MethodMatcher(methodPattern, true); + return Preconditions.check(new DeclaresMethod<>(methodMatcher), new ChangeMethodArgumentVisitor(methodMatcher)); + } + + @RequiredArgsConstructor + private class ChangeMethodArgumentVisitor extends JavaIsoVisitor { + private final MethodMatcher methodMatcher; + + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDeclaration, ExecutionContext ctx) { + J.MethodDeclaration md = super.visitMethodDeclaration(methodDeclaration, ctx); + + if (methodMatcher.matches(md.getMethodType())) { + if (md.getParameters().isEmpty() || + md.getParameters().get(0) instanceof J.Empty || + md.getParameters().size() <= parameterIndex) { + return md; + } + if (md.getParameters().get(parameterIndex) instanceof J.VariableDeclarations) { + J.VariableDeclarations parameter = (J.VariableDeclarations) md.getParameters().get(parameterIndex); + + TypeTree typeTree = createTypeTree(parameterType); + if (TypeUtils.isOfType(parameter.getType(), typeTree.getType())) { + return md; + } + + String parameterName = parameter.getVariables().get(0).getSimpleName(); + parameter = parameter.withTypeExpression(typeTree).withVariables(singletonList( + new J.VariableDeclarations.NamedVariable( + randomId(), + Space.EMPTY, + Markers.EMPTY, + new J.Identifier( + randomId(), + Space.EMPTY, + Markers.EMPTY, + emptyList(), + parameterName, + typeTree.getType(), + new JavaType.Variable( + null, + 0, + parameterName, + md.getMethodType(), + typeTree.getType(), + null + ) + ), + emptyList(), + null, + null + ) + )); + + md = autoFormat(changeParameter(md, parameter), parameter, ctx, getCursor().getParentTreeCursor()); + } + + } + return md; + } + + private J.MethodDeclaration changeParameter(J.MethodDeclaration method, J.VariableDeclarations parameter) { + List originalParameters = method.getParameters(); + List newParameters = new ArrayList<>(); + for (int i = 0; i < originalParameters.size(); i++) { + if (i == parameterIndex) { + newParameters.add(parameter); + } else { + newParameters.add(originalParameters.get(i)); + } + } + + method = method.withParameters(newParameters); + + if (parameter.getTypeExpression() != null && !(parameter.getTypeExpression() instanceof J.Identifier || parameter.getTypeExpression() instanceof J.Primitive)) { + doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(parameter.getTypeExpression())); + } + return method; + } + + private TypeTree createTypeTree(String typeName) { + int arrayIndex = typeName.lastIndexOf('['); + if (arrayIndex != -1) { + TypeTree elementType = createTypeTree(typeName.substring(0, arrayIndex)); + return new J.ArrayType( + randomId(), + Space.EMPTY, + Markers.EMPTY, + elementType, + null, + JLeftPadded.build(Space.EMPTY), + new JavaType.Array(null, elementType.getType(), null) + ); + } + int genericsIndex = typeName.indexOf('<'); + if (genericsIndex != -1) { + TypeTree rawType = createTypeTree(typeName.substring(0, genericsIndex)); + List> typeParameters = new ArrayList<>(); + for (String typeParam : typeName.substring(genericsIndex + 1, typeName.lastIndexOf('>')).split(",")) { + typeParameters.add(JRightPadded.build((Expression) createTypeTree(typeParam.trim()))); + } + return new J.ParameterizedType( + randomId(), + Space.EMPTY, + Markers.EMPTY, + rawType, + JContainer.build(Space.EMPTY, typeParameters, Markers.EMPTY), + new JavaType.Parameterized(null, (JavaType.FullyQualified) rawType.getType(), null) + ); + } + JavaType.Primitive type = JavaType.Primitive.fromKeyword(typeName); + if (type != null) { + return new J.Primitive( + randomId(), + Space.EMPTY, + Markers.EMPTY, + type + ); + } + if (typeName.equals("?")) { + return new J.Wildcard( + randomId(), + Space.EMPTY, + Markers.EMPTY, + null, + null + ); + } + if (typeName.startsWith("?") && typeName.contains("extends")) { + return new J.Wildcard( + randomId(), + Space.EMPTY, + Markers.EMPTY, + new JLeftPadded<>(Space.SINGLE_SPACE, J.Wildcard.Bound.Extends, Markers.EMPTY), + createTypeTree(typeName.substring(typeName.indexOf("extends") + "extends".length() + 1).trim()).withPrefix(Space.SINGLE_SPACE) + ); + } + if (typeName.indexOf('.') == -1) { + String javaLangType = TypeUtils.findQualifiedJavaLangTypeName(typeName); + if (javaLangType != null) { + return new J.Identifier( + randomId(), + Space.EMPTY, + Markers.EMPTY, + emptyList(), + typeName, + JavaType.buildType(javaLangType), + null + ); + } + } + TypeTree typeTree = TypeTree.build(typeName); + // somehow the type attribution is incomplete, but `ChangeType` relies on this + if (typeTree instanceof J.FieldAccess) { + typeTree = ((J.FieldAccess) typeTree).withName(((J.FieldAccess) typeTree).getName().withType(typeTree.getType())); + } else if (typeTree.getType() == null) { + typeTree = ((J.Identifier) typeTree).withType(JavaType.ShallowClass.build(typeName)); + } + return typeTree; + } + } +} diff --git a/src/main/resources/META-INF/rewrite/spring-batch-5.0.yml b/src/main/resources/META-INF/rewrite/spring-batch-5.0.yml index cb885c4ea..bfb16c520 100644 --- a/src/main/resources/META-INF/rewrite/spring-batch-5.0.yml +++ b/src/main/resources/META-INF/rewrite/spring-batch-5.0.yml @@ -33,6 +33,7 @@ recipeList: - org.openrewrite.java.spring.batch.MigrateStepBuilderFactory - org.openrewrite.java.spring.batch.MigrateItemWriterWrite - org.openrewrite.java.spring.batch.RemoveDefaultBatchConfigurer + - org.openrewrite.java.spring.batch.UpgradeSkipPolicyParameterType - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: org.springframework.batch.core.metrics.BatchMetrics newFullyQualifiedTypeName: org.springframework.batch.core.observability.BatchMetrics @@ -63,3 +64,13 @@ recipeList: - org.openrewrite.java.spring.batch.ReplaceSupportClassWithItsInterface: fullyQualifiedClassName: org.springframework.batch.repeat.listener.RepeatListenerSupport fullyQualifiedInterfaceName: org.springframework.batch.repeat.RepeatListener +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.spring.batch.UpgradeSkipPolicyParameterType +displayName: Change the type of `skipCount` parameter in `SkipPolicy` from `int` to `long` +description: The `skipCount` parameter in `org.springframework.batch.core.step.skip.SkipPolicy#shouldSkip` has been changed from `int` to `long`, this recipe updates the parameter type in the implementing classes. +recipeList: + - org.openrewrite.java.spring.ChangeMethodParameter: + methodPattern: org.springframework.batch.core.step.skip.SkipPolicy shouldSkip(java.lang.Throwable, int) + parameterIndex: 1 + parameterType: long diff --git a/src/test/java/org/openrewrite/java/spring/ChangeMethodParameterTest.java b/src/test/java/org/openrewrite/java/spring/ChangeMethodParameterTest.java new file mode 100644 index 000000000..c07226449 --- /dev/null +++ b/src/test/java/org/openrewrite/java/spring/ChangeMethodParameterTest.java @@ -0,0 +1,445 @@ +/* + * Copyright 2024 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.spring; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class ChangeMethodParameterTest implements RewriteTest { + + @DocumentExample + @Test + void primitive() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodParameter("foo.Foo#bar(..)", "long", 0)), + //language=java + java( + """ + package foo; + class Foo { + void bar(int i) { + } + } + """, + """ + package foo; + class Foo { + void bar(long i) { + } + } + """ + ) + ); + } + + @Test + void indexLargeThanZero() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodParameter("foo.Foo#bar(..)", "long", 1)), + //language=java + java( + """ + package foo; + class Foo { + void bar(int i, int j) { + } + } + """, + """ + package foo; + class Foo { + void bar(int i, long j) { + } + } + """ + ) + ); + } + + @Test + void sameType() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodParameter("foo.Foo#bar(..)", "int", 0)), + //language=java + java( + """ + package foo; + class Foo { + void bar(int i) { + } + } + """ + ) + ); + } + + @Test + void notExistsIndex() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodParameter("foo.Foo#bar(..)", "int", 1)), + //language=java + java( + """ + package foo; + class Foo { + void bar(int i) { + } + } + """ + ) + ); + } + + @Test + void methodNotMatch() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodParameter("foo.Foo#bar(..)", "long", 0)), + //language=java + java( + """ + package foo; + class Foo { + void bar(int i) { + } + } + """, + """ + package foo; + class Foo { + void bar(long i) { + } + } + """ + ) + ); + } + + @Test + void typePattern() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodParameter("*..*#bar(..)", "long", 0)), + //language=java + java( + """ + package foo; + class Foo { + void bar(int i) { + } + } + """, + """ + package foo; + class Foo { + void bar(long i) { + } + } + """ + ) + ); + } + + @Test + void primitiveArray() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodParameter("foo.Foo#bar(..)", "int[]", 0)), + //language=java + java( + """ + package foo; + class Foo { + void bar(int i) { + } + } + """, + """ + package foo; + class Foo { + void bar(int[] i) { + } + } + """ + ) + ); + } + + @Test + void parameterized() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodParameter("foo.Foo#bar(..)", "java.util.List", 0)), + //language=java + java( + """ + package foo; + class Foo { + void bar(int i) { + } + } + """, + """ + package foo; + + import java.util.List; + import java.util.regex.Pattern; + + class Foo { + void bar(List i) { + } + } + """ + ) + ); + } + + @Test + void wildcard() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodParameter("foo.Foo#bar(..)", "java.util.List", 0)), + //language=java + java( + """ + package foo; + class Foo { + void bar(int i) { + } + } + """, + """ + package foo; + + import java.util.List; + + class Foo { + void bar(List i) { + } + } + """ + ) + ); + } + + @Test + void wildcardExtends() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodParameter("foo.Foo#bar(..)", "java.util.List", 0)), + //language=java + java( + """ + package foo; + class Foo { + void bar(int i) { + } + } + """, + """ + package foo; + + import java.util.List; + + class Foo { + void bar(List i) { + } + } + """ + ) + ); + } + + @Test + void string() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodParameter("foo.Foo#bar(..)", "String", 0)), + //language=java + java( + """ + package foo; + class Foo { + void bar(int j) { + } + + void bar(int i) { + } + + void bar(int i, int j) { + } + } + """, + """ + package foo; + class Foo { + void bar(String j) { + } + + void bar(String i) { + } + + void bar(String i, int j) { + } + } + """ + ) + ); + } + + @Test + void first() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodParameter("foo.Foo#bar(..)", "long", 0)), + //language=java + java( + """ + package foo; + class Foo { + void bar(int i) { + } + + void bar(int i, int j) { + } + } + """, + """ + package foo; + class Foo { + void bar(long i) { + } + + void bar(long i, int j) { + } + } + """ + ) + ); + } + + @Test + void qualified() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodParameter("foo.Foo#bar(..)", "java.util.regex.Pattern", 0)), + //language=java + java( + """ + package foo; + class Foo { + void bar(int i) { + } + void bar(int i, int j) { + } + } + """, + """ + package foo; + + import java.util.regex.Pattern; + + class Foo { + void bar(Pattern i) { + } + + void bar(Pattern i, int j) { + } + } + """ + ) + ); + } + + @Test + void object() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodParameter("foo.Foo#bar(..)", "Object", 0)), + //language=java + java( + """ + package foo; + class Foo { + void bar(int i) { + } + } + """, + """ + package foo; + class Foo { + void bar(Object i) { + } + } + """ + ) + ); + } + + @Test + void fromInterface() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodParameter("b.B#m(String, String)", "long", 0)), + //language=java + java( + """ + package b; + public interface B { + boolean m(String i); + + boolean m(String i, String j); + } + """, + """ + package b; + public interface B { + boolean m(String i); + + boolean m(long i, String j); + } + """ + ), + //language=java + java( + """ + package foo; + import b.*; + + class Foo implements B { + @Override + public boolean m(String i) { + return true; + } + + @Override + public boolean m(String i, String j) { + return true; + } + } + """, + """ + package foo; + import b.*; + + class Foo implements B { + @Override + public boolean m(String i) { + return true; + } + + @Override + public boolean m(long i, String j) { + return true; + } + } + """ + ) + ); + } +} diff --git a/src/testWithSpringBoot_3_0/java/org/openrewrite/java/spring/batch/UpgradeSkipPolicyParameterTypeTest.java b/src/testWithSpringBoot_3_0/java/org/openrewrite/java/spring/batch/UpgradeSkipPolicyParameterTypeTest.java new file mode 100644 index 000000000..b3454d513 --- /dev/null +++ b/src/testWithSpringBoot_3_0/java/org/openrewrite/java/spring/batch/UpgradeSkipPolicyParameterTypeTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2024 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.spring.batch; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class UpgradeSkipPolicyParameterTypeTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec + .recipeFromResource("/META-INF/rewrite/spring-batch-5.0.yml", "org.openrewrite.java.spring.batch.UpgradeSkipPolicyParameterType") + .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), + "spring-batch-core-4.3.+", + "spring-batch-infrastructure-4.3.+", + "spring-beans-4.3.30.RELEASE", + "spring-batch", + "spring-boot", + "spring-core", + "spring-context" + )); + } + + @DocumentExample + @Test + void replaceParameter() { + // language=java + rewriteRun( + java( + """ + import org.springframework.batch.core.step.skip.SkipLimitExceededException; + import org.springframework.batch.core.step.skip.SkipPolicy; + + public class MySkipPolicy implements SkipPolicy { + @Override + public boolean shouldSkip(Throwable throwable, int skipCount) { + return true; + } + } + """, + """ + import org.springframework.batch.core.step.skip.SkipLimitExceededException; + import org.springframework.batch.core.step.skip.SkipPolicy; + + public class MySkipPolicy implements SkipPolicy { + @Override + public boolean shouldSkip(Throwable throwable, long skipCount) { + return true; + } + } + """ + ) + ); + } +}