From d258000f68c7738b930687fb0d53aa453e2ed167 Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Tue, 24 Dec 2024 12:45:45 +0100 Subject: [PATCH 1/5] migrate recipe as-is --- .../lombok/NegligentlyConvertEquals.java | 101 +++++++++++ .../lombok/NegligentlyConvertEqualsTest.java | 166 ++++++++++++++++++ 2 files changed, 267 insertions(+) create mode 100644 src/main/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEquals.java create mode 100644 src/test/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEqualsTest.java diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEquals.java b/src/main/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEquals.java new file mode 100644 index 0000000000..9f0fa6428b --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEquals.java @@ -0,0 +1,101 @@ +/* + * 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.MethodMatcher; +import org.openrewrite.java.tree.J; + +import static java.util.Comparator.comparing; + +@Value +@EqualsAndHashCode(callSuper = false) +public class NegligentlyConvertEquals extends Recipe { + + @Override + public String getDisplayName() { + //language=markdown + return "Replace any custom `equals` or `hashCode` methods with the `EqualsAndHashCode` annotation"; + } + + @Override + public String getDescription() { + //language=markdown + return "This recipe substitutes a class level `@EqualsAndHashCode` annotation for a custom `equals` or `hashCode` methods. " + + "If both are defined, then both will be replaced. If only one is defined then it will be replaced. " + + "This recipe does not check if the custom `equals` or `hashCode` methods behave like ones generated by the lombok annotation. " + + "Doing so is considered infeasible at this time. " + + "As a compromise this recipe finds and replaces the custom methods and relies on the user to review the changes closely. " + + "As a consequence this recipe is VERY DANGEROUS to include into a composite recipe! " + + "Users are advised to run it only in isolation."; + } + + @Override + public TreeVisitor getVisitor() { + return new Converter(); + } + + @Value + @EqualsAndHashCode(callSuper = false) + private static class Converter extends JavaIsoVisitor { + + MethodMatcher equalsMatcher = new MethodMatcher("* equals(Object)"); + MethodMatcher hashCodeMatcher = new MethodMatcher("* hashCode()"); + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + + J.ClassDeclaration classDeclAfterVisit = super.visitClassDeclaration(classDecl, ctx); + + //only thing that can have changed is removal of either equals or hash code + //and something needs to have changed before we add an annotation at class level + if (classDeclAfterVisit != classDecl) { + maybeAddImport("lombok.EqualsAndHashCode"); + + //Add annotation + JavaTemplate template = JavaTemplate.builder("@EqualsAndHashCode\n") + .imports("lombok.EqualsAndHashCode") + .javaParser(JavaParser.fromJavaVersion().classpath("lombok")) + .build(); + + return template.apply( + updateCursor(classDeclAfterVisit), + classDeclAfterVisit.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))); + } + return classDecl; + } + + + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + J.ClassDeclaration classDecl = getCursor().firstEnclosingOrThrow(J.ClassDeclaration.class); + + // The enclosing class of a J.MethodDeclaration must be known for a MethodMatcher to match it + if (equalsMatcher.matches(method, classDecl) || hashCodeMatcher.matches(method, classDecl)) { + return null; + } else { + return method; + } + } + } +} diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEqualsTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEqualsTest.java new file mode 100644 index 0000000000..e9ebfd4c8d --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEqualsTest.java @@ -0,0 +1,166 @@ +/* + * 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 NegligentlyConvertEqualsTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new NegligentlyConvertEquals()) + .parser(JavaParser.fromJavaVersion().logCompilationWarningsAndErrors(true).classpath("lombok")); + } + + @DocumentExample + @Test + void replaceEquals() { + rewriteRun(// language=java + java( + """ + class A { + + int foo; + + @Override + public boolean equals(Object o) { + return false; + } + } + """, + """ + import lombok.EqualsAndHashCode; + + @EqualsAndHashCode + class A { + + int foo; + } + """ + ) + ); + } + + @Test + void replaceEqualsInPackage() { + rewriteRun(// language=java + java( + """ + package com.example; + + class A { + + int foo; + + @Override + public boolean equals(Object o) { + return false; + } + } + """, + """ + package com.example; + + import lombok.EqualsAndHashCode; + + @EqualsAndHashCode + class A { + + int foo; + } + """ + ) + ); + } + + @Test + void replaceHashCode() { + rewriteRun(// language=java + java( + """ + package com.example; + + class A { + + int foo; + + @Override + public int hashCode() { + return 6; + } + } + """, + """ + package com.example; + + import lombok.EqualsAndHashCode; + + @EqualsAndHashCode + class A { + + int foo; + } + """ + ) + ); + } + + @Test + void replaceEqualsAndHashCode() { + rewriteRun(// language=java + java( + """ + package com.example; + + class A { + + int foo; + + @Override + public boolean equals(Object o) { + return false; + } + + @Override + public int hashCode() { + return 6; + } + + } + """, + """ + package com.example; + + import lombok.EqualsAndHashCode; + + @EqualsAndHashCode + class A { + + int foo; + + } + """ + ) + ); + } + +} From 4db9a726cf1deb3bfdd8f8236f16e0fb6be3beb1 Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Tue, 24 Dec 2024 12:59:46 +0100 Subject: [PATCH 2/5] add test for empty class --- .../lombok/NegligentlyConvertEqualsTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEqualsTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEqualsTest.java index e9ebfd4c8d..79f74cc883 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEqualsTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEqualsTest.java @@ -60,6 +60,21 @@ class A { ); } + @Test + void noCostomMethodsNoAnnotation() { + rewriteRun(// language=java + java( + """ + class A { + + int foo; + + } + """ + ) + ); + } + @Test void replaceEqualsInPackage() { rewriteRun(// language=java From b2206b6c7259e25c16e9d434f50bbececb6e542a Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 5 Jan 2025 22:11:30 +0100 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../java/migrate/lombok/NegligentlyConvertEquals.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEquals.java b/src/main/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEquals.java index 9f0fa6428b..57dd5a3e5d 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEquals.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEquals.java @@ -17,6 +17,7 @@ import lombok.EqualsAndHashCode; import lombok.Value; +import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; @@ -85,8 +86,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex return classDecl; } - - @Override + public @Nullable J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { J.ClassDeclaration classDecl = getCursor().firstEnclosingOrThrow(J.ClassDeclaration.class); From 51ffbc233d5c2799e326ca827a07290578d5ca6f Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Wed, 15 Jan 2025 17:02:01 +0100 Subject: [PATCH 4/5] fix suggestions from reviewbot --- .../java/migrate/lombok/NegligentlyConvertEquals.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEquals.java b/src/main/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEquals.java index 57dd5a3e5d..3581306fef 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEquals.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEquals.java @@ -86,8 +86,8 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex return classDecl; } - public @Nullable J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { - public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + @Override + public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { J.ClassDeclaration classDecl = getCursor().firstEnclosingOrThrow(J.ClassDeclaration.class); // The enclosing class of a J.MethodDeclaration must be known for a MethodMatcher to match it From 96631fcc7e02cc5cbf77aa52141982cc80b5862e Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 11 Aug 2025 10:39:10 +0200 Subject: [PATCH 5/5] Apply suggestions from code review --- .../java/migrate/lombok/NegligentlyConvertEquals.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEquals.java b/src/main/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEquals.java index 3581306fef..f0e4024cb3 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEquals.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/NegligentlyConvertEquals.java @@ -93,9 +93,8 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex // The enclosing class of a J.MethodDeclaration must be known for a MethodMatcher to match it if (equalsMatcher.matches(method, classDecl) || hashCodeMatcher.matches(method, classDecl)) { return null; - } else { - return method; } + return method; } } }