How to maintain Java architectures with JavaPoet and WalkMod
Usually, as developers we apply in our enterprise software the same good practices over and over again. In fact, one of the most common development tasks is to support CRUD (CREATE-READ-UPDATE-DELETE) services for all those domain entities of our application. It usually means the creation of a new REST endpoint, the creation of a new Data Access Object, etc..
In order to follow the DRY (Don’t Repeat Yourself) principle, open source solutions like JHipster, which helps to generate applications with Spring Boot + Angular, have gained lots of popularity.
However, we could need to generate code under a different architecture, where Spring Boot and Angular are not available, for example a Struts application. In such cases, JavaPoet — with more than 3K stars — is a very popular open source library that implements the builder pattern to generate Java code from scratch. For example, the following code generates a POJO User class, with a single field called name and its setter.
MethodSpec setter = MethodSpec.methodBuilder("setName")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(String.class, "name")
.addStatement("this.name=name")
.build();FieldSpec nameField = FieldSpec.builder(String.class, "name").build();TypeSpec userClass = TypeSpec.classBuilder("User")
.addModifiers(Modifier.PUBLIC)
.addMethod(setter)
.addField(nameField)
.build();
This seems very easy to use. Isn’t it? It is a really good option to generate code from scratch, but we cannot use it to upgrade our existing code base. For example, if we want to make all our domain entities Serializable, we need to override our existing Java classes; which cannot be generated from scratch. We need an extra library/solution for it.
WalkMod is an open source tool that runs code transformations whose main purpose is auto-fix coding style issues. However, its architecture allows to integrate code transformations from other engines like JavaPoet. By default, WalkMod parses each Java file and sends it to a pipeline (i.e chain) of code transformations. Finally, it writes the minimum number of changes into the java files again.
Let’s see how to automatically make our entities Serializable by combining WalkMod and JavaPoet. Imagine that we have the following entity User:
package com.example.model;public class User { private String name; public void setName(String name) {
this.name = name;
} public String getName() {
return name;
}
}
Create a file serializable.groovy with the following contents:
package org.walkmod.javalang.javapoetimport com.squareup.javapoet.*
import org.walkmod.javalang.ASTManager
import javax.lang.model.element.Modifier// User
String entityName = node.types[0].name; //com.example.model
String packageName = node.package.name.toString();//HERE we create the Serializable entity with JavaPoet
TypeSpec entity = TypeSpec.classBuilder(entityName)
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(Serializable.class).build();JavaFile javaFile = JavaFile.builder(packageName, entity).build();//HERE adding our results to WalkMod
context.addResultNode(ASTManager.parse(javaFile.toString(), true));
There are two dynamic parameters in this transformation: (1) node — which represents the compilation unit previously parsed — and (2) context — which is the place to store the generated/updated sources to write.
Then, we run a command to configure our transformation as part of the walkmod pipeline.
walkmod add -m script -Dlanguage=groovy -Dlocation=serializable.groovy
And finally, we run walkmod to apply our transformations to our existing sources:
walkmod apply
And voilà. WalkMod has merged the class generated with JavaPoet with the existing User class :
package com.example.model;
import java.io.Serializable;public class User implements Serializable {private String name;public void setName(String name) {
this.name = name;
}public String getName() {
return name;
}
}