diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/AddOrUpdateAnnotationAttributeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/AddOrUpdateAnnotationAttributeTest.java index 0314c26a7a6..a04d9fc4594 100755 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/AddOrUpdateAnnotationAttributeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/AddOrUpdateAnnotationAttributeTest.java @@ -37,14 +37,14 @@ void addValueAttribute() { java( """ import org.example.Foo; - + @Foo public class A { } """, """ import org.example.Foo; - + @Foo("hello") public class A { } @@ -53,6 +53,68 @@ public class A { ); } + @Test + void addValueAttributeClass() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "Integer.class", null)), + java( + """ + package org.example; + public @interface Foo { + Class value(); + } + """ + ), + java( + """ + import org.example.Foo; + + @Foo + public class A { + } + """, + """ + import org.example.Foo; + + @Foo(Integer.class) + public class A { + } + """ + ) + ); + } + + @Test + void addValueAttributeFullyQualifiedClass() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "java.math.BigDecimal.class", null)), + java( + """ + package org.example; + public @interface Foo { + Class value(); + } + """ + ), + java( + """ + import org.example.Foo; + + @Foo + public class A { + } + """, + """ + import org.example.Foo; + + @Foo(java.math.BigDecimal.class) + public class A { + } + """ + ) + ); + } + @Test void updateValueAttribute() { rewriteRun( @@ -69,14 +131,14 @@ void updateValueAttribute() { java( """ import org.example.Foo; - + @Foo("goodbye") public class A { } """, """ import org.example.Foo; - + @Foo("hello") public class A { } @@ -85,6 +147,38 @@ public class A { ); } + @Test + void updateValueAttributeClass() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "Integer.class", null)), + java( + """ + package org.example; + public @interface Foo { + Class value(); + } + """ + ), + + java( + """ + import org.example.Foo; + + @Foo(Long.class) + public class A { + } + """, + """ + import org.example.Foo; + + @Foo(Integer.class) + public class A { + } + """ + ) + ); + } + @Test void removeValueAttribute() { rewriteRun( @@ -117,6 +211,38 @@ public class A { ); } + @Test + void removeValueAttributeClass() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, null, null)), + java( + """ + package org.example; + public @interface Foo { + Class value(); + } + """ + ), + + java( + """ + import org.example.Foo; + + @Foo(Long.class) + public class A { + } + """, + """ + import org.example.Foo; + + @Foo + public class A { + } + """ + ) + ); + } + @Test void addNamedAttribute() { rewriteRun(spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", "500", null)), @@ -131,9 +257,9 @@ void addNamedAttribute() { java( """ import org.junit.Test; - + class SomeTest { - + @Test void foo() { } @@ -141,9 +267,9 @@ void foo() { """, """ import org.junit.Test; - + class SomeTest { - + @Test(timeout = 500) void foo() { } @@ -168,9 +294,9 @@ void replaceAttribute() { java( """ import org.junit.Test; - + class SomeTest { - + @Test(timeout = 1) void foo() { } @@ -178,9 +304,9 @@ void foo() { """, """ import org.junit.Test; - + class SomeTest { - + @Test(timeout = 500) void foo() { } @@ -205,9 +331,9 @@ void removeAttribute() { java( """ import org.junit.Test; - + class SomeTest { - + @Test(timeout = 1) void foo() { } @@ -215,9 +341,9 @@ void foo() { """, """ import org.junit.Test; - + class SomeTest { - + @Test void foo() { } @@ -244,9 +370,9 @@ void preserveExistingAttributes() { java( """ import org.junit.Test; - + class SomeTest { - + @Test(foo = "") void foo() { } @@ -254,9 +380,9 @@ void foo() { """, """ import org.junit.Test; - + class SomeTest { - + @Test(timeout = 500, foo = "") void foo() { } @@ -268,8 +394,7 @@ void foo() { @Test void implicitValueToExplicitValue() { - rewriteRun(spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "other", "1", null)) - .expectedCyclesThatMakeChanges(2), + rewriteRun(spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "other", "1", null)), java( """ package org.junit; @@ -282,9 +407,9 @@ void implicitValueToExplicitValue() { java( """ import org.junit.Test; - + class SomeTest { - + @Test("foo") void foo() { } @@ -292,9 +417,9 @@ void foo() { """, """ import org.junit.Test; - + class SomeTest { - + @Test(other = 1, value = "foo") void foo() { } @@ -304,6 +429,43 @@ void foo() { ); } + @Test + void implicitValueToExplicitValueClass() { + rewriteRun(spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "other", "1", null)), + java( + """ + package org.junit; + public @interface Test { + long other() default 0L; + Class value(); + } + """ + ), + java( + """ + import org.junit.Test; + + class SomeTest { + + @Test(Integer.class) + void foo() { + } + } + """, + """ + import org.junit.Test; + + class SomeTest { + + @Test(other = 1, value = Integer.class) + void foo() { + } + } + """ + ) + ); + } + @Test void dontChangeWhenSetToAddOnly() { rewriteRun( @@ -320,7 +482,7 @@ void dontChangeWhenSetToAddOnly() { java( """ import org.junit.Test; - + class SomeTest { @Test(other = 0) void foo() { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/AddOrUpdateAnnotationAttribute.java b/rewrite-java/src/main/java/org/openrewrite/java/AddOrUpdateAnnotationAttribute.java index fa55101ede3..aba2d59d588 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/AddOrUpdateAnnotationAttribute.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/AddOrUpdateAnnotationAttribute.java @@ -43,7 +43,7 @@ public String getDescription() { "or adds the argument if it is not already set."; } - @Option(displayName = "Annotation Type", + @Option(displayName = "Annotation type", description = "The fully qualified name of the annotation.", example = "org.junit.Test") String annotationType; @@ -61,7 +61,7 @@ public String getDescription() { @Nullable String attributeValue; - @Option(displayName = "Add Only", + @Option(displayName = "Add only", description = "When set to `true` will not change existing annotation attribute values.") @Nullable Boolean addOnly; @@ -95,7 +95,7 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { } } else { // First assume the value exists amongst the arguments and attempt to update it - AtomicBoolean foundAttributeWithDesiredValue = new AtomicBoolean(false); + AtomicBoolean foundOrSetAttributeWithDesiredValue = new AtomicBoolean(false); final J.Annotation finalA = a; List newArgs = ListUtils.map(currentArgs, it -> { if (it instanceof J.Assignment) { @@ -104,28 +104,29 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { if (attributeName == null || !attributeName.equals(var.getSimpleName())) { return it; } + foundOrSetAttributeWithDesiredValue.set(true); J.Literal value = (J.Literal) as.getAssignment(); if (newAttributeValue == null) { return null; } if (newAttributeValue.equals(value.getValueSource()) || Boolean.TRUE.equals(addOnly)) { - foundAttributeWithDesiredValue.set(true); return it; } return as.withAssignment(value.withValue(newAttributeValue).withValueSource(newAttributeValue)); } else if (it instanceof J.Literal) { // The only way anything except an assignment can appear is if there's an implicit assignment to "value" if (attributeName == null || "value".equals(attributeName)) { + foundOrSetAttributeWithDesiredValue.set(true); if (newAttributeValue == null) { return null; } J.Literal value = (J.Literal) it; if (newAttributeValue.equals(value.getValueSource()) || Boolean.TRUE.equals(addOnly)) { - foundAttributeWithDesiredValue.set(true); return it; } return ((J.Literal) it).withValue(newAttributeValue).withValueSource(newAttributeValue); } else { + // Make the attribute name explicit, before we add the new value below //noinspection ConstantConditions return ((J.Annotation) JavaTemplate.builder("value = #{}") .contextSensitive() @@ -133,11 +134,37 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { .apply(getCursor(), finalA.getCoordinates().replaceArguments(), it) ).getArguments().get(0); } + } else if (it instanceof J.FieldAccess) { + // The only way anything except an assignment can appear is if there's an implicit assignment to "value" + if (attributeName == null || "value".equals(attributeName)) { + foundOrSetAttributeWithDesiredValue.set(true); + if (newAttributeValue == null) { + return null; + } + J.FieldAccess value = (J.FieldAccess) it; + if (newAttributeValue.equals(value.toString()) || Boolean.TRUE.equals(addOnly)) { + return it; + } + //noinspection ConstantConditions + return ((J.Annotation) JavaTemplate.apply(newAttributeValue, getCursor(), finalA.getCoordinates().replaceArguments())) + .getArguments().get(0); + } else { + // Make the attribute name explicit, before we add the new value below + //noinspection ConstantConditions + return ((J.Annotation) JavaTemplate.builder("value = #{any()}") + .contextSensitive() + .build() + .apply(getCursor(), finalA.getCoordinates().replaceArguments(), it)) + .getArguments().get(0); + } } return it; }); - if (foundAttributeWithDesiredValue.get() || newArgs != currentArgs) { - return a.withArguments(newArgs); + if (newArgs != currentArgs) { + a = a.withArguments(newArgs); + } + if (foundOrSetAttributeWithDesiredValue.get()) { + return a; } // There was no existing value to update, so add a new value into the argument list String effectiveName = (attributeName == null) ? "value" : attributeName; @@ -145,10 +172,9 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { J.Assignment as = (J.Assignment) ((J.Annotation) JavaTemplate.builder("#{} = #{}") .contextSensitive() .build() - .apply(getCursor(), a.getCoordinates().replaceArguments(), effectiveName, newAttributeValue) - ).getArguments().get(0); - List newArguments = ListUtils.concat(as, a.getArguments()); - a = a.withArguments(newArguments); + .apply(getCursor(), a.getCoordinates().replaceArguments(), effectiveName, newAttributeValue)) + .getArguments().get(0); + a = a.withArguments(ListUtils.concat(as, a.getArguments())); a = autoFormat(a, ctx); }