Skip to content

Commit a2e0bc5

Browse files
authored
Add NewV1ClassToBuilder recipe (#5115)
* Add NewV1ClassToBuilder recipe This recipe transforms the usage of a V1 request class using the `new` keyword to using the V2 class and its builder. This recipe migrates many common uses for making API calls using the V1 SDK: Creating the request outside of the API call and then passing it in as a name variable: ``` SendMessageRequest request = new SendMessage().withQueueUrl(..); sqs.sendMessage(request); ``` or creating it inline ``` sqs.sendMessage(new SendMessageRequest().withQueueUrl(...)); ``` * Review comments Update migration tool tests. This also means adding the the new recipe to the main software.amazon.awssdk.UpgradeJavaSdk2 recipe. * Only run on JDK8
1 parent 23c2ad6 commit a2e0bc5

File tree

15 files changed

+875
-6
lines changed

15 files changed

+875
-6
lines changed

migration-tool/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@
8989
<version>${junit5.version}</version>
9090
<scope>test</scope>
9191
</dependency>
92+
<dependency>
93+
<groupId>software.amazon.awssdk</groupId>
94+
<artifactId>utils</artifactId>
95+
<version>${project.version}</version>
96+
</dependency>
9297
<!-- Used in UpgradeSdkDependenciesTest -->
9398
<dependency>
9499
<groupId>software.amazon.awssdk</groupId>
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
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+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.migration.internal.recipe;
17+
18+
import java.util.Collections;
19+
import org.openrewrite.ExecutionContext;
20+
import org.openrewrite.Recipe;
21+
import org.openrewrite.Tree;
22+
import org.openrewrite.TreeVisitor;
23+
import org.openrewrite.java.JavaVisitor;
24+
import org.openrewrite.java.tree.Expression;
25+
import org.openrewrite.java.tree.J;
26+
import org.openrewrite.java.tree.JContainer;
27+
import org.openrewrite.java.tree.JRightPadded;
28+
import org.openrewrite.java.tree.JavaType;
29+
import org.openrewrite.java.tree.Space;
30+
import org.openrewrite.marker.Markers;
31+
import software.amazon.awssdk.annotations.SdkInternalApi;
32+
import software.amazon.awssdk.migration.internal.utils.NamingUtils;
33+
import software.amazon.awssdk.migration.internal.utils.SdkTypeUtils;
34+
import software.amazon.awssdk.migration.recipe.NewV1ModelClassToV2;
35+
36+
/**
37+
* Internal recipe that converts V1 model creation to the builder pattern.
38+
*
39+
* @see NewV1ModelClassToV2
40+
*/
41+
@SdkInternalApi
42+
public class NewV1ClassToBuilder extends Recipe {
43+
@Override
44+
public String getDisplayName() {
45+
return "Transform 'new' expressions to builders";
46+
}
47+
48+
@Override
49+
public String getDescription() {
50+
return "Transforms 'new' expression for V1 model objects to the equivalent builder()..build() expression in V2.";
51+
}
52+
53+
@Override
54+
public TreeVisitor<?, ExecutionContext> getVisitor() {
55+
return new NewV1ClassToBuilderVisitor();
56+
}
57+
58+
// Change a new Foo() to Foo.builder().build()
59+
// Any withers called on new Foo() are moved to before .build()
60+
// Make any appropriate v1 -> v2 type changes
61+
private static class NewV1ClassToBuilderVisitor extends JavaVisitor<ExecutionContext> {
62+
// Rearrange a [...].build().with*() to [...].with*().build()
63+
@Override
64+
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) {
65+
method = super.visitMethodInvocation(method, executionContext).cast();
66+
67+
// [...].with*()
68+
if (!NamingUtils.isWither(method.getSimpleName())) {
69+
return method;
70+
}
71+
72+
Expression select = method.getSelect();
73+
74+
if (!(select instanceof J.MethodInvocation)) {
75+
return method;
76+
}
77+
78+
// [...]
79+
J.MethodInvocation selectInvoke = (J.MethodInvocation) select;
80+
81+
// [...] == [...].build()
82+
if (!selectInvoke.getSimpleName().equals("build")) {
83+
return method;
84+
}
85+
86+
// Do the reordering
87+
Expression selectInvokeSelect = selectInvoke.getSelect();
88+
89+
J.MethodInvocation newWith = method.withSelect(selectInvokeSelect);
90+
91+
return selectInvoke.withSelect(newWith);
92+
}
93+
94+
// new Foo() -> Foo.builder().build()
95+
@Override
96+
public J visitNewClass(J.NewClass newClass, ExecutionContext executionContext) {
97+
newClass = super.visitNewClass(newClass, executionContext).cast();
98+
99+
JavaType classType = newClass.getType();
100+
if (!SdkTypeUtils.isV1ModelClass(classType)) {
101+
return newClass;
102+
}
103+
104+
JavaType.FullyQualified v2Type = SdkTypeUtils.asV2Type((JavaType.FullyQualified) classType);
105+
JavaType.FullyQualified v2TypeBuilder = SdkTypeUtils.v2ModelBuilder(v2Type);
106+
107+
J.Identifier modelId = new J.Identifier(
108+
Tree.randomId(),
109+
Space.EMPTY,
110+
Markers.EMPTY,
111+
Collections.emptyList(),
112+
v2Type.getClassName(),
113+
v2Type,
114+
null
115+
);
116+
117+
J.Identifier builderMethod = new J.Identifier(
118+
Tree.randomId(),
119+
Space.EMPTY,
120+
Markers.EMPTY,
121+
Collections.emptyList(),
122+
"builder",
123+
null,
124+
null
125+
);
126+
127+
JavaType.Method methodType = new JavaType.Method(
128+
null,
129+
0L,
130+
v2Type,
131+
"builder",
132+
v2TypeBuilder,
133+
Collections.emptyList(),
134+
Collections.emptyList(),
135+
Collections.emptyList(),
136+
Collections.emptyList()
137+
);
138+
139+
J.MethodInvocation builderInvoke = new J.MethodInvocation(
140+
Tree.randomId(),
141+
Space.EMPTY,
142+
Markers.EMPTY,
143+
JRightPadded.build(modelId),
144+
null,
145+
builderMethod,
146+
JContainer.empty(),
147+
methodType
148+
149+
);
150+
151+
J.MethodInvocation buildInvoke = new J.MethodInvocation(
152+
Tree.randomId(),
153+
Space.EMPTY,
154+
Markers.EMPTY,
155+
JRightPadded.build(builderInvoke),
156+
null,
157+
new J.Identifier(
158+
Tree.randomId(),
159+
Space.EMPTY,
160+
Markers.EMPTY,
161+
Collections.emptyList(),
162+
"build",
163+
v2Type,
164+
null
165+
),
166+
JContainer.empty(),
167+
new JavaType.Method(
168+
null,
169+
0L,
170+
v2TypeBuilder,
171+
"build",
172+
v2Type,
173+
Collections.emptyList(),
174+
Collections.emptyList(),
175+
Collections.emptyList(),
176+
Collections.emptyList()
177+
)
178+
);
179+
180+
return buildInvoke;
181+
}
182+
}
183+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
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+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.migration.internal.recipe;
17+
18+
import org.openrewrite.ExecutionContext;
19+
import org.openrewrite.Recipe;
20+
import org.openrewrite.TreeVisitor;
21+
import org.openrewrite.java.JavaIsoVisitor;
22+
import org.openrewrite.java.tree.Expression;
23+
import org.openrewrite.java.tree.J;
24+
import org.openrewrite.java.tree.JavaType;
25+
import org.openrewrite.java.tree.TypeUtils;
26+
import software.amazon.awssdk.annotations.SdkInternalApi;
27+
import software.amazon.awssdk.migration.internal.utils.NamingUtils;
28+
import software.amazon.awssdk.migration.internal.utils.SdkTypeUtils;
29+
import software.amazon.awssdk.migration.recipe.NewV1ModelClassToV2;
30+
31+
/**
32+
* Internal recipe that renames fluent V1 setters (withers), to V2 equivalents.
33+
*
34+
* @see NewV1ModelClassToV2
35+
*/
36+
@SdkInternalApi
37+
public class V1SetterToV2 extends Recipe {
38+
@Override
39+
public String getDisplayName() {
40+
return "V1 Setter to V2";
41+
}
42+
43+
@Override
44+
public String getDescription() {
45+
return "Transforms a setter on a V1 model object to the equivalent in V2.";
46+
}
47+
48+
@Override
49+
public TreeVisitor<?, ExecutionContext> getVisitor() {
50+
return new V1SetterToV2Visitor();
51+
}
52+
53+
private static class V1SetterToV2Visitor extends JavaIsoVisitor<ExecutionContext> {
54+
55+
@Override
56+
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) {
57+
method = super.visitMethodInvocation(method, executionContext);
58+
59+
JavaType selectType = null;
60+
61+
Expression select = method.getSelect();
62+
if (select != null) {
63+
selectType = select.getType();
64+
}
65+
66+
if (selectType == null) {
67+
return method;
68+
}
69+
70+
if (SdkTypeUtils.isV2ModelBuilder(selectType) && !SdkTypeUtils.isV2ModelBuilder(method.getType())) {
71+
String methodName = method.getSimpleName();
72+
73+
if (NamingUtils.isWither(methodName)) {
74+
methodName = NamingUtils.removeWith(methodName);
75+
} else if (NamingUtils.isSetter(methodName)) {
76+
methodName = NamingUtils.removeSet(methodName);
77+
}
78+
79+
JavaType.Method mt = method.getMethodType();
80+
81+
if (mt != null) {
82+
mt = mt.withName(methodName)
83+
.withReturnType(selectType);
84+
85+
JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(selectType);
86+
87+
if (fullyQualified != null) {
88+
mt = mt.withDeclaringType(fullyQualified);
89+
}
90+
91+
method = method.withName(method.getName()
92+
.withSimpleName(methodName)
93+
.withType(mt))
94+
.withMethodType(mt);
95+
}
96+
}
97+
98+
return method;
99+
}
100+
}
101+
}

migration-tool/src/main/java/software/amazon/awssdk/migration/recipe/utils/NamingConversionUtils.java renamed to migration-tool/src/main/java/software/amazon/awssdk/migration/internal/utils/NamingConversionUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* permissions and limitations under the License.
1414
*/
1515

16-
package software.amazon.awssdk.migration.recipe.utils;
16+
package software.amazon.awssdk.migration.internal.utils;
1717

1818
import java.util.stream.Stream;
1919
import software.amazon.awssdk.annotations.SdkInternalApi;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
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+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.migration.internal.utils;
17+
18+
import software.amazon.awssdk.annotations.SdkInternalApi;
19+
import software.amazon.awssdk.utils.StringUtils;
20+
import software.amazon.awssdk.utils.internal.CodegenNamingUtils;
21+
22+
@SdkInternalApi
23+
public final class NamingUtils {
24+
private NamingUtils() {
25+
}
26+
27+
public static String removeWith(String name) {
28+
return removePrefix(name, "with");
29+
}
30+
31+
public static String removeSet(String name) {
32+
return removePrefix(name, "set");
33+
}
34+
35+
private static String removePrefix(String name, String prefix) {
36+
if (StringUtils.isBlank(name)) {
37+
return name;
38+
}
39+
40+
if (!name.startsWith(prefix)) {
41+
return name;
42+
}
43+
44+
name = StringUtils.replaceOnce(name, prefix, "");
45+
46+
return StringUtils.uncapitalize(CodegenNamingUtils.pascalCase(name));
47+
}
48+
49+
public static boolean isWither(String name) {
50+
return !StringUtils.isBlank(name) && name.startsWith("with");
51+
}
52+
53+
public static boolean isSetter(String name) {
54+
return !StringUtils.isBlank(name) && name.startsWith("set");
55+
}
56+
}

0 commit comments

Comments
 (0)