ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Template-Based Code Generation with Apache Velocity, Part 2 Template-Based Code Generation with Apache Velocity, Part 2

by Giuseppe Naccarato
06/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
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
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.

Pages: 1, 2, 3, 4

Next Pagearrow