<html> | |
<head> | |
<meta HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=windows-1252"> | |
<title>JET Tutorial Part 2 (Write Code that Writes Code)</title> | |
<link rel="stylesheet" href="default_style.css"> | |
<style type="text/css"> | |
<!-- | |
.jet { background-color: #FFFFCC} | |
pre { color: #4444cc} | |
--> | |
</style> | |
</head> | |
<body LINK="#0000ff" VLINK="#800080" bgcolor="white"> | |
<div align="right"> <font face="Times New Roman, Times, serif" size="2">Copyright | |
© 2003 <a href="http://www.azzurri.jp" target="_blank">Azzurri Ltd.</a></font> | |
<table border=0 cellspacing=0 cellpadding=2 width="100%"> | |
<tr> | |
<td align=LEFT valign=TOP colspan="2" bgcolor="#0080C0"><b><font face="Arial,Helvetica"><font color="#FFFFFF"> Eclipse | |
Corner Article</font></font></b></td> | |
</tr> | |
</table> | |
</div> | |
<div align="left"> | |
<h1><img src="images/Idea.jpg" height=86 width=120 align=CENTER></h1> | |
</div> | |
<p> </p> | |
<h1 ALIGN="CENTER">JET Tutorial Part 2 (Write Code that Writes Code)</h1> | |
<blockquote> | |
<b>Summary</b> | |
<br> | |
<p>In Part 2 of this JET (Java Emitter Templates) tutorial, we will take a look | |
at the JET engine API. You will learn how to write plug-ins that use the classes | |
in the JET package to generate Java source code.</p> | |
<p>As a real-world example, we will create a plug-in that takes user input and | |
generates a Typesafe Enumeration class. The generated source code is based | |
on a JET template that can be distributed with the plug-in, allowing users | |
of the plug-in to customize the generated code by editing the template.</p> | |
<p>This article also provides a short reference to the JET API.</p> | |
<p><b>By Remko Popma, Azzurri Ltd., remko.popma@azzurri.jp</b><br> | |
<font size="-1">August 26, 2003</font></p> | |
</blockquote> | |
<hr width="100%"> | |
<H2>Source Code </H2> | |
<p>To run the example or view the source for code for this article you can unzip | |
<a href="jp.azzurri.jet.article2.typesafe_enum_1.0.0.zip">jp.azzurri.jet.article2.typesafe_enum_1.0.0.zip</a> | |
into your <i>plugins/</i> subdirectory. To use the example plug-in, you must | |
have the <a href="http://eclipse.org/emf/" target="_blank">EMF</a> plug-in installed. | |
I am using version 1.1.0 build 20030620_1105VL. <b>Note:</b> <i>More recent | |
versions of EMF will support UTF-8 templates and supply APIs for handling templates | |
in other encodings.</i></p> | |
<H2>Introduction </H2> | |
<center> | |
<TABLE border="1" cellspacing="0" width="80%" cellpadding="3"> | |
<TBODY> | |
<TR> | |
<TD> | |
<p><b>Translation vs. Generation</b></p> | |
<p>An aspect of JET templates that is at first confusing is that generating | |
text takes two steps: translation and generation. The first step is | |
translating the template to a template implementation class. The second | |
step is using this template implementation class to generate the text. | |
</p> | |
<p>If your goal with JET is to generate Java source code, it can be confusing | |
that the template translation step also results in Java source code. Remember | |
that this source code is <i>not</i> the generated text. The source code | |
that is the result of the translation step is simply another form of the | |
template. </p> | |
<p>If you have used JSP and servlets before, you can think of a JET template | |
as being equivalent to a JSP page. A JET template is translated to a | |
template implementation class, just like a JSP page is translated to | |
a servlet. The second step, where the template implementation class | |
generates text, is equivalent to the servlet creating and returning | |
HTML.</p> | |
</TD> | |
</TR> | |
</TBODY> | |
</TABLE> | |
</center> | |
<p><a href="../Article-JET/jet_tutorial1.html">Part | |
1</a> of this tutorial introduced JET templates and explained how you can add | |
the JET Nature to a workspace project to have the JET Builder automatically | |
translate templates in your project to template implementation classes. </p> | |
<p>In part 2 of this tutorial, we will focus on writing a plug-in that uses the | |
classes in the JET package to generate Java source code. A plug-in that generates | |
text from a JET template can no longer rely on the JET Nature and JET Builder | |
to automatically translate templates. This is because JET Nature and JET Builder | |
operate only on workspace projects, not on plug-ins. Plug-ins need to use the | |
classes in the JET package to translate their templates. </p> | |
<p>The next section will discuss some of the classes in the <code>org.eclipse.emf.codegen</code> | |
package. We will see what the steps are to generate source code with JET, and | |
how the JET engine classes fit in. If you are anxious to see some code that | |
shows how to use these classes in practice, you can go straight to <a href="#example_plugin">A | |
Plug-in that Generates Source Code</a>. </p> | |
<H2>Some JET Classes</H2> | |
<p>In this section, we will take a closer look at some of the classes in the JET | |
package. They can roughly be divided into two groups: </p> | |
<UL> | |
<li>Lower-level classes dealing with the nuts and bolts of translating a template | |
to a template implementation class. The <code>JETCompiler</code> class brings | |
all these lower-level classes together to provide a single API for template | |
translation. </li> | |
<li>Higher-level classes that build on top of <code>JETCompiler</code> to accomplish | |
user tasks. In Part 1 of this tutorial, we have already seen <code>JETNature</code> | |
and <code>JETBuilder</code> at work. Other high-level classes are <code>CodeGen</code> | |
and <code>JETEmitter</code>. </li> | |
</UL> | |
<p>The lower-level classes are not discussed in-depth in this article. For a description | |
of all classes in the <code>org.eclipse.emf.codegen</code> plug-in, see the <a href="#jet_api_overview">JET | |
API Overview</a> section below. In the rest of this section, we will focus on | |
a few of the higher-level classes.</p> | |
<H4><code>org.eclipse.emf.codegen.jet.JETCompiler</code></H4> | |
<p><code>JETCompiler</code> is the core class for template translation. This class | |
is responsible for translating templates to the Java source code of a template | |
implementation class. The actual translation is delegated to other classes in | |
the same package. Clients create a JETCompiler object for a particular template | |
and then call the <code>parse</code> method followed by the <code>generate</code> | |
method to write the Java source code for the resulting template implementation | |
class to a specified stream.</p> | |
<H4><code>org.eclipse.emf.codegen.jet.JETEmitter</code></H4> | |
<p><code>JETEmitter</code> provides a convenient high-level API for users of the | |
JET package. The <code>generate</code> method of this class combines template | |
translation and text generation into a single step. By taking care of the gory | |
details of translating templates and compiling the Java source code of the translated | |
template implementation class, JETEmitter lets you focus on the final generator | |
output.</p> | |
<p>Another way of looking at JETEmitter is that it abstracts away the translation | |
step and lets you pretend that you can directly generate text with a template. | |
Following the <a href="http://www.joelonsoftware.com/articles/LeakyAbstractions.html" target="_blank">Law | |
of Leaky Abstractions</a>, we cannot always get away with this, and the <a href="#jetemitter_gotchas">JETEmitter | |
Gotchas</a> section below points to a few places where you have to be careful.</p> | |
<p>JETEmitter is the class we will be using in our plug-in, so we will go into | |
a little more detail here.</p> | |
<p>A JETEmitter object is constructed with the uri of the template used to generate | |
text. Any type of uri is acceptable as long as a protocol handler is available. | |
This means that <code>file:/</code> uris, <code>ftp:/</code> uris and <code>http:/</code><i> | |
</i>uris can all be used. Eclipse adds special protocol handlers for <code>platform:/base</code>/, | |
<code>platform:/plugin/</code>, <code>platform:/fragment/</code> and <code>platform:/resource/</code> | |
uris, so plug-ins can use a uri like <code>platform:/resource/myproject/myfolder/mytemplate.jet</code> | |
to specify a template file.</p> | |
<p>In our example plug-in, we will distribute our template file together with our | |
plug-in, so the template file will be located in a <i>myplugin/templates/</i> | |
folder under the Eclipse <i>plugins/</i> folder. The following code can then | |
be used to locate and generate a template from this folder:</p> | |
<PRE> String pluginId = "myplugin.id"; | |
String base = Platform.getPlugin(pluginId).getDescriptor().getInstallURL().toString(); | |
String uri = base + "templates/myTemplate.javajet"; | |
JETEmitter emitter = new JETEmitter(uri); | |
String generatedText = emitter.generate(new Object[] {parameter});</PRE> | |
<p>After constructing a JETEmitter object, clients then call <code>generate</code> | |
on it to generate text. The <code>generate</code> method will perform the following | |
steps:</p> | |
<OL> | |
<LI>Create a project called <i>.JETEmitters</i> in the workspace | |
<li>Prepare this project by giving it the Java Nature and adding classpath variables | |
to its classpath </li> | |
<li>Translate the template to a template implementation Java source file in | |
the <i>.JETEmitters</i> project </li> | |
<li>Build the project to compile the template implementation source code to | |
a Java <i>.class</i> file </li> | |
<li>Call the <code>generate</code> method on the translated Java template implementation | |
class and return the generated text as a String</li> | |
</OL> | |
<p>Our example plug-in will use JETEmitter and save the generated text to a Java | |
source file in the workspace. The figure below shows the steps for generating | |
source code using JETEmitter. </p> | |
<p><IMG alt="Using JETEmitter to generate text from a plugin" src="images/jetemitter.gif"> </p> | |
<H3><a name="jetemitter_gotchas"></a>JETEmitter Gotchas</H3> | |
<p>The JETEmitter class combines template translation and text generation into | |
a single step, which makes it a very convenient tool. However, it is important | |
that you know what takes place under the hood, otherwise you might be in for | |
some nasty surprises. This section highlights some "gotchas" that | |
I ran into, so that you don't make the same mistakes.</p> | |
<H4>1. Plug-in Initialization Required </H4> | |
<p>It is not easy to use JET outside of Eclipse. JET is designed to run only as | |
a workspace application. Any application using JET must minimally run as an | |
Eclipse "headless" application so that plug-in initialization takes | |
place. (The term headless refers to running Eclipse without the user interface.)</p> | |
<p>This means that using JETEmitter from a simple standalone application (a standard | |
Java class with a <code>main</code> method) <i>will not work</i>:</p> | |
<PRE> // This fails: cannot use JETEmitter from a standalone application | |
public static void main(String[] args) { | |
JETEmitter emitter = new JETEmitter("/myproject/templates/HelloWorld.txtjet"); | |
// this will throw a NullPointerException | |
String result = emitter.generate(new NullProgressMonitor(), {"hi" }); | |
System.out.println(result); | |
</PRE> | |
<p>Note that this is not a restriction of just the JETEmitter class, many of the | |
classes in the <code>org.eclipse.emf.codegen</code> plug-in have dependencies | |
on other plug-ins. The <a href="#appendix">Appendix</a> section below has more | |
details on using JET from standalone applications.</p> | |
<p>In the rest of this article we will assume that our code is running from inside | |
a plug-in.</p> | |
<H4>2. Classloader Issues </H4> | |
<p>You may get a NoClassDefFoundError when you pass a custom object as the argument | |
to the <code>JETEmitter.generate</code> method. This can happen if the object | |
you pass as the argument is not one of the java "bootstrap" classes (the bootstrap | |
classes are the runtime classes in rt.jar and internationalization classes in | |
i18n.jar).</p> | |
<p>To prevent this error you must specify the classloader of your plug-in when | |
using JETEmitter. If no classloader is specified, JETEmitter uses the classloader | |
of its own class, which is usually the classloader for the <code>org.eclipse.emf.codegen</code> | |
plug-in, and this classloader can't see much. In recent versions of EMF (since | |
version 1.1.0 build 20030527_0913VL), JETEmitter has a constructor that takes | |
a classloader argument.</p> | |
<p>Note that another way to specify a classloader is to subclass JETEmitter in | |
your own project; if no classloader is specified, JETEmitter will use the classloader | |
of this subclass. (If you are using an older version of EMF, there are no constructors | |
that take a classloader argument and you will have no choice but to subclass | |
JETEmitter in your own project.)</p> | |
<p>The example below shows an action class that translates and invokes a selected | |
template using JETEmitter. The example shows how a JETEmitter can be constructed | |
<img src="images/tag_1.gif" width="24" height="13">with a classloader parameter or by <img src="images/tag_2.gif" width="24" height="13">constructing | |
an anonymous subclass.</p> | |
<PRE>package jp.azzurri.jet.article2.actionexample; | |
// imports omitted | |
public class EmitAction implements IActionDelegate { | |
protected ISelection selection; | |
public void selectionChanged(IAction action, ISelection selection) { | |
this.selection = selection; | |
action.setEnabled(true); | |
} | |
public void run(IAction action) { | |
List files = (selection instanceof IStructuredSelection) | |
? ((IStructuredSelection) selection).toList() | |
: Collections.EMPTY_LIST; | |
for (Iterator i = files.iterator(); i.hasNext();) { | |
IFile file = (IFile) i.next(); | |
IPath fullPath = file.getFullPath(); | |
String templateURI = "platform:/resource" + fullPath; | |
<b>Class</b><b>Loader classloader = getClass().getClassLoader();</b> | |
<img src="images/tag_1.gif" width="24" height="13"> JETEmitter emitter = new JETEmitter(templateURI, <b>classloader</b>); | |
// <b>or: use an anonymous subclass</b> | |
<img src="images/tag_2.gif" width="24" height="13"> // emitter = <b>new JETEmitter(templateURI) {}</b>; // notice the brackets | |
try { | |
IProgressMonitor monitor = new NullProgressMonitor(); | |
String[] arguments = new String[] { "hi" }; | |
String result = emitter.generate(monitor, arguments); | |
saveGenerated(result, file); | |
} catch (Exception e) { | |
throw new RuntimeException(e); | |
} | |
} | |
} | |
// saveGenerated method omitted | |
} | |
</PRE> | |
<H4>3. Classpath Issues </H4> | |
<p>JETEmitter translates your templates to Java source files in the <i>.JETEmitters</i> | |
project, and invokes the JavaBuilder to compile these source files. If your | |
templates use classes that are not standard Java classes, or not in the EMF | |
plug-in, you will need to add these classes to the classpath of the <i>.JETEmitters</i> | |
project, or the JavaBuilder cannot compile the template implementation source | |
files.</p> | |
<p>JETEmitter has a protected method, <code>setVariable</code>, that creates a classpath | |
variable. (Classpath variables are workspace-wide and can be seen with Window | |
> Preferences > Java > Classpath Variables.) The <code>setVariable</code> | |
method loops through the runtime libraries of the specified plug-in, and creates | |
a classpath variable with the specified name that points to the last CODE-type | |
runtime library of the specified plug-in.</p> | |
<p>If all the classes your template needs can be found in the runtime library | |
of a plug-in, you probably want to use the <code>setVariable</code> method to create | |
a variable pointing to that <i>.jar</i> file.</p> | |
<p>The next step is to add this variable to the classpath of the <i>.JETEmitters</i> | |
project. Unfortunately, JETEmitter does not provide a method to do this, but | |
instead requires you to create a subclass and override the initialize method, | |
for example, to add the required variable(s). You will need to add your variable(s) | |
to the <i>.JETEmitters</i> project classpath without breaking the existing classpath.</p> | |
<p><i>This is one place where the JET package is a little EMF-centric; when the | |
.JETEmitters project is created, the JETEmitter class creates a number of EMF-only | |
classpath variables and adds them to the classpath of the .JETEmitters project. | |
There is an outstanding EMF feature request for an API for adding custom classpath | |
variables to the .JETEmitters project.</i></p> | |
<p> The example source for this article contains a JETEmitter subclass, <code>MyJETEmitter</code>, | |
which extends JETEmitter to provide a public API for adding classpath entries | |
to the <i>.JETEmitters</i> project. Basically, this class loops through the | |
existing classpath entries of the project, and compares this existing classpath | |
with the added entries, making sure that only missing entries are added. See | |
the example source for this article for details.</p> | |
<H2><a name="example_plugin"></a>A Plug-in that Generates Source Code </H2> | |
<p>In this part of the JET Tutorial, we will write an Eclipse plug-in that uses | |
a JET template to generate Java source code for typesafe enumerations.</p> | |
<p>Our plug-in has to perform the following tasks:</p> | |
<OL> | |
<LI>Collect user input values for the variables in our template: the class name, | |
the type and name of the attributes of the typesafe enumeration class, and | |
the values of these attributes for each instance. We will write a simple GUI | |
to collect these values. | |
<li>Translate the JET template file to a Java template implementation class</li> | |
<li>Invoke the template implementation class with an object that contains the | |
user input values collected by the GUI</li> | |
<li>Save the resulting generated source code to a location obtained from the | |
GUI</li> | |
</OL> | |
<p>In the following sections we will go through the steps above one by one.</p> | |
<H3><a name="typesafe_enum_example"></a>Typesafe Enumerations</H3> | |
<p>Let's have a look at a typesafe enumeration class to see what kind of source | |
code we want to generate. The Digit class below is an example typesafe enum.</p> | |
<PRE> // an example typesafe enum | |
package x.y.z; | |
public class Digit { | |
<img src="images/tag_1.gif" width="24" height="13"> public static final Digit ZERO = new Digit(0, "zero"); | |
public static final Digit ONE = new Digit(1, "one"); | |
public static final Digit TWO = new Digit(2, "two"); | |
public static final Digit THREE = new Digit(3, "three"); | |
// ... | |
public static final Digit NINE = new Digit(9, "nine"); | |
private static final Digit[] ALL = | |
{ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE}; | |
<img src="images/tag_2.gif" width="24" height="13"> private final int value; | |
private final String name; | |
private Digit(int value, String name) { | |
this.value = value; | |
this.name = name; | |
} | |
<img src="images/tag_3.gif" width="24" height="13"> public static Digit lookup(int key) { | |
for (int i = 0; i < ALL.length; i++) { | |
if (key == ALL[i].getValue()) { return ALL[i]; } | |
} | |
// lookup failed: | |
// we have no default Digit, so we throw an exception | |
<img src="images/tag_4.gif" width="24" height="13"> throw new IllegalArgumentException("No digit exists for " + key); | |
} | |
public int getValue() { return value; } | |
public int getName() { return name; } | |
public String toString() { return getName(); } | |
}</PRE> | |
<p>Let's take a closer look at this class. First of all, the Digit class has several | |
<b><img src="images/tag_1.gif" width="24" height="13">instances</b> - the constants | |
ZERO, ONE, TWO, etc. Each instance is defined by its Java variable name, "ZERO", | |
"ONE", "TWO"..., and the values for each <b><img src="images/tag_2.gif" width="24" height="13">attribute</b> | |
of the enumeration class. Most typesafe enums have one or more attributes. The | |
Digit class has two attributes: a <code>value</code> integer and a <code>name</code> | |
String.</p> | |
<p>Our example Digit class also has a <b><img src="images/tag_3.gif" width="24" height="13"><code>lookup</code></b> | |
method, which returns the instance whose <code>value</code> attribute equals | |
the specified int parameter. A lookup method introduces the concept of <b>key | |
attributes</b>. Many typesafe enums have one or more attributes that uniquely | |
distinguish one instance from another.</p> | |
<p><i>Note that key attributes are not required: the Java VM guarantees that every | |
newly constructed object is unique, so it is possible to have typesafe enumerations | |
that have no attributes at all, and simply distinguish their instances with | |
the <b>==</b> instance identity operator. This works fine, but often it is convenient | |
to have a key attribute that uniquely identifies an instance, and a <code>lookup</code> | |
method that finds an instance for a specified key value.</i></p> | |
<p>Our template does have a <code>lookup</code> method, so we need to decide what | |
to do if <img src="images/tag_4.gif" width="24" height="13">no instance is found | |
for the specified key value. Basically there are three options: throwing an | |
Exception, returning a designated "default" instance, or returning | |
<code>null</code>. Which option is best depends on the application in which | |
the class is used, so we should probably let the user decide.</p> | |
<p>Now that we've studied typesafe enums in more detail, let's summarize what | |
is customizable in a typesafe enumeration:</p> | |
<UL> | |
<LI>package name | |
<li>class name </li> | |
<li>attributes, where each attribute </li> | |
<UL> | |
<LI>has a type | |
<li>has a name </li> | |
<li>may be a key attribute </li> | |
</UL> | |
<li>instances, where each instance </li> | |
<UL> | |
<LI>has a name | |
<li>has a value for every attribute </li> | |
<li>may be the default instance to return for failed lookups</li> | |
</UL> | |
</UL> | |
<H3><a name="typesafe_enum_model"></a>A Simple Typesafe Enumeration Model </H3> | |
<p>A simple model for the customizable parts of a typesafe enum could look something | |
like this:</p> | |
<P></P> | |
<TABLE border="1" cellspacing="0"> | |
<TBODY> | |
<TR> | |
<TD><code>TypesafeEnum</code></TD> | |
</TR> | |
<TR> | |
<TD><code>getInstances() : Instance[]<br> | |
getAttributes() : Attribute[]<br> | |
getKeyAttributes() : Attribute[]<br> | |
getDefaultInstance() : Instance<br> | |
getPackageName() : String<br> | |
getClassName() : String</code></TD> | |
</TR> | |
</TBODY> | |
</TABLE> | |
<P></P> | |
<TABLE border="1" cellspacing="0"> | |
<TBODY> | |
<TR> | |
<TD><code>Instance</code></TD> | |
</TR> | |
<TR> | |
<TD><code> getName() : String<br> | |
getAttributeValues() : Properties<br> | |
getAttributeValue(Attribute) : String<br> | |
isDefault() : boolean</code></TD> | |
</TR> | |
</TBODY> | |
</TABLE> | |
<P></P> | |
<TABLE border="1" cellspacing="0"> | |
<TBODY> | |
<TR> | |
<TD><code>Attribute</code></TD> | |
</TR> | |
<TR> | |
<TD><code> getName() : String<br> | |
getType() : String<br> | |
isKey() : boolean</code></TD> | |
</TR> | |
</TBODY> | |
</TABLE> | |
<P></P> | |
<p>In the next section we will use these classes to convert our Digit class to | |
a JET template for typesafe enumerations.</p> | |
<H3><a name="typesafe_enum_template"></a>A Typesafe Enumeration Template </H3> | |
<p>Now that we have a model, we can take our Digit class and replace all Digit-specific | |
code with JET scriptlets and expressions that call our model classes. The resulting | |
template could look something like this:</p> | |
<PRE> <span class="jet"><span class="jet"><%@ jet package="translated" imports="java.util.* article2.model.*" class="TypeSafeEnumeration" %></span></span> | |
<span class="jet"><span class="jet"><% TypesafeEnum enum = (TypesafeEnum) argument; %></span></span> | |
package <span class="jet"><%=enum.getPackageName()%></span>; | |
/** | |
* This final class implements a type-safe enumeration | |
* over the valid instances of a <span class="jet"><%=enum.getClassName()%></span>. | |
* Instances of this class are immutable. | |
*/ | |
public final class <span class="jet"><%=enum.getClassName()%></span> { | |
<span class="jet"><% for (Iterator i = enum.instances(); i.hasNext(); ) { %></span> | |
<span class="jet"><% Instance instance = (Instance) i.next(); %></span> | |
// <b>instance definition</b> | |
public static final <span class="jet"><%=enum.getClassName()%></span> <span class="jet"><%=instance.getName()%></span> = | |
<img src="images/tag_1.gif" width="24" height="13"> new <span class="jet"><%=enum.getClassName()%></span>(<span class="jet"><%=instance.constructorValues()%></span>); | |
<span class="jet"><% } %></span> | |
<span class="jet"><% for (Iterator i = enum.attributes(); i.hasNext(); ) { %></span> | |
<span class="jet"><% Attribute attribute = (Attribute) i.next(); %></span> | |
// <b>attribute declaration</b> | |
<img src="images/tag_2.gif" width="24" height="13"> private final <span class="jet"><%=attribute.getType()%></span> m<span class="jet"><%=attribute.getCappedName()%></span>; | |
<span class="jet"><% } %></span> | |
/** | |
* Private <b>constructor</b>. | |
*/ | |
<img src="images/tag_3.gif" width="24" height="13"> private <span class="jet"><%=enum.getClassName()%></span>(<span class="jet"><%=enum.constructorParameterDescription()%></span>) { | |
<span class="jet"><% for (Iterator i = enum.attributes(); i.hasNext(); ) { %></span> | |
<span class="jet"><% Attribute attribute = (Attribute) i.next(); %></span> | |
<img src="images/tag_4.gif" width="24" height="13"> m<span class="jet"><%=attribute.getCappedName()%></span> = <span class="jet"><%=attribute.getUncappedName()%></span>; | |
<span class="jet"><% } %></span> | |
} | |
// <b>getter accessor methods</b> | |
<span class="jet"><% for (Iterator i = enum.attributes(); i.hasNext(); ) { %></span> | |
<span class="jet"><% Attribute attribute = (Attribute) i.next(); %></span> | |
/** | |
* Returns the <span class="jet"><%=attribute.getName()%></span>. | |
* | |
* @return the <span class="jet"><%=attribute.getName()%></span>. | |
*/ | |
public <span class="jet"><%=attribute.getType()%></span> get<span class="jet"><%=attribute.getCappedName()%></span>() { | |
return m<span class="jet"><%=attribute.getCappedName()%></span>; | |
} | |
<span class="jet"><% } %></span> | |
// lookup method omitted... | |
}</PRE> | |
<p>As you can see, the template calls some methods that were not in the simple | |
model we introduced earlier. We have added a few convenience methods, like the | |
<code><img src="images/tag_2.gif" width="24" height="13">Attribute.getCappedName()</code> | |
and <code><img src="images/tag_4.gif" width="24" height="13">getUncappedName()</code> | |
methods. Such methods help to keep the template simple.</p> | |
<p>Another example of methods we added to the model are the <code><img src="images/tag_3.gif" width="24" height="13">TypesafeEnum.constructorParameterDescription()</code> | |
method and the <code><img src="images/tag_1.gif" width="24" height="13">Instance.constructorValues()</code> | |
method. The implementation of the <code>constructorValues</code> method is shown | |
below. </p> | |
<pre>// class Instance | |
/** | |
* Convenience method that returns the attribute values of this instance, | |
* in the order expected by the constructor of this instance. | |
* | |
* @return a comma-separated list of all attribute values of this instance, | |
* formatted like <code>attrib1-value, attrib2-value (, ...)</code> | |
*/ | |
public String constructorValues() { | |
StringBuffer result = new StringBuffer(); | |
for (Iterator i = getType().attributes(); i.hasNext(); ) { | |
Attribute attribute = (Attribute) i.next(); | |
result.append(getAttributeValue(attribute)); | |
if (i.hasNext()) { | |
result.append(", "); | |
} | |
} | |
return result.toString(); | |
} | |
</pre> | |
<p>The <code>constructorValues</code> method loops through the attributes of the | |
typesafe enum, looks up the value for each attribute in the instance, and concatenates | |
these values into a string, separated by commas. For example, in our <tt>Digit</tt> | |
typesafe enum class above, this method would return <tt>"0, \"zero\""</tt> | |
for the "ZERO" instance. </p> | |
<p>We could have looped through the attribute values in the template, but that | |
would have made the template much more difficult to read. Pushing this logic | |
into the model made the template more readable and easier to maintain. On the | |
other hand, we lost some flexibility because users cannot customize that logic | |
anymore by editing the template. This is a trade-off you have to make. Which | |
is better depends on your template and your application.</p> | |
<H3><a name="gui"></a>A GUI to Collect User Input </H3> | |
<p>Now that we have a model and a template, we still need two more pieces to finish | |
our plug-in: we need a GUI to collect values from the user to populate our model | |
with, and we need to invoke our template with the populated model to generate | |
source code and save this source code to a location in the workspace.</p> | |
<p>Let's start with the GUI. The workbench provides a few wizards that do something | |
similar to what we have in mind, for example the New Class, New Interface and | |
New JUnit TestCase wizards. It probably makes sense to have our GUI look similar | |
to these wizards and make it accessible from the standard menu and toolbar locations.</p> | |
<p>Our wizard has three pages. The first page, shown below, looks like a simplified | |
version of the New Class wizard. In fact, we are using the same framework that | |
the New Class wizard uses, the <code>org.eclipse.jdt.ui.wizards</code> package. | |
In the first page, we collect the package name and the class name of the typesafe | |
enum, and the location where the result should be saved.</p> | |
<p><IMG alt="GUI wizard page one: class, package and location of the typesafe enum" src="images/enum_gui_page1.gif"> | |
</p> | |
<P></P> | |
<p>Our second page collects information on the attributes of the typesafe enum | |
class. Every attribute has a name and a type, and may be one of the key attributes. | |
Our second wizard page is shown below:</p> | |
<p><IMG alt="GUI wizard page two: attributes of the typesafe enum" src="images/enum_gui_page2.gif"></p> | |
<P></P> | |
<p>Our third and last wizard page, shown below, collects information on the instances | |
of the typesafe enum. The user inputs the instance name, and for each instance | |
provides values for all attributes.</p> | |
<p>Finally, one of the instances may be the "default" instance, which | |
is the instance returned by the <code>lookup</code> method if no instance was | |
found for the specified key attribute values.</p> | |
<p><IMG alt="GUI wizard page three: instances of the typesafe enum" src="images/enum_gui_page3.gif"></p> | |
<H3><a name="invoke_plugin_template"></a>Invoking the Template </H3> | |
<p>Now that we have a GUI to populate our model, we can finally use what we learned | |
in the first part of this article, and generate source code with our template.</p> | |
<p>When a user presses Finish on the wizard, the <code>performFinish</code> method | |
in our wizard is called. The code below shows how we <img src="images/tag_1.gif" width="24" height="13">use | |
a custom subclass of JETEmitter to <img src="images/tag_2.gif" width="24" height="13">add | |
the jar file of our plug-in to the classpath of the <i>.JETEmitters</i> project | |
before we <img src="images/tag_3.gif" width="24" height="13">call generate on | |
the JETEmitter. The generated typesafe enum source code is <img src="images/tag_4.gif" width="24" height="13">saved | |
to the location in the workspace that the user specified.</p> | |
<PRE> // class NewTypesafeEnumCreationWizard | |
protected void finishPage(IProgressMonitor monitor) | |
throws InterruptedException, CoreException { | |
String pluginId = "jp.azzurri.jet.article2.typesafe_enum"; | |
String base = Platform.getPlugin(pluginId).getDescriptor().getInstallURL().toString(); | |
String relativeUri = "templates/TypeSafeEnumeration.javajet"; | |
<img src="images/tag_1.gif" width="24" height="13"> MyJETEmitter emitter = new MyJETEmitter(base + relativeUri); | |
<img src="images/tag_2.gif" width="24" height="13"> emitter.addClasspathVariable("JET_TUTORIAL", pluginId); | |
TypesafeEnum model = mPage1.getTypesafeEnumModel(); | |
IProgressMonitor sub = new SubProgressMonitor(monitor, 1); | |
<img src="images/tag_3.gif" width="24" height="13"> String result = emitter.generate(sub, new Object[] { model }); | |
monitor.worked(1); | |
<img src="images/tag_4.gif" width="24" height="13"> IFile file = save(monitor, result.getBytes()); | |
selectAndReveal(file); | |
openResource(file); | |
}</PRE> | |
<H3><a name="pluginxml"></a>Registering our Wizard </H3> | |
<p>Our final code snippet below shows the part of our <code>plugin.xml</code> configuration | |
file where we register our wizard as a contribution to the workbench.</p> | |
<PRE> <extension point="org.eclipse.ui.newWizards"> | |
<wizard | |
<img src="images/tag_1.gif" width="24" height="13"> javatype="true" | |
name="Typesafe Enum" | |
icon="icons/newenum_wiz.gif" | |
category="org.eclipse.jdt.ui.java" | |
class="jp.azzurri.jet.article2.ui.NewTypesafeEnumCreationWizard" | |
id="jp.azzurri.jet.article2.ui.NewTypesafeEnumCreationWizard"> | |
<description> | |
Create a Typesafe Enumeration | |
</description> | |
</wizard> | |
</extension> | |
</PRE> | |
<p>Now our wizard is activated when users select File > New > Other > | |
Java > Typesafe Enum from the workbench, as shown in the image below. </p> | |
<p><IMG alt="Typesafe Enum wizard shows up in the New creation wizard" src="images/new_creation_wizard.gif"> | |
</p> | |
<p>Note that we set the <code><img src="images/tag_1.gif" width="24" height="13">javatype</code> | |
attribute to true in the wizard extension element in the <code>plugin.xml</code> | |
file. This will cause our wizard to show up as an action on the toolbar in the | |
Java Perspective, as shown in the image below.</p> | |
<p><IMG alt="Typesafe Enum wizard shows up as an action on the toolbar in the Java Perspective" src="images/new_toolbar.gif"> | |
</p> | |
<P></P> | |
<H2><a name="conclusion"></a>Conclusion </H2> | |
JET can be a great help for applications that need to generate text. Templates | |
are as much of an improvement to code generation as JSP pages were to old style | |
servlets. | |
<p>When using JET, you need to decide whether you want to distribute your templates | |
with your application, or distribute only the template implementation classes.</p> | |
<p>If your goal is to simplify the text generation capabilities of your application, | |
then using JET Nature and JET Builder to automatically translate your templates | |
is a good choice. See <a href="../Article-JET/jet_tutorial1.html">JET Tutorial | |
Part 1</a> for details. In that case you only need to distribute the translated | |
template implementation classes with your application, not the templates themselves.</p> | |
<p>On the other hand, if it is important for your application that users have | |
ultimate control over the generated text, you may want to distribute the template | |
files themselves with your application. In that case, you will need to translate | |
these templates every time you generate text. The plug-in we wrote in this article | |
is an example of this type of application.</p> | |
<p>This article explained what classes are available in the JET package to achieve | |
this and showed how to use these classes with an Eclipse plug-in. The appendix | |
below provides an overview of the JET API and shows how it can be used in headless | |
or standalone applications.</p> | |
<H2><a name="appendix"></a>Appendix</H2> | |
<H3><a name="jet_api_overview"></a>JET API Overview </H3> | |
<b>Package org.eclipse.emf.codegen</b> | |
<P></P> | |
<TABLE border="1" cellspacing="0"> | |
<TBODY> | |
<TR> | |
<TD><b>Class</b></TD> | |
<TD><b>Description</b></TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>CodeGen</code></TD> | |
<TD> | |
<p>The <code>CodeGen</code> class can translate a JET template to Java source | |
code and optionally merge the template implementation Java source code | |
with an existing Java class. <code>CodeGen</code> can be used as an Eclipse | |
headless application. The <code>run</code> method expects a String array parameter | |
of two or three elements: </p> | |
<ul> | |
<li> the uri of the template to translate </li> | |
<li>the target path where the translation result should be saved </li> | |
<li>an optional <code>JMerge</code> control model file specifying how to merge | |
the new translation result with the source code of an existing Java | |
class</li> | |
</ul> | |
</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>CodeGenPlugin</code></TD> | |
<TD>The plug-in class for the JET package.</TD> | |
</TR> | |
</TBODY> | |
</TABLE> | |
<P></P> | |
<b>Package org.eclipse.emf.codegen.jet</b> | |
<P></P> | |
<TABLE border="1" cellspacing="0"> | |
<TBODY> | |
<TR> | |
<TD><b>Class</b></TD> | |
<TD><b>Description</b></TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>IJETNature</code></TD> | |
<TD>Interface extending <code>org.eclipse.core.resources.IProjectNature</code>. | |
Defines some of the properties that a JET nature has. Implemented by <code>JETNature</code>. | |
Used as a filter for project property pages by the <code>org.eclipse.emf.codegen.ui</code> | |
plugin.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETAddNatureOperation</code></TD> | |
<TD>A <code>org.eclipse.core.resources.IWorkspaceRunnable</code> for adding the | |
JET nature to a project in the workspace. Used by the <code>AddJETNatureAction</code> | |
in the <code>org.eclipse.emf.codegen.ui</code> plug-in.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETBuilder</code></TD> | |
<TD>This class extends <code>org.eclipse.core.resources.IncrementalProjectBuilder</code>. | |
When its <code>build</code> method is invoked, it delegates to <code>JETCompileTemplateOperation</code> | |
to translate all templates in the workspace project that have been changed | |
since the previous build. Templates must be located in one of the folders | |
specified as Template Containers in the JET Nature of the project.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETCharDataGenerator</code></TD> | |
<TD>Responsible for a part of the template translation process. Generates | |
strings for the character data present in the template file. Used by <code>JETCompiler</code>.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETCompiler</code></TD> | |
<TD>This is the core class for template translation. This class is responsible | |
for translating templates to the Java source code of a template implementation | |
class. The actual translation is delegated to other classes in this package. | |
A <code>JETParser</code> is used to parse the template into template elements. | |
<code>JETCompiler</code> implements the <code>JETParseEventListener</code> interface | |
and registers itself with the parser to be notified when the parser recognizes | |
a template element. For every recognized template element, <code>JETCompiler</code> | |
uses a <code>JETGenerator</code> to translate the template element to Java source | |
code. When the template parsing is complete, <code>JETCompiler</code> uses a | |
<code>JETSkeleton</code> to assemble the Java source code elements to a single | |
compilation unit (a Java class).</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETCompileTemplateOperation</code></TD> | |
<TD>This class implements <code>org.eclipse.core.resources.IWorkspaceRunnable</code> | |
so it can execute as a batch operation within the workspace. This operation | |
takes a workspace project, one or more Template Containers and optionally | |
a list of specific template files as constructor parameters. When its <code>run</code> | |
method is invoked, it uses a <code>JETCompiler</code> to translate the template | |
files in the specified workspace project folders to Java source files for | |
template implementation classes. This operation can optionally be configured | |
to trigger a complete build of the project when it is finished to compile | |
the Java source files to <i>.class</i> files.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETConstantDataGenerator</code></TD> | |
<TD>Responsible for a part of the template translation process. Extends <code>JETCharDataGenerator</code> | |
to generate constant declarations for the strings with character data present | |
in the template file.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETCoreElement</code></TD> | |
<TD>Interface for core JET syntax elements (directive, expression, scriptlet | |
and quote-escape). Used by <code>JETParser</code>.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETEmitter</code></TD> | |
<TD>This class provides a convenient high-level API for users of this package. | |
The <code>generate</code> method of this class translates a template to Java | |
source code, compiles this source code to a template implementation class, | |
asks the template class to generate text and finally returns the generated | |
result. This class creates a Java project called <i>.JETEmitters</i> in | |
the workspace, translates the template into this project, and simply calls | |
<code>build</code> on the <i>.JETEmitters</i> project to compile the source | |
code. If translation or compilation fails, a <code>JETException</code> is thrown. | |
A template implementation Java class is "executed" by calling its <code>generate</code> | |
method.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETException</code></TD> | |
<TD>Extends <code>org.eclipse.core.runtime.CoreException</code>, but provides | |
more convenient constructors.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETExpressionGenerator</code></TD> | |
<TD>Responsible for a part of the template translation process. Extends <code>JETScriptletGenerator</code> | |
to translate JET expressions (<code><%= ... %></code> stuff) to Java source | |
code.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETGenerator</code></TD> | |
<TD>Interface for generators: classes that know how to translate part of a | |
JET template to a Java source code element.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETMark</code></TD> | |
<TD>A state object used by the <code>JETParser</code> to mark points in the JET | |
character input stream, and delegate the processing of parts of the stream | |
to other objects.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETNature</code></TD> | |
<TD> | |
<p>This class implements <code>IJETNature</code> so that it can configure | |
a workspace project with the JET Nature. When this nature is added to | |
a project, it adds a JET Builder to the front of the build spec of the | |
project. This nature defines two properties: </p> | |
<ul> | |
<li> Template Containers - a list of folders in the project that contain | |
the JET templates to translate. </li> | |
<li>Source Container - the target folder in which to save translated template | |
implementation Java classes. </li> | |
</ul> | |
<p>These properties are used by the JET Builder when performing a build.</p> | |
</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETParseEventListener</code></TD> | |
<TD>Interface for objects that know how to process parts of a JET character | |
input stream.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETParser</code></TD> | |
<TD>The main parser class. Has several inner classes for recognizing core | |
JET syntax elements (directive, expression, scriptlet and quote-escape). | |
When a core JET syntax element is recognized, the actual processing of the | |
element is delegated to a <code>JETParseEventListener</code>.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETReader</code></TD> | |
<TD>An input buffer for the JET parser. Provides a <code>stackStream</code> method | |
that others can call with the character stream to an include file. Also | |
provides many other convenience methods for the parser.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETScriptletGenerator</code></TD> | |
<TD>Responsible for a part of the template translation process. Translates | |
JET scriptlets (<code><% ... %></code> stuff) to Java source code.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JETSkeleton</code></TD> | |
<TD>This class provides an interface for assembling Java source code elements | |
into a single Java compilation unit (a Java class). Java source code elements | |
are assembled according to a class skeleton definition. A skeleton can be | |
used to add boilerplate code to a translated template implementation class. | |
This class provides a default custom template implementation class skeleton | |
definition, but can also assemble Java elements using a custom skeleton. | |
The actual parsing and generation of Java source code is delegated to classes | |
in the <code>org.eclipse.jdt.core.jdom</code> package.</TD> | |
</TR> | |
</TBODY> | |
</TABLE> | |
<P></P> | |
<b>Package org.eclipse.emf.codegen.jmerge</b> | |
<P></P> | |
<TABLE border="1" cellspacing="0"> | |
<TBODY> | |
<TR> | |
<TD><b>Class</b></TD> | |
<TD><b>Description</b></TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JControlModel</code></TD> | |
<TD>A control model that provides dictionaries and rules to drive a merge | |
process.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JMerger</code></TD> | |
<TD>A class for merging Java source files. Uses classes in the <code>org.eclipse.jdt.core.jdom</code> | |
package to parse the source code. This class can be used by application | |
code, but can also be run as an Eclipse headless application.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>JPatternDictionary</code></TD> | |
<TD>A dictionary of signatures and JDOM nodes.</TD> | |
</TR> | |
<TR> | |
<TD valign="top"><code>PropertyMerger</code></TD> | |
<TD>A class for merging property files. This class can be used by application | |
code, but can also be run as an Eclipse headless application.</TD> | |
</TR> | |
</TBODY> | |
</TABLE> | |
<P></P> | |
<H3><a name="codegen_headless_application"></a>Running CodeGen as an Eclipse Headless | |
Application </H3> | |
<p>The <code>org.eclipse.emf.codegen.CodeGen</code> class can translate a JET template to Java source code | |
and optionally merge the template implementation Java source code with an existing | |
Java class. CodeGen can be used as an Eclipse headless application ("headless" | |
means that the Eclipse GUI does not start). The <code>plugins/org.eclipse.emf.codegen/test</code> | |
folder in your Eclipse installation contains some scripts for launching the | |
CodeGen class as an Eclipse headless application. These scripts are in Unix | |
format.</p> | |
<p><img src="images/win_only.gif" width="49" height="13"> Below is an example | |
script for Windows. Note that we pass two arguments to the CodeGen class:</p> | |
<UL> | |
<li>the <img src="images/tag_1.gif" width="24" height="13">uri of the template | |
to translate </li> | |
<li>the <img src="images/tag_2.gif" width="24" height="13">target path where | |
the translation result should be saved </li> | |
</UL> | |
<p>If the target path already contains a previous translation result, and you | |
want to merge the new translation result with the existing one, you can specify | |
a <code>JMerge</code> control model file as the third argument. The <code>plugins/org.eclipse.emf.codegen/test</code> | |
folder in your Eclipse installation contains an example <code>merge.xml</code> file.</p> | |
<PRE> @echo off | |
set ECLIPSE_HOME=C:\eclipse-2.1\eclipse | |
set WORKSPACE=%ECLIPSE_HOME%\workspace | |
set OPTS=-Xmx900M -Djava.compiler=NONE -verify -cp %ECLIPSE_HOME%\startup.jar | |
set MAIN=org.eclipse.core.launcher.Main -noupdate -data %WORKSPACE% | |
<img src="images/tag_1.gif" width="24" height="13">set TEMPLATE_URI=test.javajet | |
<img src="images/tag_2.gif" width="24" height="13">set TARGET_FOLDER=C:\temp\jetstandalone\MyProject | |
set ARGUMENTS=%TEMPLATE_URI% %TARGET_FOLDER% | |
echo Shut down Eclipse before running this script. | |
java %OPTS% %MAIN% -application org.eclipse.emf.codegen.CodeGen %ARGUMENTS% | |
</PRE> | |
<H3><a name="jetc"></a>jetc: An ANT Task for Translating JET Templates Outside | |
of Eclipse </H3> | |
<p>Author: Knut Wannheden (knut.wannheden at paranor.ch) </p> | |
<p>Binary: <A href="jetc-task.jar">jetc-task.jar</A>. </p> | |
<p>The source: <A href="JETCTask.java" target=_blank>JETCTask.java</A>. </p> | |
<p>Some notes: </p> | |
<UL> | |
<LI>In the <code><taskdef/></code> you have to specify the classpath to include | |
both this task as well as the jars of a number of Eclipse plug-ins (see the | |
example buildfile). </LI> | |
<li>There are two ways to tell the jetc task which template(s) it should translate: | |
</li> | |
<UL> | |
<LI>Use a nested fileset to specify the directory containing the template | |
files. </LI> | |
<li>Specify the template uri in the "template" attribute of the task. When | |
used this way, the task supports the "class" and "package" attributes which | |
overload the JET directives "class" and "package" in the template. It can | |
also be used if the JET directive is missing altogether. Use this attribute | |
when you want to specify the class to generate outside the template. </li> | |
</UL> | |
</UL> | |
<p>Here's a simple Ant buildfile (the taskdef classpath assumes you have Eclipse | |
2.1 and EMF 1.1.0): </p> | |
<PRE> <project default="jetc_multiple_templates"> | |
<property name="eclipse.plugins.dir" location="C:/eclipse-2.1/eclipse/plugins"/> | |
<taskdef name="jetc" classname="ch.paranor.epla.structure.JETCTask"> | |
<classpath> | |
<pathelement location="jetc-task.jar"/> | |
<fileset dir="${eclipse.plugins.dir}"> | |
<include name="org.eclipse.core.boot_2.1.0/boot.jar"/> | |
<include name="org.eclipse.core.resources_2.1.0/resources.jar"/> | |
<include name="org.eclipse.core.runtime_2.1.0/runtime.jar"/> | |
<include name="org.eclipse.emf.codegen_1.1.0/runtime/codegen.jar"/> | |
<include name="org.eclipse.jdt.core_2.1.0/jdtcore.jar"/> | |
</fileset> | |
</classpath> | |
</taskdef> | |
<!-- Usage example 1: --> | |
<!-- Specify the template file in the "template" attribute. --> | |
<!-- You can use the "class" and "package" attributes to override the --> | |
<!-- "class" and "package" attributes in the template file. --> | |
<target name="jetc_single_template"> | |
<mkdir dir="jet-output"/> | |
<jetc template="test.xmljet" package="com.foo" class="Test" destdir="jet-output"/> | |
<javac srcdir="jet-output" destdir="classes"/> | |
</target> | |
<!-- Usage example 2: --> | |
<!-- Translate a bunch of template files at once. --> | |
<!-- You cannot use the "class" and "package" attributes when using a fileset. --> | |
<target name="jetc_multiple_templates"> | |
<mkdir dir="jet-output"/> | |
<jetc destdir="jet-output"> | |
<fileset dir="jet-templates" includes="*.*jet"/> | |
</jetc> | |
<javac srcdir="jet-output" destdir="classes"/> | |
</target> | |
</project></PRE> | |
<H2>Resources </H2> | |
<p><a href="http://developer.java.sun.com/developer/Books/shiftintojava/page1.html#replaceenums" target="_blank">Substitutes | |
for Missing C Constructs</a> (By Joshua Bloch)</p> | |
<a href="http://www.javaworld.com/javaworld/javatips/jw-javatip122.html" target="_blank"> | |
Java Tip 122: Beware of Java typesafe enumerations</a> (By Vladimir Roubtsov)<br> | |
<p><a href="http://www.javaworld.com/javaworld/javatips/jw-javatip133.html" target="_blank">Java | |
Tip 133: More on typesafe enums</a> (By Philip Bishop)</p> | |
<p><a href="http://www.eclipse.org/emf/">http://www.eclipse.org/emf/ </a></p> | |
<p><small>Java and all Java-based trademarks and logos are trademarks or registered trademarks of Sun | |
Microsystems, Inc. in the United States, other countries, or both.</small></p> | |
</BODY> | |
</HTML> |