From 2ea68c2069f1f260062b6ea84823f5c82f12f4f9 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 10 Aug 2025 00:51:53 +0200 Subject: [PATCH 1/5] Create `is` methods for boolean fields in `LombokValueToRecord` Fixes #812 --- .../migrate/lombok/LombokValueToRecord.java | 112 +++++++++++++++--- .../lombok/LombokValueToRecordTest.java | 90 ++++++++++++++ 2 files changed, 187 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokValueToRecord.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokValueToRecord.java index a87196915b..82d9560e42 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokValueToRecord.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokValueToRecord.java @@ -228,9 +228,11 @@ private static class LombokValueToRecordVisitor extends JavaIsoVisitor> recordTypeToMembers; @@ -249,9 +251,16 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu } J.Identifier methodName = methodInvocation.getName(); + String simpleName = methodName.getSimpleName(); + + // Don't convert is* methods as they will be provided by the generated methods + if (simpleName.startsWith(BOOLEAN_GETTER_PREFIX)) { + return methodInvocation; + } + return methodInvocation .withName(methodName - .withSimpleName(getterMethodNameToFluentMethodName(methodName.getSimpleName())) + .withSimpleName(getterMethodNameToFluentMethodName(simpleName)) ); } @@ -264,18 +273,22 @@ public J.MemberReference visitMemberReference(J.MemberReference memberRef, Execu String classFqn = ((JavaType.Class) containing.getType()).getFullyQualifiedName(); J.Identifier reference = memberReference.getReference(); String methodName = reference.getSimpleName(); - String newSimpleName = getterMethodNameToFluentMethodName(methodName); - if (recordTypeToMembers.containsKey(classFqn) && - methodName.startsWith(STANDARD_GETTER_PREFIX) && - recordTypeToMembers.get(classFqn).contains(newSimpleName)) { - - JavaType.Method methodType = memberReference.getMethodType(); - if (methodType != null) { - methodType = methodType.withName(newSimpleName); + + if (recordTypeToMembers.containsKey(classFqn)) { + // Handle get* methods + if (methodName.startsWith(STANDARD_GETTER_PREFIX)) { + String newSimpleName = getterMethodNameToFluentMethodName(methodName); + if (recordTypeToMembers.get(classFqn).contains(newSimpleName)) { + JavaType.Method methodType = memberReference.getMethodType(); + if (methodType != null) { + methodType = methodType.withName(newSimpleName); + } + return memberReference + .withReference(reference.withSimpleName(newSimpleName)) + .withMethodType(methodType); + } } - return memberReference - .withReference(reference.withSimpleName(newSimpleName)) - .withMethodType(methodType); + // Don't convert is* method references as they will be provided by generated methods } } return memberReference; @@ -295,9 +308,20 @@ private boolean isMethodInvocationOnRecordTypeClassMember(J.MethodInvocation met String methodName = methodInvocation.getName().getSimpleName(); String classFqn = classType.getFullyQualifiedName(); - return recordTypeToMembers.containsKey(classFqn) && - methodName.startsWith(STANDARD_GETTER_PREFIX) && - recordTypeToMembers.get(classFqn).contains(getterMethodNameToFluentMethodName(methodName)); + if (!recordTypeToMembers.containsKey(classFqn)) { + return false; + } + + // Handle both get* and is* methods + if (methodName.startsWith(STANDARD_GETTER_PREFIX)) { + return recordTypeToMembers.get(classFqn).contains(getterMethodNameToFluentMethodName(methodName)); + } else if (methodName.startsWith(BOOLEAN_GETTER_PREFIX)) { + // For is* methods, check if the field exists (e.g., isBar -> bar) + String fieldName = booleanGetterMethodNameToFluentMethodName(methodName); + return recordTypeToMembers.get(classFqn).contains(fieldName); + } + + return false; } private static boolean isClassExpression(@Nullable Expression expression) { @@ -318,6 +342,24 @@ private static String getterMethodNameToFluentMethodName(String methodName) { return fluentMethodName.toString(); } + private static String booleanGetterMethodNameToFluentMethodName(String methodName) { + if (!methodName.startsWith(BOOLEAN_GETTER_PREFIX)) { + return methodName; + } + + StringBuilder fluentMethodName = new StringBuilder( + methodName.substring(BOOLEAN_GETTER_PREFIX.length())); + + if (fluentMethodName.length() == 0) { + return ""; + } + + char firstMemberChar = fluentMethodName.charAt(0); + fluentMethodName.setCharAt(0, Character.toLowerCase(firstMemberChar)); + + return fluentMethodName.toString(); + } + private static List mapToConstructorArguments(List memberVariables) { return memberVariables .stream() @@ -338,6 +380,43 @@ private J.ClassDeclaration addExactToStringMethod(J.ClassDeclaration classDeclar memberVariablesToString(getMemberVariableNames(memberVariables)))); } + private J.ClassDeclaration addBooleanGetterMethods(J.ClassDeclaration classDeclaration, + List memberVariables) { + List booleanGetters = new ArrayList<>(); + for (J.VariableDeclarations varDecl : memberVariables) { + JavaType type = varDecl.getType(); + if (type != null && isBooleanType(type)) { + for (J.VariableDeclarations.NamedVariable var : varDecl.getVariables()) { + String fieldName = var.getSimpleName(); + String capitalizedFieldName = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); + String returnType = type instanceof JavaType.Primitive ? "boolean" : "Boolean"; + booleanGetters.add("public " + returnType + " is" + capitalizedFieldName + "() { return " + fieldName + "; }"); + } + } + } + if (!booleanGetters.isEmpty()) { + String allMethods = String.join("\n", booleanGetters); + JavaTemplate template = JavaTemplate + .builder(allMethods) + .contextSensitive() + .build(); + return classDeclaration.withBody(template + .apply(new Cursor(getCursor(), classDeclaration.getBody()), + classDeclaration.getBody().getCoordinates().lastStatement())); + } + return classDeclaration; + } + + private boolean isBooleanType(JavaType type) { + if (type instanceof JavaType.Primitive) { + return type == JavaType.Primitive.Boolean; + } else if (type instanceof JavaType.Class) { + String fqn = ((JavaType.Class) type).getFullyQualifiedName(); + return "java.lang.Boolean".equals(fqn); + } + return false; + } + private static String memberVariablesToString(Set memberVariables) { return memberVariables .stream() @@ -390,6 +469,9 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration cd, Execution classDeclaration = addExactToStringMethod(classDeclaration, memberVariables); } + // Add is* methods for boolean fields + classDeclaration = addBooleanGetterMethods(classDeclaration, memberVariables); + return maybeAutoFormat(cd, classDeclaration, ctx); } } diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/LombokValueToRecordTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/LombokValueToRecordTest.java index 0f0888df4c..303050175b 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/LombokValueToRecordTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/LombokValueToRecordTest.java @@ -273,6 +273,96 @@ public record A( ); } + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/812") + @Test + void booleanFieldWithIsGetter() { + //language=java + rewriteRun( + java( + """ + import lombok.Value; + + @Value + public class Foo { + boolean bar; + } + """, + """ + public record Foo( + boolean bar) { + public boolean isBar() { + return bar; + } + } + """ + ), + java( + """ + public class Baz { + public void baz(Foo foo) { + foo.isBar(); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/812") + @Test + void multipleBooleanFields() { + //language=java + rewriteRun( + s -> s.typeValidationOptions(TypeValidation.none()), + java( + """ + import lombok.Value; + + @Value + public class Config { + boolean enabled; + Boolean active; + String name; + } + """, + """ + public record Config( + boolean enabled, + Boolean active, + String name) { + public boolean isEnabled() { + return enabled; + } + + public Boolean isActive() { + return active; + } + } + """ + ), + java( + """ + public class ConfigUser { + public void useConfig(Config config) { + if (config.isEnabled() && config.isActive()) { + System.out.println(config.getName()); + } + } + } + """, + """ + public class ConfigUser { + public void useConfig(Config config) { + if (config.isEnabled() && config.isActive()) { + System.out.println(config.name()); + } + } + } + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/449") @Test void methodReferences() { From dcb1924980e5a02451a0d1be8b1c7f0ce40625d2 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 10 Aug 2025 00:56:54 +0200 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../java/migrate/lombok/LombokValueToRecord.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokValueToRecord.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokValueToRecord.java index 82d9560e42..f6271f0582 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokValueToRecord.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokValueToRecord.java @@ -315,7 +315,8 @@ private boolean isMethodInvocationOnRecordTypeClassMember(J.MethodInvocation met // Handle both get* and is* methods if (methodName.startsWith(STANDARD_GETTER_PREFIX)) { return recordTypeToMembers.get(classFqn).contains(getterMethodNameToFluentMethodName(methodName)); - } else if (methodName.startsWith(BOOLEAN_GETTER_PREFIX)) { + } + if (methodName.startsWith(BOOLEAN_GETTER_PREFIX)) { // For is* methods, check if the field exists (e.g., isBar -> bar) String fieldName = booleanGetterMethodNameToFluentMethodName(methodName); return recordTypeToMembers.get(classFqn).contains(fieldName); @@ -410,7 +411,8 @@ private J.ClassDeclaration addBooleanGetterMethods(J.ClassDeclaration classDecla private boolean isBooleanType(JavaType type) { if (type instanceof JavaType.Primitive) { return type == JavaType.Primitive.Boolean; - } else if (type instanceof JavaType.Class) { + } + if (type instanceof JavaType.Class) { String fqn = ((JavaType.Class) type).getFullyQualifiedName(); return "java.lang.Boolean".equals(fqn); } From df628aa12a96457cf8938b851e6ae342bd9e1b96 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 10 Aug 2025 14:42:03 +0200 Subject: [PATCH 3/5] Also convert boolean methods --- .../migrate/lombok/LombokValueToRecord.java | 94 ++----------------- .../lombok/LombokValueToRecordTest.java | 22 ++--- 2 files changed, 18 insertions(+), 98 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokValueToRecord.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokValueToRecord.java index f6271f0582..17882e14ea 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokValueToRecord.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokValueToRecord.java @@ -117,7 +117,6 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex } private boolean isRelevantClass(J.ClassDeclaration classDeclaration) { - List allAnnotations = classDeclaration.getAllAnnotations(); return classDeclaration.getType() != null && J.ClassDeclaration.Kind.Type.Record != classDeclaration.getKind() && hasMatchingAnnotations(classDeclaration) && @@ -228,7 +227,6 @@ private static class LombokValueToRecordVisitor extends JavaIsoVisitor bar) - String fieldName = booleanGetterMethodNameToFluentMethodName(methodName); - return recordTypeToMembers.get(classFqn).contains(fieldName); - } - return false; } @@ -331,25 +314,7 @@ private static boolean isClassExpression(@Nullable Expression expression) { private static String getterMethodNameToFluentMethodName(String methodName) { StringBuilder fluentMethodName = new StringBuilder( - methodName.replace(STANDARD_GETTER_PREFIX, "")); - - if (fluentMethodName.length() == 0) { - return ""; - } - - char firstMemberChar = fluentMethodName.charAt(0); - fluentMethodName.setCharAt(0, Character.toLowerCase(firstMemberChar)); - - return fluentMethodName.toString(); - } - - private static String booleanGetterMethodNameToFluentMethodName(String methodName) { - if (!methodName.startsWith(BOOLEAN_GETTER_PREFIX)) { - return methodName; - } - - StringBuilder fluentMethodName = new StringBuilder( - methodName.substring(BOOLEAN_GETTER_PREFIX.length())); + methodName.replaceFirst("^(get|is)", "")); if (fluentMethodName.length() == 0) { return ""; @@ -381,44 +346,6 @@ private J.ClassDeclaration addExactToStringMethod(J.ClassDeclaration classDeclar memberVariablesToString(getMemberVariableNames(memberVariables)))); } - private J.ClassDeclaration addBooleanGetterMethods(J.ClassDeclaration classDeclaration, - List memberVariables) { - List booleanGetters = new ArrayList<>(); - for (J.VariableDeclarations varDecl : memberVariables) { - JavaType type = varDecl.getType(); - if (type != null && isBooleanType(type)) { - for (J.VariableDeclarations.NamedVariable var : varDecl.getVariables()) { - String fieldName = var.getSimpleName(); - String capitalizedFieldName = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); - String returnType = type instanceof JavaType.Primitive ? "boolean" : "Boolean"; - booleanGetters.add("public " + returnType + " is" + capitalizedFieldName + "() { return " + fieldName + "; }"); - } - } - } - if (!booleanGetters.isEmpty()) { - String allMethods = String.join("\n", booleanGetters); - JavaTemplate template = JavaTemplate - .builder(allMethods) - .contextSensitive() - .build(); - return classDeclaration.withBody(template - .apply(new Cursor(getCursor(), classDeclaration.getBody()), - classDeclaration.getBody().getCoordinates().lastStatement())); - } - return classDeclaration; - } - - private boolean isBooleanType(JavaType type) { - if (type instanceof JavaType.Primitive) { - return type == JavaType.Primitive.Boolean; - } - if (type instanceof JavaType.Class) { - String fqn = ((JavaType.Class) type).getFullyQualifiedName(); - return "java.lang.Boolean".equals(fqn); - } - return false; - } - private static String memberVariablesToString(Set memberVariables) { return memberVariables .stream() @@ -471,9 +398,6 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration cd, Execution classDeclaration = addExactToStringMethod(classDeclaration, memberVariables); } - // Add is* methods for boolean fields - classDeclaration = addBooleanGetterMethods(classDeclaration, memberVariables); - return maybeAutoFormat(cd, classDeclaration, ctx); } } diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/LombokValueToRecordTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/LombokValueToRecordTest.java index 303050175b..3141ee894f 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/LombokValueToRecordTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/LombokValueToRecordTest.java @@ -290,11 +290,7 @@ public class Foo { """ public record Foo( boolean bar) { - public boolean isBar() { - return bar; - } - } - """ + }""" ), java( """ @@ -303,6 +299,13 @@ public void baz(Foo foo) { foo.isBar(); } } + """, + """ + public class Baz { + public void baz(Foo foo) { + foo.bar(); + } + } """ ) ); @@ -330,13 +333,6 @@ public record Config( boolean enabled, Boolean active, String name) { - public boolean isEnabled() { - return enabled; - } - - public Boolean isActive() { - return active; - } } """ ), @@ -353,7 +349,7 @@ public void useConfig(Config config) { """ public class ConfigUser { public void useConfig(Config config) { - if (config.isEnabled() && config.isActive()) { + if (config.enabled() && config.active()) { System.out.println(config.name()); } } From bb48986a3af49d8224abfd2d6e3d7d14e1727f7b Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 10 Aug 2025 14:44:41 +0200 Subject: [PATCH 4/5] Update expectations around `Boolean` fields --- .../java/migrate/lombok/LombokValueToRecordTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/LombokValueToRecordTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/LombokValueToRecordTest.java index 3141ee894f..e44bcad45a 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/LombokValueToRecordTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/LombokValueToRecordTest.java @@ -316,7 +316,6 @@ public void baz(Foo foo) { void multipleBooleanFields() { //language=java rewriteRun( - s -> s.typeValidationOptions(TypeValidation.none()), java( """ import lombok.Value; @@ -340,7 +339,7 @@ public record Config( """ public class ConfigUser { public void useConfig(Config config) { - if (config.isEnabled() && config.isActive()) { + if (config.isEnabled() && config.getActive()) { System.out.println(config.getName()); } } From c5bdbd977a46033ef9223806c797e3543bae5201 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 10 Aug 2025 14:47:58 +0200 Subject: [PATCH 5/5] Condense logic --- .../migrate/lombok/LombokValueToRecord.java | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokValueToRecord.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokValueToRecord.java index 17882e14ea..f707a2a829 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokValueToRecord.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokValueToRecord.java @@ -264,19 +264,17 @@ public J.MemberReference visitMemberReference(J.MemberReference memberRef, Execu J.Identifier reference = memberReference.getReference(); String methodName = reference.getSimpleName(); - if (recordTypeToMembers.containsKey(classFqn)) { - // Handle get* and is* methods - if (methodName.startsWith(STANDARD_GETTER_PREFIX) || methodName.startsWith(BOOLEAN_GETTER_PREFIX)) { - String newSimpleName = getterMethodNameToFluentMethodName(methodName); - if (recordTypeToMembers.get(classFqn).contains(newSimpleName)) { - JavaType.Method methodType = memberReference.getMethodType(); - if (methodType != null) { - methodType = methodType.withName(newSimpleName); - } - return memberReference - .withReference(reference.withSimpleName(newSimpleName)) - .withMethodType(methodType); + if (recordTypeToMembers.containsKey(classFqn) && + (methodName.startsWith(STANDARD_GETTER_PREFIX) || methodName.startsWith(BOOLEAN_GETTER_PREFIX))) { + String newSimpleName = getterMethodNameToFluentMethodName(methodName); + if (recordTypeToMembers.get(classFqn).contains(newSimpleName)) { + JavaType.Method methodType = memberReference.getMethodType(); + if (methodType != null) { + methodType = methodType.withName(newSimpleName); } + return memberReference + .withReference(reference.withSimpleName(newSimpleName)) + .withMethodType(methodType); } } } @@ -297,15 +295,10 @@ private boolean isMethodInvocationOnRecordTypeClassMember(J.MethodInvocation met String methodName = methodInvocation.getName().getSimpleName(); String classFqn = classType.getFullyQualifiedName(); - if (!recordTypeToMembers.containsKey(classFqn)) { - return false; - } + return recordTypeToMembers.containsKey(classFqn) && + (methodName.startsWith(STANDARD_GETTER_PREFIX) || methodName.startsWith(BOOLEAN_GETTER_PREFIX)) && + recordTypeToMembers.get(classFqn).contains(getterMethodNameToFluentMethodName(methodName)); - // Handle both get* and is* methods - if (methodName.startsWith(STANDARD_GETTER_PREFIX) || methodName.startsWith(BOOLEAN_GETTER_PREFIX)) { - return recordTypeToMembers.get(classFqn).contains(getterMethodNameToFluentMethodName(methodName)); - } - return false; } private static boolean isClassExpression(@Nullable Expression expression) {