ORIGINAL DRAFT

Code generation is one of the areas in which XML has been successfully applied. One of the advantages of generating code from an XML description is the notion of working with declarative statements, rather than procedural code. This also has a tendency of conserving space, making the XML representation more compact than the generated code. Despite the promise of this kind of approach, in practice it’s only really applicable in certain cases. One of those cases is the generation of containers in Java, which we’ll demonstrate for you in this installment.

Let’s take a moment to consider where data-oriented JavaBeans (property containers) are most applicable, in order to get a sense of where this solution might be most useful. The Model-View-Controller (MVC) paradigm provides effective separation between the user’s view of a piece or collection of data, the controller of that data and the data model itself. When this architecture is well implemented, the model should really be little more than a data container. In effect, even if the model were to contain some programming logic, the need to avoid this manifests itself when you need to transmit or store and retrieve the data, so the controller should really apply that logic rather than the model.

That being said, it’s clear that generating data models is a good idea when you know there will never be any logic programmed into the container. Other than encapsulating the data and providing accessors to get and set values, the data container has few responsibilities, making it an ideal candidate for automatic code generation. Obviously there are times when this is not a good idea, but when it’s applicable, you can save considerable time by working from a more compact representation, stored in XML. Even if the generated code is nothing more than a starting point for more complex classes, the time you save may be quite significant.

Our implementation is designed to take a simple XML description for multiple JavaBean objects, so a single source can generate a fairly voluminous amount of code, depending on your needs. Our objective is to generate classes with appropriate accessors that follow the JavaBean model. The resulting code can be compiled and used in your application. Accessors provide structured access to the data values contained in the JavaBean and you can avoid the overhead of repetitive coding by simply describing the properties you want to provide access to.

Let’s take a quick look at an example description.

<?xml version="1.0"?>
<DATABEANS>
  <BEAN name="SimpleBean">
    <PROPERTY
      name="name"
      type="String"
      access="get,set" />
    <PROPERTY
      name="bounds"
      type="Rectangle"
      access="get,set" />
  </BEAN>
  <BEAN name="BooleanBean">
    <PROPERTY
      name="flag"
      type="boolean"
      access="is,set" />
    <PROPERTY
      name="list"
      type="int[]"
      access="get,set" />
  </BEAN>
</DATABEANS>

This representation specifies two beans, SimpleBean and BooleanBean. The first has two properties, name and bounds, with String and Rectangle types, respectively. The second also has two properties, a boolean flag and an integer list. We also specify whether we want to see “is”, “get” and/or “set” methods. The “is” method signature is rarely used unless we are dealing with a boolean value, so the “is” and “get” methods are never really used together in practice.

This example is simplified. We may need to specify one or more import statements, whether a class extends another and what that class may be, and whether a class implements one or more interfaces. This is easy enough to handle by adding a few more tags to the bean specification.

<BEAN name="BeanClassName">
  <IMPORTS packages="java.io.*,java.awt.*" />
  <EXTENDS class="Object" />
  <IMPLEMENTS interfaces="Serializable,Pluggable" />
  ...
</BEAN>

This keeps the description as compact as possible, while providing all the salient information.

Looking at this problem, you may think that using XSLT would be a viable approach, and I would have to agree. But this is a Java column so we’ll be approaching the problem with a Java programmer’s bias. Neither approach is inappropriate. In fact, the Java solution will handle much larger sets and may well do so much faster than XSLT. If you’re more adept with XSLT than I am, you might be able to prove me wrong. But let’s leave those issues behind and take a look at what a Java-based solution would look like. After all, we’re on our Java Break.

We’ll be using a SAX parser to process the originating XML descriptions. We prefer SAX to DOM because SAX can process very large documents without much memory overhead. It also tends to perform much faster. As we parse the XML document, we’ll construct a suitable internal representation for each JavaBean and associated properties. After collecting this information, we’ll write out suitable classes in source code form, for subsequent compilation.

Figure 1: BeanGenerator classes. The BeanList, 
BeanProperties and BeanProperty classes provide an internal data representation. BeanHandler does the parsing,
BeanWriter generates the source code and BeanGenerator is the main processing class.

Figure 1: BeanGenerator classes. The BeanList, BeanProperties and BeanProperty classes provide an internal data representation. BeanHandler does the parsing, BeanWriter generates the source code and BeanGenerator is the main processing class.

Figure 1 shows the classes we’ll build to accomplish our goals. Main functionality is controlled by the BeanGenerator class. The BeanHandler extends the SAX2 DefaultHandler class and constructs appropriate bean objects in the form of BeanProperties collections that contain BeanProperty objects. These are collected in a BeanList to make them easy to manage. The BeanWriter generates source code when we’re finished collecting the specification from the XML description file.

The BeanList, BeanProperties and BeanProperty files do little more than store data and would themselves be candidates for automated generation if it weren’t for the need for them to pre-exist. The more interesting classes we’ll take a look at are BeanHandler, BeanWriter and BeanGenerator, which ties the former two together.

Listing 1 shows the code for BeanHandler, which extends the SAX2 DefaultHandler. The constructor initializes the BeanList and we provide a getBeanList method to access the list after processing, but most of the work is done in the startElement method. We watch for the BEAN tag and add a new BeanProperties object to the BeanList when we see it. For convenience, we also set this instance to be the currentBean, making it easy to add new properties as we encounter them.

If we see the IMPORTS tag, we call the setImportList method to add the value of the packages attribute. If we see an EXTENDS tag, we call the setExtendsClass method to add the value of the class attribute. If we see an IMPLEMENTS tag, we call the setInterfaceList method to add the value of the interfaces attribute. When we see the PROPERTY tag, we create a new BeanProperty object and add it to the currentBean. BeanProperty provides a constructor that accepts an Attributes object, which it then uses to extract the name, type and access attribute values.

The BeanWriter (Listing 2) writes the code we’re interested in generating to suitable files. The constructor expects a BeanList instance and the generation process is triggered by the write method, which iterates through each BeanProperties object in the BeanList and calls the writeBean method to write out the code. The writeBean method uses the BeanProperties getName method to create a file name by appending “.java”, and sets up a PrintWriter to print the necessary text.

The first thing we do is call a method called writeImports to handle import statements at the beginning of the file. We then declare a public class using the class name and call writeExtends and writeImplements to handle an optional class extension and an interface implementation list, if one exists. After this, we make two passes through the BeanProperty list. The first declares instance variables using the type and name, each declared as protected. This is a personal choice, since many would argue that these should be declared as private. In my experience, however, this makes it impossible to access instance variables in a subclass, so I prefer using protected access control rather than private.

During the second traversal of the BeanProperty list, we generate method code, using the writeAccessors method, which branches based on the existence of the “is”, “get” and “set” values in the access attribute. We implement three methods to handle the nominal cases - writeIsAccessor, writeGetAccessor and writeSetAcessor. In each case, the code that gets generated is based on the type provided in the property description.

There are additional cases required when you’re dealing with an array value to support indexed accessors. These are implemented by the writeIndexedIsAccessor, writeIndexedGetAccessor and writeIndexedSetAcessor methods, which are similar to the nominal cases mentioned before, providing indexed access into specific values in the array. We also use a writeArraySizeAccessor method to generate an accessor that returns the size of the array for convenience.

The BeanGenerator class, in Listing 3, ties things together and provides a command line interface to the source XML file. You can call it by typing “java BeanGenerator " where the "" is the filename you want to see processed. After creating a BeanHandler instance, we use the Java JAXP interface to get a SAX parser and call the parse method with the BeanHandler as the second argument. After this, we can call the handler.getBeanList method and create a BeanWriter instance with the BeanList as an argument in the constructor. Finally, we call the BeanWriter's write method to output the code to suitable files.

The output from the generator for the SimpleBean looks like this.

import java.io.*;
import java.util.*;

public class SimpleBean
  extends Object
  implements Serializable
{
  protected String name;
  protected Rectangle bounds;

  public String getName()
  {
    return name;
  }

  public void setName(String name)
  {
    this.name = name;
  }

  public Rectangle getBounds()
  {
    return bounds;
  }

  public void setBounds(Rectangle bounds)
  {
    this.bounds = bounds;
  }
}

As you can see, there’s nothing much to the code but you can avoid plenty of typing and potential errors that might creep in if you had to write the code by hand. The output from more complex classes is considerably more interesting, so you might want to experiment with the XML source file to see just what can be generated. In the end, however, how useful the code is depends on how you apply the generator to your data containers.

If you feel adventurous, you could automate other aspects of JavaBean generation, including the use of BeanInfo descriptors, the generation and handling of property change events and more. Even in this form, these classes perform an interesting function that can save you considerable time that would otherwise be spent typing repeated code. When you need to develop a large number of data containers in Java this approach is an excellent solution that can also reduce the number of bugs introduced by typos along the way. I hope you find it useful.

Listing 1

import org.xml.sax.helpers.*;
import org.xml.sax.*;

public class BeanHandler extends DefaultHandler
{
  protected BeanList beanList;
  protected BeanProperties currentBean;
  
  public BeanHandler()
  {
    beanList = new BeanList();
  }
  
  public BeanList getBeanList()
  {
    return beanList;
  }
  
  public void startElement(
    String uri, String localName, String tagName,
    Attributes attributes)
  {
    if (tagName.equalsIgnoreCase("BEAN"))
    {
      currentBean = new BeanProperties(attributes);
      beanList.addBean(currentBean);
    }
    if (tagName.equalsIgnoreCase("IMPORTS"))
    {
      currentBean.setImportList(
        attributes.getValue("packages"));
    }
    if (tagName.equalsIgnoreCase("EXTENDS"))
    {
      currentBean.setExtendsClass(
        attributes.getValue("class"));
    }
    if (tagName.equalsIgnoreCase("IMPLEMENTS"))
    {
      currentBean.setInterfaceList(
        attributes.getValue("interfaces"));
    }
    if (tagName.equalsIgnoreCase("PROPERTY"))
    {
      currentBean.addProperty(new BeanProperty(attributes));
    }
  }
}

Listing 2

import java.io.*;
import java.util.*;

public class BeanWriter
{
  protected String tab = "  ";
  protected BeanList beanList;
  
  public BeanWriter(BeanList beanList)
  {
    this.beanList = beanList;
  }

  public void write()
    throws IOException
  {
    int size = beanList.size();
    for (int i = 0; i < size; i++)
    {
      writeBean(beanList.getBean(i));
    }
  }
  
  protected void writeBean(BeanProperties bean)
    throws IOException
  {
    String name = bean.getName();
    File file = new File(name + ".java");
    System.out.println("Generating: " + file);
    
    PrintWriter writer = new PrintWriter(new FileWriter(file));
    
    writeImports(writer, bean.getImportList());
    writer.println();
    writer.println("public class " + name);
    writeExtends(writer, bean.getExtendsClass());
    writeImplements(writer, bean.getInterfaceList());
    writer.println('{');
    
    int size = bean.size();
    for (int i = 0; i < size; i++)
    {
      writeDeclaration(writer, bean.getProperty(i));
    }
    writer.println();
    
    for (int i = 0; i < size; i++)
    {
      writeAccessors(writer, bean.getProperty(i));
    }
    writer.println('}');
    writer.close();
  }
  
  protected String[] splitList(String text, String delimiters)
  {
    StringTokenizer tokenizer =
      new StringTokenizer(text, delimiters, false);
    int count = tokenizer.countTokens();
    String[] list = new String[count];
    for (int i = 0; i < count; i++)
    {
      list[i] = tokenizer.nextToken().trim();
    }
    return list;
  }
  
  protected void writeImports(PrintWriter writer, String importList)
  {
    if (importList == null) return;
    String[] list = splitList(importList, ",");
    for (int i = 0; i < list.length; i++)
    {
      writer.println("import " + list[i] + ";");
    }
  }
  
  protected void writeExtends(PrintWriter writer, String extendsClass)
  {
    if (extendsClass == null) return;
    writer.println(tab + "extends " + extendsClass);
  }
  
  protected void writeImplements(PrintWriter writer, String interfaceList)
  {
    if (interfaceList == null) return;
    String[] list = splitList(interfaceList, ",");
    writer.print(tab + "implements ");
    for (int i = 0; i < list.length; i++)
    {
      if (i > 0) writer.print(", ");
      writer.print(list[i]);
    }
    writer.println();
  }
  
  protected void writeDeclaration(PrintWriter writer, BeanProperty prop)
    throws IOException
  {
    String type = prop.getType();
    String name = prop.getName();
    writer.println(tab + "protected " + type + " " + name + ';');
  }
  
  protected void writeAccessors(PrintWriter writer, BeanProperty prop)
    throws IOException
  {
    String type = prop.getType();
    String access = prop.getAccess();
      
    boolean array = type.endsWith("[]");
    boolean is = access.indexOf("is") > -1;
    boolean get = access.indexOf("get") > -1;
    boolean set = access.indexOf("set") > -1;
    
    if (is) writeIsAccessor(writer, prop);
    if (get) writeGetAccessor(writer, prop);
    if (set) writeSetAccessor(writer, prop);
    
    if (array)
    {
      writeArraySizeAccessor(writer,prop);
      if (is) writeIndexedIsAccessor(writer, prop);
      if (get) writeIndexedGetAccessor(writer, prop);
      if (set) writeIndexedSetAccessor(writer, prop);
    }
  }

  protected String entitle(String name)
  {
    String head = "" + name.charAt(0);
    String tail = name.substring(1);
    return head.toUpperCase() + tail;
  }
  
  protected void writeIsAccessor(PrintWriter writer, BeanProperty prop)
  {
    String type = prop.getType();
    String name = prop.getName();
    writer.println(tab + "public " + type + " is" + entitle(name) + "()");
    writer.println(tab + "{");
    writer.println(tab + tab + "return " + name + ';');
    writer.println(tab + "}");
    writer.println();
  }
  
  protected void writeGetAccessor(PrintWriter writer, BeanProperty prop)
  {
    String type = prop.getType();
    String name = prop.getName();
    writer.println(tab + "public " + type + " get" + entitle(name) + "()");
    writer.println(tab + "{");
    writer.println(tab + tab + "return " + name + ';');
    writer.println(tab + "}");
    writer.println();
  }
  
  protected void writeSetAccessor(PrintWriter writer, BeanProperty prop)
  {
    String type = prop.getType();
    String name = prop.getName();
    writer.print(tab + "public void set" + entitle(name));
    writer.println('(' + type + ' ' + name + ')');
    writer.println(tab + "{");
    writer.println(tab + tab + "this." + name + " = " + name + ';');
    writer.println(tab + "}");
    writer.println();
  }

  protected void writeArraySizeAccessor(PrintWriter writer, BeanProperty prop)
  {
    String type = prop.getType();
    String name = prop.getName();
    writer.println(tab + "public int get" + entitle(name) + "Size()");
    writer.println(tab + "{");
    writer.println(tab + tab + "return " + name + ".length;");
    writer.println(tab + "}");
    writer.println();
  }
  
  protected void writeIndexedIsAccessor(PrintWriter writer, BeanProperty prop)
  {
    String type = prop.getType();
    type = type.substring(0, type.length() - 2);
    String name = prop.getName();
    writer.println(tab + "public " + type + " is" +
      entitle(name) + "Item(int index)");
    writer.println(tab + "{");
    writer.println(tab + tab + "return " + name + "[index]");
    writer.println(tab + "}");
    writer.println();
  }
  
  protected void writeIndexedGetAccessor(PrintWriter writer, BeanProperty prop)
  {
    String type = prop.getType();
    type = type.substring(0, type.length() - 2);
    String name = prop.getName();
    writer.println(tab + "public " + type + " get" +
      entitle(name) + "Item(int index)");
    writer.println(tab + "{");
    writer.println(tab + tab + "return " + name + "[index];");
    writer.println(tab + "}");
    writer.println();
  }
  
  protected void writeIndexedSetAccessor(PrintWriter writer, BeanProperty prop)
  {
    String type = prop.getType();
    type = type.substring(0, type.length() - 2);
    String name = prop.getName();
    writer.print(tab + "public void set" + entitle(name) + "Item");
    writer.println("(int index, " + type + " item)");
    writer.println(tab + "{");
    writer.println(tab + tab + name + "[index] = item;");
    writer.println(tab + "}");
    writer.println();
  }
}

Listing 3

import java.io.*;
import javax.xml.parsers.*;
import org.xml.sax.helpers.*;
import org.xml.sax.*;

public class BeanGenerator
{
  public static void main(String[] args)
    throws Exception
  {
    File file = new File(args[0]);
    BeanHandler handler = new BeanHandler();
    
    SAXParserFactory factory = SAXParserFactory.newInstance();
    factory.setValidating(false);
    factory.setNamespaceAware(false);
    
    SAXParser parser = factory.newSAXParser();
    parser.parse(file, handler);
    
    BeanWriter writer = new BeanWriter(handler.getBeanList());
    writer.write();
  }
}