Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
a3e22b4
feat: add recipe that converts explicit getters to the lombok annotation
timo-a Dec 7, 2024
d6eebf2
chore: IntelliJ auto-formatter
timo-a Dec 8, 2024
b38f90d
add licence header
timo-a Dec 8, 2024
c35b7d6
Apply suggestions from code review
timo-a Dec 8, 2024
f06e270
Apply suggestions from code review
timtebeek Dec 8, 2024
e6fd1bb
roll back nullable annotation
timo-a Dec 8, 2024
4b7fb67
Light polish
timtebeek Dec 8, 2024
665e73a
Rename and add Lombok tag
timtebeek Dec 9, 2024
91c579b
Also handle field access
timtebeek Dec 9, 2024
c7d3feb
Push down method and variable name matching into utils
timtebeek Dec 9, 2024
f51e00f
Demonstrate failing case of nested inner class getter
timtebeek Dec 9, 2024
80ec548
fix: year in licence header
timo-a Dec 10, 2024
0bb8f73
migrate existing recipe as-is
timo-a Dec 10, 2024
5e9e379
deactivate getter test for development
timo-a Dec 10, 2024
7510f63
Rename and add Lombok tag
timo-a Dec 10, 2024
46519dd
fix year in licence header
timo-a Dec 10, 2024
9bba4fe
chore: IntelliJ auto-formatter
timo-a Dec 10, 2024
7f4d486
apply best practices
timo-a Dec 10, 2024
f68e252
light polish
timo-a Dec 10, 2024
0700c20
copy from: Also handle field access
timo-a Dec 10, 2024
60ee08f
minor changes
timo-a Dec 10, 2024
d438061
Minimize changes with `main` branch ahead of rebase to avoid conflicts
timtebeek Dec 14, 2024
8cc33ce
Merge branch 'main' into lombok/setter
timtebeek Dec 14, 2024
989d7c9
Merge branch 'main' into lombok/setter
timtebeek Dec 15, 2024
6fd95b7
Resolve compilation issues
timtebeek Dec 15, 2024
22810ec
Ensure there is no change for a nested Setter
timtebeek Dec 15, 2024
a1288ef
Extract a reusable FieldAnnotator class
timtebeek Dec 15, 2024
108fdfd
Adopt now shared `FieldAnnotator` for setters as well
timtebeek Dec 15, 2024
81c9699
Convert most of the checks as used for UseLombokGetter
timtebeek Dec 15, 2024
868328b
Add one more style we ought to cover
timtebeek Dec 15, 2024
a118477
Add remaining checks to make all tests pass
timtebeek Dec 15, 2024
0356976
Move down variable and method closer to usage
timtebeek Dec 15, 2024
3bbb8ad
Inline variables used only once
timtebeek Dec 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* 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.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.ExecutionContext;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;

import static java.util.Comparator.comparing;
import static lombok.AccessLevel.PUBLIC;

@Value
@EqualsAndHashCode(callSuper = false)
class FieldAnnotator extends JavaIsoVisitor<ExecutionContext> {

Class<?> annotation;
JavaType field;
AccessLevel accessLevel;

@Override
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) {
for (J.VariableDeclarations.NamedVariable variable : multiVariable.getVariables()) {
if (variable.getName().getFieldType() == field) {
maybeAddImport(annotation.getName());
maybeAddImport("lombok.AccessLevel");
String suffix = accessLevel == PUBLIC ? "" : String.format("(AccessLevel.%s)", accessLevel.name());
return JavaTemplate.builder("@" + annotation.getSimpleName() + suffix)
.imports(annotation.getName(), "lombok.AccessLevel")
.javaParser(JavaParser.fromJavaVersion().classpath("lombok"))
.build().apply(getCursor(), multiVariable.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName)));
}
}
return multiVariable;
}
}
63 changes: 55 additions & 8 deletions src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,21 @@ static boolean isGetter(J.MethodDeclaration method) {
J.Identifier identifier = (J.Identifier) returnExpression;
if (identifier.getFieldType() != null && declaringType == identifier.getFieldType().getOwner()) {
// Check return: type and matching field name
return hasMatchingTypeAndName(method, identifier.getType(), identifier.getSimpleName());
return hasMatchingTypeAndGetterName(method, identifier.getType(), identifier.getSimpleName());
}
} else if (returnExpression instanceof J.FieldAccess) {
J.FieldAccess fieldAccess = (J.FieldAccess) returnExpression;
Expression target = fieldAccess.getTarget();
if (target instanceof J.Identifier && ((J.Identifier) target).getFieldType() != null &&
declaringType == ((J.Identifier) target).getFieldType().getOwner()) {
// Check return: type and matching field name
return hasMatchingTypeAndName(method, fieldAccess.getType(), fieldAccess.getSimpleName());
return hasMatchingTypeAndGetterName(method, fieldAccess.getType(), fieldAccess.getSimpleName());
}
}
return false;
}

private static boolean hasMatchingTypeAndName(J.MethodDeclaration method, @Nullable JavaType type, String simpleName) {
private static boolean hasMatchingTypeAndGetterName(J.MethodDeclaration method, @Nullable JavaType type, String simpleName) {
if (method.getType() == type) {
String deriveGetterMethodName = deriveGetterMethodName(type, simpleName);
return method.getSimpleName().equals(deriveGetterMethodName);
Expand All @@ -83,15 +83,62 @@ private static String deriveGetterMethodName(@Nullable JavaType type, String fie
return "get" + StringUtils.capitalize(fieldName);
}

static AccessLevel getAccessLevel(J.MethodDeclaration modifiers) {
if (modifiers.hasModifier(Public)) {
static boolean isSetter(J.MethodDeclaration method) {
// Check return type: void
if (method.getType() != JavaType.Primitive.Void) {
return false;
}
// Check signature: single parameter
if (method.getParameters().size() != 1 || method.getParameters().get(0) instanceof J.Empty) {
return false;
}
// Check body: just an assignment
if (method.getBody() == null || //abstract methods can be null
method.getBody().getStatements().size() != 1 ||
!(method.getBody().getStatements().get(0) instanceof J.Assignment)) {
return false;
}

// Check there's no up/down cast between parameter and field
J.VariableDeclarations.NamedVariable param = ((J.VariableDeclarations) method.getParameters().get(0)).getVariables().get(0);
Expression variable = ((J.Assignment) method.getBody().getStatements().get(0)).getVariable();
if (param.getType() != variable.getType()) {
return false;
}

// Method name has to match
JavaType.FullyQualified declaringType = method.getMethodType().getDeclaringType();
if (variable instanceof J.Identifier) {
J.Identifier assignedVar = (J.Identifier) variable;
if (hasMatchingSetterMethodName(method, assignedVar.getSimpleName())) {
// Check field is declared on method type
return assignedVar.getFieldType() != null && declaringType == assignedVar.getFieldType().getOwner();
}
} else if (variable instanceof J.FieldAccess) {
J.FieldAccess assignedField = (J.FieldAccess) variable;
if (hasMatchingSetterMethodName(method, assignedField.getSimpleName())) {
Expression target = assignedField.getTarget();
// Check field is declared on method type
return target instanceof J.Identifier && ((J.Identifier) target).getFieldType() != null &&
declaringType == ((J.Identifier) target).getFieldType().getOwner();
}
}

return false;
}

private static boolean hasMatchingSetterMethodName(J.MethodDeclaration method, String simpleName) {
return method.getSimpleName().equals("set" + StringUtils.capitalize(simpleName));
}

static AccessLevel getAccessLevel(J.MethodDeclaration methodDeclaration) {
if (methodDeclaration.hasModifier(Public)) {
return PUBLIC;
} else if (modifiers.hasModifier(Protected)) {
} else if (methodDeclaration.hasModifier(Protected)) {
return PROTECTED;
} else if (modifiers.hasModifier(Private)) {
} else if (methodDeclaration.hasModifier(Private)) {
return PRIVATE;
}
return PACKAGE;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,21 @@
*/
package org.openrewrite.java.migrate.lombok;

import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Value;
import org.jspecify.annotations.Nullable;
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.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;

import java.util.Collections;
import java.util.List;
import java.util.Set;

import static java.util.Comparator.comparing;
import static lombok.AccessLevel.PUBLIC;

@Value
@EqualsAndHashCode(callSuper = false)
Expand Down Expand Up @@ -66,12 +61,14 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
if (returnExpression instanceof J.Identifier &&
((J.Identifier) returnExpression).getFieldType() != null) {
doAfterVisit(new FieldAnnotator(
Getter.class,
((J.Identifier) returnExpression).getFieldType(),
LombokUtils.getAccessLevel(method)));
return null;
} else if (returnExpression instanceof J.FieldAccess &&
((J.FieldAccess) returnExpression).getName().getFieldType() != null) {
doAfterVisit(new FieldAnnotator(
Getter.class,
((J.FieldAccess) returnExpression).getName().getFieldType(),
LombokUtils.getAccessLevel(method)));
return null;
Expand All @@ -81,29 +78,4 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
}
};
}


@Value
@EqualsAndHashCode(callSuper = false)
static class FieldAnnotator extends JavaIsoVisitor<ExecutionContext> {

JavaType field;
AccessLevel accessLevel;

@Override
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) {
for (J.VariableDeclarations.NamedVariable variable : multiVariable.getVariables()) {
if (variable.getName().getFieldType() == field) {
maybeAddImport("lombok.Getter");
maybeAddImport("lombok.AccessLevel");
String suffix = accessLevel == PUBLIC ? "" : String.format("(AccessLevel.%s)", accessLevel.name());
return JavaTemplate.builder("@Getter" + suffix)
.imports("lombok.Getter", "lombok.AccessLevel")
.javaParser(JavaParser.fromJavaVersion().classpath("lombok"))
.build().apply(getCursor(), multiVariable.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName)));
}
}
return multiVariable;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* 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.Setter;
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;

import java.util.Collections;
import java.util.Set;

@Value
@EqualsAndHashCode(callSuper = false)
public class UseLombokSetter extends Recipe {

@Override
public String getDisplayName() {
return "Convert setter methods to annotations";
}

@Override
public String getDescription() {
return "Convert trivial setter methods to `@Setter` annotations on their respective fields.";
}

@Override
public Set<String> getTags() {
return Collections.singleton("lombok");
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
if (LombokUtils.isSetter(method)) {
Expression assignmentVariable = ((J.Assignment) method.getBody().getStatements().get(0)).getVariable();
if (assignmentVariable instanceof J.FieldAccess &&
((J.FieldAccess) assignmentVariable).getName().getFieldType() != null) {
doAfterVisit(new FieldAnnotator(Setter.class,
((J.FieldAccess) assignmentVariable).getName().getFieldType(),
LombokUtils.getAccessLevel(method)));
return null; //delete

} else if (assignmentVariable instanceof J.Identifier &&
((J.Identifier) assignmentVariable).getFieldType() != null) {
doAfterVisit(new FieldAnnotator(Setter.class,
((J.Identifier) assignmentVariable).getFieldType(),
LombokUtils.getAccessLevel(method)));
return null; //delete
}
}
return method;
}
};
}
}
Loading
Loading