2024-03-28
javapoet syntax example

Which framework to generate source code ?

https://sylvainleroy.com/wp-admin/options-general.php?page=ad-inserter.php#tab-2

I have been recently writings tools to convert Beanshell code in Java. This is a technical post to compare some frameworks to generate source code.

Contents

How to generate 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 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 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 the 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(".lang.String[]", "args");
System.out.println(javaClass)

The main drawback of this solution is that I did not find any way to insert invalid 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 source code.

All things are easy to understand and manipulate : structures, flow 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

Sylvain Leroy

Senior Software Quality Manager and Solution Architect in Switzerland, I have previously created my own company, Tocea, in Software Quality Assurance. Now I am offering my knowledge and services in a small IT Consulting company : Byoskill and a website www.byoskill.com Currently living in Lausanne (CH)

View all posts by Sylvain Leroy →