Introduction
When we think of XSL (eXtensible Stylesheet Language), we usually think of transforming XML into HTML, WML, or other XML formats. However, it’s also possible to transform XML into virtually any format. In this article, we’ll learn how to use XSL to generate Java programs.
The following outline is a roadmap for how we’ll cover the topic:
- First, we’ll begin with a short discussion about the code generation process.
- Then, we’ll compare and contrast how code generation with XSL differs from other languages.
- Next, we’ll design an XML file representing the meta data about our Java program.
- Then, we’ll build the code generation stylesheet and review it step by step.
- Finally, we’ll wrap up by summarizing what we’ve learned.
The Code Generation Process
Let’s take a look at the code generation process. Meta data is used to describe the characteristics about the program to be generated. The meta data can be represented in various formats such as relational data, properties files, XML, or other formats. A working program serves as the model that the generated programs should emulate. A code generation program uses the meta data and code fragments from the model program to generate new programs.
Generated programs share the characteristics of the model program. If the model program is syntactically correct and well tested, the generated program will be syntactically correct and well tested. If a bug is found in the model program, it can be fixed in the code generator and all of the generated programs can be regenerated and corrected. In short, code generation can save a lot of coding and testing.
Despite the benefits of code generation, it can promote a cut and paste type of mentality. It is wise to consider generalizing behavior and using inheritance or composition as well as code generation in an overall strategy.
Another important consideration in code generation is determining whether customization of generated programs is allowed. When customization is allowed, regeneration may not be possible to pick up new features.
Code Generation With XSL vs. Other Languages
For years, every good SQL programmer and DBA has used code generation techniques to generate SQL or other programs from SQL. The reason that this technique worked so well was that relational database management systems have excellent meta data describing all of the database objects. The SQL language made it trivial to query this meta data, combine it with code fragments, and output generated programs.
Other languages, such as Perl, are also widely used to generate build scripts, code, and the like. However, when using scripting languages, there may not be a standard data format and protocol that the language is optimized to use as in the case of SQL. On the positive side of the equation, it is much easier to write modular code in Perl than SQL scripts.
XML, as with a relational database, excels at representing structured data. XML happens to be a more portable and lightweight format. Just as SQL was able to easily query the meta data from relational tables, XSL and XPath can easily query data from XML documents, combine it with code templates, and output generated programs.
Whereas SQL code generation scripts were often ugly monsters, XSL was designed to have modular templates of code that could be easily applied to nodes in the meta data document. It provides the best of both worlds. Like SQL, it is optimized for a given data format and protocol—programmatic XML parsing isn’t required. And like scripting languages such as Perl, code templates can be easily modularized.
Generating a JavaBean With XSL
To demonstrate code generation with XSL, we’ll generate a JavaBean class. The most basic definition of a JavaBean is a class with a null constructor and several properties with getter and setter methods. It’s not a complex example, but not a trivial one either. Many integrated development environments (IDE) have wizards that generate Java classes, so you are probably familiar with the concept. Once you learn the basics, you’ll be able to generate many programs using this technique.
In our code generation example, we’ll use a few features of XSL that come in handy from time to time:
- Modes will be used to apply the same nodes to different templates.
- A called template will be used for a common string manipulation routine.
JavaBean XML
Let’s begin by designing the XML meta data for our JavaBean class.
A JavaBean must contain Package, Imports, Superclass, and Properties elements. The Imports element may contain zero to n Import elements. The Properties element can contain 1 to n Property elements.
Let’s compose a sample XML stream to flesh out the details:
<?xml version="1.0" encoding="UTF-8"?> <JavaBean name="Customer"> <Package>com.developer</Package> <Imports> <Import>java.util.Collection</Import> </Imports> <Superclass name="Object"/> <Properties> <Property name="name" type="String"/> <Property name="addrln1" type="String"/> <Property name="addrln2" type="String"/> <Property name="city" type="String"/> <Property name="state" type="String"/> <Property name="zip" type="String"/> <Property name="contacts" type="Collection"/> </Properties> </JavaBean>
We begin with our JavaBean element. Its name attribute represents the class name.
The Package element represents the package for this class. Although there may be multiple Import elements under the Imports node, we only have one for this class. The Superclass of this bean is Object.
There are several Property elements under the Properties element. The Property elements will be used to generate the instance variables, accessors (getter methods), and mutators (setter methods) for our bean.
JavaBean Style Sheet
We’ve modeled the meta data representing a Java Bean in XML. Now let’s build our code generation stylesheet. We’ll take a look at the templates in this stylesheet bit by bit beginning with our root template.
<xsl:stylesheet version="1.0" xmlns_xsl="http://www.w3.org/ 1999/XSL/Transform"> <xsl:output method="text"/> <xsl:template match="/"> package <xsl:value-of select="//Package"/>; <xsl:apply-templates select="//Import"/> public class <xsl:value-of select="JavaBean/@name"/> extends <xsl:value-of select="//Superclass/@name"/> { <xsl:apply-templates select="//Property" mode="instanceVariable"/> public <xsl:value-of select="JavaBean/@name"/>() { } <xsl:apply-templates select="//Property" mode="accessor"/> <xsl:apply-templates select="//Property" mode="mutator"/> } </xsl:template>
First of all, notice that the <xsl:output method="text"/> indicates that we are outputting plain text rather than HTML or XML from this stylesheet.
The root template builds the shell of the Java program we are generating:
- We begin by creating the package statement for the program. The <xsl:value-of> element is used to “pull” the appropriate data from the meta data document, in this case the value of the Package node, into the static code fragments.
- Next, templates are applied for all of the Import elements.
- Then, the class declaration pulls the JavaBean/@name and SuperClass/@name from the metadata document. The constructor for our Java program is built using the same technique.
The template for outputting import statements is very straightforward. This template is invoked once for every Import node.
<xsl:template match="Import"> import <xsl:value-of select="."/>; </xsl:template>
Generating Instance Variables, Getters and Setters
The Property meta data is going to be used to generate three different portions of our Java program:
- The instance variables
- The accessor, or getter, methods
- The mutator, or setter, methods
Because we want to process the Property nodes three times, producing different output each time, the mode attribute is used when templates are applied to distinguish which templates get invoked.
We’re going to need a utility routine for converting lower case property names to mixed case. For example, the city property will have a corresponding getCity() and setCity() method. So, we’ll build a template to do this conversion for us and call it in the appropriate place.
<xsl:template name="initCap"> <xsl:param name="x"/> <xsl:value-of select="translate(substring($x,1,1) ,'abcdefghijklmnopqrstuvwxyz' ,'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/> <xsl:value-of select="substring($x,2)"/> </xsl:template>
The initCap template takes in a variable x as a parameter. The first character of x is extracted with substring($x,1,1). The translate function is used to convert lower case to upper case for this character. This letter is then output via <xsl:value-of>. Next, the remaining characters of the word are extracted with substring($x,2) and output with <xsl:value-of>.
Now, let’s look at the templates matching on Property nodes beginning with the instanceVariable template.
<xsl:template match="Property" mode="instanceVariable"> private <xsl:value-of select="@type"/><xsl:text> </xsl:text><xsl:value-of select="@name"/>; </xsl:template>
When templates are applied to Property nodes in instanceVariable mode, the instance variables are generated for our Java program in the format:
private String city;
Now, let’s look at the accessor template:
<xsl:template match="Property" mode="accessor"> public <xsl:value-of select="@type"/> get<xsl:call-template name="initCap"> <xsl:with-param name="x" select="@name"/> </xsl:call-template>() { return <xsl:value-of select="@name"/>; } </xsl:template>
When templates are applied to Property nodes in accessor mode, the getter methods are generated for our Java program in the format:
public String getCity() { return city; }
Notice that the initCap template is passed the name attribute of the Property element in order to output it in mixed case.
Finally, let’s look at the mutator template:
<xsl:template match="Property" mode="mutator"> public void set<xsl:call-template name="initCap"> <xsl:with-param name="x" select="@name"/> </xsl:call-template>(<xsl:value-of select="@type"/> the<xsl:call-template name="initCap"> <xsl:with-param name="x" select="@name"/> </xsl:call-template>) { <xsl:value-of select="@name"/> = the<xsl:call-template name="initCap"> <xsl:with-param name="x" select="@name"/> </xsl:call-template>; } </xsl:template>
When templates are applied to Property nodes in mutator mode, the setter methods are generated as follows:
public void setCity(String theCity) { city = theCity; }
Once again, the initCap template is used to generate mixed case. The formatting of these templates is not the most readable. However, the goal here is to make the generated program readable.
Generated Java Bean
To view the complete generated program, click here.
Think of how much time it would take to hand code this class versus the time spent to author the meta data file. And the generated code will compile and run properly the first time.
Summary
XSL can do more than transform XML into other markup languages. It is an excellent code generation language. Meta data can be quite naturally structured in XML. XSL can easily process the meta data via a “pull” or rules-based fashion and invoke code fragments in modular templates.
We developed a JavaBean generator to demonstrate the concept. XSL techniques such as modes and called routines were used to modularize the code. The code generation technique demonstrated here could also be used to generate EJB classes, JDBC code, or classes running on a framework like Struts. But the rest is up to you.
Code Examples
To download the example XML stream and stylesheet, click here.
About the Author
Jeff Ryan is an architect for Hartford Financial Services. He has eighteen years of experience designing and developing automated solutions to business problems. His current focus is on Java, XML, and Web Services technology. He may be reached at jryan@thehartford.com. |
Other Articles Written by Jeff Ryan |