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 63f16fb0ef1..fc520e7f8ab 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 @@ -15,8 +15,11 @@ */ package org.openrewrite.java; +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.SourceSpec; import static org.openrewrite.java.Assertions.java; @@ -25,7 +28,7 @@ class AddOrUpdateAnnotationAttributeTest implements RewriteTest { @Test void addValueAttribute() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "hello", null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "hello", null, null, null)), java( """ package org.example; @@ -56,7 +59,7 @@ public class A { @Test void addValueAttributeClass() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "Integer.class", null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "Integer.class", null, null, null)), java( """ package org.example; @@ -87,7 +90,7 @@ public class A { @Test void addValueAttributeFullyQualifiedClass() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "java.math.BigDecimal.class", null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "java.math.BigDecimal.class", null, null, null)), java( """ package org.example; @@ -118,7 +121,7 @@ public class A { @Test void updateValueAttribute() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "hello", null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "hello", null, null, null)), java( """ package org.example; @@ -150,7 +153,7 @@ public class A { @Test void updateValueAttributeClass() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "Integer.class", null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "Integer.class", null, null, null)), java( """ package org.example; @@ -182,7 +185,7 @@ public class A { @Test void removeValueAttribute() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, null, null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, null, null, null, null)), java( """ package org.example; @@ -214,7 +217,7 @@ public class A { @Test void removeValueAttributeClass() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, null, null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, null, null, null, null)), java( """ package org.example; @@ -245,7 +248,7 @@ public class A { @Test void addNamedAttribute() { - rewriteRun(spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", "500", null, null)), + rewriteRun(spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", "500", null, null, null)), java( """ package org.junit; @@ -282,7 +285,7 @@ void foo() { @Test void replaceAttribute() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", "500", null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", "500", null, null, null)), java( """ package org.junit; @@ -319,7 +322,7 @@ void foo() { @Test void removeAttribute() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", null, null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", null, null, null, null)), java( """ package org.junit; @@ -356,7 +359,7 @@ void foo() { @Test void preserveExistingAttributes() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", "500", null, null)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "timeout", "500", null, null, null)), java( """ package org.junit; @@ -394,7 +397,7 @@ void foo() { @Test void implicitValueToExplicitValue() { - rewriteRun(spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "other", "1", null, null)), + rewriteRun(spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "other", "1", null, null, null)), java( """ package org.junit; @@ -431,7 +434,7 @@ void foo() { @Test void implicitValueToExplicitValueClass() { - rewriteRun(spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "other", "1", null, null)), + rewriteRun(spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "other", "1", null, null, null)), java( """ package org.junit; @@ -469,7 +472,7 @@ void foo() { @Test void dontChangeWhenSetToAddOnly() { rewriteRun( - spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "other", "1", true, false)), + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.junit.Test", "other", "1", null, true, false)), java( """ package org.junit; @@ -500,6 +503,7 @@ void arrayInAnnotationAttribute() { "org.example.Foo", "array", "newTest", + null, false, false)), java( @@ -536,6 +540,7 @@ void arrayInputMoreThanOneInAnnotationAttribute() { "org.example.Foo", "array", "newTest1,newTest2", + null, false, false)), java( @@ -572,6 +577,7 @@ void addArrayInputInAnnotationAttribute() { "org.example.Foo", "array", "newTest1,newTest2", + null, false, false)), java( @@ -608,6 +614,7 @@ void addArrayInputInAnnotationAttributeWithAppendTrue() { "org.example.Foo", "array", "newTest1,newTest2", + null, false, true)), java( @@ -644,6 +651,7 @@ void addArrayInputInAnnotationAttributeEmptyBraces() { "org.example.Foo", "array", "newTest1,newTest2", + null, false, null)), java( @@ -681,6 +689,7 @@ void removeArrayInputInAnnotationAttribute() { "array", null, null, + null, false)), java( """ @@ -717,6 +726,7 @@ void addOtherAttributeInArrayAnnotation() { "string", "test", null, + null, false)), java( """ @@ -753,6 +763,7 @@ void appendSingleValueToExistingArrayAttribute() { "org.example.Foo", "array", "b", + null, false, true)), java( @@ -789,6 +800,7 @@ void appendMultipleValuesToExistingArrayAttribute() { "org.example.Foo", "array", "b,c", + null, false, true)), java( @@ -825,6 +837,7 @@ void appendMultipleValuesToExistingArrayAttributeWithOverlap() { "org.example.Foo", "array", "b,c", + null, false, true)), java( @@ -861,6 +874,7 @@ void appendMultipleValuesToExistingArrayAttributeNonSet() { "org.example.Foo", "array", "b,c", + null, true, true)), java( @@ -901,6 +915,7 @@ void addAttributeToNestedAnnotationArray() { "attribute", "", null, + null, false)), java( """ @@ -938,4 +953,336 @@ public class A { ) ); } + + @Nested + class OnMatch { + @Test + void matchValue() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "hello", "goodbye", null, null)), + java( + """ + package org.example; + public @interface Foo { + String value() default ""; + } + """, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo("goodbye") + public class A { + } + """, + """ + import org.example.Foo; + + @Foo("hello") + public class A { + } + """ + ) + ); + } + + @Test + void matchEnumValue() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "Values.TWO", "Values.ONE", null, null)), + java( + """ + package org.example; + public @interface Foo { + Values value() default ""; + } + public enum Values {ONE, TWO} + """, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo(Values.ONE) + public class A { + } + """, + """ + import org.example.Foo; + + @Foo(Values.TWO) + public class A { + } + """ + ) + ); + } + + @Test + void matchValueInArray() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute( + "org.example.Foo", + null, + "hello", + "goodbye", + null, + true)), + java( + """ + package org.example; + public @interface Foo { + String[] value() default ""; + } + """, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo({"goodbye", "hi"}) + public class A { + } + """, + """ + import org.example.Foo; + + @Foo({"hi", "hello"}) + public class A { + } + """ + ) + ); + } + + @Test + void noMatchValue() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "hello", "hi", null, null)), + java( + """ + package org.example; + public @interface Foo { + String value() default ""; + } + """, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo("goodbye") + public class A { + } + """ + ) + ); + } + + @Test + void matchClass() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "Integer.class", "Long.class", null, null)), + java( + """ + package org.example; + public @interface Foo { + Class value(); + } + """, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo(Long.class) + public class A { + } + """, + """ + import org.example.Foo; + + @Foo(Integer.class) + public class A { + } + """ + ) + ); + } + + @Test + void nomatchClass() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute("org.example.Foo", null, "Integer.class", "Double.class", null, null)), + java( + """ + package org.example; + public @interface Foo { + Class value(); + } + """, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo(Long.class) + public class A { + } + """ + ) + ); + } + } + + @Nested + class AsValueAttribute { + + @Language("java") + private static final String FOO_ANNOTATION_WITH_STRING_ARRAY_VALUE = """ + package org.example; + public @interface Foo { + String[] value() default {}; + } + """; + + @Test + void implicitWithNullAttributeName() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute( + "org.example.Foo", + null, + "b", + null, + false, + true)), + java( + FOO_ANNOTATION_WITH_STRING_ARRAY_VALUE, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo({"a"}) + public class A { + } + """, + """ + import org.example.Foo; + + @Foo({"a", "b"}) + public class A { + } + """ + ) + ); + } + + @Test + void implicitWithAttributeNameValue() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute( + "org.example.Foo", + null, + "b", + null, + false, + true)), + java( + FOO_ANNOTATION_WITH_STRING_ARRAY_VALUE, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo(value = {"a"}) + public class A { + } + """, + """ + import org.example.Foo; + + @Foo(value = {"a", "b"}) + public class A { + } + """ + ) + ); + } + + @Test + void explicitWithNullAttributeName() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute( + "org.example.Foo", + null, + "b", + null, + false, + true)), + java( + FOO_ANNOTATION_WITH_STRING_ARRAY_VALUE, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo(value = {"a"}) + public class A { + } + """, + """ + import org.example.Foo; + + @Foo(value = {"a", "b"}) + public class A { + } + """ + ) + ); + } + + @Test + void explicitWithAttributeNameValue() { + rewriteRun( + spec -> spec.recipe(new AddOrUpdateAnnotationAttribute( + "org.example.Foo", + "value", + "b", + null, + false, + true)), + java( + FOO_ANNOTATION_WITH_STRING_ARRAY_VALUE, + SourceSpec::skip + ), + java( + """ + import org.example.Foo; + + @Foo(value = {"a"}) + public class A { + } + """, + """ + import org.example.Foo; + + @Foo(value = {"a", "b"}) + public class A { + } + """ + ) + ); + } + } } 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 05257701bfd..38cb78f8d66 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/AddOrUpdateAnnotationAttribute.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/AddOrUpdateAnnotationAttribute.java @@ -18,6 +18,7 @@ import lombok.EqualsAndHashCode; import lombok.Value; import lombok.With; +import org.jetbrains.annotations.Contract; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.internal.ListUtils; @@ -28,11 +29,11 @@ import java.util.Arrays; import java.util.List; -import java.util.Objects; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; +import static java.util.Objects.requireNonNull; import static org.openrewrite.Tree.randomId; @Value @@ -46,7 +47,7 @@ public String getDisplayName() { @Override public String getDescription() { return "Some annotations accept arguments. This recipe sets an existing argument to the specified value, " + - "or adds the argument if it is not already set."; + "or adds the argument if it is not already set."; } @Option(displayName = "Annotation type", @@ -67,6 +68,12 @@ public String getDescription() { @Nullable String attributeValue; + @Option(displayName = "Old Attribute value", + description = "The current value of the attribute, this can be used to filter where the change is applied. Set to `null` for wildcard behavior.", + example = "400") + @Nullable + String oldAttributeValue; + @Option(displayName = "Add only", description = "When set to `true` will not change existing annotation attribute values.") @Nullable @@ -74,9 +81,9 @@ public String getDescription() { @Option(displayName = "Append array", description = "If the attribute is an array, setting this option to `true` will append the value(s). " + - "In conjunction with `addOnly`, it is possible to control duplicates: " + - "`addOnly=true`, always append. " + - "`addOnly=false`, only append if the value is not already present.") + "In conjunction with `addOnly`, it is possible to control duplicates: " + + "`addOnly=true`, always append. " + + "`addOnly=false`, only append if the value is not already present.") @Nullable Boolean appendArray; @@ -93,7 +100,7 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { String newAttributeValue = maybeQuoteStringArgument(attributeName, attributeValue, a); List currentArgs = a.getArguments(); if (currentArgs == null || currentArgs.isEmpty() || currentArgs.get(0) instanceof J.Empty) { - if (newAttributeValue == null) { + if (newAttributeValue == null || oldAttributeValue != null) { return a; } @@ -104,8 +111,8 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { .apply(getCursor(), a.getCoordinates().replaceArguments(), newAttributeValue); } else { String newAttributeValueResult = newAttributeValue; - if (((JavaType.FullyQualified) Objects.requireNonNull(a.getAnnotationType().getType())).getMethods().stream().anyMatch(method -> method.getReturnType().toString().equals("java.lang.String[]"))) { - String attributeValueCleanedUp = attributeValue.replaceAll("\\s+","").replaceAll("[\\s+{}\"]",""); + if (((JavaType.FullyQualified) requireNonNull(a.getAnnotationType().getType())).getMethods().stream().anyMatch(method -> method.getReturnType().toString().equals("java.lang.String[]"))) { + String attributeValueCleanedUp = attributeValue.replaceAll("\\s+", "").replaceAll("[\\s+{}\"]", ""); List attributeList = Arrays.asList(attributeValueCleanedUp.contains(",") ? attributeValueCleanedUp.split(",") : new String[]{attributeValueCleanedUp}); newAttributeValueResult = attributeList.stream() .map(String::valueOf) @@ -124,9 +131,13 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { if (it instanceof J.Assignment) { J.Assignment as = (J.Assignment) it; J.Identifier var = (J.Identifier) as.getVariable(); - if (attributeName == null || !attributeName.equals(var.getSimpleName())) { + if (attributeName == null && !"value".equals(var.getSimpleName())) { + return it; + } + if (attributeName != null && !attributeName.equals(var.getSimpleName())) { return it; } + foundOrSetAttributeWithDesiredValue.set(true); if (newAttributeValue == null) { @@ -134,8 +145,8 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { } if (as.getAssignment() instanceof J.NewArray) { - List jLiteralList = ((J.NewArray) as.getAssignment()).getInitializer(); - String attributeValueCleanedUp = attributeValue.replaceAll("\\s+","").replaceAll("[\\s+{}\"]",""); + List jLiteralList = requireNonNull(((J.NewArray) as.getAssignment()).getInitializer()); + String attributeValueCleanedUp = attributeValue.replaceAll("\\s+", "").replaceAll("[\\s+{}\"]", ""); List attributeList = Arrays.asList(attributeValueCleanedUp.contains(",") ? attributeValueCleanedUp.split(",") : new String[]{attributeValueCleanedUp}); if (as.getMarkers().findFirst(AlreadyAppended.class).filter(ap -> ap.getValues().equals(newAttributeValue)).isPresent()) { @@ -149,36 +160,47 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { if (Boolean.FALSE.equals(addOnly) && attributeValIsAlreadyPresent(jLiteralList, newAttributeListValue)) { continue; } + if (oldAttributeValue != null && !oldAttributeValue.equals(attrListValues)) { + continue; + } changed = true; - jLiteralList.add(new J.Literal(randomId(), Space.EMPTY, Markers.EMPTY, newAttributeListValue, newAttributeListValue, null, JavaType.Primitive.String)); + Expression e = requireNonNull(((J.Annotation) JavaTemplate.builder(newAttributeListValue) + .contextSensitive() + .build() + .apply(getCursor(), finalA.getCoordinates().replaceArguments())) + .getArguments()).get(0); + jLiteralList.add(e); } return changed ? as.withAssignment(((J.NewArray) as.getAssignment()).withInitializer(jLiteralList)) .withMarkers(as.getMarkers().add(new AlreadyAppended(randomId(), newAttributeValue))) : as; } int m = 0; - for (int i = 0; i< Objects.requireNonNull(jLiteralList).size(); i++){ - if (i >= attributeList.size()){ + for (int i = 0; i < requireNonNull(jLiteralList).size(); i++) { + if (i >= attributeList.size()) { jLiteralList.remove(i); i--; continue; } String newAttributeListValue = maybeQuoteStringArgument(attributeName, attributeList.get(i), finalA); - if (jLiteralList.size() == i+1){ - m = i+1; + if (jLiteralList.size() == i + 1) { + m = i + 1; } - if (newAttributeListValue != null && newAttributeListValue.equals(((J.Literal) jLiteralList.get(i)).getValueSource()) || Boolean.TRUE.equals(addOnly)) { + if (newAttributeListValue.equals(((J.Literal) jLiteralList.get(i)).getValueSource()) || Boolean.TRUE.equals(addOnly)) { + continue; + } + if (oldAttributeValue != null && !oldAttributeValue.equals(attributeList.get(i))) { continue; } jLiteralList.set(i, ((J.Literal) jLiteralList.get(i)).withValue(newAttributeListValue).withValueSource(newAttributeListValue).withPrefix(jLiteralList.get(i).getPrefix())); } - if (jLiteralList.size() < attributeList.size() || Boolean.TRUE.equals(addOnly)){ - if (Boolean.TRUE.equals(addOnly)){ + if (jLiteralList.size() < attributeList.size() || Boolean.TRUE.equals(addOnly)) { + if (Boolean.TRUE.equals(addOnly)) { m = 0; } - for (int j = m; j < attributeList.size(); j++){ + for (int j = m; j < attributeList.size(); j++) { String newAttributeListValue = maybeQuoteStringArgument(attributeName, attributeList.get(j), finalA); jLiteralList.add(j, new J.Literal(randomId(), jLiteralList.get(j - 1).getPrefix(), Markers.EMPTY, newAttributeListValue, newAttributeListValue, null, JavaType.Primitive.String)); } @@ -190,6 +212,9 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { if (newAttributeValue.equals(value.getValueSource()) || Boolean.TRUE.equals(addOnly)) { return it; } + if (!valueMatches(value, oldAttributeValue)) { + return it; + } return as.withAssignment(value.withValue(newAttributeValue).withValueSource(newAttributeValue)); } } else if (it instanceof J.Literal) { @@ -203,6 +228,9 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { if (newAttributeValue.equals(value.getValueSource()) || Boolean.TRUE.equals(addOnly)) { return it; } + if (!valueMatches(value, oldAttributeValue)) { + return it; + } return ((J.Literal) it).withValue(newAttributeValue).withValueSource(newAttributeValue); } else { // Make the attribute name explicit, before we add the new value below @@ -217,6 +245,9 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { // 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 (!valueMatches(it, oldAttributeValue)) { + return it; + } if (newAttributeValue == null) { return null; } @@ -236,33 +267,132 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) { .apply(getCursor(), finalA.getCoordinates().replaceArguments(), it)) .getArguments().get(0); } + } else if (it instanceof J.NewArray) { + if (it.getMarkers().findFirst(AlreadyAppended.class).filter(ap -> ap.getValues().equals(newAttributeValue)).isPresent()) { + return it; + } + + if (newAttributeValue == null) { + return null; + } + + J.NewArray arrayValue = (J.NewArray) it; + List jLiteralList = requireNonNull(arrayValue.getInitializer()); + String attributeValueCleanedUp = attributeValue.replaceAll("\\s+", "").replaceAll("[\\s+{}\"]", ""); + List attributeList = Arrays.asList(attributeValueCleanedUp.contains(",") ? attributeValueCleanedUp.split(",") : new String[]{attributeValueCleanedUp}); + + if (Boolean.TRUE.equals(appendArray)) { + boolean changed = false; + for (String attrListValues : attributeList) { + String newAttributeListValue = maybeQuoteStringArgument(attributeName, attrListValues, finalA); + if (Boolean.FALSE.equals(addOnly) && attributeValIsAlreadyPresent(jLiteralList, newAttributeListValue)) { + continue; + } + changed = true; + + Expression e = requireNonNull(((J.Annotation) JavaTemplate.builder(newAttributeListValue) + .contextSensitive() + .build() + .apply(getCursor(), finalA.getCoordinates().replaceArguments())) + .getArguments()).get(0); + jLiteralList.add(e); + } + if (oldAttributeValue != null) { // remove old value from array + jLiteralList = ListUtils.map(jLiteralList, val -> valueMatches(val, oldAttributeValue) ? null : val); + } + + return changed ? arrayValue.withInitializer(jLiteralList) + .withMarkers(it.getMarkers().add(new AlreadyAppended(randomId(), newAttributeValue))) : it; + } + int m = 0; + for (int i = 0; i < requireNonNull(jLiteralList).size(); i++) { + if (i >= attributeList.size()) { + jLiteralList.remove(i); + i--; + continue; + } + + String newAttributeListValue = maybeQuoteStringArgument(attributeName, attributeList.get(i), finalA); + if (jLiteralList.size() == i + 1) { + m = i + 1; + } + + if (newAttributeListValue.equals(((J.Literal) jLiteralList.get(i)).getValueSource()) || Boolean.TRUE.equals(addOnly)) { + continue; + } + if (oldAttributeValue != null && !oldAttributeValue.equals(newAttributeListValue)) { + continue; + } + + jLiteralList.set(i, ((J.Literal) jLiteralList.get(i)).withValue(newAttributeListValue).withValueSource(newAttributeListValue).withPrefix(jLiteralList.get(i).getPrefix())); + } + if (jLiteralList.size() < attributeList.size() || Boolean.TRUE.equals(addOnly)) { + if (Boolean.TRUE.equals(addOnly)) { + m = 0; + } + for (int j = m; j < attributeList.size(); j++) { + String newAttributeListValue = maybeQuoteStringArgument(attributeName, attributeList.get(j), finalA); + + Expression e = requireNonNull(((J.Annotation) JavaTemplate.builder(newAttributeListValue) + .contextSensitive() + .build() + .apply(getCursor(), finalA.getCoordinates().replaceArguments())) + .getArguments()).get(0); + jLiteralList.add(j, e); + } + } + + return arrayValue.withInitializer(jLiteralList); } return it; }); + if (newArgs != currentArgs) { a = a.withArguments(newArgs); } - if (foundOrSetAttributeWithDesiredValue.get()) { - a = maybeAutoFormat(original, a, ctx); - return a; + if (!foundOrSetAttributeWithDesiredValue.get() && !attributeValIsAlreadyPresent(newArgs, newAttributeValue)) { + // There was no existing value to update, so add a new value into the argument list + String effectiveName = (attributeName == null) ? "value" : attributeName; + //noinspection ConstantConditions + J.Assignment as = (J.Assignment) ((J.Annotation) JavaTemplate.builder("#{} = #{}") + .contextSensitive() + .build() + .apply(getCursor(), a.getCoordinates().replaceArguments(), effectiveName, newAttributeValue)) + .getArguments().get(0); + a = a.withArguments(ListUtils.concat(as, a.getArguments())); } - // There was no existing value to update, so add a new value into the argument list - String effectiveName = (attributeName == null) ? "value" : attributeName; - //noinspection ConstantConditions - J.Assignment as = (J.Assignment) ((J.Annotation) JavaTemplate.builder("#{} = #{}") - .contextSensitive() - .build() - .apply(getCursor(), a.getCoordinates().replaceArguments(), effectiveName, newAttributeValue)) - .getArguments().get(0); - a = a.withArguments(ListUtils.concat(as, a.getArguments())); - a = maybeAutoFormat(original, a, ctx); } - + a = maybeAutoFormat(original, a, ctx); return a; } }); } + private static boolean valueMatches(@Nullable Expression expression, @Nullable String oldAttributeValue) { + if (expression == null) { + return oldAttributeValue == null; + } + if (oldAttributeValue == null) { // null means wildcard + return true; + } else if (expression instanceof J.Literal) { + return oldAttributeValue.equals(((J.Literal) expression).getValue()); + } else if (expression instanceof J.FieldAccess) { + J.FieldAccess fa = (J.FieldAccess) expression; + String currentValue = ((J.Identifier) fa.getTarget()).getSimpleName() + "." + fa.getSimpleName(); + return oldAttributeValue.equals(currentValue); + } else if (expression instanceof J.Identifier) { // class names, static variables .. + if (oldAttributeValue.endsWith(".class")) { + String className = TypeUtils.toString(requireNonNull(expression.getType())) + ".class"; + return className.endsWith(oldAttributeValue); + } else { + return oldAttributeValue.equals(((J.Identifier) expression).getSimpleName()); + } + } else { + throw new IllegalArgumentException("Unexpected expression type: " + expression.getClass()); + } + } + + @Contract("_, null, _ -> null; _, !null, _ -> !null") private static @Nullable String maybeQuoteStringArgument(@Nullable String attributeName, @Nullable String attributeValue, J.Annotation annotation) { if ((attributeValue != null) && attributeIsString(attributeName, annotation)) { return "\"" + attributeValue + "\""; @@ -284,7 +414,10 @@ private static boolean attributeIsString(@Nullable String attributeName, J.Annot return false; } - private static boolean attributeValIsAlreadyPresent(List expression, @Nullable String attributeValue) { + private static boolean attributeValIsAlreadyPresent(@Nullable List expression, @Nullable String attributeValue) { + if (expression == null) { + return attributeValue == null; + } for (Expression e : expression) { if (e instanceof J.Literal) { J.Literal literal = (J.Literal) e; @@ -292,6 +425,9 @@ private static boolean attributeValIsAlreadyPresent(List expression, return true; } } + if (e instanceof J.NewArray) { + return attributeValIsAlreadyPresent(((J.NewArray) e).getInitializer(), attributeValue); + } } return false; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/recipes/MissingOptionExample.java b/rewrite-java/src/main/java/org/openrewrite/java/recipes/MissingOptionExample.java index 847f84fdc87..100279d44dc 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/recipes/MissingOptionExample.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/recipes/MissingOptionExample.java @@ -80,7 +80,7 @@ public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ct } } - AddOrUpdateAnnotationAttribute addOrUpdateAnnotationAttribute = new AddOrUpdateAnnotationAttribute(ORG_OPENREWRITE_OPTION, "example", "TODO Provide a usage example for the docs", true, false); + AddOrUpdateAnnotationAttribute addOrUpdateAnnotationAttribute = new AddOrUpdateAnnotationAttribute(ORG_OPENREWRITE_OPTION, "example", "TODO Provide a usage example for the docs", null, true, false); return (J.Annotation) addOrUpdateAnnotationAttribute.getVisitor().visitNonNull(an, ctx, getCursor().getParent()); } });