diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/SummarizeGetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/SummarizeGetter.java new file mode 100644 index 000000000..df9f9b20e --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/SummarizeGetter.java @@ -0,0 +1,128 @@ +/* + * 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 static java.util.Comparator.comparing; + +@Value +@EqualsAndHashCode(callSuper = false) +public class SummarizeGetter extends Recipe { + + @Override + public String getDisplayName() { + return "Summarize @Getter on fields to class level annotation"; + } + + @Override + public String getDescription() { + //language=markdown + return "Substitutes a class level `@Getter` annotation for annotations on every field."; + } + + @Override + public TreeVisitor getVisitor() { + return new Summarizer(); + } + + + @Value + @EqualsAndHashCode(callSuper = false) + private static class Summarizer extends JavaIsoVisitor { + private static final String ALL_FIELDS_DECORATED_ACC = "ALL_FIELDS_DECORATED_ACC"; + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + + //initialize variable to store if all encountered fields have getters + getCursor().putMessage(ALL_FIELDS_DECORATED_ACC, true); + + //delete methods, note down corresponding fields + J.ClassDeclaration classDeclAfterVisit = super.visitClassDeclaration(classDecl, ctx); + + boolean allFieldsAnnotated = getCursor().pollNearestMessage(ALL_FIELDS_DECORATED_ACC); + + //only thing that can have changed is removal of getter methods + //and something needs to have changed before we add an annotation at class level + if (classDeclAfterVisit != classDecl && allFieldsAnnotated) { + //Add annotation + JavaTemplate template = JavaTemplate.builder("@Getter\n") + .imports("lombok.Getter") + .javaParser(JavaParser.fromJavaVersion().classpath("lombok")) + .build(); + + return template.apply( + updateCursor(classDeclAfterVisit), + classDeclAfterVisit.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))); + } + return classDecl; + } + + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations variableDecls, ExecutionContext ctx){ + + boolean allFieldsAnnotatedSoFar = getCursor().getNearestMessage(ALL_FIELDS_DECORATED_ACC); + if (!allFieldsAnnotatedSoFar) { + return variableDecls; + } + J.VariableDeclarations visited = super.visitVariableDeclarations(variableDecls, ctx); + + boolean hasGetterAnnotation = variableDecls != visited; + if (hasGetterAnnotation) { + return fixFormat(variableDecls, visited, ctx); + } else { + getCursor().putMessageOnFirstEnclosing(J.ClassDeclaration.class, ALL_FIELDS_DECORATED_ACC, false); + } + return variableDecls; + } + + private J.VariableDeclarations fixFormat(J.VariableDeclarations initial, J.VariableDeclarations visited, ExecutionContext ctx) { + //as of August 2024 manual fixes to the format are necessary. Hopefully in the future this method becomes obsolete + + boolean isAnnotationOnLineAbove = initial.toString().contains("@Getter\n"); + + boolean isTopAnnotationRemoved = !initial.getLeadingAnnotations().isEmpty() && + initial.getLeadingAnnotations() + .get(0) + .getSimpleName().equals("Getter"); + + if (isAnnotationOnLineAbove && isTopAnnotationRemoved) { + String minus1NewLine = visited.getPrefix().getWhitespace().replaceFirst("\n", ""); + visited = visited.withPrefix(visited.getPrefix().withWhitespace(minus1NewLine)); + } + return autoFormat(visited, ctx); + } + + @Override + public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) { + return annotation.getSimpleName().equals("Getter") + && annotation.getArguments() == null //no Access level, or other arguments + //should only trigger on field annotation, not class annotation + && getCursor().getParent().getValue() instanceof J.VariableDeclarations + ? null // -> delete + : annotation; // -> keep + } + } +} diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/SummarizeSetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/SummarizeSetter.java new file mode 100644 index 000000000..2dba3ed45 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/SummarizeSetter.java @@ -0,0 +1,134 @@ +/* + * 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 static java.util.Comparator.comparing; + +@Value +@EqualsAndHashCode(callSuper = false) +public class SummarizeSetter extends Recipe { + + @Override + public String getDisplayName() { + return "Summarize @Setter on fields to class level annotation"; + } + + @Override + public String getDescription() { + //language=markdown + return "Substitutes a class level `@Setter` annotation for annotations on every field."; + } + + @Override + public TreeVisitor getVisitor() { + return new Summarizer(); + } + + + @Value + @EqualsAndHashCode(callSuper = false) + private static class Summarizer extends JavaIsoVisitor { + private static final String ALL_FIELDS_DECORATED_ACC = "ALL_FIELDS_DECORATED_ACC"; + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + + //initialize variable to store if all encountered fields have setters + getCursor().putMessage(ALL_FIELDS_DECORATED_ACC, true); + + //delete methods, note down corresponding fields + J.ClassDeclaration classDeclAfterVisit = super.visitClassDeclaration(classDecl, ctx); + + boolean allFieldsAnnotated = getCursor().pollNearestMessage(ALL_FIELDS_DECORATED_ACC); + + //only thing that can have changed is removal of setter methods + //and something needs to have changed before we add an annotation at class level + if (classDeclAfterVisit != classDecl && allFieldsAnnotated) { + //Add annotation + JavaTemplate template = JavaTemplate.builder("@Setter\n") + .imports("lombok.Setter") + .javaParser(JavaParser.fromJavaVersion().classpath("lombok")) + .build(); + + return template.apply( + updateCursor(classDeclAfterVisit), + classDeclAfterVisit.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))); + } + return classDecl; + } + + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations variableDecls, ExecutionContext ctx){ + + boolean allFieldsAnnotatedSoFar = getCursor().getNearestMessage(ALL_FIELDS_DECORATED_ACC); + if (!allFieldsAnnotatedSoFar) { + return variableDecls; + } + J.VariableDeclarations visited = super.visitVariableDeclarations(variableDecls, ctx); + + boolean hasSetterAnnotation = variableDecls != visited; + if (hasSetterAnnotation) { + return fixFormat(variableDecls, visited, ctx); + } else if (variableDecls.hasModifier(J.Modifier.Type.Final) || + variableDecls.hasModifier(J.Modifier.Type.Static)) { + //final fields and static field don't need to have an annotation + return visited; + } else { + getCursor().putMessageOnFirstEnclosing(J.ClassDeclaration.class, ALL_FIELDS_DECORATED_ACC, false); + } + return variableDecls; + } + + private J.VariableDeclarations fixFormat(J.VariableDeclarations initial, J.VariableDeclarations visited, ExecutionContext ctx) { + //as of August 2024 manual fixes to the format are necessary. Hopefully in the future this method becomes obsolete + + boolean isAnnotationOnLineAbove = initial.toString().contains("@Setter\n"); + + boolean isTopAnnotationRemoved = !initial.getLeadingAnnotations().isEmpty() && + initial.getLeadingAnnotations() + .get(0) + .getSimpleName().equals("Setter"); + + if (isAnnotationOnLineAbove && isTopAnnotationRemoved) { + String minus1NewLine = visited.getPrefix().getWhitespace().replaceFirst("\n", ""); + visited = visited.withPrefix(visited.getPrefix().withWhitespace(minus1NewLine)); + } + return autoFormat(visited, ctx); + } + + @Override + public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) { + boolean isSetterAnnotated = annotation.getSimpleName().equals("Setter") + && annotation.getArguments() == null; //no Access level, or other arguments + + return isSetterAnnotated + //should only trigger on field annotation, not class annotation + && getCursor().getParent().getValue() instanceof J.VariableDeclarations + ? null // -> delete + : annotation; // -> keep + } + } +} diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/SummarizeGetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/SummarizeGetterTest.java new file mode 100644 index 000000000..59a88e66b --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/lombok/SummarizeGetterTest.java @@ -0,0 +1,277 @@ +/* + * 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.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class SummarizeGetterTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new SummarizeGetter()); + } + + @DocumentExample + @Test + void replaceOneFieldGetter() { + rewriteRun(// language=java + java( + """ + import lombok.Getter; + + class A { + + @Getter + int foo; + + } + """, + """ + import lombok.Getter; + + @Getter + class A { + + int foo; + + } + """ + ) + ); + } + + @Test + void replaceOneFieldGetterWhenInFront() { + rewriteRun(// language=java + java( + """ + import lombok.Getter; + + class A { + + @Getter int foo = 9; + + } + """, + """ + import lombok.Getter; + + @Getter + class A { + + int foo = 9; + + } + """ + ) + ); + } + + @Test + void otherAnnotationAbove() { + rewriteRun(// language=java + java( + """ + import lombok.Getter; + import lombok.Setter; + + class A { + + @Setter + @Getter + int foo; + + } + """, + """ + import lombok.Getter; + import lombok.Setter; + + @Getter + class A { + + @Setter + int foo; + + } + """ + ) + ); + } + + @Test + void otherAnnotationBelow() { + rewriteRun(// language=java + java( + """ + import lombok.Getter; + import lombok.Setter; + + class A { + + @Getter + @Setter + int foo; + + } + """, + """ + import lombok.Getter; + import lombok.Setter; + + @Getter + class A { + + @Setter + int foo; + + } + """ + ) + ); + } + + @Test + void otherAnnotationsAround() { + rewriteRun(// language=java + java( + """ + import lombok.Getter; + import lombok.Setter; + import lombok.Singular; + + class A { + + @Singular + @Getter + @Setter + int foo; + + } + """, + """ + import lombok.Getter; + import lombok.Setter; + import lombok.Singular; + + @Getter + class A { + + @Singular + @Setter + int foo; + + } + """ + ) + ); + } + + @Test + void doNothingWhenNotEveryFieldIsAnnotated() { + rewriteRun(// language=java + java( + """ + import lombok.Getter; + + class A { + + @Getter + int foo; + + int bar; + + } + """ + ) + ); + } + + @Test + void doNothingWhenAFieldHasSpecialConfig() { + rewriteRun(// language=java + java( + """ + import lombok.AccessLevel; + import lombok.Getter; + + class A { + + @Getter + int foo; + + @Getter(AccessLevel.PACKAGE) + int bar; + + } + """ + ) + ); + } + + @Test + void manyFields() { + rewriteRun(// language=java + java( + """ + import lombok.AccessLevel; + import lombok.Getter; + + class A { + + @Getter + int foo; + + @Getter + int bar; + + @Getter + int foobar; + + @Getter + int barfoo; + + } + """, + """ + import lombok.AccessLevel; + import lombok.Getter; + + @Getter + class A { + + int foo; + + int bar; + + int foobar; + + int barfoo; + + } + """ + ) + ); + } + + + +} diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/SummarizeSetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/SummarizeSetterTest.java new file mode 100644 index 000000000..cdc18dbf0 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/lombok/SummarizeSetterTest.java @@ -0,0 +1,420 @@ +/* + * 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.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class SummarizeSetterTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new SummarizeSetter()); + } + + @DocumentExample + @Test + void replaceOneFieldGetter() { + rewriteRun(// language=java + java( + """ + import lombok.Setter; + + class A { + + @Setter + int foo; + + } + """, + """ + import lombok.Setter; + + @Setter + class A { + + int foo; + + } + """ + ) + ); + } + + @Test + void replaceOneFieldGetterWhenInFront() { + rewriteRun(// language=java + java( + """ + import lombok.Setter; + + class A { + + @Setter int foo = 9; + + } + """, + """ + import lombok.Setter; + + @Setter + class A { + + int foo = 9; + + } + """ + ) + ); + } + + @Test + void otherAnnotationAbove() { + rewriteRun(// language=java + java( + """ + import lombok.Getter; + import lombok.Setter; + + class A { + + @Getter + @Setter + int foo; + + } + """, + """ + import lombok.Getter; + import lombok.Setter; + + @Setter + class A { + + @Getter + int foo; + + } + """ + ) + ); + } + + @Test + void otherAnnotationBelow() { + rewriteRun(// language=java + java( + """ + import lombok.Getter; + import lombok.Setter; + + class A { + + @Setter + @Getter + int foo; + + } + """, + """ + import lombok.Getter; + import lombok.Setter; + + @Setter + class A { + + @Getter + int foo; + + } + """ + ) + ); + } + + @Test + void otherAnnotationsAround() { + rewriteRun(// language=java + java( + """ + import lombok.Getter; + import lombok.Setter; + import lombok.Singular; + + class A { + + @Singular + @Setter + @Getter + int foo; + + } + """, + """ + import lombok.Getter; + import lombok.Setter; + import lombok.Singular; + + @Setter + class A { + + @Singular + @Getter + int foo; + + } + """ + ) + ); + } + + @Test + void doNothingWhenNotEveryFieldIsAnnotated() { + rewriteRun(// language=java + java( + """ + import lombok.Setter; + + class A { + + @Setter + int foo; + + int bar; + + } + """ + ) + ); + } + + @Test + void doNothingWhenAFieldHasSpecialConfig() { + rewriteRun(// language=java + java( + """ + import lombok.AccessLevel; + import lombok.Setter; + + class A { + + @Setter + int foo; + + @Setter(AccessLevel.PACKAGE) + int bar; + + } + """ + ) + ); + } + + @Test + void manyFields() { + rewriteRun(// language=java + java( + """ + import lombok.AccessLevel; + import lombok.Setter; + + class A { + + @Setter + int foo; + + @Setter + int bar; + + @Setter + int foobar; + + @Setter + int barfoo; + + } + """, + """ + import lombok.AccessLevel; + import lombok.Setter; + + @Setter + class A { + + int foo; + + int bar; + + int foobar; + + int barfoo; + + } + """ + ) + ); + } + + /** + * The occurrence of final fields does not stand in the way of a class level @Setter. + * Lombok won't create Setters for them, but it does compile. + */ + @Test + void finalField() { + rewriteRun(// language=java + java( + """ + import lombok.AccessLevel; + import lombok.Setter; + + class A { + + @Setter + int foo; + + final int bar; + + @Setter + final int foobar; + + @Setter + int barfoo; + + } + """, + """ + import lombok.AccessLevel; + import lombok.Setter; + + @Setter + class A { + + int foo; + + final int bar; + + final int foobar; + + int barfoo; + + } + """ + ) + ); + } + + /** + * The occurrence of static fields does not stand in the way of a class level @Setter. + * Lombok won't create Setters for them, but it does compile. + */ + @Test + void staticField() { + rewriteRun(// language=java + java( + """ + import lombok.AccessLevel; + import lombok.Setter; + + class A { + + @Setter + int foo; + + static int bar; + + @Setter + static int foobar; + + @Setter + int barfoo; + + } + """, + """ + import lombok.AccessLevel; + import lombok.Setter; + + @Setter + class A { + + int foo; + + static int bar; + + static int foobar; + + int barfoo; + + } + """ + ) + ); + } + + /** + * The occurrence of final static fields does not stand in the way of a class level @Setter. + * Lombok won't create Setters for them, but it does compile. + */ + @Test + void staticFinalField() { + rewriteRun(// language=java + java( + """ + import lombok.AccessLevel; + import lombok.Setter; + + class A { + + @Setter + int foo; + + static final int bar; + + @Setter + static final int foobar; + + @Setter + int barfoo; + + } + """, + """ + import lombok.AccessLevel; + import lombok.Setter; + + @Setter + class A { + + int foo; + + static final int bar; + + static final int foobar; + + int barfoo; + + } + """ + ) + ); + } + + +}