Skip to content

Commit 2cc3f02

Browse files
committed
Fix type info for FQ class values in array append
When appending a fully qualified class (like com.example.MyClass.class) to an existing class array annotation attribute: 1. Use JavaTemplate.builder() with dependsOn to provide a class stub so the created expression has proper type information 2. Use the FQ class name in the template so it can resolve against the stub 3. Add ShortenFullyQualifiedTypeReferences as doAfterVisit to shorten the FQ name to the simple name (MyClass.class) 4. Fix attributeNameOrValIsAlreadyPresent to handle FQ class names when checking for duplicates by comparing both the full string and the simplified class name 5. Fix maybeAddImport to use onlyIfReferenced=false for FQ classes (same as we did for enums)
1 parent 89b5137 commit 2cc3f02

2 files changed

Lines changed: 71 additions & 3 deletions

File tree

rewrite-java-test/src/test/java/org/openrewrite/java/AddOrUpdateAnnotationAttributeTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.openrewrite.Issue;
2424
import org.openrewrite.test.RewriteTest;
2525
import org.openrewrite.test.SourceSpec;
26+
import org.openrewrite.test.TypeValidation;
2627

2728
import static org.openrewrite.java.Assertions.java;
2829

@@ -2770,6 +2771,49 @@ public class A {}
27702771
);
27712772
}
27722773

2774+
@Test
2775+
void appendFullyQualifiedClassValueToExistingClassAttribute() {
2776+
rewriteRun(
2777+
spec -> spec.recipe(new AddOrUpdateAnnotationAttribute(
2778+
"org.example.Foo",
2779+
null,
2780+
"com.example.MyClass.class",
2781+
null,
2782+
null,
2783+
true
2784+
)).typeValidationOptions(TypeValidation.all()),
2785+
java(
2786+
"""
2787+
package com.example;
2788+
public class MyClass {}
2789+
"""
2790+
),
2791+
java(
2792+
"""
2793+
package org.example;
2794+
public @interface Foo {
2795+
Class<?>[] value();
2796+
}
2797+
"""
2798+
),
2799+
java(
2800+
"""
2801+
import org.example.Foo;
2802+
2803+
@Foo(Integer.class)
2804+
public class A {}
2805+
""",
2806+
"""
2807+
import com.example.MyClass;
2808+
import org.example.Foo;
2809+
2810+
@Foo({Integer.class, MyClass.class})
2811+
public class A {}
2812+
"""
2813+
)
2814+
);
2815+
}
2816+
27732817
@Test
27742818
void appendClassValueToExistingNamedClassAttribute() {
27752819
rewriteRun(

rewrite-java/src/main/java/org/openrewrite/java/AddOrUpdateAnnotationAttribute.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public J.Annotation visitAnnotation(J.Annotation original, ExecutionContext ctx)
9999

100100
String newAttributeValue;
101101
if (isFullyQualifiedClass()) {
102-
maybeAddImport(attributeValue.substring(0, attributeValue.length() - 6));
102+
maybeAddImport(attributeValue.substring(0, attributeValue.length() - 6), false);
103103
newAttributeValue = attributeValue;
104104
} else if (isFullyQualifiedEnumValue(a)) {
105105
maybeAddImport(getEnumClassName(attributeValue), false);
@@ -152,6 +152,9 @@ public J.Annotation visitAnnotation(J.Annotation original, ExecutionContext ctx)
152152

153153
if (original != a) {
154154
doAfterVisit(new SimplifySingleElementAnnotation().getVisitor());
155+
if (isFullyQualifiedClass()) {
156+
doAfterVisit(new ShortenFullyQualifiedTypeReferences().getVisitor());
157+
}
155158
}
156159
return maybeAutoFormat(original, a, ctx);
157160
}
@@ -299,8 +302,19 @@ private J.NewArray createNewArrayWithExistingAndNew(J.Annotation annotation, J.F
299302
// Add new values, skipping duplicates - use template for non-string values
300303
for (String attribute : newValues) {
301304
if (!attributeNameOrValIsAlreadyPresent(initializer, singleton(attribute))) {
305+
JavaTemplate.Builder templateBuilder = JavaTemplate.builder("#{}");
306+
String templateValue = attribute;
307+
// For FQ classes, add stub so type info is resolved and use FQ name in template
308+
if (isFullyQualifiedClass()) {
309+
String fqClassName = attributeValue.substring(0, attributeValue.length() - 6);
310+
String packageName = fqClassName.substring(0, fqClassName.lastIndexOf('.'));
311+
String simpleName = fqClassName.substring(fqClassName.lastIndexOf('.') + 1);
312+
templateBuilder = templateBuilder.javaParser(JavaParser.fromJavaVersion()
313+
.dependsOn("package " + packageName + "; public class " + simpleName + " {}"));
314+
templateValue = fqClassName + ".class";
315+
}
302316
//noinspection ConstantConditions
303-
Expression newExpr = JavaTemplate.<J.Annotation>apply("#{}", getCursor(), annotation.getCoordinates().replaceArguments(), attribute)
317+
Expression newExpr = templateBuilder.build().<J.Annotation>apply(getCursor(), annotation.getCoordinates().replaceArguments(), templateValue)
304318
.getArguments().get(0).withPrefix(SINGLE_SPACE);
305319
initializer.add(newExpr);
306320
}
@@ -510,7 +524,17 @@ private boolean attributeNameOrValIsAlreadyPresent(Expression e, Collection<?> v
510524
} else if (e instanceof J.Literal) {
511525
return values.contains(((J.Literal) e).getValue() + "");
512526
} else if (e instanceof J.FieldAccess) {
513-
return values.contains(e.toString());
527+
J.FieldAccess fa = (J.FieldAccess) e;
528+
String fullString = fa.toString();
529+
if (values.contains(fullString)) {
530+
return true;
531+
}
532+
// For class literals, also check the simple name (e.g., com.example.MyClass.class -> MyClass.class)
533+
if (fullString.endsWith(".class") && fa.getTarget() instanceof J.FieldAccess) {
534+
String simpleName = getFullyQualifiedClass(fullString);
535+
return values.contains(simpleName);
536+
}
537+
return false;
514538
} else if (e instanceof J.NewArray) {
515539
List<Expression> initializer = ((J.NewArray) e).getInitializer();
516540
return (initializer == null && attributeValue == null) || (initializer != null && attributeNameOrValIsAlreadyPresent(initializer, values));

0 commit comments

Comments
 (0)