Generating Java with JCodeModel

Have you come across the misfortune of needing to auto-generate Java source code? Luckily, anything you’ve wanted to do with Java has already been done — and auto-generating Java is no different. I recently used JCodeModel to translate from JSON to Java — it worked great but it lacks any tutorial-style documentation. This article means to fill that gap. If you feel the need to go more in-depth, consult the the Javadoc.

Getting Started - The JCodeModel

We require an instance of JCodeModel to hold the context of the generated Java code. So, to start, create an instance of JCodeModel.

JCodeModel codeModel = new JCodeModel();

Any Java code that you generate relates to this root JCodeModel. Think of the JCodeModel as the root node in the Document Object Model (DOM) for your Java code. Each method on a node typically edits the current node or creates a child node. Using these methods allows you to build a tree-like structure describing the code you want to generate.

Your first class

Given the root of your model, you generate a Java package and from that Java package generate a Java class.

JPackage jp = codeModel._package("com.sookocheff.example");
JDefinedClass jc = jp._class("Generated");

You add Javadoc to your class using the javadoc() class method.

jc.javadoc().add("Generated class.");

Adding variables

Our class is more useful with some variables to hold state. Let’s add some.

JFieldVar constantField = jc.field(JMod.PUBLIC | JMod.FINAL | JMod.STATIC, String.class, "CONSTANT", JExpr.lit("VALUE"));

Methods and variables take modifiers that adjust their signature. In this example, we make a public static final variable of type String. The variable is named CONSTANT. The value is the evaluation of an expression denoted by the JExpr class, in this case we use a literal expression denoted by lit.

Let’s add a private variable to our class as another example. In this case, we create a variable of the form private Integer var;.

JFieldVar varField = jc.field(JMod.PRIVATE, Integer.class, "var");

Methods

Let’s add some methods to access our variables. For the simple example of defining a JavaBean-style object with get and set methods we can use our previously defined variables to declare our type signatures.

A get method that returns our private variable looks like public Long getVar() { return varField; }. You would express that with JCodeModel using a public modifier and the return type from the previously declared variable. You can fill the body of the method using body() and _return().

JMethod getVar = jc.method(JMod.PUBLIC, varField.type(), "getVar");
getVar.body()._return(varField);

A set method for putting to our private variable looks like public void setVar(Long var) { this.var = var; }. Using JCodeModel we first define the method using a public modifier and void return type, then specify the parameter to our method using the variables name and return type. The expression for assigning to our variable uses references to the variable we previously declared.

JMethod setVar = jc.method(JMod.PUBLIC, codeMode.VOID, "setVar");
setVar.param(varField.type(), varField.name());
setVar.body().assign(JExpr._this().ref(varField.name()), JExpr.ref(varField.name()));

Annotations

Annotations are done by adding a call to annotate to your class or method. As an example, we can add an override annotation to the getVar method.

getter.annotate(Override.class);

Implementing Interfaces

Implementing interfaces is done by calling the _implements method on your class.

JPackage jp = codeModel._package("com.sookocheff.example");
JDefinedClass jc = jp._class("Generated");
jc._implements(Serializable.class);

Wrapping Up

Let’s combine the functions we’ve learned so far into a single example.

package com.sookocheff.example;

import com.sun.codemodel.*;

import java.io.File;
import java.io.Serializable;

/**
 * Example JCodeModel application.
 */
public class Main {

    public static void main(String[] args) throws Exception {

        // Instantiate a new JCodeModel
        JCodeModel codeModel = new JCodeModel();

        // Create a new package
        JPackage jp = codeModel._package("com.sookocheff.codemodel");

        // Create a new class
        JDefinedClass jc = jp._class("GeneratedClass");

        // Implement Serializable
        jc._implements(Serializable.class);

        // Add Javadoc
        jc.javadoc().add("A JCodeModel example.");

        // Add default constructor
        jc.constructor(JMod.PUBLIC).javadoc().add("Creates a new " + jc.name() + ".");

        // Add constant serializable id
        jc.field(JMod.STATIC | JMod.FINAL, Long.class, "serialVersionUID", JExpr.lit(1L));

        // Add private variable
        JFieldVar quantity = jc.field(JMod.PRIVATE, Integer.class, "quantity");

        // Add get method
        JMethod getter = jc.method(JMod.PUBLIC, quantity.type(), "getQuantity");
        getter.body()._return(quantity);
        getter.javadoc().add("Returns the quantity.");
        getter.javadoc().addReturn().add(quantity.name());

        // Add set method
        JMethod setter = jc.method(JMod.PUBLIC, codeModel.VOID, "setQuantity");
        setter.param(quantity.type(), quantity.name());
        setter.body().assign(JExpr._this().ref(quantity.name()), JExpr.ref(quantity.name()));
        setter.javadoc().add("Set the quantity.");
        setter.javadoc().addParam(quantity.name()).add("the new quantity");

        // Generate the code
        codeModel.build(new File("src/main/java/"));
    }
}

Running the preceding code gives the following output.

package com.sookocheff.codemodel;

import java.io.Serializable;


/**
 * A JCodeModel example.
 *
 */
public class GeneratedClass
    implements Serializable
{

    final static Long serialVersionUID = 1L;
    private Integer quantity;

    /**
     * Creates a new GeneratedClass.
     *
     */
    public GeneratedClass() {
    }

    /**
     * Returns the quantity.
     *
     * @return
     *     quantity
     */
    public Integer getQuantity() {
        return quantity;
    }

    /**
     * Set the quantity.
     *
     * @param quantity
     *     the new quantity
     */
    public void setQuantity(Integer quantity) {
        this.quantity = quantity;
    }

}

JCodeModel is flexible enough to use for generating interfaces, annotations, documentation, and classes. These examples serve as a starting point for additional work. With JCodeModel, the only limit is your imagination.

comments powered by Disqus