Skip to content

Add NewV1ClassToBuilder recipe #5115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions migration-tool/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@
<version>${junit5.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>utils</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Used in UpgradeSdkDependenciesTest -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.migration.internal.recipe;

import java.util.Collections;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.Tree;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JContainer;
import org.openrewrite.java.tree.JRightPadded;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.Space;
import org.openrewrite.marker.Markers;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.migration.internal.utils.NamingUtils;
import software.amazon.awssdk.migration.internal.utils.SdkTypeUtils;
import software.amazon.awssdk.migration.recipe.NewV1ModelClassToV2;

/**
* Internal recipe that converts V1 model creation to the builder pattern.
*
* @see NewV1ModelClassToV2
*/
@SdkInternalApi
public class NewV1ClassToBuilder extends Recipe {
@Override
public String getDisplayName() {
return "Transform 'new' expressions to builders";
}

@Override
public String getDescription() {
return "Transforms 'new' expression for V1 model objects to the equivalent builder()..build() expression in V2.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new NewV1ClassToBuilderVisitor();
}

// Change a new Foo() to Foo.builder().build()
// Any withers called on new Foo() are moved to before .build()
// Make any appropriate v1 -> v2 type changes
private static class NewV1ClassToBuilderVisitor extends JavaVisitor<ExecutionContext> {
// Rearrange a [...].build().with*() to [...].with*().build()
@Override
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) {
method = super.visitMethodInvocation(method, executionContext).cast();

// [...].with*()
if (!NamingUtils.isWither(method.getSimpleName())) {
return method;
}

Expression select = method.getSelect();

if (!(select instanceof J.MethodInvocation)) {
return method;
}

// [...]
J.MethodInvocation selectInvoke = (J.MethodInvocation) select;

// [...] == [...].build()
if (!selectInvoke.getSimpleName().equals("build")) {
return method;
}

// Do the reordering
Expression selectInvokeSelect = selectInvoke.getSelect();

J.MethodInvocation newWith = method.withSelect(selectInvokeSelect);

return selectInvoke.withSelect(newWith);
}

// new Foo() -> Foo.builder().build()
@Override
public J visitNewClass(J.NewClass newClass, ExecutionContext executionContext) {
newClass = super.visitNewClass(newClass, executionContext).cast();

JavaType classType = newClass.getType();
if (!SdkTypeUtils.isV1ModelClass(classType)) {
return newClass;
}

JavaType.FullyQualified v2Type = SdkTypeUtils.asV2Type((JavaType.FullyQualified) classType);
JavaType.FullyQualified v2TypeBuilder = SdkTypeUtils.v2ModelBuilder(v2Type);

J.Identifier modelId = new J.Identifier(
Tree.randomId(),
Space.EMPTY,
Markers.EMPTY,
Collections.emptyList(),
v2Type.getClassName(),
v2Type,
null
);

J.Identifier builderMethod = new J.Identifier(
Tree.randomId(),
Space.EMPTY,
Markers.EMPTY,
Collections.emptyList(),
"builder",
null,
null
);

JavaType.Method methodType = new JavaType.Method(
null,
0L,
v2Type,
"builder",
v2TypeBuilder,
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList()
);

J.MethodInvocation builderInvoke = new J.MethodInvocation(
Tree.randomId(),
Space.EMPTY,
Markers.EMPTY,
JRightPadded.build(modelId),
null,
builderMethod,
JContainer.empty(),
methodType

);

J.MethodInvocation buildInvoke = new J.MethodInvocation(
Tree.randomId(),
Space.EMPTY,
Markers.EMPTY,
JRightPadded.build(builderInvoke),
null,
new J.Identifier(
Tree.randomId(),
Space.EMPTY,
Markers.EMPTY,
Collections.emptyList(),
"build",
v2Type,
null
),
JContainer.empty(),
new JavaType.Method(
null,
0L,
v2TypeBuilder,
"build",
v2Type,
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList()
)
);

return buildInvoke;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.migration.internal.recipe;

import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.TypeUtils;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.migration.internal.utils.NamingUtils;
import software.amazon.awssdk.migration.internal.utils.SdkTypeUtils;
import software.amazon.awssdk.migration.recipe.NewV1ModelClassToV2;

/**
* Internal recipe that renames fluent V1 setters (withers), to V2 equivalents.
*
* @see NewV1ModelClassToV2
*/
@SdkInternalApi
public class V1SetterToV2 extends Recipe {
@Override
public String getDisplayName() {
return "V1 Setter to V2";
}

@Override
public String getDescription() {
return "Transforms a setter on a V1 model object to the equivalent in V2.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new V1SetterToV2Visitor();
}

private static class V1SetterToV2Visitor extends JavaIsoVisitor<ExecutionContext> {

@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) {
method = super.visitMethodInvocation(method, executionContext);

JavaType selectType = null;

Expression select = method.getSelect();
if (select != null) {
selectType = select.getType();
}

if (selectType == null) {
return method;
}

if (SdkTypeUtils.isV2ModelBuilder(selectType) && !SdkTypeUtils.isV2ModelBuilder(method.getType())) {
String methodName = method.getSimpleName();

if (NamingUtils.isWither(methodName)) {
methodName = NamingUtils.removeWith(methodName);
} else if (NamingUtils.isSetter(methodName)) {
methodName = NamingUtils.removeSet(methodName);
}

JavaType.Method mt = method.getMethodType();

if (mt != null) {
mt = mt.withName(methodName)
.withReturnType(selectType);

JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(selectType);

if (fullyQualified != null) {
mt = mt.withDeclaringType(fullyQualified);
}

method = method.withName(method.getName()
.withSimpleName(methodName)
.withType(mt))
.withMethodType(mt);
}
}

return method;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* permissions and limitations under the License.
*/

package software.amazon.awssdk.migration.recipe.utils;
package software.amazon.awssdk.migration.internal.utils;

import java.util.stream.Stream;
import software.amazon.awssdk.annotations.SdkInternalApi;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.migration.internal.utils;

import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.awssdk.utils.internal.CodegenNamingUtils;

@SdkInternalApi
public final class NamingUtils {
private NamingUtils() {
}

public static String removeWith(String name) {
return removePrefix(name, "with");
}

public static String removeSet(String name) {
return removePrefix(name, "set");
}

private static String removePrefix(String name, String prefix) {
if (StringUtils.isBlank(name)) {
return name;
}

if (!name.startsWith(prefix)) {
return name;
}

name = StringUtils.replaceOnce(name, prefix, "");

return StringUtils.uncapitalize(CodegenNamingUtils.pascalCase(name));
}

public static boolean isWither(String name) {
return !StringUtils.isBlank(name) && name.startsWith("with");
}

public static boolean isSetter(String name) {
return !StringUtils.isBlank(name) && name.startsWith("set");
}
}
Loading