187 lines
5.5 KiB
Java
187 lines
5.5 KiB
Java
package com.yourorg;
|
|
|
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
import lombok.EqualsAndHashCode;
|
|
import lombok.Value;
|
|
import org.jspecify.annotations.NonNull;
|
|
import org.openrewrite.*;
|
|
import org.openrewrite.java.JavaIsoVisitor;
|
|
import org.openrewrite.java.JavaTemplate;
|
|
import org.openrewrite.java.tree.J;
|
|
|
|
/**
|
|
* A simple recipe that adds a hello() method to a specified class.
|
|
*
|
|
* This example demonstrates:
|
|
* - Basic recipe structure with @Value and @EqualsAndHashCode
|
|
* - Using @Option for configurable parameters
|
|
* - Using JavaTemplate for code generation
|
|
* - Checking preconditions before making changes
|
|
* - Following the "do no harm" principle
|
|
*
|
|
* Based on the official OpenRewrite tutorial:
|
|
* https://docs.openrewrite.org/authoring-recipes/writing-a-java-refactoring-recipe
|
|
*/
|
|
@Value
|
|
@EqualsAndHashCode(callSuper = false)
|
|
public class SayHelloRecipe extends Recipe {
|
|
|
|
@Option(
|
|
displayName = "Fully Qualified Class Name",
|
|
description = "A fully qualified class name indicating which class to add a hello() method to.",
|
|
example = "com.yourorg.FooBar"
|
|
)
|
|
@NonNull
|
|
String fullyQualifiedClassName;
|
|
|
|
@JsonCreator
|
|
public SayHelloRecipe(@NonNull @JsonProperty("fullyQualifiedClassName") String fullyQualifiedClassName) {
|
|
this.fullyQualifiedClassName = fullyQualifiedClassName;
|
|
}
|
|
|
|
@Override
|
|
public String getDisplayName() {
|
|
return "Say 'Hello'";
|
|
}
|
|
|
|
@Override
|
|
public String getDescription() {
|
|
return "Adds a \"hello\" method to the specified class.";
|
|
}
|
|
|
|
@Override
|
|
public TreeVisitor<?, ExecutionContext> getVisitor() {
|
|
// Always return a new instance - never cache visitors
|
|
return new JavaIsoVisitor<ExecutionContext>() {
|
|
|
|
@Override
|
|
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
|
|
// Step 1: Traverse the subtree first
|
|
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);
|
|
|
|
// Step 2: Check if this is the class we're looking for
|
|
// Do no harm: return unchanged if this isn't the target class
|
|
if (cd.getType() == null || !cd.getType().getFullyQualifiedName().equals(fullyQualifiedClassName)) {
|
|
return cd;
|
|
}
|
|
|
|
// Step 3: Check if the class already has a hello() method
|
|
// Do no harm: don't add if it already exists
|
|
boolean helloMethodExists = cd.getBody().getStatements().stream()
|
|
.filter(statement -> statement instanceof J.MethodDeclaration)
|
|
.map(J.MethodDeclaration.class::cast)
|
|
.anyMatch(methodDeclaration -> "hello".equals(methodDeclaration.getName().getSimpleName()));
|
|
|
|
if (helloMethodExists) {
|
|
return cd;
|
|
}
|
|
|
|
// Step 4: Add the hello() method using JavaTemplate
|
|
// The template uses #{} for parameter substitution
|
|
J.Block body = JavaTemplate.apply(
|
|
"public String hello() { return \"Hello from #{}!\"; }",
|
|
new Cursor(getCursor(), cd.getBody()),
|
|
cd.getBody().getCoordinates().lastStatement(),
|
|
fullyQualifiedClassName
|
|
);
|
|
|
|
return cd.withBody(body);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// TEST CLASS
|
|
// ============================================================
|
|
|
|
/*
|
|
package com.yourorg;
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
import org.openrewrite.DocumentExample;
|
|
import org.openrewrite.test.RecipeSpec;
|
|
import org.openrewrite.test.RewriteTest;
|
|
|
|
import static org.openrewrite.java.Assertions.java;
|
|
|
|
class SayHelloRecipeTest implements RewriteTest {
|
|
|
|
@Override
|
|
public void defaults(RecipeSpec spec) {
|
|
spec.recipe(new SayHelloRecipe("com.yourorg.FooBar"));
|
|
}
|
|
|
|
@DocumentExample
|
|
@Test
|
|
void addsHelloToFooBar() {
|
|
rewriteRun(
|
|
java(
|
|
"""
|
|
package com.yourorg;
|
|
|
|
class FooBar {
|
|
}
|
|
""",
|
|
"""
|
|
package com.yourorg;
|
|
|
|
class FooBar {
|
|
public String hello() {
|
|
return "Hello from com.yourorg.FooBar!";
|
|
}
|
|
}
|
|
"""
|
|
)
|
|
);
|
|
}
|
|
|
|
@Test
|
|
void doesNotChangeExistingHello() {
|
|
rewriteRun(
|
|
java(
|
|
"""
|
|
package com.yourorg;
|
|
|
|
class FooBar {
|
|
public String hello() { return ""; }
|
|
}
|
|
"""
|
|
)
|
|
);
|
|
}
|
|
|
|
@Test
|
|
void doesNotChangeOtherClasses() {
|
|
rewriteRun(
|
|
java(
|
|
"""
|
|
package com.yourorg;
|
|
|
|
class OtherClass {
|
|
}
|
|
"""
|
|
)
|
|
);
|
|
}
|
|
}
|
|
*/
|
|
|
|
// ============================================================
|
|
// YAML USAGE
|
|
// ============================================================
|
|
|
|
/*
|
|
Save this in src/main/resources/META-INF/rewrite/say-hello.yml:
|
|
|
|
---
|
|
type: specs.openrewrite.org/v1beta/recipe
|
|
name: com.yourorg.SayHelloToFooBar
|
|
displayName: Say Hello to FooBar
|
|
description: Adds a hello() method to the FooBar class.
|
|
recipeList:
|
|
- com.yourorg.SayHelloRecipe:
|
|
fullyQualifiedClassName: com.yourorg.FooBar
|
|
*/
|