@@ -64,7 +64,6 @@ import com.fasterxml.jackson.annotation.JsonCreator;
6464import com.fasterxml.jackson.annotation.JsonProperty ;
6565import lombok.EqualsAndHashCode ;
6666import lombok.Value ;
67- import org.jspecify.annotations.NonNull ;
6867import org.openrewrite.* ;
6968
7069// Making your recipe immutable helps make them idempotent and eliminates a variety of possible bugs.
@@ -76,27 +75,16 @@ public class SayHelloRecipe extends Recipe {
7675 @Option (displayName = " Fully Qualified Class Name" ,
7776 description = " A fully qualified class name indicating which class to add a hello() method to." ,
7877 example = " com.yourorg.FooBar" )
79- @NonNull
8078 String fullyQualifiedClassName;
8179
82- public SayHelloRecipe () {
83- fullyQualifiedClassName = " " ;
84- }
85-
86- // All recipes must be serializable. This is verified by RewriteTest.rewriteRun() in your tests.
87- @JsonCreator
88- public SayHelloRecipe (@NonNull @JsonProperty (" fullyQualifiedClassName" ) String fullyQualifiedClassName ) {
89- this . fullyQualifiedClassName = fullyQualifiedClassName;
90- }
91-
9280 @Override
9381 public String getDisplayName () {
94- return " Say Hello" ;
82+ return " Say ' Hello' " ;
9583 }
9684
9785 @Override
9886 public String getDescription () {
99- return " Adds a \" hello\" method to the specified class." ;
87+ return " Adds a ` hello` method to the specified class." ;
10088 }
10189
10290 // TODO: Override getVisitor() to return a JavaIsoVisitor to perform the refactoring
@@ -111,7 +99,6 @@ package com.yourorg;
11199
112100import com.fasterxml.jackson.annotation.JsonCreator ;
113101import com.fasterxml.jackson.annotation.JsonProperty ;
114- import org.jspecify.annotations.NonNull ;
115102import org.openrewrite.* ;
116103
117104import java.util.Objects ;
@@ -123,49 +110,20 @@ public class SayHelloRecipe extends Recipe {
123110 @Option (displayName = " Fully Qualified Class Name" ,
124111 description = " A fully qualified class name indicating which class to add a hello() method to." ,
125112 example = " com.yourorg.FooBar" )
126- @NonNull
127113 String fullyQualifiedClassName;
128-
129- @Override
130- public boolean equals (Object o ) {
131- if (this == o) return true ;
132- if (o == null || getClass() != o. getClass()) return false ;
133- if (! super . equals(o)) return false ;
134- SayHelloRecipe that = (SayHelloRecipe ) o;
135- return Objects . equals(fullyQualifiedClassName, that. fullyQualifiedClassName);
136- }
137-
138- @Override
139- public int hashCode () {
140- return Objects . hash(super . hashCode(), fullyQualifiedClassName);
141- }
142-
143- public void setFullyQualifiedClassName (String fullyQualifiedClassName ) {
144- this . fullyQualifiedClassName = fullyQualifiedClassName;
145- }
146-
147- public String getFullyQualifiedClassName () {
148- return fullyQualifiedClassName;
149- }
150-
151- public SayHelloRecipe () {
152- fullyQualifiedClassName = " " ;
153- }
154-
155- // All recipes must be serializable. This is verified by RewriteTest.rewriteRun() in your tests.
156- @JsonCreator
157- public SayHelloRecipe (@NonNull @JsonProperty (" fullyQualifiedClassName" ) String fullyQualifiedClassName ) {
114+
115+ public SayHelloRecipe (String fullyQualifiedClassName ) {
158116 this . fullyQualifiedClassName = fullyQualifiedClassName;
159117 }
160118
161119 @Override
162120 public String getDisplayName () {
163- return " Say Hello" ;
121+ return " Say ' Hello' " ;
164122 }
165123
166124 @Override
167125 public String getDescription () {
168- return " Adds a \" hello\" method to the specified class." ;
126+ return " Adds a ` hello` method to the specified class." ;
169127 }
170128
171129 @Override
@@ -175,6 +133,20 @@ public class SayHelloRecipe extends Recipe {
175133 ' }' ;
176134 }
177135
136+ @Override
137+ public boolean equals (Object o ) {
138+ if (this == o) return true ;
139+ if (o == null || getClass() != o. getClass()) return false ;
140+ if (! super . equals(o)) return false ;
141+ SayHelloRecipe that = (SayHelloRecipe ) o;
142+ return Objects . equals(fullyQualifiedClassName, that. fullyQualifiedClassName);
143+ }
144+
145+ @Override
146+ public int hashCode () {
147+ return Objects . hash(super . hashCode(), fullyQualifiedClassName);
148+ }
149+
178150 // TODO: Override getVisitor() to return a JavaIsoVisitor to perform the refactoring
179151}
180152```
@@ -316,21 +288,19 @@ public class SayHelloRecipe extends Recipe {
316288 @Override
317289 public TreeVisitor<?, ExecutionContext > getVisitor () {
318290 // getVisitor() should always return a new instance of the visitor to avoid any state leaking between cycles
319- return new SayHelloVisitor ();
320- }
321-
322- public class SayHelloVisitor extends JavaIsoVisitor<ExecutionContext > {
323- @Override
324- public J .ClassDeclaration visitClassDeclaration (J .ClassDeclaration classDecl , ExecutionContext executionContext ) {
325- // TODO: Filter out classes that don't match the fully qualified name
291+ return new JavaIsoVisitor<ExecutionContext > () {
292+ @Override
293+ public J .ClassDeclaration visitClassDeclaration (J .ClassDeclaration classDecl , ExecutionContext ctx ){
294+ // TODO: Filter out classes that don't match the fully qualified name
326295
327- // TODO: Filter out classes that already have a `hello()` method
296+ // TODO: Filter out classes that already have a `hello()` method
328297
329- // TODO: Add a `hello()` method to classes that need it
330- return classDecl;
331- }
298+ // TODO: Add a `hello()` method to classes that need it
299+ return classDecl;
300+ }
301+ } ;
332302 }
333- }
303+ }
334304```
335305
336306Now, let's work through each of those TODOs.
@@ -344,19 +314,22 @@ All of our logic lives inside of the `visitClassDeclaration` method. To filter o
344314public class SayHelloRecipe extends Recipe {
345315 // ...
346316
347- public class SayHelloVisitor extends JavaIsoVisitor<ExecutionContext > {
348- @Override
349- public J .ClassDeclaration visitClassDeclaration (J .ClassDeclaration classDecl , ExecutionContext executionContext ) {
350- // Don't make changes to classes that don't match the fully qualified name
351- if (classDecl. getType() == null || ! classDecl. getType(). getFullyQualifiedName(). equals(fullyQualifiedClassName)) {
352- return classDecl;
353- }
317+ @Override
318+ public TreeVisitor<?, ExecutionContext > getVisitor () {
319+ return new JavaIsoVisitor<ExecutionContext > () {
320+ @Override
321+ public J .ClassDeclaration visitClassDeclaration (J .ClassDeclaration classDecl , ExecutionContext ctx ) {
322+ // Don't make changes to classes that don't match the fully qualified name
323+ if (! TypeUtils . isOfClassType(classDecl. getType(), fullyQualifiedClassName)) {
324+ return classDecl;
325+ }
354326
355- // TODO: Filter out classes that already have a `hello()` method
327+ // TODO: Filter out classes that already have a `hello()` method
356328
357- // TODO: Add a `hello()` method to classes that need it
358- return classDecl;
359- }
329+ // TODO: Add a `hello()` method to classes that need it
330+ return classDecl;
331+ }
332+ };
360333 }
361334}
362335```
@@ -369,29 +342,32 @@ To filter out classes that already have a `hello()` method, we need to first fig
369342// ...
370343public class SayHelloRecipe extends Recipe {
371344 // ...
345+ @Override
346+ public TreeVisitor<?, ExecutionContext > getVisitor () {
347+ return new JavaIsoVisitor<ExecutionContext > () {
348+ @Override
349+ public J .ClassDeclaration visitClassDeclaration (J .ClassDeclaration classDecl , ExecutionContext ctx ) {
350+ // Don't make changes to classes that don't match the fully qualified name
351+ if (! TypeUtils . isOfClassType(classDecl. getType(), fullyQualifiedClassName)) {
352+ return classDecl;
353+ }
372354
373- public class SayHelloVisitor extends JavaIsoVisitor<ExecutionContext > {
374- @Override
375- public J .ClassDeclaration visitClassDeclaration (J .ClassDeclaration classDecl , ExecutionContext executionContext ) {
376- // Don't make changes to classes that don't match the fully qualified name
377- if (classDecl. getType() == null || ! classDecl. getType(). getFullyQualifiedName(). equals(fullyQualifiedClassName)) {
378- return classDecl;
379- }
355+ // Check if the class already has a method named "hello".
356+ boolean helloMethodExists = classDecl. getBody(). getStatements(). stream()
357+ .filter(J . MethodDeclaration . class:: isInstance)
358+ .map(J . MethodDeclaration . class:: cast)
359+ .map(J . MethodDeclaration :: getSimpleName)
360+ .anyMatch(" hello" :: equals);
380361
381- // Check if the class already has a method named "hello"
382- boolean helloMethodExists = classDecl. getBody(). getStatements(). stream()
383- .filter(statement - > statement instanceof J . MethodDeclaration )
384- .map(J . MethodDeclaration . class:: cast)
385- .anyMatch(methodDeclaration - > methodDeclaration. getName(). getSimpleName(). equals(" hello" ));
362+ // If the class already has a `hello()` method, don't make any changes to it.
363+ if (helloMethodExists) {
364+ return classDecl;
365+ }
386366
387- // If the class already has a `hello()` method, don't make any changes to it.
388- if (helloMethodExists) {
367+ // TODO: Add a `hello()` method to classes that need it
389368 return classDecl;
390369 }
391-
392- // TODO: Add a `hello()` method to classes that need it
393- return classDecl;
394- }
370+ };
395371 }
396372}
397373```
@@ -405,21 +381,10 @@ Templates are created using the `JavaTemplate.builder()` method. Within a templa
405381Here is what a template like that might look like for our recipe:
406382
407383``` java
408- // ...
409- public class SayHelloRecipe extends Recipe {
410- // ...
411-
412- public class SayHelloVisitor extends JavaIsoVisitor<ExecutionContext > {
413- private final JavaTemplate helloTemplate =
414- JavaTemplate . builder( " public String hello() { return \" Hello from #{}!\" ; }" )
415- .build();
416-
417- @Override
418- public J .ClassDeclaration visitClassDeclaration (J .ClassDeclaration classDecl , ExecutionContext executionContext ) {
419- // ...
420- }
421- }
422- }
384+ JavaTemplate . apply(" public String hello() { return \" Hello from #{}!\" ; }" ,
385+ updateCursor(classDecl),
386+ classDecl. getBody(). getCoordinates(). addMethodDeclaration(Comparator . comparing(J . MethodDeclaration :: getSimpleName)),
387+ fullyQualifiedClassName);
423388```
424389
425390We then could use that template to add a ` hello() ` method as desired by:
@@ -429,36 +394,39 @@ We then could use that template to add a `hello()` method as desired by:
429394public class SayHelloRecipe extends Recipe {
430395 // ...
431396
432- public class SayHelloVisitor extends JavaIsoVisitor<ExecutionContext > {
433- private final JavaTemplate helloTemplate =
434- JavaTemplate . builder( " public String hello() { return \" Hello from #{}!\" ; }" )
435- .build();
436-
437- @Override
438- public J .ClassDeclaration visitClassDeclaration (J .ClassDeclaration classDecl , ExecutionContext executionContext ) {
439- // Don't make changes to classes that don't match the fully qualified name
440- if (classDecl. getType() == null || ! classDecl. getType(). getFullyQualifiedName(). equals(fullyQualifiedClassName)) {
441- return classDecl;
442- }
397+ @Override
398+ public TreeVisitor<?, ExecutionContext > getVisitor () {
399+ return new JavaIsoVisitor<ExecutionContext > () {
400+ private final JavaTemplate helloTemplate =
401+ JavaTemplate . builder(" public String hello() { return \" Hello from #{}!\" ; }" )
402+ .build();
443403
444- // Check if the class already has a method named "hello"
445- boolean helloMethodExists = classDecl. getBody(). getStatements(). stream()
446- .filter(statement - > statement instanceof J . MethodDeclaration )
447- .map(J . MethodDeclaration . class:: cast)
448- .anyMatch(methodDeclaration - > methodDeclaration. getName(). getSimpleName(). equals(" hello" ));
404+ @Override
405+ public J .ClassDeclaration visitClassDeclaration (J .ClassDeclaration classDecl , ExecutionContext executionContext ) {
406+ // Don't make changes to classes that don't match the fully qualified name
407+ if (classDecl. getType() == null || ! classDecl. getType(). getFullyQualifiedName(). equals(fullyQualifiedClassName)) {
408+ return classDecl;
409+ }
449410
450- // If the class already has a `hello()` method, don't make any changes to it.
451- if (helloMethodExists) {
452- return classDecl;
453- }
411+ // Check if the class already has a method named "hello"
412+ boolean helloMethodExists = classDecl. getBody(). getStatements(). stream()
413+ .filter(statement - > statement instanceof J . MethodDeclaration )
414+ .map(J . MethodDeclaration . class:: cast)
415+ .anyMatch(methodDeclaration - > methodDeclaration. getName(). getSimpleName(). equals(" hello" ));
454416
455- // Interpolate the fullyQualifiedClassName into the template and use the resulting LST to update the class body
456- classDecl = classDecl . withBody( helloTemplate . apply( new Cursor (getCursor(), classDecl . getBody()),
457- classDecl. getBody() . getCoordinates() . lastStatement(),
458- fullyQualifiedClassName ));
417+ // If the class already has a `hello()` method, don't make any changes to it.
418+ if (helloMethodExists) {
419+ return classDecl;
420+ }
459421
460- return classDecl;
461- }
422+ // insert the defined method into the existing class declaration
423+ return JavaTemplate . apply(
424+ " public String hello() { return \" Hello from #{}!\" ; }" ,
425+ updateCursor(classDecl),
426+ classDecl. getBody(). getCoordinates(). addMethodDeclaration(Comparator . comparing(J . MethodDeclaration :: getSimpleName)),
427+ fullyQualifiedClassName);
428+ }
429+ };
462430 }
463431}
464432```
0 commit comments