Skip to content

Commit dfbee69

Browse files
holgpartimtebeek
andauthored
Improve handling of interfaces in LombokValueToRecord (#452)
* fix: exclude only conflicting interfaces this also considers methods inherited by super-interfaces. * Fix indentation of text blocks in tests * Apply formatter to LombokValueToRecord.java * Slight polish --------- Co-authored-by: Tim te Beek <[email protected]>
1 parent f1d17f5 commit dfbee69

File tree

2 files changed

+148
-57
lines changed

2 files changed

+148
-57
lines changed

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

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -99,36 +99,67 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex
9999
return cd;
100100
}
101101

102-
assert cd.getType() != null : "Class type must not be null";
102+
assert cd.getType() != null : "Class type must not be null"; // Checked in isRelevantClass
103+
Set<String> memberVariableNames = getMemberVariableNames(memberVariables);
104+
if (implementsConflictingInterfaces(cd, memberVariableNames)) {
105+
return cd;
106+
}
107+
103108
acc.putIfAbsent(
104109
cd.getType().getFullyQualifiedName(),
105-
getMemberVariableNames(memberVariables));
110+
memberVariableNames);
106111

107112
return cd;
108113
}
109114

110115
private boolean isRelevantClass(J.ClassDeclaration classDeclaration) {
116+
List<J.Annotation> allAnnotations = classDeclaration.getAllAnnotations();
111117
return classDeclaration.getType() != null
112-
&& !J.ClassDeclaration.Kind.Type.Record.equals(classDeclaration.getKind())
113-
&& classDeclaration.getAllAnnotations().stream()
114-
.allMatch(ann -> LOMBOK_VALUE_MATCHER.matches(ann) && (ann.getArguments() == null || ann.getArguments().isEmpty()))
115-
&& !hasGenericTypeParameter(classDeclaration)
116-
&& classDeclaration.getBody().getStatements().stream().allMatch(this::isRecordCompatibleField)
117-
&& !hasIncompatibleModifier(classDeclaration)
118-
&& !implementsInterfaces(classDeclaration);
118+
&& !J.ClassDeclaration.Kind.Type.Record.equals(classDeclaration.getKind())
119+
&& !allAnnotations.isEmpty()
120+
&& allAnnotations.stream().allMatch(ann -> LOMBOK_VALUE_MATCHER.matches(ann) && (ann.getArguments() == null || ann.getArguments().isEmpty()))
121+
&& !hasGenericTypeParameter(classDeclaration)
122+
&& classDeclaration.getBody().getStatements().stream().allMatch(this::isRecordCompatibleField)
123+
&& !hasIncompatibleModifier(classDeclaration);
119124
}
120125

121126
/**
122127
* If the class target class implements an interface, transforming it to a record will not work in general,
123128
* because the record access methods do not have the "get" prefix.
124129
*
125130
* @param classDeclaration
126-
* @return
131+
* @return true if the class implements an interface with a getter method based on a member variable
127132
*/
128-
private boolean implementsInterfaces(J.ClassDeclaration classDeclaration) {
133+
private boolean implementsConflictingInterfaces(J.ClassDeclaration classDeclaration, Set<String> memberVariableNames) {
129134
List<TypeTree> classDeclarationImplements = classDeclaration.getImplements();
130-
return !(classDeclarationImplements == null || classDeclarationImplements.isEmpty());
135+
if (classDeclarationImplements == null) {
136+
return false;
137+
}
138+
return classDeclarationImplements.stream().anyMatch(implemented -> {
139+
JavaType type = implemented.getType();
140+
if (type instanceof JavaType.FullyQualified) {
141+
return isConflictingInterface((JavaType.FullyQualified) type, memberVariableNames);
142+
} else {
143+
return false;
144+
}
145+
});
131146
}
147+
148+
private static boolean isConflictingInterface(JavaType.FullyQualified implemented, Set<String> memberVariableNames) {
149+
boolean hasConflictingMethod = implemented.getMethods().stream()
150+
.map(JavaType.Method::getName)
151+
.map(LombokValueToRecordVisitor::getterMethodNameToFluentMethodName)
152+
.anyMatch(memberVariableNames::contains);
153+
if (hasConflictingMethod) {
154+
return true;
155+
}
156+
List<JavaType.FullyQualified> superInterfaces = implemented.getInterfaces();
157+
if (superInterfaces != null) {
158+
return superInterfaces.stream().anyMatch(i -> isConflictingInterface(i, memberVariableNames));
159+
}
160+
return false;
161+
}
162+
132163
private boolean hasGenericTypeParameter(J.ClassDeclaration classDeclaration) {
133164
List<J.TypeParameter> typeParameters = classDeclaration.getTypeParameters();
134165
return typeParameters != null && !typeParameters.isEmpty();
@@ -182,10 +213,10 @@ private static class LombokValueToRecordVisitor extends JavaIsoVisitor<Execution
182213
private static final String TO_STRING_MEMBER_DELIMITER = "\", \" +\n";
183214
private static final String STANDARD_GETTER_PREFIX = "get";
184215

185-
private final Boolean useExactToString;
216+
private final @Nullable Boolean useExactToString;
186217
private final Map<String, Set<String>> recordTypeToMembers;
187218

188-
public LombokValueToRecordVisitor(Boolean useExactToString, Map<String, Set<String>> recordTypeToMembers) {
219+
public LombokValueToRecordVisitor(@Nullable Boolean useExactToString, Map<String, Set<String>> recordTypeToMembers) {
189220
this.useExactToString = useExactToString;
190221
this.recordTypeToMembers = recordTypeToMembers;
191222
}
@@ -220,8 +251,8 @@ private boolean isMethodInvocationOnRecordTypeClassMember(J.MethodInvocation met
220251
String classFqn = classType.getFullyQualifiedName();
221252

222253
return recordTypeToMembers.containsKey(classFqn)
223-
&& methodName.startsWith(STANDARD_GETTER_PREFIX)
224-
&& recordTypeToMembers.get(classFqn).contains(getterMethodNameToFluentMethodName(methodName));
254+
&& methodName.startsWith(STANDARD_GETTER_PREFIX)
255+
&& recordTypeToMembers.get(classFqn).contains(getterMethodNameToFluentMethodName(methodName));
225256
}
226257

227258
private static boolean isClassExpression(@Nullable Expression expression) {

src/test/java/org/openrewrite/java/migrate/lombok/LombokValueToRecordTest.java

Lines changed: 101 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,18 @@ void valueAnnotatedClassWithUseExactOptionKeepsLombokToString() {
4848
java(
4949
"""
5050
import lombok.Value;
51-
51+
5252
@Value
5353
public class Test {
5454
String field1;
55-
55+
5656
String field2;
5757
}
5858
""",
5959
"""
6060
public record Test(
6161
String field1,
62-
62+
6363
String field2) {
6464
@Override
6565
public String toString() {
@@ -84,17 +84,17 @@ void convertOnlyValueAnnotatedClassWithoutDefaultValuesToRecord() {
8484
java(
8585
"""
8686
package example;
87-
87+
8888
import lombok.Value;
89-
89+
9090
@Value
9191
public class A {
9292
String test;
9393
}
9494
""",
9595
"""
9696
package example;
97-
97+
9898
public record A(
9999
String test) {
100100
}
@@ -103,31 +103,31 @@ public record A(
103103
java(
104104
"""
105105
package example;
106-
106+
107107
public class UserOfA {
108-
108+
109109
private final A record;
110-
110+
111111
public UserOfA() {
112112
this.record = new A("some value");
113113
}
114-
114+
115115
public String getRecordValue() {
116116
return record.getTest();
117117
}
118118
}
119119
""",
120120
"""
121121
package example;
122-
122+
123123
public class UserOfA {
124-
124+
125125
private final A record;
126-
126+
127127
public UserOfA() {
128128
this.record = new A("some value");
129129
}
130-
130+
131131
public String getRecordValue() {
132132
return record.test();
133133
}
@@ -148,7 +148,7 @@ void onlyRemoveAnnotationFromRecords() {
148148
149149
import lombok.ToString;
150150
import lombok.Value;
151-
151+
152152
@Value
153153
public class A {
154154
String test;
@@ -162,7 +162,7 @@ public class B {
162162
""",
163163
"""
164164
package example;
165-
165+
166166
import lombok.ToString;
167167
import lombok.Value;
168168
@@ -209,6 +209,38 @@ record B(
209209
"""
210210
)
211211
);
212+
213+
214+
}
215+
216+
@Test
217+
void interfaceIsImplementedThatDoesNotDefineFieldGetter() {
218+
//language=java
219+
rewriteRun(
220+
s -> s.typeValidationOptions(TypeValidation.none()),
221+
java(
222+
"""
223+
package example;
224+
225+
import lombok.Value;
226+
import java.io.Serializable;
227+
228+
@Value
229+
public class A implements Serializable {
230+
String test;
231+
}
232+
""",
233+
"""
234+
package example;
235+
236+
import java.io.Serializable;
237+
238+
public record A(
239+
String test) implements Serializable {
240+
}
241+
"""
242+
)
243+
);
212244
}
213245

214246
@Nested
@@ -220,11 +252,11 @@ void classWithExplicitConstructor() {
220252
java(
221253
"""
222254
import lombok.Value;
223-
255+
224256
@Value
225257
public class A {
226258
String test;
227-
259+
228260
public A() {
229261
this.test = "test";
230262
}
@@ -243,10 +275,10 @@ void classWithFieldAnnotations() {
243275
"""
244276
import com.fasterxml.jackson.annotation.JsonProperty;
245277
import lombok.Value;
246-
278+
247279
@Value
248280
public class A {
249-
281+
250282
@JsonProperty
251283
String test;
252284
}
@@ -262,11 +294,11 @@ void classWithExplicitMethods() {
262294
java(
263295
"""
264296
import lombok.Value;
265-
297+
266298
@Value
267299
public class A {
268300
String test;
269-
301+
270302
public String getTest() {
271303
return test;
272304
}
@@ -283,7 +315,7 @@ void genericClass() {
283315
java(
284316
"""
285317
import lombok.Value;
286-
318+
287319
@Value
288320
public class A<T extends Object> {
289321
T test;
@@ -301,7 +333,7 @@ void nonJava17Class() {
301333
java(
302334
"""
303335
import lombok.Value;
304-
336+
305337
@Value
306338
public class A {
307339
String test;
@@ -321,7 +353,7 @@ void classWithMultipleLombokAnnotations() {
321353
"""
322354
import lombok.Value;
323355
import lombok.experimental.Accessors;
324-
356+
325357
@Value
326358
@Accessors(fluent = true)
327359
public class A {
@@ -352,7 +384,7 @@ void classWithStaticField() {
352384
java(
353385
"""
354386
import lombok.Value;
355-
387+
356388
@Value
357389
public class A {
358390
static String disqualifyingField;
@@ -406,24 +438,52 @@ public class A {
406438
}
407439

408440
@Test
409-
void classImplementingInterfaces() {
441+
void classImplementingConflictingInterface() {
410442
//language=java
411443
rewriteRun(
412444
java(
413-
"""
414-
package example;
415-
416-
import lombok.Value;
417-
418-
interface I {
419-
String getTest();
420-
}
421-
422-
@Value
423-
public class A implements I {
424-
String test;
425-
}
426-
""")
445+
"""
446+
package example;
447+
448+
import lombok.Value;
449+
450+
interface I {
451+
String getTest();
452+
}
453+
454+
@Value
455+
public class A implements I {
456+
String test;
457+
}
458+
"""
459+
)
460+
);
461+
462+
}
463+
464+
@Test
465+
void classImplementingConflictingInterfaceWithInheritance() {
466+
//language=java
467+
rewriteRun(
468+
java(
469+
"""
470+
package example;
471+
472+
import lombok.Value;
473+
474+
interface I {
475+
String getTest();
476+
}
477+
478+
interface J extends I {
479+
}
480+
481+
@Value
482+
public class A implements J {
483+
String test;
484+
}
485+
"""
486+
)
427487
);
428488

429489
}

0 commit comments

Comments
 (0)