Template-Based Code Generation with Apache Velocity, Part 2
by Giuseppe Naccarato06/02/2004
As described in part one of this series, code generation typically uses a template engine to transform some kind of "model" into compilable code, given the formatting specified by a template. In this second part of the series, I will show how to use Velocity inside of a code generator that is based on a well-defined Internal Object Model (IOM). You will see two different strategies:
- Using the Internal Object Model as the data model for the Velocity transformation.
- Using a Platform-Specific Model (PSM) in order to balance the logic between generator and template.
I'm going to show how to implement both approaches and explain why the latter is a better solution.
Internal Object Model Generator
In my article "Writing a Code Generator in Java," I proposed the architecture for a code generator shown in Figure 1.

Figure 1. The IOM code generator architecture
You can see three different modules in the code generator:
Importer: Reads the data model as input and translates it into a platform-independent internal format based on an object model.
Internal Object Model: Is the platform-independent internal format and could be considered the core of the code-generator architecture. IOM contains a set of classes that make it easy to manipulate the information coming from the model to generate outputs. The structure of the object model is a very important issue -- when well designed, it can be a powerful representation, independent of the target technology (Java, C++, etc.), that can easily be converted into source code.
Exporter: Accesses the IOM and takes the relevant information to generate code. As we will see, it can use templates in order to drive the generation process.
The class diagram in Figure 2 shows a basic Internal Object Model.

Figure 2. Class diagram for a basic Internal Object Model
As a convention, IOM classes start with the "IOM" prefix. The IOMClass
class models a class. It has an aggregation with the IOMAttribute
and IOMOperation classes because classes have attributes
and operations. Since operations have parameters, an aggregation is
modelled between the IOMOperation and the IOMParameter
classes. The IOMAssociation class represents an
association. It has two roles (start and end) that are modelled by the IOMRole
class, which is associated to the IOMClass' class that
represents the start or end class of the UML association. Although this
design is very simple, it will give you an idea of how to implement a
fully functional IOM. In a real context, each class should provide
specific attributes that somehow will drive the code generation. For
example, for the IOMClass class you would specify these
attributes: name, stereotype, visibility, type (abstract, transient,
persistent) and documentation. In addition, important aspects such as
base classes, derived classes, exception classes, and packages must be
supported by adding new classes and associations to the model.
In order to put Velocity in the game, you have to implement the exporter module in a way such that that it uses Velocity, as explained in part one of this series, to generate code. The exporter module is represented by this interface:
public interface Exporter {
public void initialize() throws Exception;
public void startClass(IOMClass cl) throws Exception;
public void endClass(IOMClass cl) throws Exception;
public void startAssociation(IOMAssociation association,
IOMClass currentClass) throws Exception;
public void endAssociation(IOMAssociation association,
IOMClass currentClass) throws Exception;
public void startOperation(IOMOperation operation)
throws Exception;
public void endOperation(IOMOperation operation)
throws Exception;
public void startAttribute(IOMAttribute attribute)
throws Exception;
public void endAttribute(IOMAttribute attribute)
throws Exception;
public void startParameter(IOMParameter parameter)
throws Exception;
public void endParameter(IOMParameter parameter)
throws Exception;
public void finalize() throws Exception;
}
The generator has also an important component, called navigator,
that navigates through the Internal Object Model and invokes the
methods of the Exporter interface. The idea is closely
bound to the SAX parser. The navigator calls the startClass
method each time a IOMClass object is processed, and in
general, the startXxx method each time an instance of IOMXxx
is processed. The navigator then invokes the endXxx
methods when an instance of IOMXxx is finished
processing. The following IOMNavigator class implements
the navigator:
package com.codegenerator;
import java.util.*;
public class IOMNavigator {
private Exporter exporter = null;
public IOMNavigator(Exporter exporter) {
this.exporter = exporter;
}
public void start() throws Exception {
exporter.initialize();
// Class navigation
for (int i = 0;
i < IOMController.getClasses().size();
i++) {
IOMClass cl =
(IOMClass) IOMController.getClasses().get(i);
exporter.startClass(cl);
// Attribute navigation
for (int j = 0; j < cl.getAttributes().size(); j++) {
IOMAttribute attribute =
(IOMAttribute) cl.getAttributes().get(j);
exporter.startAttribute(attribute);
exporter.endAttribute(attribute);
}
// Associtaion Navigation
ArrayList associations = cl.getMyAssociations();
for (int j = 0; j < associations.size(); j++) {
IOMAssociation ass =
(IOMAssociation) associations.get(j);
exporter.startAssociation(ass,cl);
exporter.endAssociation(ass,cl);
}
// Operation navigation
for (int k = 0;
k < cl.getOperations().size();
k++) {
IOMOperation operation =
(IOMOperation) cl.getOperations().get(k);
exporter.startOperation(operation);
// Parameter navigation
for (int t = 0;
t < operation.getParameters().size();
t++) {
IOMParameter param =
(IOMParameter) operation.getParameters().get(t);
exporter.startParameter(param);
exporter.endParameter(param);
}
exporter.endOperation(operation);
}
exporter.endClass(cl);
}
exporter.finalize();
}
}
The constructor takes an Exporter object as input
and stores it into the exporter private member. The start
method starts the navigation processes; it first processes classes,
then attributes, associations (using the getAllMyAssocitiations
user method), operations, and parameters.
Now that it's clear how that framework works, we can concentrate
on the implementation of the Exporter interface that uses
Velocity as template engine.