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.