Skip to content

Commit 6bf8502

Browse files
ckcdtimtebeekgithub-actions[bot]
committed
Add ChangeMethodParameter for modify parameters in Spring Batch method declaration (#631)
* Add ChangeMethodParameter for modify parameters in method declaration Signed-off-by: Kun Chang <[email protected]> * Minor polish * Add correct year * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Add UpgradeSkipPolicyParameterType for Spring Batch Signed-off-by: Kun Chang <[email protected]> * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Fix tests * Also show ability to change interface methods --------- Signed-off-by: Kun Chang <[email protected]> Co-authored-by: Tim te Beek <[email protected]> Co-authored-by: Tim te Beek <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 4d67107 commit 6bf8502

File tree

4 files changed

+774
-0
lines changed

4 files changed

+774
-0
lines changed
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
* <p>
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+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
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 org.openrewrite.java.spring;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.RequiredArgsConstructor;
20+
import lombok.Value;
21+
import org.openrewrite.*;
22+
import org.openrewrite.java.JavaIsoVisitor;
23+
import org.openrewrite.java.MethodMatcher;
24+
import org.openrewrite.java.search.DeclaresMethod;
25+
import org.openrewrite.java.service.ImportService;
26+
import org.openrewrite.java.tree.*;
27+
import org.openrewrite.marker.Markers;
28+
29+
import java.util.ArrayList;
30+
import java.util.List;
31+
32+
import static java.util.Collections.emptyList;
33+
import static java.util.Collections.singletonList;
34+
import static org.openrewrite.Tree.randomId;
35+
36+
/**
37+
* A recipe to change parameter type for a method declaration.
38+
* <p>
39+
* NOTE: This recipe is usually used for initial modification of parameter changes.
40+
* After modifying method parameters using this recipe, you may also need to modify
41+
* the method definition as needed to avoid compilation errors.
42+
*/
43+
@Value
44+
@EqualsAndHashCode(callSuper = false)
45+
public class ChangeMethodParameter extends Recipe {
46+
47+
/**
48+
* A method pattern that is used to find matching method declarations.
49+
* See {@link MethodMatcher} for details on the expression's syntax.
50+
*/
51+
@Option(displayName = "Method pattern",
52+
description = "A method pattern that is used to find the method declarations to modify.",
53+
example = "com.yourorg.A foo(int, int)")
54+
String methodPattern;
55+
56+
@Option(displayName = "Parameter type",
57+
description = "The new type of the parameter that gets updated.",
58+
example = "java.lang.String")
59+
String parameterType;
60+
61+
@Option(displayName = "Parameter index",
62+
description = "A zero-based index that indicates the position at which the parameter will be added.",
63+
example = "0")
64+
Integer parameterIndex;
65+
66+
@Override
67+
public String getInstanceNameSuffix() {
68+
return String.format("`%s` in methods `%s`", parameterType, methodPattern);
69+
}
70+
71+
@Override
72+
public String getDisplayName() {
73+
return "Change parameter type for a method declaration";
74+
}
75+
76+
@Override
77+
public String getDescription() {
78+
return "Change parameter type for a method declaration, identified by a method pattern.";
79+
}
80+
81+
@Override
82+
public TreeVisitor<?, ExecutionContext> getVisitor() {
83+
MethodMatcher methodMatcher = new MethodMatcher(methodPattern, true);
84+
return Preconditions.check(new DeclaresMethod<>(methodMatcher), new ChangeMethodArgumentVisitor(methodMatcher));
85+
}
86+
87+
@RequiredArgsConstructor
88+
private class ChangeMethodArgumentVisitor extends JavaIsoVisitor<ExecutionContext> {
89+
private final MethodMatcher methodMatcher;
90+
91+
@Override
92+
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDeclaration, ExecutionContext ctx) {
93+
J.MethodDeclaration md = super.visitMethodDeclaration(methodDeclaration, ctx);
94+
95+
if (methodMatcher.matches(md.getMethodType())) {
96+
if (md.getParameters().isEmpty() ||
97+
md.getParameters().get(0) instanceof J.Empty ||
98+
md.getParameters().size() <= parameterIndex) {
99+
return md;
100+
}
101+
if (md.getParameters().get(parameterIndex) instanceof J.VariableDeclarations) {
102+
J.VariableDeclarations parameter = (J.VariableDeclarations) md.getParameters().get(parameterIndex);
103+
104+
TypeTree typeTree = createTypeTree(parameterType);
105+
if (TypeUtils.isOfType(parameter.getType(), typeTree.getType())) {
106+
return md;
107+
}
108+
109+
String parameterName = parameter.getVariables().get(0).getSimpleName();
110+
parameter = parameter.withTypeExpression(typeTree).withVariables(singletonList(
111+
new J.VariableDeclarations.NamedVariable(
112+
randomId(),
113+
Space.EMPTY,
114+
Markers.EMPTY,
115+
new J.Identifier(
116+
randomId(),
117+
Space.EMPTY,
118+
Markers.EMPTY,
119+
emptyList(),
120+
parameterName,
121+
typeTree.getType(),
122+
new JavaType.Variable(
123+
null,
124+
0,
125+
parameterName,
126+
md.getMethodType(),
127+
typeTree.getType(),
128+
null
129+
)
130+
),
131+
emptyList(),
132+
null,
133+
null
134+
)
135+
));
136+
137+
md = autoFormat(changeParameter(md, parameter), parameter, ctx, getCursor().getParentTreeCursor());
138+
}
139+
140+
}
141+
return md;
142+
}
143+
144+
private J.MethodDeclaration changeParameter(J.MethodDeclaration method, J.VariableDeclarations parameter) {
145+
List<Statement> originalParameters = method.getParameters();
146+
List<Statement> newParameters = new ArrayList<>();
147+
for (int i = 0; i < originalParameters.size(); i++) {
148+
if (i == parameterIndex) {
149+
newParameters.add(parameter);
150+
} else {
151+
newParameters.add(originalParameters.get(i));
152+
}
153+
}
154+
155+
method = method.withParameters(newParameters);
156+
157+
if (parameter.getTypeExpression() != null && !(parameter.getTypeExpression() instanceof J.Identifier || parameter.getTypeExpression() instanceof J.Primitive)) {
158+
doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(parameter.getTypeExpression()));
159+
}
160+
return method;
161+
}
162+
163+
private TypeTree createTypeTree(String typeName) {
164+
int arrayIndex = typeName.lastIndexOf('[');
165+
if (arrayIndex != -1) {
166+
TypeTree elementType = createTypeTree(typeName.substring(0, arrayIndex));
167+
return new J.ArrayType(
168+
randomId(),
169+
Space.EMPTY,
170+
Markers.EMPTY,
171+
elementType,
172+
null,
173+
JLeftPadded.build(Space.EMPTY),
174+
new JavaType.Array(null, elementType.getType(), null)
175+
);
176+
}
177+
int genericsIndex = typeName.indexOf('<');
178+
if (genericsIndex != -1) {
179+
TypeTree rawType = createTypeTree(typeName.substring(0, genericsIndex));
180+
List<JRightPadded<Expression>> typeParameters = new ArrayList<>();
181+
for (String typeParam : typeName.substring(genericsIndex + 1, typeName.lastIndexOf('>')).split(",")) {
182+
typeParameters.add(JRightPadded.build((Expression) createTypeTree(typeParam.trim())));
183+
}
184+
return new J.ParameterizedType(
185+
randomId(),
186+
Space.EMPTY,
187+
Markers.EMPTY,
188+
rawType,
189+
JContainer.build(Space.EMPTY, typeParameters, Markers.EMPTY),
190+
new JavaType.Parameterized(null, (JavaType.FullyQualified) rawType.getType(), null)
191+
);
192+
}
193+
JavaType.Primitive type = JavaType.Primitive.fromKeyword(typeName);
194+
if (type != null) {
195+
return new J.Primitive(
196+
randomId(),
197+
Space.EMPTY,
198+
Markers.EMPTY,
199+
type
200+
);
201+
}
202+
if (typeName.equals("?")) {
203+
return new J.Wildcard(
204+
randomId(),
205+
Space.EMPTY,
206+
Markers.EMPTY,
207+
null,
208+
null
209+
);
210+
}
211+
if (typeName.startsWith("?") && typeName.contains("extends")) {
212+
return new J.Wildcard(
213+
randomId(),
214+
Space.EMPTY,
215+
Markers.EMPTY,
216+
new JLeftPadded<>(Space.SINGLE_SPACE, J.Wildcard.Bound.Extends, Markers.EMPTY),
217+
createTypeTree(typeName.substring(typeName.indexOf("extends") + "extends".length() + 1).trim()).withPrefix(Space.SINGLE_SPACE)
218+
);
219+
}
220+
if (typeName.indexOf('.') == -1) {
221+
String javaLangType = TypeUtils.findQualifiedJavaLangTypeName(typeName);
222+
if (javaLangType != null) {
223+
return new J.Identifier(
224+
randomId(),
225+
Space.EMPTY,
226+
Markers.EMPTY,
227+
emptyList(),
228+
typeName,
229+
JavaType.buildType(javaLangType),
230+
null
231+
);
232+
}
233+
}
234+
TypeTree typeTree = TypeTree.build(typeName);
235+
// somehow the type attribution is incomplete, but `ChangeType` relies on this
236+
if (typeTree instanceof J.FieldAccess) {
237+
typeTree = ((J.FieldAccess) typeTree).withName(((J.FieldAccess) typeTree).getName().withType(typeTree.getType()));
238+
} else if (typeTree.getType() == null) {
239+
typeTree = ((J.Identifier) typeTree).withType(JavaType.ShallowClass.build(typeName));
240+
}
241+
return typeTree;
242+
}
243+
}
244+
}

src/main/resources/META-INF/rewrite/spring-batch-5.0.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ recipeList:
3333
- org.openrewrite.java.spring.batch.MigrateStepBuilderFactory
3434
- org.openrewrite.java.spring.batch.MigrateItemWriterWrite
3535
- org.openrewrite.java.spring.batch.RemoveDefaultBatchConfigurer
36+
- org.openrewrite.java.spring.batch.UpgradeSkipPolicyParameterType
3637
- org.openrewrite.java.ChangeType:
3738
oldFullyQualifiedTypeName: org.springframework.batch.core.metrics.BatchMetrics
3839
newFullyQualifiedTypeName: org.springframework.batch.core.observability.BatchMetrics
@@ -63,3 +64,13 @@ recipeList:
6364
- org.openrewrite.java.spring.batch.ReplaceSupportClassWithItsInterface:
6465
fullyQualifiedClassName: org.springframework.batch.repeat.listener.RepeatListenerSupport
6566
fullyQualifiedInterfaceName: org.springframework.batch.repeat.RepeatListener
67+
---
68+
type: specs.openrewrite.org/v1beta/recipe
69+
name: org.openrewrite.java.spring.batch.UpgradeSkipPolicyParameterType
70+
displayName: Change the type of `skipCount` parameter in `SkipPolicy` from `int` to `long`
71+
description: The `skipCount` parameter in `org.springframework.batch.core.step.skip.SkipPolicy#shouldSkip` has been changed from `int` to `long`, this recipe updates the parameter type in the implementing classes.
72+
recipeList:
73+
- org.openrewrite.java.spring.ChangeMethodParameter:
74+
methodPattern: org.springframework.batch.core.step.skip.SkipPolicy shouldSkip(java.lang.Throwable, int)
75+
parameterIndex: 1
76+
parameterType: long

0 commit comments

Comments
 (0)