I have been recently writings tools to convert Beanshell code in Java. This is a technical post to compare some frameworks to generate java source code.
How to generate java source code ?
It exists several solutions that I would classify in three categories.
- using a template engine
- using an AST/Source to code framework
- others
Each solution has it drawbacks and strengths.
Template engine
Well with a template engine like Velocity or JET, you basically can write anything you want.
You won’t be constrained by an API and you may also generate syntaxic invalid files.
The main drawback is that you can’t write in a one shot your implementation. You will have to store your template in your resources, write a code to access it before generating your code.
Depending of the template engine, it can be a real pain.
public class HelloVelocity {
public static void main(String[] args) {
// Boring code to initialize the template engine
VelocityEngine ve = new VelocityEngine();
ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
ve.init();
// Try to find the code template
Template t = ve.getTemplate("codeTemplate.vm");
VelocityContext ctx = new VelocityContext();
// Initialize the context.
ctx.put("name", "velocity");
ctx.put("date", (new Date()).toString());
List temp = new ArrayList();
temp.add("1");
temp.add("2");
ctx.put("list", temp);
StringWriter sw = new StringWriter();
// Finally render
t.merge(ctx, sw);
System.out.println(sw.toString());
}
}
Using an AST/Source to source generator
I will present you an interesting framework based on JDT Java compiler.
This framework is called Roaster, is open-source and hosted on Github.
This is a maven library which provides a fluent API to generate java classes. Here an example:
final JavaClassSource javaClass = Roaster.create(JavaClassSource.class); javaClass.setPackage("com.company.example").setName("Person"); javaClass.addInterface(Serializable.class); javaClass.addField() .setName("serialVersionUID") .setType("long") .setLiteralInitializer("1L") .setPrivate() .setStatic(true) .setFinal(true); javaClass.addProperty(Integer.class, "id").setMutable(false); javaClass.addProperty(String.class, "firstName"); javaClass.addProperty("String", "lastName"); javaClass.addMethod() .setConstructor(true) .setPublic() .setBody("this.id = id;") .addParameter(Integer.class, "id");
Where the framework differs, is that it relies on JDT. The source code (parsed or created programmatically is associated to a JDT Dom Tree). Basically we are using the Eclipse functionalities to build the Java source code and unparse it.
Therefore, an hybrid approach can be chosen mixing parsing and programmation to produce your source code.
JavaClassSource javaClass =
Roaster.parse(JavaClassSource.class, "public class SomeClass {}");
javaClass.addMethod()
.setPublic()
.setStatic(true)
.setName("main")
.setReturnTypeVoid()
.setBody("System.out.println(\"Hello World\");")
.addParameter("java.lang.String[]", "args");
System.out.println(javaClass)
The main drawback of this solution is that I did not find any way to insert invalid java code (aka a plain String) into my class structure. The string is parsed by JDT and if its invalid, it throws an exception. Since I potentially have some invalid structures in Beanshell to be converted as Java, I have been blocked without solution.
Other frameworks
I want to present an outsider : JavaPoet.
This framework hosted on Github is half-way between a template engine and an AST generation framework,
JavaPoet is offering a nice Fluent interface to produce your Java source code.
All things are easy to understand and manipulate : structures, flow control statements, types.
MethodSpec main = MethodSpec.methodBuilder("main") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class) .addParameter(String[].class, "args") .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") .build(); TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(main) .build(); JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld) .build(); javaFile.writeTo(System.out);
I have been using it in several projects and it’s possible to inject plain Strings directly inside a valid structure. Rather efficient (it generates the package folder structure for you), it has a main drawback, you cannot declare your own imports (at the exception of the static ones).
Therefore if you are building your code using a mix of Fluent blocks and String templates, you will be soon short of imports to have a perfectly compiling code.
And you know what, they don’t want to add the feature grr :
https://github.com/square/javapoet/issues/512
https://github.com/square/javapoet/issues/507