Skip to content

Commit 8315711

Browse files
Try something with AddAttributeToAnnotationIfClassExists
1 parent e9ad1c6 commit 8315711

File tree

3 files changed

+443
-0
lines changed

3 files changed

+443
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright © 2023 XDEV Software (https://xdev.software)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package software.xdev.spring.data.eclipse.store;
17+
18+
import org.jetbrains.annotations.NotNull;
19+
import org.openrewrite.ExecutionContext;
20+
import org.openrewrite.Option;
21+
import org.openrewrite.Recipe;
22+
import org.openrewrite.TreeVisitor;
23+
24+
import lombok.AllArgsConstructor;
25+
import lombok.EqualsAndHashCode;
26+
import lombok.Getter;
27+
import lombok.NoArgsConstructor;
28+
import lombok.Setter;
29+
30+
31+
@Getter
32+
@Setter
33+
@AllArgsConstructor
34+
@NoArgsConstructor
35+
@EqualsAndHashCode(callSuper = true)
36+
public class AddAttributeToAnnotationIfClassExists extends Recipe
37+
{
38+
@Option(displayName = "::::TODO:::Existing annotation type",
39+
description =
40+
"::::TODO:::Annotation type that is already existing. Recipe is looking for this annotation to add the "
41+
+ "new "
42+
+ "annotation.",
43+
example = "software.xdev")
44+
String className;
45+
46+
@Option(displayName = "Annotation Type",
47+
description = "The fully qualified name of the annotation.",
48+
example = "org.junit.Test")
49+
String annotationType;
50+
51+
@Option(displayName = "Attribute name",
52+
description = "The name of attribute to change. If omitted defaults to 'value'.",
53+
required = false,
54+
example = "timeout")
55+
String attributeName;
56+
57+
@Option(displayName = "Attribute value",
58+
description = "The value to set the attribute to. Set to `null` to remove the attribute.",
59+
example = "500")
60+
String attributeValue;
61+
62+
@Override
63+
public @NotNull String getDisplayName()
64+
{
65+
return "AddValueToAnnotationIfDependencyExists";
66+
}
67+
68+
@Override
69+
public @NotNull String getDescription()
70+
{
71+
return "::::TODO:::Add the a new annotation to an existing annotation.";
72+
}
73+
74+
@Override
75+
public @NotNull TreeVisitor<?, ExecutionContext> getVisitor()
76+
{
77+
if(!this.doesClasspathContainClass(this.className))
78+
{
79+
return TreeVisitor.noop();
80+
}
81+
return new AddOrUpdateAnnotationAttribute(
82+
this.annotationType,
83+
this.attributeName,
84+
this.attributeValue,
85+
false
86+
).getVisitor();
87+
}
88+
89+
private boolean doesClasspathContainClass(final String classToCheck)
90+
{
91+
try
92+
{
93+
Class.forName(classToCheck, false, this.getClass().getClassLoader());
94+
return true;
95+
}
96+
catch(final ClassNotFoundException e)
97+
{
98+
return false;
99+
}
100+
}
101+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
/*
2+
* Copyright © 2023 XDEV Software (https://xdev.software)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package software.xdev.spring.data.eclipse.store;
17+
18+
import java.util.Arrays;
19+
import java.util.List;
20+
import java.util.concurrent.atomic.AtomicBoolean;
21+
22+
import org.openrewrite.ExecutionContext;
23+
import org.openrewrite.Option;
24+
import org.openrewrite.Preconditions;
25+
import org.openrewrite.Recipe;
26+
import org.openrewrite.TreeVisitor;
27+
import org.openrewrite.internal.ListUtils;
28+
import org.openrewrite.internal.lang.Nullable;
29+
import org.openrewrite.java.JavaIsoVisitor;
30+
import org.openrewrite.java.JavaTemplate;
31+
import org.openrewrite.java.search.UsesType;
32+
import org.openrewrite.java.tree.Expression;
33+
import org.openrewrite.java.tree.J;
34+
import org.openrewrite.java.tree.JavaType;
35+
import org.openrewrite.java.tree.TypeUtils;
36+
37+
import lombok.EqualsAndHashCode;
38+
import lombok.Value;
39+
40+
41+
@Value
42+
@EqualsAndHashCode(callSuper = true)
43+
public class AddOrUpdateAnnotationAttribute extends Recipe
44+
{
45+
@Override
46+
public String getDisplayName()
47+
{
48+
return "Add or update annotation attribute";
49+
}
50+
51+
@Override
52+
public String getDescription()
53+
{
54+
return "Some annotations accept arguments. This recipe sets an existing argument to the specified value, " +
55+
"or adds the argument if it is not already set.";
56+
}
57+
58+
@Option(displayName = "Annotation Type",
59+
description = "The fully qualified name of the annotation.",
60+
example = "org.junit.Test")
61+
String annotationType;
62+
63+
@Option(displayName = "Attribute name",
64+
description = "The name of attribute to change. If omitted defaults to 'value'.",
65+
required = false,
66+
example = "timeout")
67+
@Nullable
68+
String attributeName;
69+
70+
@Option(displayName = "Attribute value",
71+
description = "The value to set the attribute to. Set to `null` to remove the attribute.",
72+
example = "500")
73+
@Nullable
74+
String attributeValue;
75+
76+
@Option(displayName = "Add Only",
77+
description = "When set to `true` will not change existing annotation attribute values.")
78+
@Nullable
79+
Boolean addOnly;
80+
81+
@Override
82+
public TreeVisitor<?, ExecutionContext> getVisitor()
83+
{
84+
return Preconditions.check(new UsesType<>(this.annotationType, false), new JavaIsoVisitor<>()
85+
{
86+
@Override
87+
public J.Annotation visitAnnotation(J.Annotation a, final ExecutionContext ctx)
88+
{
89+
if(!TypeUtils.isOfClassType(a.getType(), AddOrUpdateAnnotationAttribute.this.annotationType))
90+
{
91+
return a;
92+
}
93+
94+
final String newAttributeValue =
95+
maybeQuoteStringArgument(AddOrUpdateAnnotationAttribute.this.attributeName,
96+
AddOrUpdateAnnotationAttribute.this.attributeValue, a);
97+
final List<Expression> currentArgs = a.getArguments();
98+
if(currentArgs == null || currentArgs.isEmpty())
99+
{
100+
if(newAttributeValue == null)
101+
{
102+
return a;
103+
}
104+
105+
if(AddOrUpdateAnnotationAttribute.this.attributeName == null || "value".equals(
106+
AddOrUpdateAnnotationAttribute.this.attributeName))
107+
{
108+
return JavaTemplate.builder("#{}")
109+
.contextSensitive()
110+
.build()
111+
.apply(this.getCursor(), a.getCoordinates().replaceArguments(), newAttributeValue);
112+
}
113+
else
114+
{
115+
final String[] classNames = AddOrUpdateAnnotationAttribute.this.attributeValue.replace("{", "")
116+
.replace("}", "")
117+
.split(",");
118+
Arrays.stream(classNames).forEach(className ->
119+
this.maybeAddImport(className));
120+
return JavaTemplate.builder("#{} = #{}")
121+
.contextSensitive()
122+
.build()
123+
.apply(
124+
this.getCursor(),
125+
a.getCoordinates().replaceArguments(),
126+
AddOrUpdateAnnotationAttribute.this.attributeName,
127+
newAttributeValue);
128+
}
129+
}
130+
else
131+
{
132+
// First assume the value exists amongst the arguments and attempt to update it
133+
final AtomicBoolean foundAttributeWithDesiredValue = new AtomicBoolean(false);
134+
final J.Annotation finalA = a;
135+
final List<Expression> newArgs = ListUtils.map(currentArgs, it -> {
136+
if(it instanceof J.Assignment)
137+
{
138+
final J.Assignment as = (J.Assignment)it;
139+
final J.Identifier var = (J.Identifier)as.getVariable();
140+
if(AddOrUpdateAnnotationAttribute.this.attributeName == null
141+
|| !AddOrUpdateAnnotationAttribute.this.attributeName.equals(var.getSimpleName()))
142+
{
143+
return it;
144+
}
145+
if(!(as.getAssignment() instanceof J.Literal))
146+
{
147+
foundAttributeWithDesiredValue.set(true);
148+
return it;
149+
}
150+
final J.Literal value = (J.Literal)as.getAssignment();
151+
if(newAttributeValue == null)
152+
{
153+
return null;
154+
}
155+
if(newAttributeValue.equals(value.getValueSource()) || Boolean.TRUE.equals(
156+
AddOrUpdateAnnotationAttribute.this.addOnly))
157+
{
158+
foundAttributeWithDesiredValue.set(true);
159+
return it;
160+
}
161+
return as.withAssignment(value.withValue(newAttributeValue)
162+
.withValueSource(newAttributeValue));
163+
}
164+
else if(it instanceof J.Literal)
165+
{
166+
// The only way anything except an assignment can appear is if there's an implicit
167+
// assignment to "value"
168+
if(AddOrUpdateAnnotationAttribute.this.attributeName == null || "value".equals(
169+
AddOrUpdateAnnotationAttribute.this.attributeName))
170+
{
171+
if(newAttributeValue == null)
172+
{
173+
return null;
174+
}
175+
final J.Literal value = (J.Literal)it;
176+
if(newAttributeValue.equals(value.getValueSource()) || Boolean.TRUE.equals(
177+
AddOrUpdateAnnotationAttribute.this.addOnly))
178+
{
179+
foundAttributeWithDesiredValue.set(true);
180+
return it;
181+
}
182+
return ((J.Literal)it).withValue(newAttributeValue).withValueSource(newAttributeValue);
183+
}
184+
else
185+
{
186+
// noinspection ConstantConditions
187+
return ((J.Annotation)JavaTemplate.builder("value = #{}")
188+
.contextSensitive()
189+
.build()
190+
.apply(this.getCursor(), finalA.getCoordinates().replaceArguments(), it)
191+
).getArguments().get(0);
192+
}
193+
}
194+
return it;
195+
});
196+
if(foundAttributeWithDesiredValue.get() || newArgs != currentArgs)
197+
{
198+
return a.withArguments(newArgs);
199+
}
200+
// There was no existing value to update, so add a new value into the argument list
201+
final String effectiveName = (AddOrUpdateAnnotationAttribute.this.attributeName == null) ?
202+
"value" :
203+
AddOrUpdateAnnotationAttribute.this.attributeName;
204+
// noinspection ConstantConditions
205+
final J.Assignment as = (J.Assignment)((J.Annotation)JavaTemplate.builder("#{} = #{}")
206+
.contextSensitive()
207+
.build()
208+
.apply(
209+
this.getCursor(),
210+
a.getCoordinates().replaceArguments(),
211+
effectiveName,
212+
newAttributeValue)
213+
).getArguments().get(0);
214+
final List<Expression> newArguments = ListUtils.concat(as, a.getArguments());
215+
a = a.withArguments(newArguments);
216+
a = this.autoFormat(a, ctx);
217+
}
218+
219+
return a;
220+
}
221+
});
222+
}
223+
224+
@Nullable
225+
private static String maybeQuoteStringArgument(
226+
@Nullable final String attributeName,
227+
@Nullable final String attributeValue,
228+
final J.Annotation annotation)
229+
{
230+
if((attributeValue != null) && attributeIsString(attributeName, annotation))
231+
{
232+
return "\"" + attributeValue + "\"";
233+
}
234+
else
235+
{
236+
return attributeValue;
237+
}
238+
}
239+
240+
private static boolean attributeIsString(@Nullable final String attributeName, final J.Annotation annotation)
241+
{
242+
final String actualAttributeName = (attributeName == null) ? "value" : attributeName;
243+
final JavaType.Class annotationType = (JavaType.Class)annotation.getType();
244+
if(annotationType != null)
245+
{
246+
for(final JavaType.Method m : annotationType.getMethods())
247+
{
248+
if(m.getName().equals(actualAttributeName))
249+
{
250+
return TypeUtils.isOfClassType(m.getReturnType(), "java.lang.String");
251+
}
252+
}
253+
}
254+
return false;
255+
}
256+
}
257+

0 commit comments

Comments
 (0)