Skip to content

Commit 438146d

Browse files
WiP
1 parent 4509b20 commit 438146d

File tree

2 files changed

+242
-2
lines changed

2 files changed

+242
-2
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1782,7 +1782,7 @@ public class A {}
17821782
"""
17831783
import org.example.Const;
17841784
import org.example.FooDefaultStringArray;
1785-
@FooDefaultStringArray(Const.X.Y.FIRST_CONST, "b")
1785+
@FooDefaultStringArray({Const.X.Y.FIRST_CONST, "b"})
17861786
@FooDefaultStringArray(Const.X.Y.SECOND_CONST)
17871787
@FooDefaultStringArray({Const.X.Y.FIRST_CONST, "b"})
17881788
@FooDefaultStringArray(Const.X.Y.SECOND_CONST)

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

Lines changed: 241 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,220 @@ public String getDescription() {
9292

9393
@Override
9494
public TreeVisitor<?, ExecutionContext> getVisitor() {
95+
TreeVisitor<?, ExecutionContext> v = Preconditions.check(new UsesType<>(annotationType, false), new JavaIsoVisitor<ExecutionContext>() {
96+
private String attributeNameOrDefault() {
97+
return attributeName == null ? "value" : attributeName;
98+
}
99+
100+
private List<JavaType.Method> getMethods(J.Annotation annotation) {
101+
return ((JavaType.FullyQualified) requireNonNull(annotation.getAnnotationType().getType())).getMethods();
102+
}
103+
104+
private Optional<JavaType.Method> findMethod(J.Annotation annotation, String methodName) {
105+
for (JavaType.Method it : getMethods(annotation)) {
106+
if (methodName.equals(it.getName())) {
107+
return Optional.of(it);
108+
}
109+
}
110+
return Optional.empty();
111+
}
112+
113+
private String getUsefulNameFromFieldAccess(J.FieldAccess fa) {
114+
if (!(fa.getTarget() instanceof J.Identifier)) {
115+
return fa.toString();
116+
}
117+
return ((J.Identifier) fa.getTarget()).getSimpleName() + "." + fa.getSimpleName();
118+
}
119+
120+
private void addPossibleClassImports(@Nullable String value) {
121+
if (value == null) {
122+
return;
123+
}
124+
for (String singleVal : value.split(",")) {
125+
if (singleVal.endsWith(".class") && StringUtils.countOccurrences(singleVal, ".") > 1) {
126+
maybeAddImport(singleVal.substring(0, singleVal.length() - 6));
127+
}
128+
}
129+
}
130+
131+
private boolean attributeMatchesName(Expression e, String name) {
132+
if (e instanceof J.Assignment) {
133+
J.Assignment as = (J.Assignment) e;
134+
if (as.getVariable() instanceof J.Identifier) {
135+
return ((J.Identifier) as.getVariable()).getSimpleName().equals(name);
136+
}
137+
}
138+
return name.equals("value");
139+
}
140+
141+
private boolean alreadyContainsAttributeOfName(J.Annotation annotation, String name) {
142+
List<Expression> existingArguments = annotation.getArguments();
143+
if (existingArguments == null) {
144+
return false;
145+
}
146+
for (Expression e : annotation.getArguments()) {
147+
if (attributeMatchesName(e, name)) {
148+
return true;
149+
}
150+
}
151+
return false;
152+
}
153+
154+
private boolean valueMatches(Expression expression, String oldAttributeValue) {
155+
if (expression instanceof J.Literal) {
156+
return oldAttributeValue.equals(((J.Literal) expression).getValue());
157+
} else if (expression instanceof J.FieldAccess) {
158+
J.FieldAccess fa = (J.FieldAccess) expression;
159+
String currentValue = getUsefulNameFromFieldAccess(fa);
160+
return oldAttributeValue.equals(currentValue);
161+
} else if (expression instanceof J.Identifier) { // class names, static variables, ...
162+
if (oldAttributeValue.endsWith(".class")) {
163+
String className = TypeUtils.toString(requireNonNull(expression.getType())) + ".class";
164+
return className.endsWith(oldAttributeValue);
165+
}
166+
return oldAttributeValue.equals(((J.Identifier) expression).getSimpleName());
167+
}
168+
throw new IllegalArgumentException("Unexpected expression type: " + expression.getClass());
169+
}
170+
171+
private J.Empty newEmpty() {
172+
return new J.Empty(randomId(), SINGLE_SPACE, EMPTY);
173+
}
174+
175+
private List<Expression> updateInitializerDroppingMatched(@Nullable List<Expression> initializer, String searchValue) {
176+
List<Expression> updatedInitializer = ListUtils.filter(ListUtils.map(initializer, e -> {
177+
if (valueMatches(e, searchValue)) {
178+
return newEmpty();
179+
}
180+
return e;
181+
}), e -> !(e instanceof J.Empty));
182+
return updatedInitializer == null ? emptyList() : updatedInitializer;
183+
}
184+
185+
private List<Expression> updateInitializerChangingMatched(@Nullable List<Expression> initializer, String searchValue, String newValue) {
186+
List<Expression> updatedInitializer = ListUtils.map(initializer, e -> {
187+
if (valueMatches(e, searchValue)) {
188+
// TODO - Change from this to specific setup based on newValue and appendArray
189+
return e;
190+
}
191+
return e;
192+
});
193+
return updatedInitializer == null ? emptyList() : updatedInitializer;
194+
}
195+
196+
// attributeValue == null
197+
private J.Annotation tryRemoveAnnotationAttribute(J.Annotation annotation, String searchAttribute, @Nullable String searchValue) {
198+
List<Expression> updatedArgs = ListUtils.map(annotation.getArguments(), it -> {
199+
if (attributeMatchesName(it, searchAttribute)) {
200+
if (searchValue == null) {
201+
return newEmpty();
202+
}
203+
if (it instanceof J.Assignment) {
204+
J.Assignment as = (J.Assignment) it;
205+
Expression asValue = as.getAssignment();
206+
if (asValue instanceof J.NewArray) {
207+
J.NewArray asArray = (J.NewArray) asValue;
208+
List<Expression> updatedInitializer = updateInitializerDroppingMatched(asArray.getInitializer(), searchValue);
209+
return as.withAssignment(asArray.withInitializer(updatedInitializer));
210+
}
211+
if (valueMatches(asValue, searchValue)) {
212+
return newEmpty();
213+
}
214+
} else if (it instanceof J.NewArray) {
215+
J.NewArray itArray = (J.NewArray) it;
216+
List<Expression> updatedInitializer = updateInitializerDroppingMatched(itArray.getInitializer(), searchValue);
217+
return itArray.withInitializer(updatedInitializer);
218+
} else if (valueMatches(it, searchValue)) {
219+
return newEmpty();
220+
}
221+
}
222+
return it;
223+
});
224+
return annotation.withArguments(ListUtils.filter(updatedArgs, it -> !(it instanceof J.Empty)));
225+
}
226+
227+
private J.Annotation tryAddAnnotationAttribute(J.Annotation annotation, String newAttribute, String newValue) {
228+
// TODO
229+
return annotation;
230+
}
231+
232+
private J.Annotation tryUpdateAnnotationAttribute(J.Annotation annotation, String searchAttribute, @Nullable String searchValue, String newValue) {
233+
List<Expression> updatedArgs = ListUtils.map(annotation.getArguments(), it -> {
234+
if (attributeMatchesName(it, searchAttribute)) {
235+
if (searchValue == null) {
236+
if (it instanceof J.Assignment) {
237+
J.Assignment as = (J.Assignment) it;
238+
// TODO - overwriting using as.withAssignment(...), but differs by newValue typing and appendArray
239+
}
240+
// TODO - overwriting using new, but differs by newValue typing and appendArray
241+
} else {
242+
if (it instanceof J.Assignment) {
243+
J.Assignment as = (J.Assignment) it;
244+
Expression asValue = as.getAssignment();
245+
if (asValue instanceof J.NewArray) {
246+
J.NewArray asArray = (J.NewArray) asValue;
247+
List<Expression> updatedInitializer = updateInitializerChangingMatched(asArray.getInitializer(), searchValue, newValue);
248+
return as.withAssignment(asArray.withInitializer(updatedInitializer));
249+
}
250+
if (valueMatches(asValue, searchValue)) {
251+
// TODO instantiate the correct typing
252+
}
253+
}
254+
// TODO: else
255+
}
256+
}
257+
return it;
258+
});
259+
return annotation.withArguments(updatedArgs);
260+
}
261+
262+
@Override
263+
public J.Annotation visitAnnotation(J.Annotation original, ExecutionContext ctx) {
264+
J.Annotation a = super.visitAnnotation(original, ctx);
265+
String searchAttribute = attributeNameOrDefault();
266+
String searchValue = oldAttributeValue;
267+
// if not the right type of annotation or cannot find the method for a non-shallow class
268+
if (
269+
!TypeUtils.isOfClassType(a.getType(), annotationType) ||
270+
!(a.getType() instanceof JavaType.ShallowClass || findMethod(a, searchAttribute).isPresent())
271+
) {
272+
return a;
273+
}
274+
boolean existingAttribute = alreadyContainsAttributeOfName(a, searchAttribute);
275+
// if only want to add, but it already has attribute, ignores new attributeValue
276+
if (TRUE.equals(addOnly) && existingAttribute) {
277+
return a;
278+
}
279+
280+
// if you want to remove
281+
if (attributeValue == null) {
282+
// if you can't update anything
283+
if (!existingAttribute || TRUE.equals(addOnly)) {
284+
return a;
285+
}
286+
a = tryRemoveAnnotationAttribute(a, searchAttribute, searchValue);
287+
} else {
288+
// if you can't update anything
289+
if (existingAttribute && TRUE.equals(addOnly)) {
290+
return a;
291+
}
292+
if (existingAttribute) {
293+
a = tryUpdateAnnotationAttribute(a, searchAttribute, searchValue, attributeValue);
294+
} else {
295+
a = tryAddAnnotationAttribute(a, searchAttribute, attributeValue);
296+
}
297+
}
298+
addPossibleClassImports(attributeValue);
299+
300+
// TODO: double check this (and also simplification in general)
301+
if (original != a) {
302+
doAfterVisit(new SimplifySingleElementAnnotation().getVisitor());
303+
}
304+
return maybeAutoFormat(original, a, ctx);
305+
}
306+
});
307+
308+
95309
return Preconditions.check(new UsesType<>(annotationType, false), new JavaIsoVisitor<ExecutionContext>() {
96310
@Override
97311
public J.Annotation visitAnnotation(J.Annotation original, ExecutionContext ctx) {
@@ -370,6 +584,26 @@ private String attributeName() {
370584
}
371585

372586
private List<Expression> updateInitializer(J.Annotation annotation, List<Expression> initializerList, List<String> attributeList) {
587+
if (TRUE.equals(appendArray)) {
588+
if (oldAttributeValue != null) {
589+
// if initializer contains old attribute value
590+
// append new values (de-duped) to end of attribute's existing value
591+
// else
592+
// do not append
593+
} else {
594+
// append new values (de-duped) to end of attribute's existing value
595+
}
596+
} else {
597+
if (oldAttributeValue != null) {
598+
// if initializer contains old attribute value
599+
// replace existing old attribute value in initializer with new values
600+
// else
601+
// do not replace
602+
} else {
603+
// replace initializer with new values
604+
}
605+
}
606+
373607
// If `oldAttributeValue` is defined, replace the old value with the new value(s). Ignore the `appendArray` option in this case.
374608
if (oldAttributeValue != null) {
375609
return ListUtils.flatMap(initializerList, it -> {
@@ -379,10 +613,16 @@ private List<Expression> updateInitializer(J.Annotation annotation, List<Express
379613
J.Literal newLiteral = new J.Literal(randomId(), SINGLE_SPACE, EMPTY, attribute, maybeQuoteStringArgument(annotation, attribute), null, JavaType.Primitive.String);
380614
newItemsList.add(newLiteral);
381615
}
382-
return newItemsList;
616+
if (!TRUE.equals(appendArray)) {
617+
return newItemsList;
618+
}
383619
} else if (it instanceof J.Empty) {
384620
return new ArrayList<>();
385621
}
622+
if (TRUE.equals(appendArray)) {
623+
newItemsList.add(0, it);
624+
return newItemsList;
625+
}
386626
return it;
387627
});
388628
}

0 commit comments

Comments
 (0)