Tutorial: Generating Java Files with Spring and Mustache
If you need to generate complex model from a large source data, you should try this
Join the DZone community and get the full member experience.
Join For FreeIn this tutorial, we are going to use Spring Boot and Mustache to generate a model using a given set of data.
The data was extracted from a rules and validation table. Here's a quick explanation of how it works — we receive a request with SOAP, and we validate its structure by reading this data, for example, what type of request it is and we transform each field if necessary. The problem appears when there are many requests that we have to validate, so we need to generate and adapt a specific object for each type of request.
In this tutorial, we will create our model from a source file called data.txt, generating java files with properties and annotations in a Builder pattern structure
1. Create a Spring Boot Project in https://start.spring.io
In this tutorial, we don't need any additional dependencies except for the defaults dependencies.
2. Add the Mustache Java Library in the pom.xml File
This library works with Java 8:
x
<dependency>
<groupId>com.github.spullara.mustache.java</groupId>
<artifactId>compiler</artifactId>
<version>0.9.6</version>
</dependency>
3. Create a Template for the Class (Builder Pattern)
Here the Mustache manual https://mustache.github.io/mustache.5.html
xxxxxxxxxx
package {{packages}}
import java.io.Serializable;
import javax.validation.constraints.*;
/**
*
* @author Fsanmiguel
*/
public class {{classname}} implements Serializable{
private static final long serialVersionUID = {{serialVersionUID}};
{{#properties}}
{{#annotations}}
{{&.}}
{{/annotations}}
private String {{property}};
{{/properties}}
//getters
{{#properties}}
public String {{getter}}() {
return {{property}};
}
{{/properties}}
private {{classname}}(Builder builder){
{{#properties}}
this.{{property}} = builder.{{property}};
{{/properties}}
}
public static Builder new{{classname}}() {
return new Builder();
}
public static class Builder {
{{#properties}}
private String {{property}};
{{/properties}}
private Builder() {}
public {{classname}} build() {
return new {{classname}}(this);
}
{{#properties}}
public Builder with{{camelcase}}(String {{property}}) {
this.{{property}} = {{property}};
return this;
}
{{/properties}}
}
}
4. Create a Context Class to Adapt the Given Data to the Template
In this tutorial, we need a simple class that matches the template property names
xxxxxxxxxx
package com.modelgenerator.modelgenerator.model;
import org.springframework.util.StringUtils;
import java.util.List;
public class Context {
List<Property> properties;
List<String> getters;
List<String> setters;
List<String> constants;
String classname;
String serialVersionUID;
String packages;
public List<Property> getProperties() {
return properties;
}
public void setProperties(List<Property> properties) {
this.properties = properties;
}
public String getClassname() {
return classname;
}
public void setClassname(String classname) {
this.classname = classname;
}
public String getSerialVersionUID() {
return serialVersionUID;
}
public void setSerialVersionUID(String serialVersionUID) {
this.serialVersionUID = serialVersionUID;
}
public String getPackages() {
return packages;
}
public void setPackages(String packages) {
this.packages = packages;
}
public List<String> getGetters() {
return getters;
}
public List<String> getSetters() {
return setters;
}
public void setGetters(List<String> getters) {
this.getters = getters;
}
public void setSetters(List<String> setters) {
this.setters = setters;
}
public List<String> getConstants() {
return constants;
}
public void setConstants(List<String> constants) {
this.constants = constants;
}
public static class Property {
String property;
String getter;
String camelcase;
List<String> annotations;
String type = "String";
public Property(String property, List<String> annotations) {
this.annotations = annotations;
this.property = property;
this.getter = "get"+StringUtils.capitalize(property);
this.camelcase = StringUtils.capitalize(property);
}
public Property(String property, List<String> annotations, String type) {
this.annotations = annotations;
this.property = property;
this.getter = "get"+StringUtils.capitalize(property);
this.camelcase = StringUtils.capitalize(property);
this.type = type;
}
}
}
5. Read and Transform the Data to the Context Class
xxxxxxxxxx
package com.modelgenerator.modelgenerator;
import com.modelgenerator.modelgenerator.model.Context;
import com.modelgenerator.modelgenerator.model.MessField;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class ModelGeneratorApp {
private static Logger LOG = LoggerFactory
.getLogger(ModelGeneratorApp.class);
public static void main(String[] args) throws IOException {
Path data = Paths.get("./data1.txt");
List<String> content = Files.readAllLines(data);
//TODO fill this list with the given data
List<MessField> messFieldList = new ArrayList<>();
String networks [] = {"ACCL","ACCL-MLD",};
String messages [] = {"1A","2A","3A","4A"};
String formatClassName = "{0}{1}";
Arrays.asList(networks).stream()
.forEach(n -> {
Arrays.asList(messages).stream()
.forEach(m -> {
String className = MessageFormat.format(formatClassName,n,m);
className = className.replace("-","");
generateClasses(className,messFieldList,n,m );
});
});
}
public static void generateClasses(String className,
List<MessField> messFieldList,
String network,
String message) {
//filter the data and group by a field
Map<String, List<MessField>> collect = messFieldList
.stream()
.distinct()
.filter(s -> network.equals(s.getVi_network()))
.filter(s -> message.equals(s.getVi_mess_name())).
collect(Collectors.groupingBy(MessField::getVi_variablename));
Context context = new Context();
context.setClassname(className);
context.setPackages("com.modelgenerator.modelgenerator;");
context.setSerialVersionUID("312312412312312312L");
List<Context.Property> properties = collect.entrySet()
.stream()
.map(f -> new Context.Property(camelCase(f.getKey()),
Arrays.asList("@NotNull")))
.collect(Collectors.toList());
context.setProperties(properties);
String content = mustacheContent(context, "dtotemplate.mustache");
generateFile(MessageFormat.format("./classes/{0}.java", className), content);
}
public static String mustacheContent(Object context, String template) {
try {
StringWriter writer = new StringWriter();
MustacheFactory mf = new DefaultMustacheFactory();
Mustache mustache = mf.compile(template);
mustache.execute(writer, context).flush();
return writer.toString();
} catch (Exception ex) {
LOG.error("mustache error", ex);
}
return "";
}
public static void generateFile(String className, String content) {
LOG.info("GENERATING "+ className);
try {
Files.deleteIfExists(Paths.get(className));
Files.write(Paths.get(className), content.getBytes(), StandardOpenOption.CREATE_NEW);
} catch (IOException e) {
LOG.error("Error generating file", e);
}
}
public static String camelCase(String word) {
String[] chars = word.split("");
Boolean upper = false;
String results = "";
for (String c : chars) {
if (!"_".equals(c)) {
results += upper ? c.toUpperCase() : c;
upper = false;
} else {
upper = true;
}
}
return results;
}
}
6. Examine the Generated Objects
You can run several times until you get the Java files correctly
Here's the output:
Here's a generated Java file:
package com.modelgenerator.modelgenerator;
import java.io.Serializable;
import javax.validation.constraints.*;
/**
*
* @author Fsanmiguel
*/
public class ACCL2A implements Serializable{
private static final long serialVersionUID = 312312412312312312L;
private String destinoFondos;
private String codProcedimiento;
private String cuentaOrigen;
private String numOrdenAch;
private String cuentaDestino;
private String origenFondos;
private String titularOriginante;
private String codSucursalOriginante;
private String etc;
private String codSubDestinatario;
private String codSubOriginante;
private String codServicio;
private String canal;
private String tipOrden;
private String codOriginante;
private String glosa;
private String codPaisOriginante;
private String titularDestinatario;
private String tipCuentaDestino;
private String importe;
private String numOrdenOriginante;
private String ciNitOriginante;
private String tipoDocumento;
private String codDestinatario;
private String codMoneda;
private String codPaisDestinatario;
private String tipCuentaOrigen;
private String codCamara;
private String ciNitDestinatario;
private String fecCamara;
//getters
public String getDestinoFondos() {
return destinoFondos;
}
public String getCodProcedimiento() {
return codProcedimiento;
}
public String getCuentaOrigen() {
return cuentaOrigen;
}
public String getNumOrdenAch() {
return numOrdenAch;
}
public String getCuentaDestino() {
return cuentaDestino;
}
public String getOrigenFondos() {
return origenFondos;
}
public String getTitularOriginante() {
return titularOriginante;
}
public String getCodSucursalOriginante() {
return codSucursalOriginante;
}
public String getEtc() {
return etc;
}
public String getCodSubDestinatario() {
return codSubDestinatario;
}
public String getCodSubOriginante() {
return codSubOriginante;
}
public String getCodServicio() {
return codServicio;
}
public String getCanal() {
return canal;
}
public String getTipOrden() {
return tipOrden;
}
public String getCodOriginante() {
return codOriginante;
}
public String getGlosa() {
return glosa;
}
public String getCodPaisOriginante() {
return codPaisOriginante;
}
public String getTitularDestinatario() {
return titularDestinatario;
}
public String getTipCuentaDestino() {
return tipCuentaDestino;
}
public String getImporte() {
return importe;
}
public String getNumOrdenOriginante() {
return numOrdenOriginante;
}
public String getCiNitOriginante() {
return ciNitOriginante;
}
public String getTipoDocumento() {
return tipoDocumento;
}
public String getCodDestinatario() {
return codDestinatario;
}
public String getCodMoneda() {
return codMoneda;
}
public String getCodPaisDestinatario() {
return codPaisDestinatario;
}
public String getTipCuentaOrigen() {
return tipCuentaOrigen;
}
public String getCodCamara() {
return codCamara;
}
public String getCiNitDestinatario() {
return ciNitDestinatario;
}
public String getFecCamara() {
return fecCamara;
}
private ACCL2A(Builder builder){
this.destinoFondos = builder.destinoFondos;
this.codProcedimiento = builder.codProcedimiento;
this.cuentaOrigen = builder.cuentaOrigen;
this.numOrdenAch = builder.numOrdenAch;
this.cuentaDestino = builder.cuentaDestino;
this.origenFondos = builder.origenFondos;
this.titularOriginante = builder.titularOriginante;
this.codSucursalOriginante = builder.codSucursalOriginante;
this.etc = builder.etc;
this.codSubDestinatario = builder.codSubDestinatario;
this.codSubOriginante = builder.codSubOriginante;
this.codServicio = builder.codServicio;
this.canal = builder.canal;
this.tipOrden = builder.tipOrden;
this.codOriginante = builder.codOriginante;
this.glosa = builder.glosa;
this.codPaisOriginante = builder.codPaisOriginante;
this.titularDestinatario = builder.titularDestinatario;
this.tipCuentaDestino = builder.tipCuentaDestino;
this.importe = builder.importe;
this.numOrdenOriginante = builder.numOrdenOriginante;
this.ciNitOriginante = builder.ciNitOriginante;
this.tipoDocumento = builder.tipoDocumento;
this.codDestinatario = builder.codDestinatario;
this.codMoneda = builder.codMoneda;
this.codPaisDestinatario = builder.codPaisDestinatario;
this.tipCuentaOrigen = builder.tipCuentaOrigen;
this.codCamara = builder.codCamara;
this.ciNitDestinatario = builder.ciNitDestinatario;
this.fecCamara = builder.fecCamara;
}
public static Builder newACCL2A() {
return new Builder();
}
public static class Builder {
private String destinoFondos;
private String codProcedimiento;
private String cuentaOrigen;
private String numOrdenAch;
private String cuentaDestino;
private String origenFondos;
private String titularOriginante;
private String codSucursalOriginante;
private String etc;
private String codSubDestinatario;
private String codSubOriginante;
private String codServicio;
private String canal;
private String tipOrden;
private String codOriginante;
private String glosa;
private String codPaisOriginante;
private String titularDestinatario;
private String tipCuentaDestino;
private String importe;
private String numOrdenOriginante;
private String ciNitOriginante;
private String tipoDocumento;
private String codDestinatario;
private String codMoneda;
private String codPaisDestinatario;
private String tipCuentaOrigen;
private String codCamara;
private String ciNitDestinatario;
private String fecCamara;
private Builder() {}
public ACCL2A build() {
return new ACCL2A(this);
}
public Builder withDestinoFondos(String destinoFondos) {
this.destinoFondos = destinoFondos;
return this;
}
public Builder withCodProcedimiento(String codProcedimiento) {
this.codProcedimiento = codProcedimiento;
return this;
}
public Builder withCuentaOrigen(String cuentaOrigen) {
this.cuentaOrigen = cuentaOrigen;
return this;
}
public Builder withNumOrdenAch(String numOrdenAch) {
this.numOrdenAch = numOrdenAch;
return this;
}
public Builder withCuentaDestino(String cuentaDestino) {
this.cuentaDestino = cuentaDestino;
return this;
}
public Builder withOrigenFondos(String origenFondos) {
this.origenFondos = origenFondos;
return this;
}
public Builder withTitularOriginante(String titularOriginante) {
this.titularOriginante = titularOriginante;
return this;
}
public Builder withCodSucursalOriginante(String codSucursalOriginante) {
this.codSucursalOriginante = codSucursalOriginante;
return this;
}
public Builder withEtc(String etc) {
this.etc = etc;
return this;
}
public Builder withCodSubDestinatario(String codSubDestinatario) {
this.codSubDestinatario = codSubDestinatario;
return this;
}
public Builder withCodSubOriginante(String codSubOriginante) {
this.codSubOriginante = codSubOriginante;
return this;
}
public Builder withCodServicio(String codServicio) {
this.codServicio = codServicio;
return this;
}
public Builder withCanal(String canal) {
this.canal = canal;
return this;
}
public Builder withTipOrden(String tipOrden) {
this.tipOrden = tipOrden;
return this;
}
public Builder withCodOriginante(String codOriginante) {
this.codOriginante = codOriginante;
return this;
}
public Builder withGlosa(String glosa) {
this.glosa = glosa;
return this;
}
public Builder withCodPaisOriginante(String codPaisOriginante) {
this.codPaisOriginante = codPaisOriginante;
return this;
}
public Builder withTitularDestinatario(String titularDestinatario) {
this.titularDestinatario = titularDestinatario;
return this;
}
public Builder withTipCuentaDestino(String tipCuentaDestino) {
this.tipCuentaDestino = tipCuentaDestino;
return this;
}
public Builder withImporte(String importe) {
this.importe = importe;
return this;
}
public Builder withNumOrdenOriginante(String numOrdenOriginante) {
this.numOrdenOriginante = numOrdenOriginante;
return this;
}
public Builder withCiNitOriginante(String ciNitOriginante) {
this.ciNitOriginante = ciNitOriginante;
return this;
}
public Builder withTipoDocumento(String tipoDocumento) {
this.tipoDocumento = tipoDocumento;
return this;
}
public Builder withCodDestinatario(String codDestinatario) {
this.codDestinatario = codDestinatario;
return this;
}
public Builder withCodMoneda(String codMoneda) {
this.codMoneda = codMoneda;
return this;
}
public Builder withCodPaisDestinatario(String codPaisDestinatario) {
this.codPaisDestinatario = codPaisDestinatario;
return this;
}
public Builder withTipCuentaOrigen(String tipCuentaOrigen) {
this.tipCuentaOrigen = tipCuentaOrigen;
return this;
}
public Builder withCodCamara(String codCamara) {
this.codCamara = codCamara;
return this;
}
public Builder withCiNitDestinatario(String ciNitDestinatario) {
this.ciNitDestinatario = ciNitDestinatario;
return this;
}
public Builder withFecCamara(String fecCamara) {
this.fecCamara = fecCamara;
return this;
}
}
}
Conclusion
There are situations when it becomes very repetitive to write a complete and complex model in a migration or a new implementation, and maybe the definition of each file can be structure in a source data. This is very useful and can save a lot of time.
Here the source code: https://github.com/farvher/modelgenerator
Published at DZone with permission of Farith Sanmiguel. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments