Skip to content

Commit

Permalink
allow @builder annotation in LombokValueToBuilder (#470)
Browse files Browse the repository at this point in the history
* allow @builder annotations

@builder is often used with @value.
It also works really well with records.

* Document the nested if; Reformat to clear accidental changes

---------

Co-authored-by: Tim te Beek <[email protected]>
  • Loading branch information
holgpar and timtebeek authored May 2, 2024
1 parent dda4fa4 commit 2d67368
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand All @@ -41,6 +42,7 @@
public class LombokValueToRecord extends ScanningRecipe<Map<String, Set<String>>> {

private static final AnnotationMatcher LOMBOK_VALUE_MATCHER = new AnnotationMatcher("@lombok.Value()");
private static final AnnotationMatcher LOMBOK_BUILDER_MATCHER = new AnnotationMatcher("@lombok.Builder()");

@Option(displayName = "Add a `toString()` implementation matching Lombok",
description = "When set the `toString` format from Lombok is used in the migrated record.",
Expand Down Expand Up @@ -116,13 +118,29 @@ private boolean isRelevantClass(J.ClassDeclaration classDeclaration) {
List<J.Annotation> allAnnotations = classDeclaration.getAllAnnotations();
return classDeclaration.getType() != null
&& !J.ClassDeclaration.Kind.Type.Record.equals(classDeclaration.getKind())
&& !allAnnotations.isEmpty()
&& allAnnotations.stream().allMatch(ann -> LOMBOK_VALUE_MATCHER.matches(ann) && (ann.getArguments() == null || ann.getArguments().isEmpty()))
&& hasMatchingAnnotations(classDeclaration)
&& !hasGenericTypeParameter(classDeclaration)
&& classDeclaration.getBody().getStatements().stream().allMatch(this::isRecordCompatibleField)
&& !hasIncompatibleModifier(classDeclaration);
}

private static Predicate<J.Annotation> matchAnnotationWithNoArguments(AnnotationMatcher matcher) {
return ann -> matcher.matches(ann) && (ann.getArguments() == null || ann.getArguments().isEmpty());
}

private static boolean hasMatchingAnnotations(J.ClassDeclaration classDeclaration) {
List<J.Annotation> allAnnotations = classDeclaration.getAllAnnotations();
if (allAnnotations.stream().anyMatch(matchAnnotationWithNoArguments(LOMBOK_VALUE_MATCHER))) {
// Tolerate a limited set of other annotations like Builder, that work well with records too
return allAnnotations.stream().allMatch(
matchAnnotationWithNoArguments(LOMBOK_VALUE_MATCHER)
// compatible annotations can be added here
.or(matchAnnotationWithNoArguments(LOMBOK_BUILDER_MATCHER))
);
}
return false;
}

/**
* If the class target class implements an interface, transforming it to a record will not work in general,
* because the record access methods do not have the "get" prefix.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,39 @@ public record A(
);
}

@Test
void plainLombokBuilder() {
//language=java
rewriteRun(
s -> s.typeValidationOptions(TypeValidation.none()),
java(
"""
package example;
import lombok.Value;
import lombok.Builder;
@Value
@Builder
public class A implements Serializable {
String test;
}
""",
"""
package example;
import lombok.Builder;
@Builder
public record A(
String test) implements Serializable {
}
"""
)
);

}

@Nested
class Unchanged {
@Test
Expand Down

0 comments on commit 2d67368

Please sign in to comment.