Need Help - Custom Recipe to add a new method declaration with target return type and parameter types #6511
-
|
Hello, I'm trying to add a new method declaration to the specific class. Here is my recipe code. package com.example.openrewrite.recipe.sample;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.*;
import org.openrewrite.NlsRewrite.Description;
import org.openrewrite.NlsRewrite.DisplayName;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.TreeVisitingPrinter;
import org.openrewrite.java.tree.*;
import org.openrewrite.marker.Markers;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import static java.util.Collections.emptyList;
import static org.openrewrite.Tree.randomId;
@Value
@EqualsAndHashCode(callSuper = false)
public class AddMethod extends Recipe {
@Option(displayName = "Return Type", description = "The return type of the method to be added", example = "String")
String returnType;
@Option(displayName = "Method Name", description = "The name of the method to be added", example = "sampleMethod")
String methodName;
@Option(displayName = "Parameter Type", description = "The type of the parameter of the method to be added", example = "int")
String parameterType;
@Option(displayName = "Method Pattern", description = "The method pattern to check for existing methods", example = "String sampleMethod(int)", required = false)
String methodPattern;
@Option(displayName = "Fully Qualified Class Name", description = "The fully qualified name of the class where the method will be added", example = "com.example.SampleClass", required = false)
String fullyQualifiedClassName;
@JsonCreator
public AddMethod(@JsonProperty("returnType") String returnType, @JsonProperty("methodName") String methodName,
@JsonProperty("parameterType") String parameterType, @JsonProperty("methodPattern") String methodPattern,
@JsonProperty("fullyQualifiedClassName") String fullyQualifiedClassName) {
this.returnType = returnType;
this.methodName = methodName;
this.parameterType = parameterType;
this.methodPattern = methodPattern;
this.fullyQualifiedClassName = fullyQualifiedClassName;
}
public @Description String getDescription() {
return "Add a sample method.";
}
@Override
public @DisplayName String getDisplayName() {
return "Add Method";
}
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new AddSampleMethodVisitor();
}
public class AddSampleMethodVisitor extends JavaIsoVisitor<ExecutionContext> {
// private final String METHOD_TEMPLATE = "public #{} #{}(#{} param) { return null; }";
private final String METHOD_TEMPLATE = "public #{any()} #{}(#{any()} param) { return null; }";
private final MethodMatcher METHOD_MATCHER = new MethodMatcher(methodPattern , true);// Fixed
@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);
if (fullyQualifiedClassName != null
&& !fullyQualifiedClassName.equals(classDecl.getType().getFullyQualifiedName())) {
return cd;
}
System.out.println("Visiting Target Class: " + TreeVisitingPrinter.printTree(cd));
boolean hasTargetMethod = classDecl.getBody().getStatements().stream()
.filter(statement -> statement instanceof J.MethodDeclaration)
.map(J.MethodDeclaration.class::cast)
.anyMatch(methodDeclaration -> METHOD_MATCHER.matches(methodDeclaration, classDecl));
if (hasTargetMethod) {
System.out.println("Method already exists in class: " + classDecl.getSimpleName());
return cd;
}
// JavaTemplate template = JavaTemplate.builder(METHOD_TEMPLATE)
// .doAfterVariableSubstitution(System.out::println)
// .doBeforeParseTemplate(System.out::println)
// .build();
// cd = template.apply(updateCursor(cd),
// cd.getBody().getCoordinates().lastStatement(),
// returnType,
// methodName,
// parameterType);
JavaTemplate template = JavaTemplate.builder(METHOD_TEMPLATE)
.imports(returnType, parameterType)
.contextSensitive()
.doAfterVariableSubstitution(System.out::println)
.doBeforeParseTemplate(System.out::println)
.build();
cd = template.apply(updateCursor(cd),
cd.getBody().getCoordinates().lastStatement(),
createIdentifier(returnType),
methodName,
createIdentifier(parameterType));
System.out.println("Modified Target Class: " + TreeVisitingPrinter.printTree(cd));
return cd;
}
private TypeTree createIdentifier(String typeName) {
return new J.Identifier(
randomId(),
Space.EMPTY,
Markers.EMPTY,
emptyList(),
typeName,
JavaType.buildType(typeName),
null);
}
}
}And the test code is as below: @Test
void addSampleMethod() {
rewriteRun(
spec -> spec.recipe(new AddMethod("com.example.SampleDto", "sampleMethod", "com.example.SampleDto", "*..* sampleMethod(..)", "com.example.SampleClass")),
java(
"""
package com.example;
public class SampleDto {
}
"""
),
java(
"""
package com.example;
public class SampleClass {
public void existingMethod() {
}
}
""",
"""
package com.example;
public class SampleClass {
public void existingMethod() {
}
public com.example.SampleDto sampleMethod(com.example.SampleDto param) {
return null;
}
}
"""
)
);
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 7 replies
-
|
For custom types you'll likely want to pass in a JavaParser with appropriate classpath entries into |
Beta Was this translation helpful? Give feedback.
Are you using the debugger or code coverage already to see which parts of your recipe execute? Because it sounds like there might be an early return that's throwing you off.
And more in general I can say a template like
public #{any()} #{}(#{any()} param) { return null; }is not recommended; we see that more often with folks starting out that they want to over generalize use cases and put large parts of their recipe into declarative yaml. Instead aim to write specific recipes for the cases you're after, with templates that already contain the types you're intending to use, and you should have a much better experience. Hope that helps!