Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

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
Also available in:   Russian

Activity:  28328 views
Comments:  

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.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

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=

IBM SmartCloud trial. No charge.

IBM PureSystems on a kaleideoscope background

Unleash the power of hybrid cloud computing today!


Special offers