¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
dev
Daniel Peña 19/03/2025 Cargando comentarios…
Como hemos visto anteriormente, las recetas se distribuyen en artefactos Maven y, para crear nuestras propias recetas, necesitamos crear un proyecto Maven e importar el core de open-rewrite, extender sus clases y escribir nuestras ejecuciones.
¿Por qué es necesario que aprendamos a escribir nuestras propias recetas? Porque hay veces que no encajan con nuestras necesidades de proyecto o simplemente queremos hacer algún tipo de tarea específica y no hay ninguna que nos sirva.
OpenRewrite nos ofrece un “starter” con todas las dependencias necesarias para empezar a escribir nuestras recetas. Desde ahí tenemos la opción de escribirlas con Gradle, pero vamos a ver cómo hacerlo con Maven.
Para empezar, creamos un proyecto Maven:
mvn -B archetype:generate -DgroupId=com.openrewrite.demo.recipe -DartifactId=dppware-recipe -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4
Editamos el POM y añadimos las dependencias que nos sugiere la web. nos quedará así:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.openrewrite.demo.recipe</groupId>
<artifactId>dppware-recipe</artifactId>
<version>1.0-SNAPSHOT</version>
<name>dppware-recipe</name>
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.testSource>17</maven.compiler.testSource>
<maven.compiler.testTarget>17</maven.compiler.testTarget>
<lombok.version>1.18.28</lombok.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-recipe-bom</artifactId>
<version>2.0.7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- rewrite-java dependencies only necessary for Java Recipe development -->
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java-17</artifactId>
<scope>runtime</scope>
</dependency>
<!-- rewrite-maven dependency only necessary for Maven Recipe development -->
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-maven</artifactId>
<scope>compile</scope>
</dependency>
<!-- rewrite-yaml dependency only necessary for Yaml Recipe development -->
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-yaml</artifactId>
<scope>compile</scope>
</dependency>
<!-- rewrite-properties dependency only necessary for Properties Recipe development -->
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-properties</artifactId>
<scope>compile</scope>
</dependency>
<!-- rewrite-xml dependency only necessary for XML Recipe development -->
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-xml</artifactId>
<scope>compile</scope>
</dependency>
<!-- lombok is optional, but recommended for authoring recipes -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!-- For authoring tests for any kind of Recipe -->
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M9</version>
</plugin>
</plugins>
</build>
</project>
Compilamos para ver que todo va bien y, a continuación, empezamos a escribir nuestra primera receta.
Una receta es una clase Java que se extiende de la clase abstracta org.openrewrite.Recipe. Creamos una clase llamada FirstRecipe, extendemos Recipe e implementamos 3 métodos básicos:
package com.openrewrite.demo.recipe;
import org.openrewrite.Recipe;
public class FirstRecipe extends Recipe{
@Override
public String getDescription() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getDisplayName() {
// TODO Auto-generated method stub
return null;
}
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
// TODO Auto-generated method stub
return super.getVisitor();
}
}
Por un lado, getDescription y getDisplayName son métodos del core de Rewrite usados para encontrar la receta y también para el log /report de la ejecución de la receta.
Por otro lado, **getVisitor es el corazón de la ejecución de una receta. Como vimos anteriormente, el árbol LST está formado por nodos y estos nodos pueden ser de tipo method, variableDeclaration, Class, CompilationUnit… En la web oficial tenemos todos los elementos.
function helloWorld() {
console.log("Hola!");
}
El patrón “visitor” visitará todos los nodos que se construyan en el árbol LST y aplicará para cada uno las ejecuciones que definamos en nuestro visitor. Ahora vamos a crear un visitor propio en lugar de devolver el super.getVisitor():
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
// TODO Auto-generated method stub
return new JavaVisitor<ExecutionContext>() {
};
}
Antes de escribir la implementación, debemos saber qué tipo de elemento del árbol LST queremos modificar. Es decir, si queremos cambiar una variable, una definición de método, una clase porque vamos a añadir una nueva variable...
En caso de querer modificar elementos Java, usaremos JavaVisitor. Vemos que JavaVisitor nos ofrece varios métodos que podemos implementar de manera sencilla para manipular elementos Java:
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
// TODO Auto-generated method stub
return new JavaVisitor<ExecutionContext>() {
@Override
public J visitMethodDeclaration(MethodDeclaration method, ExecutionContext p) {
// TODO Auto-generated method stub
return super.visitMethodDeclaration(method, p);
}
@Override
public J visitMethodInvocation(MethodInvocation method, ExecutionContext p) {
// TODO Auto-generated method stub
return super.visitMethodInvocation(method, p);
}
@Override
public J visitVariableDeclarations(VariableDeclarations multiVariable, ExecutionContext p) {
// TODO Auto-generated method stub
return super.visitVariableDeclarations(multiVariable, p);
}
@Override
public J visitClassDeclaration(ClassDeclaration classDecl, ExecutionContext p) {
// TODO Auto-generated method stub
return super.visitClassDeclaration(classDecl, p);
}
@Override
public J visitImport(Import import_, ExecutionContext p) {
// TODO Auto-generated method stub
return super.visitImport(import_, p);
}
...etc..
};
Vamos a crear un proyecto sencillo Maven que haga de conejillo de indias para nuestras recetas. Para ello, vamos a un directorio vacío y ejecutamos el archetype de Maven para crear un proyecto sencillo:
cd /tmp
mvn -B archetype:generate -DgroupId=com.openrewrite.demo.testing -DartifactId=testing-recipe-project -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4
cd testing-recipe-project
Inicializamos el directorio como repo git para poder ver los cambios de manera más sencilla:
git init
git add -A
git commit -m "initial import"
Queremos añadir comentarios a todas las clases del proyecto. Nuestro visitor está focalizado en visitar las declaraciones de clase que existan en nuestro proyecto, así que debemos implementar el método visitClassDeclaration de nuestro JavaVisitor.
Creamos una receta llamada AddCommentToClassDeclaration que quedará así:
package com.openrewrite.demo.recipe;
import java.util.Collections;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.tree.J.ClassDeclaration;
import org.openrewrite.java.tree.TextComment;
import org.openrewrite.marker.Markers;
public class AddCommentToClassDeclaration extends Recipe {
private final String COMMENT = "This is AddCommentToClassDeclaration";
@Override
public String getDescription() {
return "AddCommentToClassDeclaration";
}
@Override
public String getDisplayName() {
return "AddCommentToClassDeclaration";
}
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new AddComment();
}
public class AddComment extends JavaIsoVisitor<ExecutionContext> {
@Override
public ClassDeclaration visitClassDeclaration(ClassDeclaration classDecl, ExecutionContext p) {
if (classDecl.getComments().isEmpty()) { //Idempotent execution
classDecl = classDecl.withComments(Collections.singletonList(
new TextComment(true, COMMENT, "\n", Markers.EMPTY)
));
}
return super.visitClassDeclaration(classDecl, p);
}
}
}
Como vemos en la declaración del método, su ejecución comprueba si existen comentarios para añadirlos en caso de que no existiera. Vamos a crear una clase java de prueba en el proyecto para ver como se comporta:
package com.openrewrite.demo.testing;
public class Ship {
}
El resultado esperado es que solo se añada comentario a la clase Ship, ya que la App y AppTest ya tenían comentarios de la creación del proyecto. Compilamos nuestro módulo de recetas:
[INFO] Installing /home/dpena/development/workspaces/dppware/gitlab/dppware-adhoc/back/pocs/openrewrite/dppware-recipe/pom.xml to /home/dpena/.m2/repository/com/dppware/recipe/dppware-recipe/1.0-SNAPSHOT/dppware-recipe-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
Vamos al proyecto y ejecutamos el plugin de OpenRewrite usando las coordenadas de nuestro artefacto y el nombre de la receta. Nos quedara así:
cd /tmp/testing-recipe-project
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=com.openrewrite.demo.recipe:dppware-recipe:1.0-SNAPSHOT \
-Drewrite.activeRecipes=com.openrewrite.demo.recipe.AddCommentToClassDeclaration
[INFO] --- rewrite-maven-plugin:5.3.1:run (default-cli) @ testing-recipe-project ---
[INFO] Using active recipe(s) [com.openrewrite.demo.recipe.AddCommentToClassDeclaration]
[INFO] Using active styles(s) []
[INFO] Validating active recipes...
[INFO] Project [testing-recipe-project] Resolving Poms...
[INFO] Project [testing-recipe-project] Parsing source files
[INFO] Running recipe(s)...
[WARNING] Changes have been made to src/main/java/com/dppware/testing/Ship.java by:
[WARNING] com.openrewrite.demo.recipe.AddCommentToClassDeclaration
[WARNING] Please review and commit the results.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
Vemos que ha habido cambios, hacemos el diff:
git diff
diff --git a/src/main/java/com/dppware/testing/Ship.java b/src/main/java/com/dppware/testing/Ship.java
index e8cfb8f..a321053 100644
--- a/src/main/java/com/dppware/testing/Ship.java
+++ b/src/main/java/com/dppware/testing/Ship.java
@@ -1,6 +1,7 @@
package com.openrewrite.demo.testing;
+/*This is AddCommentToClassDeclaration*/
public class Ship {
}
diff --git a/target/classes/com/dppware/testing/Ship.class b/target/classes/com/dppware/testing/Ship.class
Por último, guardamos los cambios:
git add -A
git commit -m "Added class comments”
Escribimos otra receta cuyo target de visitor son las compilationUnit. No es la clase, es una unidad de compilación que engloba la definición de paquetería, dependencias de import, etc. Vamos a añadir una licencia “dppware” a todas las clases, y nos quedará así:
package com.openrewrite.demo.recipe;
import java.util.Collections;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.tree.J.CompilationUnit;
import org.openrewrite.java.tree.TextComment;
import org.openrewrite.marker.Markers;
public class AddCommentToCompilationUnit extends Recipe {
private final String COMMENT = "Quien tuviere narices de copiar este código será castigado con 3 flexiones y 2 cafés de la máquina del pasillo";
@Override
public String getDescription() {
return "AddCommentToCompilationUnit";
}
@Override
public String getDisplayName() {
return "AddCommentToCompilationUnit";
}
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new AddComment();
}
public class AddComment extends JavaIsoVisitor<ExecutionContext> {
@Override
public CompilationUnit visitCompilationUnit(CompilationUnit cu, ExecutionContext p) {
if (cu.getComments().isEmpty()) { //Idempotent execution
cu = cu.withComments(Collections.singletonList(
new TextComment(true, COMMENT, "\n", Markers.EMPTY)
));
}
return super.visitCompilationUnit(cu, p);
}
}
}
Re-compilamos para empaquetar nuestras recetas y ejecutamos indicando el nombre de la receta com.openrewrite.demo.recipe.AddCommentToCompilationUnit.
Compilamos nuestro módulo de recetas:
mvn clean install
[INFO] Installing /home/dpena/development/workspaces/dppware/gitlab/dppware-adhoc/back/pocs/openrewrite/dppware-recipe/pom.xml to /home/dpena/.m2/repository/com/dppware/recipe/dppware-recipe/1.0-SNAPSHOT/dppware-recipe-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
Vamos al proyecto y ejecutamos el plugin de OpenRewrite usando las coordenadas de nuestro artefacto y el nombre de la receta. Nos quedara así:
cd /tmp/testing-recipe-project
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=com.openrewrite.demo.recipe:dppware-recipe:1.0-SNAPSHOT \
-Drewrite.activeRecipes=com.openrewrite.demo.recipe.AddCommentToCompilationUnit
[INFO] Using active recipe(s) [com.openrewrite.demo.recipe.AddCommentToCompilationUnit]
[INFO] Using active styles(s) []
[INFO] Validating active recipes...
[INFO] Project [testing-recipe-project] Resolving Poms...
[INFO] Project [testing-recipe-project] Parsing source files
[INFO] Running recipe(s)...
[WARNING] Changes have been made to src/main/java/com/dppware/testing/Ship.java by:
[WARNING] com.openrewrite.demo.recipe.AddCommentToCompilationUnit
[WARNING] Changes have been made to src/main/java/com/dppware/testing/App.java by:
[WARNING] com.openrewrite.demo.recipe.AddCommentToCompilationUnit
[WARNING] Changes have been made to src/test/java/com/dppware/testing/AppTest.java by:
[WARNING] com.openrewrite.demo.recipe.AddCommentToCompilationUnit
[WARNING] Please review and commit the results.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.669 s
[INFO] Finished at: 2023-07-19T12:59:11+02:00
[INFO] ------------------------------------------------------------------------
Git diff nos indica los cambios y observamos que se ha añadido el comentario a la unidad de compilación Java. Guardamos:
git add -A
git commit -m "Added compilation Unit comments"
Hay que tener en cuenta que una declaración de una variable no es solo a nivel de clase, puedes definir una variable como signatura de un método o también dentro de un método, dentro de un bucle… Así que esta receta requiere un poco más de trasteo con el API del manejo de LST. La receta nos quedará así:
package com.openrewrite.demo.recipe;
import org.openrewrite.Cursor;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.TextComment;
import org.openrewrite.marker.Markers;
public class AddCommentToClassVariable extends Recipe {
private final String COMMENT = "This is AddCommentToVariable";
@Override
public String getDescription() {
return "AddCommentToClassVariable";
}
@Override
public String getDisplayName() {
return "AddCommentToClassVariable";
}
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new AddComment();
}
public class AddComment extends JavaIsoVisitor<ExecutionContext> {
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext executionContext) {
multiVariable = super.visitVariableDeclarations(multiVariable, executionContext);
if(isSingleVarDeclaration(multiVariable) && isNotMethodArgumentDeclarationVar(getCursor())){
if (multiVariable.getComments().size() == 0) { //Idempotent
multiVariable = multiVariable.withComments(ListUtils.concat(
multiVariable.getComments(),
new TextComment(false, "comment", "\n"+multiVariable.getPrefix().getIndent(), Markers.EMPTY)
));
}
}
return multiVariable;
}
/**
* Does not affect to multiple same type declaration: String variableA,variableB
*/
private boolean isSingleVarDeclaration(J.VariableDeclarations multiVariable) {
return 1 == multiVariable.getVariables().size();
}
/**
* Returns true if is a
* @param nv
* @return
*/
private boolean isNotMethodArgumentDeclarationVar(Cursor cursor) {
return cursor.getParent().firstEnclosing(J.MethodDeclaration.class) == null;
}
}
}
Ahora compilamos nuestro artefacto de recetas y la dejamos lista para usar:
mvn clean install
Como añade un comentario a una variable propertyA , vamos a añadir una variable a la clase Ship.java del proyecto donde estamos realizando las ejecuciones:
/*Quien tuviere narices de copiar este código será castigado con 3 flexiones y 2 cafés de la máquina del pasillo*/
package com.openrewrite.demo.testing;
/*This is AddCommentToClassDeclaration*/
public class Ship {
private String propertyA;
}
Y ahora ejecutamos nuestra receta:
cd /tmp/testing-recipe-project
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=com.openrewrite.demo.recipe:dppware-recipe:1.0-SNAPSHOT \
-Drewrite.activeRecipes=com.openrewrite.demo.recipe.AddCommentToClassVariable
[INFO] Running recipe(s)...
[WARNING] Changes have been made to src/main/java/com/dppware/testing/Ship.java by:
[WARNING] com.openrewrite.demo.recipe.AddCommentToClassVariable
[WARNING] Please review and commit the results.
Por último, consultamos el diff para ver qué cambios ha hecho nuestra receta:
public class Ship {
+ //comment
+ private String propertyA;
}
Hemos visto cómo podemos preparar y distribuir nuestras recetas y también cómo reutilizar recetas comunes de terceros que forman distintos pasos dentro de nuestra receta final.
En el siguiente post vamos a profundizar y escribir nuestras propias recetas para modificaciones de código Java específicas, ya que muchas veces nuestro código es específico y necesitamos realizar refactorizaciones propias en las que las recetas de terceros no nos valen.
Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.
Cuéntanos qué te parece.