Skip to content

Commit 88ac76d

Browse files
committed
Update writing-a-java-refactoring-recipe.md example to use:
- available utils - simpler streams - direct `JavaTemplate` usage
1 parent 27a8b54 commit 88ac76d

File tree

1 file changed

+81
-102
lines changed

1 file changed

+81
-102
lines changed

docs/authoring-recipes/writing-a-java-refactoring-recipe.md

Lines changed: 81 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -465,80 +465,72 @@ With all of that done, the complete `SayHelloRecipe` looks like this:
465465
```java
466466
package com.yourorg;
467467

468-
import com.fasterxml.jackson.annotation.JsonCreator;
469-
import com.fasterxml.jackson.annotation.JsonProperty;
470468
import lombok.EqualsAndHashCode;
471469
import lombok.Value;
472-
import org.jspecify.annotations.NonNull;
473-
import org.openrewrite.*;
470+
import org.openrewrite.ExecutionContext;
471+
import org.openrewrite.Option;
472+
import org.openrewrite.Recipe;
473+
import org.openrewrite.TreeVisitor;
474474
import org.openrewrite.java.JavaIsoVisitor;
475475
import org.openrewrite.java.JavaTemplate;
476476
import org.openrewrite.java.tree.J;
477+
import org.openrewrite.java.tree.TypeUtils;
478+
479+
import java.util.Comparator;
477480

478481
// Making your recipe immutable helps make them idempotent and eliminates categories of possible bugs.
479482
// Configuring your recipe in this way also guarantees that basic validation of parameters will be done for you by rewrite.
480483
// Also note: All recipes must be serializable. This is verified by RewriteTest.rewriteRun() in your tests.
481484
@Value
482485
@EqualsAndHashCode(callSuper = false)
483486
public class SayHelloRecipe extends Recipe {
484-
@Option(displayName = "Fully Qualified Class Name",
485-
description = "A fully qualified class name indicating which class to add a hello() method to.",
486-
example = "com.yourorg.FooBar")
487-
@NonNull
487+
@Option(displayName = "Fully qualified class name",
488+
description = "A fully qualified class name indicating which class to add a `hello()` method to.",
489+
example = "`com.yourorg.FooBar`")
488490
String fullyQualifiedClassName;
489491

490-
// All recipes must be serializable. This is verified by RewriteTest.rewriteRun() in your tests.
491-
@JsonCreator
492-
public SayHelloRecipe(@NonNull @JsonProperty("fullyQualifiedClassName") String fullyQualifiedClassName) {
493-
this.fullyQualifiedClassName = fullyQualifiedClassName;
494-
}
495-
496492
@Override
497493
public String getDisplayName() {
498494
return "Say Hello";
499495
}
500496

501497
@Override
502498
public String getDescription() {
503-
return "Adds a \"hello\" method to the specified class.";
499+
return "Adds a `hello` method to the specified class.";
504500
}
505501

506502
@Override
507503
public TreeVisitor<?, ExecutionContext> getVisitor() {
508504
// getVisitor() should always return a new instance of the visitor to avoid any state leaking between cycles
509-
return new SayHelloVisitor();
510-
}
511-
512-
public class SayHelloVisitor extends JavaIsoVisitor<ExecutionContext> {
513-
private final JavaTemplate helloTemplate =
514-
JavaTemplate.builder( "public String hello() { return \"Hello from #{}!\"; }")
515-
.build();
516-
517-
@Override
518-
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) {
519-
// Don't make changes to classes that don't match the fully qualified name
520-
if (classDecl.getType() == null || !classDecl.getType().getFullyQualifiedName().equals(fullyQualifiedClassName)) {
521-
return classDecl;
522-
}
523-
524-
// Check if the class already has a method named "hello".
525-
boolean helloMethodExists = classDecl.getBody().getStatements().stream()
526-
.filter(statement -> statement instanceof J.MethodDeclaration)
527-
.map(J.MethodDeclaration.class::cast)
528-
.anyMatch(methodDeclaration -> methodDeclaration.getName().getSimpleName().equals("hello"));
529-
530-
// If the class already has a `hello()` method, don't make any changes to it.
531-
if (helloMethodExists) {
532-
return classDecl;
505+
return new JavaIsoVisitor<ExecutionContext>() {
506+
507+
@Override
508+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
509+
// Don't make changes to classes that don't match the fully qualified name
510+
if (!TypeUtils.isOfClassType(classDecl.getType(), fullyQualifiedClassName)) {
511+
return classDecl;
512+
}
513+
514+
// Check if the class already has a method named "hello".
515+
boolean helloMethodExists = classDecl.getBody().getStatements().stream()
516+
.filter(J.MethodDeclaration.class::isInstance)
517+
.map(J.MethodDeclaration.class::cast)
518+
.map(J.MethodDeclaration::getSimpleName)
519+
.anyMatch("hello"::equals);
520+
521+
// If the class already has a `hello()` method, don't make any changes to it.
522+
if (helloMethodExists) {
523+
return classDecl;
524+
}
525+
526+
// insert the defined method into the existing class declaration
527+
return JavaTemplate.apply(
528+
"public String hello() { return \"Hello from #{}!\"; }",
529+
updateCursor(classDecl),
530+
classDecl.getBody().getCoordinates().addMethodDeclaration(Comparator.comparing(J.MethodDeclaration::getSimpleName)),
531+
fullyQualifiedClassName);
533532
}
534-
535-
// Interpolate the fullyQualifiedClassName into the template and use the resulting LST to update the class body
536-
classDecl = classDecl.withBody( helloTemplate.apply(new Cursor(getCursor(), classDecl.getBody()),
537-
classDecl.getBody().getCoordinates().lastStatement(),
538-
fullyQualifiedClassName ));
539-
540-
return classDecl;
541-
}
533+
};
542534
}
543535
}
544536
```
@@ -549,31 +541,30 @@ public class SayHelloRecipe extends Recipe {
549541
```java
550542
package com.yourorg;
551543

552-
import com.fasterxml.jackson.annotation.JsonCreator;
553-
import com.fasterxml.jackson.annotation.JsonProperty;
554-
import lombok.Value;
555544
import org.jspecify.annotations.NonNull;
556-
import org.openrewrite.*;
545+
import org.openrewrite.ExecutionContext;
546+
import org.openrewrite.Option;
547+
import org.openrewrite.Recipe;
548+
import org.openrewrite.TreeVisitor;
557549
import org.openrewrite.java.JavaIsoVisitor;
558550
import org.openrewrite.java.JavaTemplate;
559551
import org.openrewrite.java.tree.J;
552+
import org.openrewrite.java.tree.TypeUtils;
560553

554+
import java.util.Comparator;
561555
import java.util.Objects;
562556

563557
// Making your recipe immutable helps make them idempotent and eliminates categories of possible bugs.
564558
// Configuring your recipe in this way also guarantees that basic validation of parameters will be done for you by rewrite.
565559
// Also note: All recipes must be serializable. This is verified by RewriteTest.rewriteRun() in your tests.
566-
@Value
567-
public class SayHelloRecipe extends Recipe {
568-
@Option(displayName = "Fully Qualified Class Name",
569-
description = "A fully qualified class name indicating which class to add a hello() method to.",
570-
example = "com.yourorg.FooBar")
560+
public final class SayHelloRecipe extends Recipe {
561+
@Option(displayName = "Fully qualified class name",
562+
description = "A fully qualified class name indicating which class to add a `hello()` method to.",
563+
example = "`com.yourorg.FooBar`")
571564
@NonNull
572-
String fullyQualifiedClassName;
565+
private final String fullyQualifiedClassName;
573566

574-
// All recipes must be serializable. This is verified by RewriteTest.rewriteRun() in your tests.
575-
@JsonCreator
576-
public SayHelloRecipe(@NonNull @JsonProperty("fullyQualifiedClassName") String fullyQualifiedClassName) {
567+
public SayHelloRecipe(@NonNull String fullyQualifiedClassName) {
577568
this.fullyQualifiedClassName = fullyQualifiedClassName;
578569
}
579570

@@ -584,13 +575,41 @@ public class SayHelloRecipe extends Recipe {
584575

585576
@Override
586577
public String getDescription() {
587-
return "Adds a \"hello\" method to the specified class.";
578+
return "Adds a `hello` method to the specified class.";
588579
}
589580

590581
@Override
591582
public TreeVisitor<?, ExecutionContext> getVisitor() {
592583
// getVisitor() should always return a new instance of the visitor to avoid any state leaking between cycles
593-
return new SayHelloVisitor();
584+
return new JavaIsoVisitor<ExecutionContext>() {
585+
586+
@Override
587+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
588+
// Don't make changes to classes that don't match the fully qualified name
589+
if (!TypeUtils.isOfClassType(classDecl.getType(), fullyQualifiedClassName)) {
590+
return classDecl;
591+
}
592+
593+
// Check if the class already has a method named "hello".
594+
boolean helloMethodExists = classDecl.getBody().getStatements().stream()
595+
.filter(J.MethodDeclaration.class::isInstance)
596+
.map(J.MethodDeclaration.class::cast)
597+
.map(J.MethodDeclaration::getSimpleName)
598+
.anyMatch("hello"::equals);
599+
600+
// If the class already has a `hello()` method, don't make any changes to it.
601+
if (helloMethodExists) {
602+
return classDecl;
603+
}
604+
605+
// insert the defined method into the existing class declaration
606+
return JavaTemplate.apply(
607+
"public String hello() { return \"Hello from #{}!\"; }",
608+
updateCursor(classDecl),
609+
classDecl.getBody().getCoordinates().addMethodDeclaration(Comparator.comparing(J.MethodDeclaration::getSimpleName)),
610+
fullyQualifiedClassName);
611+
}
612+
};
594613
}
595614

596615
@Override
@@ -613,46 +632,6 @@ public class SayHelloRecipe extends Recipe {
613632
public int hashCode() {
614633
return Objects.hash(super.hashCode(), fullyQualifiedClassName);
615634
}
616-
617-
public String getFullyQualifiedClassName() {
618-
return fullyQualifiedClassName;
619-
}
620-
621-
public void setFullyQualifiedClassName(String fullyQualifiedClassName) {
622-
this.fullyQualifiedClassName = fullyQualifiedClassName;
623-
}
624-
625-
public class SayHelloVisitor extends JavaIsoVisitor<ExecutionContext> {
626-
private final JavaTemplate helloTemplate =
627-
JavaTemplate.builder( "public String hello() { return \"Hello from #{}!\"; }")
628-
.build();
629-
630-
@Override
631-
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) {
632-
// Don't make changes to classes that don't match the fully qualified name
633-
if (classDecl.getType() == null || !classDecl.getType().getFullyQualifiedName().equals(fullyQualifiedClassName)) {
634-
return classDecl;
635-
}
636-
637-
// Check if the class already has a method named "hello".
638-
boolean helloMethodExists = classDecl.getBody().getStatements().stream()
639-
.filter(statement -> statement instanceof J.MethodDeclaration)
640-
.map(J.MethodDeclaration.class::cast)
641-
.anyMatch(methodDeclaration -> methodDeclaration.getName().getSimpleName().equals("hello"));
642-
643-
// If the class already has a `hello()` method, don't make any changes to it.
644-
if (helloMethodExists) {
645-
return classDecl;
646-
}
647-
648-
// Interpolate the fullyQualifiedClassName into the template and use the resulting LST to update the class body
649-
classDecl = classDecl.withBody( helloTemplate.apply(new Cursor(getCursor(), classDecl.getBody()),
650-
classDecl.getBody().getCoordinates().lastStatement(),
651-
fullyQualifiedClassName ));
652-
653-
return classDecl;
654-
}
655-
}
656635
}
657636
```
658637
</TabItem>

0 commit comments

Comments
 (0)