diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseDataParameterized.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseDataParameterized.java new file mode 100644 index 000000000..1ce9fce77 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseDataParameterized.java @@ -0,0 +1,123 @@ +/* + * 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.migrate.lombok; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.tree.J; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.Comparator.comparing; + +@Value +@EqualsAndHashCode(callSuper = false) +public class UseDataParameterized extends Recipe { + @Override + public String getDisplayName() { + return "Summarize class annotations into @Data"; + } + + @Override + public String getDescription() { + return "Summarize class annotations into @Data."; + } + + List exceptions; + + @Override + public TreeVisitor getVisitor() { + return new Summarizer(exceptions == null ? Collections.emptyList(): exceptions); + } + + private static class Summarizer extends JavaIsoVisitor { + + Set annotationsToReplace = Stream + .of( + "ToString", + "EqualsAndHashCode", + "Getter", + "Setter", + "RequiredArgsConstructor") + .collect(Collectors.toSet()); + + Set needed; + + public Summarizer(List exceptions) { + needed = annotationsToReplace + .stream() + .filter(a -> !exceptions.contains(a)) + .collect(Collectors.toSet()); + } + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + + J.ClassDeclaration visited = super.visitClassDeclaration(classDecl, ctx); + + Set namesOfRemainingAnotations = visited.getLeadingAnnotations() + .stream() + .map(J.Annotation::getSimpleName) + .collect(Collectors.toSet()); + Set namesOfRemovedAnnotations = classDecl.getLeadingAnnotations() + .stream() + .map(J.Annotation::getSimpleName) + .filter(a -> !namesOfRemainingAnotations.contains(a)) + .collect(Collectors.toSet()); + + if (visited != classDecl && namesOfRemovedAnnotations.containsAll(needed)) { + + maybeRemoveImport("lombok.ToString"); + maybeRemoveImport("lombok.EqualsAndHashCode"); + maybeRemoveImport("lombok.Getter"); + maybeRemoveImport("lombok.Setter"); + maybeRemoveImport("lombok.RequiredArgsConstructor"); + maybeAddImport("lombok.Data"); + + JavaTemplate template = JavaTemplate.builder("@Data\n") + .imports("lombok.Data") + .javaParser(JavaParser.fromJavaVersion().classpath("lombok")) + .build(); + + return template.apply( + updateCursor(visited), + visited.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))); + } + return classDecl; + } + + @Override + public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) { + return annotationsToReplace.contains(annotation.getSimpleName()) + && annotation.getArguments() == null //no arguments of any kind. Too strict? + //should only trigger on class annotation + && getCursor().getParent().getValue() instanceof J.ClassDeclaration + ? null + : annotation; + } + + } +} diff --git a/src/main/resources/META-INF/rewrite/use-lombok-data-convert-negligently.yml b/src/main/resources/META-INF/rewrite/use-lombok-data-convert-negligently.yml new file mode 100644 index 000000000..f45cb20aa --- /dev/null +++ b/src/main/resources/META-INF/rewrite/use-lombok-data-convert-negligently.yml @@ -0,0 +1,27 @@ +# +# 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. +# + +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.migrate.lombok.UseDataNegligently +displayName: Summarize class annotations into @Data negligently +description: >- + Summarize class annotations into @Data even if @ToString or @EqualsAndHashCode are missing. You might need to manually clean up the result!. +recipeList: + - org.openrewrite.java.migrate.lombok.UseDataParameterized: + exceptions: + - EqualsAndHashCode + - ToString diff --git a/src/main/resources/META-INF/rewrite/use-lombok-data.yml b/src/main/resources/META-INF/rewrite/use-lombok-data.yml new file mode 100644 index 000000000..21b64477a --- /dev/null +++ b/src/main/resources/META-INF/rewrite/use-lombok-data.yml @@ -0,0 +1,25 @@ +# +# 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. +# + +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.migrate.lombok.UseData +displayName: Summarize class annotations into @Data +description: >- + Summarize class annotations into @Data. +recipeList: + - org.openrewrite.java.migrate.lombok.UseDataParameterized: + exceptions: [] diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseDataNegligentlyTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseDataNegligentlyTest.java new file mode 100644 index 000000000..9f016f33d --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseDataNegligentlyTest.java @@ -0,0 +1,139 @@ +/* + * 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.migrate.lombok; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class UseDataNegligentlyTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipeFromResources("org.openrewrite.java.migrate.lombok.UseDataNegligently") + .parser(JavaParser.fromJavaVersion().logCompilationWarningsAndErrors(true) + .classpath("lombok")); + } + + @DocumentExample + @Test + void replaceOneFieldData() { + rewriteRun(// language=java + java( + """ + import lombok.Getter; + import lombok.Setter; + import lombok.RequiredArgsConstructor; + + @Getter + @Setter + @RequiredArgsConstructor + class A {} + """, + """ + import lombok.Data; + + @Data + class A {} + """ + ) + ); + } + + @Test + void replaceWhenOptionalAnnotationsPresent() { + rewriteRun(// language=java + java( + """ + import lombok.ToString; + import lombok.EqualsAndHashCode; + import lombok.Getter; + import lombok.Setter; + import lombok.RequiredArgsConstructor; + + @ToString + @EqualsAndHashCode + @Getter + @Setter + @RequiredArgsConstructor + class A {} + """, + """ + import lombok.Data; + + @Data + class A {} + """ + ) + ); + } + + @Test + void otherAnnotationsAround() { + rewriteRun(// language=java + java( + """ + import lombok.*; + import lombok.extern.java.Log; + + @NoArgsConstructor + @ToString + @EqualsAndHashCode + @Getter + @Setter + @RequiredArgsConstructor + @Log + class A {} + """, + """ + import lombok.Data; + import lombok.NoArgsConstructor; + import lombok.extern.java.Log; + + @Data + @NoArgsConstructor + @Log + class A {} + """ + ) + ); + } + + @Test + void oneAnnotationMissing() { + rewriteRun(// language=java + java( + """ + import lombok.*; + import lombok.extern.java.Log; + + @NoArgsConstructor + @ToString + @EqualsAndHashCode + @Getter + @RequiredArgsConstructor + @Log + class A {} + """ + ) + ); + } + +} diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseDataTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseDataTest.java new file mode 100644 index 000000000..bdc6db9ea --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseDataTest.java @@ -0,0 +1,179 @@ +/* + * 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.migrate.lombok; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class UseDataTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipeFromResources("org.openrewrite.java.migrate.lombok.UseData") + .parser(JavaParser.fromJavaVersion().logCompilationWarningsAndErrors(true) + .classpath("lombok")); + } + + @DocumentExample + @Test + void replaceOneFieldData() { + rewriteRun(// language=java + java( + """ + import lombok.ToString; + import lombok.EqualsAndHashCode; + import lombok.Getter; + import lombok.Setter; + import lombok.RequiredArgsConstructor; + + @ToString + @EqualsAndHashCode + @Getter + @Setter + @RequiredArgsConstructor + class A {} + """, + """ + import lombok.Data; + + @Data + class A {} + """ + ) + ); + } + + @Test + void otherAnnotationAbove() { + rewriteRun(// language=java + java( + """ + import lombok.ToString; + import lombok.EqualsAndHashCode; + import lombok.Getter; + import lombok.Setter; + import lombok.RequiredArgsConstructor; + import lombok.extern.java.Log; + + @Log + @ToString + @EqualsAndHashCode + @Getter + @Setter + @RequiredArgsConstructor + class A {} + """, + """ + import lombok.Data; + import lombok.extern.java.Log; + + @Data + @Log + class A {} + """ + ) + ); + } + + @Test + void otherAnnotationBelow() { + rewriteRun(// language=java + java( + """ + import lombok.ToString; + import lombok.EqualsAndHashCode; + import lombok.Getter; + import lombok.Setter; + import lombok.RequiredArgsConstructor; + import lombok.extern.java.Log; + + @ToString + @EqualsAndHashCode + @Getter + @Setter + @RequiredArgsConstructor + @Log + class A {} + """, + """ + import lombok.Data; + import lombok.extern.java.Log; + + @Data + @Log + class A {} + """ + ) + ); + } + + @Test + void otherAnnotationsAround() { + rewriteRun(// language=java + java( + """ + import lombok.*; + import lombok.extern.java.Log; + + @NoArgsConstructor + @ToString + @EqualsAndHashCode + @Getter + @Setter + @RequiredArgsConstructor + @Log + class A {} + """, + """ + import lombok.Data; + import lombok.NoArgsConstructor; + import lombok.extern.java.Log; + + @Data + @NoArgsConstructor + @Log + class A {} + """ + ) + ); + } + + @Test + void oneAnnotationMissing() { + rewriteRun(// language=java + java( + """ + import lombok.*; + import lombok.extern.java.Log; + + @NoArgsConstructor + @ToString + @EqualsAndHashCode + @Getter + @RequiredArgsConstructor + @Log + class A {} + """ + ) + ); + } + +}