Using Transforms - Pyknic/CodeGen GitHub Wiki

When generating complex systems, you often need to generate the code in steps. In CodeGen, this can be done using Transforms. A Transform is a component that can transform one type of model into another type. If you for an example have three classes A, B and C, you could specify a View for C and two Transforms to convert an A or a B into C. Then you can generate code from either A, B or C. The transformation of A or B into C will be performed in the background.

Transforms from A or B to C and then to code.

Example of Transforms

In this example we have a database model we want to generate code from. A database consists of a project containing multiple Tables and each Table has multiple Columns. We use transforms to convert a table into a File model and three different transforms to create member variables, setter methods and getter methods from the columns.

The code in the example below is generated in the following way:

An example of how the generation works

Main.java

The main file parses a groovy file and builds a model from it. The list of tables are then used to generate classes.

public class Main {
    public static void main(String... args) {
        final Project p = new ProjectImpl();
        GroovyParser.fromGroovy(p, Paths.get("src/main/groovy/", "databaseModel.groovy"));
        final List<Table> tables = p.traversalOf(Table.class).collect(Collectors.toList());

        final Generator gen = new JavaGenerator(new ConfigInstaller());
        System.out.println(gen.onEach(tables) // Print the generated code.
            .collect(Collectors.joining("----------------------------\n")
        ));
    }
}

ConfigInstaller.java

We need to create our own Installer when we declare custom transforms. Since we will be using the default transforms as well, we can extend JavaInstaller but change the name to "ConfigInstaller". If we want to generate more than one class from each table, we can easily create more transforms and install them here.

public class ConfigInstaller extends JavaInstaller {
    public ConfigInstaller() {
        super ("ConfigInstaller");
        install(Column.class, Method.class, ColumnToGetter.class);
        install(Column.class, Method.class, ColumnToSetter.class);
        install(Column.class, Field.class, ColumnToMember.class);
        install(Table.class, File.class, TableToEntity.class);
    }
}

TableToEntity.java

The TableToEntity Transform delegates generation of fields, setters and getters to their individual transforms.

public class TableToEntity implements Transform<Table, File> {
    @Override
    public Optional<File> transform(Generator gen, Table model) {
        final String name = ucfirst(model.getTableName());
        
        final File file = File.of("org/examples/" + name + ".java");
        final Class entity = Class.of(name).public_();
        
        final List<Column> columns = model.getChildren()
            .streamOf(Column.class)
            .collect(Collectors.toList());
        
        // Transform columns to member fields.
        gen.metaOn(columns, Field.class)
            .map(m -> m.getResult())
            .forEach(f -> entity.add(f));
        
        // Transform columns to setters and getters.
        gen.metaOn(columns, Method.class)
            .map(m -> m.getResult())
            .forEach(m -> entity.add(m));
        
        file.add(entity);
        file.call(new AutoImports(gen.getDependencyMgr()));
        
        return Optional.of(file);
    }
}

ColumnToMember.java

public class ColumnToMember implements Transform<Column, Field> {
    @Override
    public Optional<Field> transform(Generator gen, Column model) {
        final String name = lcfirst(model.getAlias().orElse(model.getName()));
        final Type type = Type.of(model.getMapping());
        return Optional.of(Field.of(name, type).private_());
    }
}

ColumnToSetter.java

In this Transform we will use the RenderStack to determine the type of the class we are currently generating.

public class ColumnToSetter implements Transform<Column, Method> {
    @Override
    public Optional<Method> transform(Generator gen, Column model) {
        final String name = model.getAlias().orElse(model.getName());
        return Optional.of(Method.of("set" + ucfirst(name), thisType(gen, model))
            .public_()
            .add(Field.of(lcfirst(name), Type.of(model.getMapping())))
            .add("this." + lcfirst(name) + " = " + lcfirst(name) + ";")
            .add("return this;")
        );
    }
    
    public static Type thisType(Generator gen, Column model) {
        final RenderStack stack = gen.getRenderStack();
        return Type.of("org.example." + ucfirst(stack.fromTop(Table.class).findFirst()
            .orElseThrow(() -> new UnsupportedOperationException("Could not find parent table."))
            .getName()
        ));
    }
}

ColumnToGetter.java

public class ColumnToGetter implements Transform<Column, Method> {
    @Override
    public Optional<Method> transform(Generator gen, Column model) {
        final String name = model.getAlias().orElse(model.getName());
        return Optional.of(Method.of("get" + ucfirst(name), Type.of(model.getMapping()))
            .public_()
            .add("return this." + lcfirst(name) + ";")
        );
    }
}

Generated code

This is how the generated code of one of the tables looks like.

package org.examples;

import org.example.User;

public class User {
    
    private String mail;
    private Integer id;
    
    public String getMail() {
        return this.mail;
    }
    
    public User setMail(String mail) {
        this.mail = mail;
        return this;
    }
    
    public User setId(Integer id) {
        this.id = id;
        return this;
    }
    
    public Integer getId() {
        return this.id;
    }
}
⚠️ **GitHub.com Fallback** ⚠️