Skip to main content

Model with the Eclipse Modeling Framework, Part 2: Generate code with Eclipse's Java Emitter Templates

Generate code with Eclipse's Java Emitter Templates

Adrian Powell, Senior Software Developer, IBM 
Adrian Powell started work with Java tooling at IBM when he joined the VisualAge for Java Enterprise Tooling team where he spent two misguided years manually writing a code generator. Since then, he has developed tools and plug-ins for almost every version of Eclipse and VisualAge for Java. Adrian is currently working in the Vancouver Centre for IBM e-Business Innovation, where he is writing his replacement.

Summary:  Eclipse's Java™ Emitter Templates (JET) is an open source tool for generating code within the Eclipse Modeling Framework (EMF). JET is similar to Java Server Pages, but is powerful and flexible enough to generate Java, SQL, and any other languages, including JSPs. This article covers how to create and configure JET and deploy it in a variety of environments.

View more content in this series

Date:  27 Apr 2004
Level:  Intermediate
Activity:  3421 views

Overview of Java Emitter Templates (JET)

Developers commonly use tools which generate repetitive code. Eclipse users are familiar with standard tools to generate for(;;) loops, main() methods, and accessor methods for selected attributes. Automating these simple, mechanical tasks speeds up development and makes our lives easier. In some cases, such as generating deploy code for J2EE servers, the code generation may save us time and it may hide implementation-specific complexity which is what makes it possible to deploy to different J2EE servers. Code generation isn't just for large tool vendors, but can be used effectively within many projects. Eclipse's Java Emitter Templates (JET), which is packaged as a part of the Eclipse Modeling Framework (EMF), is a simple and functional way to add code generation to your project. In this article, we will explore how JET can be used in a variety of environments.


What is JET?

Java Emitter Templates are very similar to Java Server Pages (JSPs). Both JETs and JSPs use the same syntax, and are compiled to Java behind the scenes. Both are used to separate the responsibility for rendering pages from the model and controller. Both accept objects passed into them as an input argument, both allow inserting string values within code ("expressions"), and allow direct use of Java code to perform loops, declare variable, or perform logical flows ("scriptlets"). Both are good ways of representing the structure of a generated object (web page, Java class, or file) while supporting customization of the details.

JETs differ from JSPs in a few key ways. In a JET, the structure of the markup may be changed to support generating code in different languages. Typically the input to a JET will be a configuration file and not user input (though there is nothing forbidding this). And also typically, JET processing will take place only once for a given workflow. These are not technical limitations, and you may find uses for JETs which are quite different.


Starting out

Creating a Template

To work with JETs, create a new Java Project, JETExample and set the source folder to be src. To enable JET for this project, right click and select Add JET Nature. This will create a templates directory off the root of your new project. The default JET configuration uses the project root as the destination for the compiled Java files. To straighten this out, open the properties window for the project, select JET Settings, and set the source container to be src. When the JET compiler runs, it will output the JET Java files into the correct source folder.

Now we are ready to create the first JET. The JET compiler creates a Java source file for each JET, so the convention is to name the template NewClass.javajet, where NewClass will be the name of whatever class will be generated. This isn't enforced, but it helps to avoid confusion.

Start by creating a new file in the templates directory called GenDAO.javajet. You will get a dialog box warning you of compile errors on line 1 column 1 of your new file. If you look closely at the details, it is telling you "The jet directive is missing". This is technically correct as we have just created an empty file, but it can be confusing and misleading. Click 'OK' to close the warning and 'Cancel' to clear the New File dialog (the file is already created). To avoid this problem from coming up again, our first task is to create the jet directive.

Every JET must start with the jet directive. This tells the JET compiler what the compiled Java template will look like (not what the template generates, just what the compiled template class looks like; the terminology is confusing, so bear with me). It also gives some of the standard Java class information. For example, we'll use the following:


Listing 1. Sample jet declaration
<%@ jet
    package="com.ibm.pdc.example.jet.gen"
    class="GenDAO"
    imports="java.util.* com.ibm.pdc.example.jet.model.*"
    %>

Listing 1 is really self-explanatory. When the JET template is compiled, it will create a Java file GenDAO in com.ibm.pdc.example.jet.gen which will import the given packages. Again, this is just what the template will look like, not what the template will generate -- that comes next. Notice that the Java file name for the JET output is defined in the jet declaration, and is unrelated to the filename. If two templates declare the same class name, then they will interfere each others' changes with no warning. This can happen if you copy and paste template files without properly modifying all of the jet declaration. Because there are warnings when you try to create new files in the template directory, copy and paste is common, so stay on your guard.

Like JSPs which get their information via pre-declared variables like session, error, context, and request, JETs use pre-declared variables to pass information into the template. JETs use only two implicit variables: stringBuffer of type StringBuffer (surprise) which is used to build the output string when generate() is called; and the argument, handily called argument of type Object. The first line of a typical JET template will be to cast this to a more appropriate class, as shown in Listing 2.


Listing 2. JET argument initialization
<% GenDBModel genDBModel = (GenDBModel)argument; %>

package <%= genDBModel.getPackageName() %>;

As you can see, the default syntax for JETs is identical to JSPs, with <%...%> used to escape code or "scriptlets", and <%= ... %> used to print the value of an expression. Like JSPs, judicious use of <% ... %> tags will allow you to add any logical loops or constructs, just as you would be able to do in any Java method. For example:


Listing 3. Scriptlets and expressions
Welcome <%= user.getName() %>!
<% if ( user.getDaysSinceLastVisit() > 5 ) { %>
Whew, thanks for coming back.  We thought we'd lost you!
<% } else { %>
Back so soon?  Don't you have anything better to do?
<% } %>

When you have completed defining your JET, save it and right click on it in the Package Explorer. Select Compile Template. If everything goes well, a new class GenDAO will be created in the com.ibm.pdc.example.jet.gen package. It just has one method on it, public String generate(Object argument) (see Listing 4), the result of which will be whatever you have defined in the javajet template file.


Listing 4. A basic JET compiled Java which prints "Hello <%=argument%>"
package com.ibm.pdc.example.jet.gen;

import java.util.*;

public class GenDAO
{
  protected final String NL = System.getProperties().getProperty("line.separator");
  protected final String TEXT_1 = NL + "Hello, ";
  protected final String TEXT_2 = NL + "\t ";

  public String generate(Object argument)
  {
    StringBuffer stringBuffer = new StringBuffer();
    stringBuffer.append(TEXT_1);
    stringBuffer.append( argument );
    stringBuffer.append(TEXT_2);
    return stringBuffer.toString();
  }
}

Breaking out common code

After writing a few templates, you might notice some common elements being repeated, for instance something as simple as adding a copyright declaration to all of your generated code. As with JSPs, this is handled by the include declaration. Place any elements you wish to include in a file, say 'copyright.inc' and then, in your javajet template, add the statement <%@ include file="copyright.inc" %>. The include file will be added completely into the compiled output, so it can reference any variables which have been declared up until that point. The extension .inc can be whatever you choose, just don't pick anything ending with jet or JET will try to compile your include file with understandably poor results.

Customizing the JET compilation

If an include file is not sufficient and you want to add additional methods or customize the generation, the simplest way is to create a new JET skeleton. A skeleton file is a template which describes what the compiled JET template will look like. The default skeleton looks like Listing 5.


Listing 5. Default JET skeleton
public class CLASS
{
    public String generate(Object argument)
    {
        return "";
    }
}

All of the import statements will go at the top, CLASS will be replaced with the name of the class that we set in the class attribute of the jet declaration, and the body of the generate() method will be replaced with the code to do all of the generation. So, to change what the compiled template code looks like, we just have to create a new skeleton file and perform whatever customization we want, but still leave these basic elements in place.

To create a custom skeleton, create a new file in the templates directory called custom.skeleton as shown in Listing 6.


Listing 6. Custom JET skeleton
public class CLASS
{
    private java.util.Date getDate() {
        return new java.util.Date();
    }
		 
    public String generate(Object argument) {
        return "";
    }
}

Then in any JET template which you want to use this custom skeleton, add the attribute skeleton="custom.skeleton" to the jet declaration in the javajet file.

Alternatively, you could have this extend a baseclass as public class CLASS extends MyGenerator, and add all necessary helper methods in the base class. This is a little cleaner, as it keeps the common code common, and it makes development easier as the JET compiler doesn't always give the nicest error messages.

Custom skeletons also allows you to change the method name and argument list for the generate() method, so a sufficiently perverse developer can make very customized templates. I was slightly inaccurate when I said that JET replaces the body of generate() with the code to generate. It actually just replaces the body of the last method declared in the skeleton, so careless code changes to the skeleton can be a good way to hurt yourself and confuse your coworkers.


Working with CodeGen

As you can see, once the template has been compiled, it is a standard Java class. To use this in an application, you need only distribute the compiled template class and not the javajet template. Alternatively, you may wish to give the user the ability to make changes to the template and at startup time automatically recompile the template. The Eclipse Modeling Framework (EMF) does this, so anyone with the need or interest can go into plugins/org.eclipse.emf.codegen.ecore/templates and change how the EMF generates their model or editor.

If you only wish to only distribute the compiled template class, the build process may be automated. So far, we've only seen how to compile the JET templates using the JET Eclipse plugin, but we can script this or do the generation as an ANT task.

Runtime template compilation

To give the end users the power to customize your templates (and the frustration of debugging them), you can choose to compile your templates at runtime. There are several ways of doing this and for the first pass we'll use the utility class org.eclipse.emf.codegen.jet.JETEmitter which abstracts away some of the details for us. The obvious (but generally wrong) code is quite simple, as shown in Listing 7.


Listing 7. Simplistic JETEmitter usage (often wrong)
String uri = "platform:/templates/MyClass.javajet";
JETEmitter jetEmitter = new JETEmitter( uri );
String generated = jetEmitter.generate( new NullProgressMonitor(), new Object[]{argument} );

You'll find the first problem if you try to run this in a standard main() method. The generate() method will throw a NullPointerException because JETEmitter assumes that it is being called by a plugin. In its initialization, it calls CodeGenPlugin.getPlugin().getString(), which will fail as CodeGenPlugin.getPlugin() will be null.

The simple solution of turning this code into a plugin will work, but not completely. The current implementation of JETEmitter creates a hidden project called .JETEmitters which will contain the generated code. However, JETEmitter does not add the classpath of the plugin to this new project, so the generated code will not compile if it references any objects outside of the standard Java library. The early builds for version 2.0.0 appear to be addressing this issue, but as of early April, they still don't have this fully implemented. To work around this problem, you must extend the JETEmitter class to override the initialize() method and add in your own classpath entries. Remko Popma has written a good example jp.azzurri.jet.article2.codegen.MyJETEmitter(see Resources) which will handle this until JET adds this feature properly. The modified code looks like Listing 8.


Listing 8. Proper JETEmitter call
String base = Platform.getPlugin(PLUGIN_ID).getDescriptor().getInstallURL().toString();
String uri = base + "templates/GenTestCase.javajet";
MyJETEmitter jetEmitter = new MyJETEmitter( uri );
jetEmitter.addClasspathVariable( "JET_EXAMPLE", PLUGIN_ID);
String generated = jetEmitter.generate( new NullProgressMonitor(), 
   new Object[]{genClass} ); 		 

Command line

Happily compiling a JET from the command line isn't troubled by the classpath issues which make compilation from a main() method so difficult. In the case above, the difficulty wasn't compiling the javajet to Java code but compiling this Java code to .class. From the command line, we have much more control over the classpath so breaking the steps up makes everything smooth and easy. The only trick is that we need to run Eclipse in a "headless" (without the user interface) mode, but even this has been taken care of. To compile a JET, look at plugins/org.eclipse.emf.codegen_1.1.0/test. This directory contains sample scripts for Windows and Unix, and a sample JET to verify.

As an ANT task

There is an ANT Task, jetc, which may take either a single template attribute, or a fileset for multiple templates. Once you configure the classpath of the jetc task, compilation of the template will be as smooth as with standard Java classes. See the Resources for more information on how to acquire and use the task.


Customizing JET to generate JSPs

As a default, JET uses "<%" and "%>" to markup their template, but this is the same markup that JSPs use. If you wish to generate JSPs, you will have to change the delimiters. You do this in the jet declaration at the head of the template, using the startTag and endTag attributes, as in Listing 9. In this case, I've used "[%" and "%]" for the start and end delimiters, and as you can see, the "[%= expression %]" is treated properly, just like "<%= expression %>" before.


Listing 9. JET template with modified tags
<%@ jet 
    package="com.ibm.pdc.example.jet.gen" 
    class="JspGen"
    imports="java.util.* " 
    startTag = "[%"
    endTag = "%]"
    %>

[% String argValue = (String)argument; %]
package [%= argValue %];


Tying it all together

It's an unfortunate truth that much code is reused through copy-and-paste, on the big scale and the small. Many times the solution isn't obvious, and even object-oriented languages may not help. In the cases where the same basic code pattern is repeated, but with small implementation changes, placing the common code in a template and then using JET to generate the variations is an excellent way to save mechanical time and effort. JSPs have forged this path already, so JET borrows heavily from their success. JETs use the same basic layout and semantics as JSPs, but allow greater customization. Templates may be precompiled for greater control, or distributed and compiled at runtime for greater flexibility.

In the next article, we will look at making the generated code ready for Prime Time by allowing users to customize the code and still allow regeneration by integrating our changes on a field-by-field or method-by-method basis, or even more fine-grained levels. We will also bundle it all up in a plugin to show one way of integrating code generation into your development process.


Resources

Learn

Get products and technologies

Discuss

About the author

Adrian Powell started work with Java tooling at IBM when he joined the VisualAge for Java Enterprise Tooling team where he spent two misguided years manually writing a code generator. Since then, he has developed tools and plug-ins for almost every version of Eclipse and VisualAge for Java. Adrian is currently working in the Vancouver Centre for IBM e-Business Innovation, where he is writing his replacement.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source, Java technology
ArticleID=11914
ArticleTitle=Model with the Eclipse Modeling Framework, Part 2: Generate code with Eclipse's Java Emitter Templates
publish-date=04272004
author1-email=
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers