Skip to content

Commit ed02764

Browse files
timo-agithub-actions[bot]timtebeek
authored
New recipe to adopt Lombok setter method names (#632)
* migrate recipes as-is * fix year in license * bring back original helper methods * minor polish * remove line in recipe spec * Update src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update src/test/java/org/openrewrite/java/migrate/lombok/NormalizeSetterTest.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix build * fix build * markdown and comments * support no field access * Inline MethodRecorder & rename list of methods not renamed * Remove unnecessary packages * Capture current behavior with a running test * fix test * Apply suggestions from code review (of getter pr) * Group the tests that expect no change * Use early returns in `isEffectivelySetter` * Adopt `TypeUtils.isOverride` * Use MethodMatcher.methodPattern(method) * Use`getTypesInUse().getDeclaredMethods()` * Expect to fail for a case not covered yet * Inline MethodAcc * refactor: rename to sth. less ambiguous * Update src/main/java/org/openrewrite/java/migrate/lombok/AdoptLombokSetterMethodNames.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Minimize changes with Getter equivalent --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Tim te Beek <tim@moderne.io> Co-authored-by: Tim te Beek <timtebeek@gmail.com>
1 parent 6b94940 commit ed02764

File tree

3 files changed

+764
-1
lines changed

3 files changed

+764
-1
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.migrate.lombok;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.jspecify.annotations.Nullable;
21+
import org.openrewrite.ExecutionContext;
22+
import org.openrewrite.ScanningRecipe;
23+
import org.openrewrite.Tree;
24+
import org.openrewrite.TreeVisitor;
25+
import org.openrewrite.java.ChangeMethodName;
26+
import org.openrewrite.java.JavaIsoVisitor;
27+
import org.openrewrite.java.MethodMatcher;
28+
import org.openrewrite.java.tree.Expression;
29+
import org.openrewrite.java.tree.J;
30+
import org.openrewrite.java.tree.JavaType;
31+
import org.openrewrite.java.tree.TypeUtils;
32+
33+
import java.util.ArrayList;
34+
import java.util.List;
35+
import java.util.Set;
36+
37+
@Value
38+
@EqualsAndHashCode(callSuper = false)
39+
public class AdoptLombokSetterMethodNames extends ScanningRecipe<List<AdoptLombokSetterMethodNames.RenameRecord>> {
40+
41+
private final static String DO_NOT_RENAME = "DO_NOT_RENAME";
42+
43+
@Override
44+
public String getDisplayName() {
45+
return "Rename setter methods to fit Lombok";
46+
}
47+
48+
@Override
49+
public String getDescription() {
50+
return "Rename methods that are effectively setter to the name Lombok would give them.\n" +
51+
"Limitations:\n" +
52+
" - If two methods in a class are effectively the same setter then one's name will be corrected and the others name will be left as it is.\n" +
53+
" - If the correct name for a method is already taken by another method then the name will not be corrected.\n" +
54+
" - Method name swaps or circular renaming within a class cannot be performed because the names block each other.\n" +
55+
"E.g. `int getFoo() { return ba; } int getBa() { return foo; }` stays as it is.";
56+
}
57+
58+
@Value
59+
public static class RenameRecord {
60+
String methodPattern;
61+
String newMethodName;
62+
}
63+
64+
@Override
65+
public List<RenameRecord> getInitialValue(ExecutionContext ctx) {
66+
return new ArrayList<>();
67+
}
68+
69+
@Override
70+
public TreeVisitor<?, ExecutionContext> getScanner(List<RenameRecord> renameRecords) {
71+
return new JavaIsoVisitor<ExecutionContext>() {
72+
@Override
73+
public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) {
74+
// Cheaply collect all declared methods; this also means we do not support clashing nested class methods
75+
Set<JavaType.Method> declaredMethods = cu.getTypesInUse().getDeclaredMethods();
76+
List<String> existingMethodNames = new ArrayList<>();
77+
for (JavaType.Method method : declaredMethods) {
78+
existingMethodNames.add(method.getName());
79+
}
80+
getCursor().putMessage(DO_NOT_RENAME, existingMethodNames);
81+
return super.visitCompilationUnit(cu, ctx);
82+
}
83+
84+
@Override
85+
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
86+
if (method.getMethodType() == null || method.getBody() == null ||
87+
!LombokUtils.isEffectivelySetter(method) ||
88+
TypeUtils.isOverride(method.getMethodType())) {
89+
return method;
90+
}
91+
92+
JavaType.Variable fieldType;
93+
Expression variable = ((J.Assignment) method.getBody().getStatements().get(0)).getVariable();
94+
if (variable instanceof J.FieldAccess) {
95+
fieldType = ((J.FieldAccess) variable).getName().getFieldType();
96+
} else if (variable instanceof J.Identifier) {
97+
fieldType = ((J.Identifier) variable).getFieldType();
98+
} else {
99+
return method;
100+
}
101+
102+
// If method already has the name it should have, then nothing to be done
103+
String expectedMethodName = LombokUtils.deriveSetterMethodName(fieldType);
104+
if (expectedMethodName.equals(method.getSimpleName())) {
105+
return method;
106+
}
107+
108+
// If the desired method name is already taken by an existing method, the current method cannot be renamed
109+
List<String> doNotRename = getCursor().getNearestMessage(DO_NOT_RENAME);
110+
assert doNotRename != null;
111+
if (doNotRename.contains(expectedMethodName)) {
112+
return method;
113+
}
114+
115+
renameRecords.add(new RenameRecord(MethodMatcher.methodPattern(method), expectedMethodName));
116+
doNotRename.remove(method.getSimpleName()); //actual method name becomes available again
117+
doNotRename.add(expectedMethodName); //expected method name now blocked
118+
return method;
119+
}
120+
};
121+
}
122+
123+
@Override
124+
public TreeVisitor<?, ExecutionContext> getVisitor(List<RenameRecord> renameRecords) {
125+
return new TreeVisitor<Tree, ExecutionContext>() {
126+
@Override
127+
public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
128+
for (RenameRecord rr : renameRecords) {
129+
tree = new ChangeMethodName(rr.methodPattern, rr.newMethodName, true, null)
130+
.getVisitor().visit(tree, ctx);
131+
}
132+
return tree;
133+
}
134+
};
135+
}
136+
}

src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ public static boolean isEffectivelyGetter(J.MethodDeclaration method) {
103103
}
104104

105105
public static String deriveGetterMethodName(@Nullable JavaType type, String fieldName) {
106-
107106
if (type == JavaType.Primitive.Boolean) {
108107
boolean alreadyStartsWithIs = fieldName.length() >= 3 &&
109108
fieldName.substring(0, 3).matches("is[A-Z]");
@@ -173,6 +172,40 @@ private static boolean hasMatchingSetterMethodName(J.MethodDeclaration method, S
173172
return method.getSimpleName().equals("set" + StringUtils.capitalize(simpleName));
174173
}
175174

175+
public static boolean isEffectivelySetter(J.MethodDeclaration method) {
176+
if (method.getType() != JavaType.Primitive.Void) {
177+
return false;
178+
}
179+
if (method.getParameters().size() != 1 || method.getParameters().get(0) instanceof J.Empty) {
180+
return false;
181+
}
182+
183+
J.VariableDeclarations variableDeclarations = (J.VariableDeclarations) method.getParameters().get(0);
184+
J.VariableDeclarations.NamedVariable param = variableDeclarations.getVariables().get(0);
185+
String paramName = param.getName().toString();
186+
187+
if (method.getBody() == null ||
188+
method.getBody().getStatements().size() != 1 ||
189+
!(method.getBody().getStatements().get(0) instanceof J.Assignment)) {
190+
return false;
191+
}
192+
J.Assignment assignment = (J.Assignment) method.getBody().getStatements().get(0);
193+
194+
if (!(assignment.getVariable() instanceof J.FieldAccess) && !(assignment.getVariable() instanceof J.Identifier)) {
195+
return false;
196+
}
197+
198+
JavaType fieldType = assignment.getVariable().getType();
199+
// assigned value is exactly the parameter
200+
return assignment.getAssignment().toString().equals(paramName) &&
201+
param.getType() != null &&
202+
param.getType().equals(fieldType); // type of parameter and field have to match
203+
}
204+
205+
public static String deriveSetterMethodName(JavaType.Variable fieldType) {
206+
return "set" + StringUtils.capitalize(fieldType.getName());
207+
}
208+
176209
static AccessLevel getAccessLevel(J.MethodDeclaration methodDeclaration) {
177210
if (methodDeclaration.hasModifier(Public)) {
178211
return PUBLIC;

0 commit comments

Comments
 (0)