Template Based Wizards in JBuilder

By: Christopher Judd

Abstract: Describes how to flexibly generate source code using a template based approach and the JBuilder OpenTools API.

Prerequisites:

  • Understanding of Java and Java Syntax
  • Familiarity with JBuilder
  • Understanding of JBuilder OpenTools API is helpful but not necessary

Background

An important function of an integrated development environment (IDE) is generating repetitive and trivial source code. JBuilder supports source code generation through wizards found in the Object Gallery (see Figure 1). The Object Gallery can be located by selecting File | New from the main menu. Depending on whether JBuilder Enterprise, Professional or Personal is installed, varying wizards ranging from the basic Class and Project wizards to complex EJB and CORBA wizards will be enabled. When invoked, wizards display a series of dialogs that requests information from the developer. The information provided is then used to generate the desired source code.

JBuilder 5.0 Enterprise Object Gallery

Figure 1 - JBuilder 5.0 Enterprise Object Gallery

Problem

Like most JBuilder users, I use the Class wizard on a regular basis. The Class wizard is very effective at generating basic class skeleton code (see Listing 1). However, the results are too generic and require additional manipulation. This is especially true for organizations that have strict coding standards. For example, I often have to perform two additional operations to format the source code generated by the class wizard to comply with my organization's required standards. First, I move the generated project comments prior to the import statements since they document the project and not the class itself. Second, I add a predefined licensing agreement to the top of the java file using a code template (ctrl+j) (see Listing 2). While these may seem like petty requests, doing this for every class does add up to a significant amount of time.

package com.actsi.ot.wizard.classes;

import com.borland.primetime.wizard.BasicWizard;

/**
 * Title: Gallery Open Tools
 * Description:  Demonstration of using JBuilder OpenTools API and Apache Velocity to create flexible Object Gallery Wizards.
 * Copyright:    Copyright (c) 2001
 * Company:      ACTS, Inc. (www.actsi.com)
 * @author Christopher M. Judd (juddc20@hotmail.com)
 * @version 1.0
 */

public class ClassWizard extends BasicWizard {
  public ClassWizard() {
  }
}
Listing 1 - Default generated class code
/**
 * Copyright (c) 2001 Advanced Computing Technical Services Inc.
 * (ACTS, Inc - www.actsi.com).  All rights reserved.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL ACTS, Inc. OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
 
package com.actsi.ot.wizard.classes;

/**
 * Title:        Gallery Open Tools
 * Description:  Demonstration of using JBuilder OpenTools API and Apache
 *               Velocity to create flexible Object Gallery Wizards.
 */

import com.borland.primetime.wizard.BasicWizard;

/**
 *
 * @author Christopher M. Judd (juddc20@hotmail.com)
 * @version 1.0
 */
 
public class ClassWizard extends BasicWizard { 
  public ClassWizard() {
  }
}
Listing 2 - Generated code after making changes

The goal of this article is to demonstrate how to generate source code by creating a new JBuilder class wizard that will use a flexible template based approach. This technique can save time and generate source code that better adheres to company standards. A secondary goal is to demonstrate how JBuilder can be extended through its OpenTool API.

The flexibility of a template based approach allows developers to change the resulting source code without modifying and recompiling the wizard. This is advantageous since most wizards including those shipped with JBuilder do not provide the source code to be modified.

JBuilder OpenTools API

It's not practical for JBuilder to provide every possible wizard developers need. It would take Borland entirely too much time, money and marketing research to make JBuilder aware of every Java API. Instead, the JBuilder development team has provided a general Java IDE with the basic wizards and an extremely extensible application programming interface (API). JBuilder extensions are called OpenTools and use JBuilder's OpenTools API to produce plug-ins similar to those found in web browsers. Examples of potential OpenTools extensions would include custom wizards, nodes, menus, structure viewers and content viewers. Almost every aspect of JBuilder is accessible through the OpenTools API.

This article discusses one such OpenTool, the wizard, but if you are interested in learning more about the capabilities of the OpenTools API check out the documentation that ships with JBuilder. The documentation can be found by selecting Help | Help Topics from the JBuilder main menu. In the contents list there is a JBuilder OpenTools Document node. Double clicking the node reveals a Developing OpenTools node that describes creating OpenTools in a tutorial format and a JBuilder OpenTools API Documentation node with content in Java Doc format.

Many developers have created OpenTools to make Java development more productive. Some developers openly share them with the rest of the JBuilder community. See websites listed in Resources for available OpenTools.

I am often asked why JBuilder does not support specific features or customizations, I respond with, "Because you have not created an OpenTool for that". Borland has enabled us to make ourselves more productive. Don't be shy, take advantage of it.

Cookie Cutter Solution

A template is a document containing both static and dynamic content defined at execution time. A template engine combines both the static and dynamic content to produce the desired output. In recent years, template based approaches have become extremely popular due to their flexibility and ease of modification. This is not only true for source code generation, web development has completely changed due to template driven approaches. JSP (JavaServer Pages) (see Listing 3), ASP (Active Server Pages) and XSLT (eXtensible Stylesheet Language Templates) are all examples of such approaches that can produce HTML.

<html>
 <head>
  <title>SimpleBeanJSP</title>
 </head>
<jsp:useBean id="SimpleBeanId" scope="session" class="com.simple.beans.SimpleBean" />
<jsp:setProperty name="SimpleBeanId" property="*" />
 <body>
  <h1>JBuilder Generated JSP</h1>
   <form method="post">
    <br>Enter new value : <input name="sample"><br>
    <br><br>
    <input type="submit" name="Submit" value="Submit">
    <input type="reset" value="Reset">
    <br>
    Value of Bean property is :<jsp:getProperty name="SimpleBeanId" property="sample" />
  </form>
 </body>
</html>
Listing 3 - JavaServer Page (JSP)

For the new class wizard, the basic stub of a class will be defined as the template while a developer will define the dynamic content using the wizard dialog. This approach adapts to organizational requirements and standards by allowing developers to modify or customize the template to meet their needs.

Not so open OpenTools API

The OpenTools API does include a Template Engine. This might be obvious since JBuilder has a templates directory which contains a number of *.template files. However, this portion of the OpenTools API and the template syntax is undocumented. Historically, undocumented portions of the OpenTools API are unsupported and subject to change. This makes them especially difficult to use. Additionally, the template engine and related classes are only used by the IDL (Interface Definition Language) wizards. Some of the OpenTools API Template Engine classes are difficult to use because they appear to be tightly coupled to the IDL wizards. Another challenge I found using the OpenTools API Template Engine was conditional statements must exist on their own line causing unusual line breaks.

If you are concerned about using the OpenTools API Template Engine after the issues I have raised, skip to the next section where I introduce an alternative template engine. Otherwise Listing 4 and 5 demonstrate using the OpenTools API Template Engine and template syntax respectively.

The OpenTools API Template Engine classes can be found in the com.borland.jbuilder.template.engine package. The first step in using the engine is to create an instance of the TemplateEngine class and set the current project. Next, add key/value pairs using the TemplateEngine's put() method. The TemplateItem class identifies the template file from JBuilder's template directory, the output file and the package it belongs in. The template is then processed by invoking the executeTemplate() method on the TemplateEngine instance identifying the TemplateEngine, TemplateItem instance and whether the standard header should be included. The processing of the template returns a Node containing the resulting stream.

The OpenTools API Template Engine syntax denotes statements with []. Using the values contained with in the Template Engine's key/value pairs requires a simple call to a get() method indicating the key name. Conditional statements use an IF/ENDIF construct.

protected void finish() throws VetoException {
    super.finish();
    try {
      TemplateEngine te = new TemplateEngine();
      if(jbproject != null) {

        te.setProject(jbproject);

        // copy the properties from the dialog to the template engine.
        Properties props = classPage.getProperties();

        String key = null;
        for(Enumeration enum = props.propertyNames(); enum.hasMoreElements(); ) {
          key = (String)enum.nextElement();
          te.put(key,props.getProperty(key));
        }

        // determine file name
        String classname = props.getProperty("Class","Untitled");
        String packagename = props.getProperty("Package","");

        Url[] sourcePaths = jbproject.getPaths().getSourcePath();
        String outputFile = sourcePaths[0].getFullName()+ "/" + packagename.replace('.','/') + "/" + classname + ".java";

                              //template filename (defaults to templates directory)
                              //output filename,output package
        TemplateItem ti = new TemplateItem("class.jbte.template",
                                           outputFile,
                                           packagename);

        // Generate source code
        Node node = te.executeTemplate(te,ti,false); //template engine, template item, use standard header

        // Open node in Content Pane
        Node[] nodes = new Node[1];
        nodes[0] = node;
        Browser.getActiveBrowser().openNodes(nodes, node);

        // Refresh Project Pane
        host.getBrowser().getProjectView().refreshTree();
      }
    } catch (Exception ex) {
      if(PrimeTime.isVerbose()) {
        System.out.println(ex.getMessage());
        ex.printStackTrace();
      }
    }
  }
Listing 4 - Example of a method using the OpenTools API Template Engine (com.actsi.to.wizard.classes.jbte.ClassWizard)
[IF get("Package") != ""]
package [get("Package")];
[ENDIF]

[get("Public")] class [get("Class")] extends [get("SuperClass")]{
[IF get("Constructor") == "true"]
  public [get("Class")]() {
  }

[ENDIF]
[IF get("Main") == "true"]
  public static void main(String[] args) {
  }
[ENDIF]
}
Listing 5 - Example of OpenTools template syntax (class.jbte.template)

Generating with Velocity

Due to the challenges and objections of using the OpenTools API Template Engine, I have chosen to use a different template engine. This does require deploying an additional development environment jar, but that seems like a fair tradeoff for the benefits of ease of use and clear documentation. Using a third party template engine also provides the ability to use the templates outside of JBuilder. Initially, when I searched for a third party template engine I thought I would create a framework that would hide the details of which template engine was being used. My goal was to make it work like the JAXP API. However, since each template engine has a proprietary syntax unlike XML, switching template engines would require a rewrite of the actual template. Therefore, I did not take the time to decouple the wizard from the template engine. My hope is in the future Borland will decouple the OpenTools API Template Engine from the IDL wizards making it a multiple purpose template engine as well as provide good documentation on the API and template syntax.

As I mentioned before, there are many template based approaches in web development today. One such template engine is Velocity, an Open Source Java-based Apache Jakarta sub project. See Resources for links to download it, the documentation and related articles. It was originally implemented to support the Model 2 or Model-View-Controller (MVC) web architecture. This architecture allows for a separation of presentation, data and workflow. But because Velocity was implemented as a generic template engine, it can be used to generate just about any text output including Java source code.

Velocity Template Syntax

One of Velocity's advantages is its simple but powerful syntax. The syntax supports comments, variables, conditions, loops and includes as well as other features. See Listing 11 for a full listing of the example template that accompanies this article.

Comments

Single line comments are denoted by a ##. Everything between the ## and the end of the line is ignored by the template engine. Comments can be an important part of documenting your template. Especially if someone else will be modifying the template. In this example, I choose to document every variable passed to the template by identifying the name, type and description of the reference (see Listing 6).

## Variables:
## License String determines whether license should be displayed
## Comments String determines whether comments should be displayed
## Company String project property company
## Class String class name
## SuperClass String super class name
## Methods List of Strings list of abstract method signatures
Listing 6 - Use of Velocity comments to document template

Variables

Variables are referenced by a $variable or the formal notation ${variable}. Variables can refer to any object reference providing access to the object's attributes and methods. Variables can be passed to the template engine using the Context class (see Listing 7) or declared within the template itself with the #set directive (see Listing 8).

  Context context = TemplateFactory.getContext();
  context.put("Author",jbproject.getProperty("sys","Author",""));
  context.put("Title",jbproject.getProperty("sys","Title",""));

  Template template = TemplateFactory.getTemplate("class.template");

  FileWriter fw = new FileWriter(outputFile);

  template.merge(context,fw);
  fw.close();
Listing 7 - Use of a Context instance to set variables for the template
 #set( $Public = "public ")
Listing 8 - Setting a variable of Public with inside the template

Conditions

The #if directive can be used to conditionally perform some action within the template. Often this is used to include or exclude a portion of the output. Listing 9 demonstrates using the $License variable to determine whether the license.template should be included in the output.

#if ($License == "true")
  #include( "license.template" )
#end
Listing 9 - Use of the conditional and include directives

Loops

The #foreach directive is used to iterate through a List, Map or Array. Using Listing 10 as an example, the List $Methods is iterated though for each item in the list. During each iteration, the instance in the list is assigned to the $method variable. The $method variable can then be used like any variable.

#foreach( $method in $Methods )
  $method {
  }
#end
Listing 10 - Example of a looping directive

Includes

Velocity supports the include directive for reusing or separating portions of the template. In this example, I chose to split the license into its own template to allow it to be used by multiple source code templates (see Listing 9).

## Variables:
## License String determines whether license should be displayed
## Comments String determines whether comments should be displayed
## Constructor String determines whether the constructor should be displayed
## Main String determines whether the main method should be displayed
## Public String determines whether the class is public
## Package String package name
## Title String project title
## Description String project property description
## Version String project property version
## Author String project property author
## Copyright String project property copyright
## Company String project property company
## Class String class name
## SuperClass String super class name
## Methods List of Strings list of abstract method signatures
#if ($License == "true")
#include( "license.template" )
#end
#if ($Package != "")
package $Package;

#end
#if($Comments == "true")
## todo: get specific comments
/**
 * Title:        $Title
 * Description:  $Description
 */



/**
 *
 * @author $Author
 * @version $Version
 */

#end
#if($Public != "")
 #set( $Public = "public ")
#end
${Public}class ${Class}#if($SuperClass !="") extends ${SuperClass}#end {
#if($Constructor == "true")

  public ${Class}() {
  }

#end
#if($Main == "true")

  public static void main(String[] args) {
  }

#end
#if($Abstract == "true")
#foreach( $method in $Methods )
  $method {
  }
#end

#end
}
Listing 11 - Complete class.template file

Abracadabra

To utilize the template, the JBuilder OpenTools API will be used to create the simplest of OpenTools, the wizard. A wizard can either reside in the Object Gallery or on the Wizard menu. Because template wizards will generate new files they will most likely reside in the Object Gallery. Most of the classes related to building wizards are located in the com.borland.primetime.wizard package.

OpenTool Project Setup

Setting up a template wizard project requires configuring some libraries and setting some project properties. The library configurations should be named Velocity and JBuilder. The Velocity library should list the velocity-1.1.jar file in the class tab. To compile the project and allow for debugging, add the following jars from JBuilder's lib directory to the JBuilder library: jbuilder.jar, gnuregexp.jar, jdcl.jar lawt.jar, xml4j.jar, xerces.jar, parser.jar, jdom.jar, help.jar. Every jar in JBuilder's lib directory could be added but it is not necessary and just slows down the loading of JBuilder during debugging. If during the debugging process an existing OpenTool is needed, it will also need to be added from JBuilder's lib/ext directory.

Debugging an OpenTool involves starting a new JBuilder process from within JBuilder. It is in the new process that the OpenTool is loaded for testing. Enabling OpenTool debugging requires the setting of project properties. On the Project | Project Properties... Run tab, set the Main Class to com.borland.jbuilder.JBuilder and the Application Parameters to -verbose -nosplash. Now pressing the Run Project or Debug Project buttons will start another instance of JBuilder. Breakpoints and other debugging techniques work as normal.

TemplateFactory

Velocity's template engine requires some initialization before its use. To consolidate the initialization, I use the Factory design pattern. In the static section of the class, the file.resource.loader.path property is set to use JBuilder's template directory. Additionally, the default behavior of Velocity is to log the activities of the engine. In this case, logging is not necessary so the runtime.log.logsystem.class property is set to load the com.actsi.ot.util.EmptyLogSystem which implements Velocity's org.apache.velocity.runtime.log.LogSystem interface and ignores the calls (see Listing 12).

public class EmptyLogSystem implements LogSystem {
  /** Does nothing (like most of my code) */
  public void logVelocityMessage(int level, java.lang.String message) {}
}  
Listing 12 - com.actsi.ot.util.EmptyLogSystem ignores logging requests

The TemplateFactory also provides a simple interface for obtaining a Template and Context (see Listing 13).

public class TemplateFactory {

  // Configure Velocity Template Engine
  static {
    Properties p = new Properties();
    p.setProperty("runtime.log.logsystem.class","com.actsi.ot.util.EmptyLogSystem");
    p.setProperty("file.resource.loader.path", TemplateEngine.getTemplateDirectory());
    try {
      Velocity.init(p);
    } catch (Exception ex) {
      if (PrimeTime.isVerbose()) {
        System.out.println("Error configuring Velocity Template Engine:");
        ex.printStackTrace();
      }
    }
  }

  // Prevents class from being instanciated
  private TemplateFactory() {}

  /**
   * Template representing the template file found in the JBuilder template directory
   * @param templateName template file name
   * @return Template instance
   * @throws ParseErrorException Error parsing template file
   * @throws ResourceNotFoundException Template file not found
   * @throws Exception Not sure
   */
  public static Template getTemplate(String templateName) throws ParseErrorException, ResourceNotFoundException, Exception {
    return Velocity.getTemplate(templateName);
  }

  /**
   * Data context
   * @return Context instance
   */
  public static Context getContext() {
    return new VelocityContext();
  }
Listing 13 - Entire com.actsi.ot.util.TemplateFactory class

Wizard Classes

Creating a JBuilder wizard generally requires extending three classes: BasicWizard, WizardAction and BasicWizardPage. The BasicWizard is the concrete class that simplifies implementing the Wizard interface. In this case the class extending BasicWizard is the ClassWizard. Depending on the requirements of the wizard; the finish(), wizardCompleted() and invokeWizard() methods may need to be overridden. The wizardCompleted() method is invoked after finish() completes or the cancel button was selected. This is a good place to clean up resources. Since there are no resources to clean up in the ClassWizard the wizardCompleted() method will rely on the empty method implementation of the BasicWizard class. The invokeWizard() method is used to determine the first page of the wizard and perform some initialization. Listing 14 shows the initializations of private data members as well as the wizard's title and pages. The selecting of the initial wizard page is delegated to the superclass BasicWizard which selects the first page added using the addWizardPage() method.

  public WizardPage invokeWizard(WizardHost host) {
    this.host = host;
    Project p = host.getBrowser().getActiveProject();
    if (p instanceof JBProject) {
      jbproject = (JBProject) p;
    }

    setWizardTitle("New Class Wizard (Velocity Template Engine)");
    classPage = new ClassWizardPage(jbproject);
    addWizardPage(classPage);
    return super.invokeWizard(host);
Listing 14 - ClassWizard's invokeWizard method

Most of the real work performed by a wizard is done in the finish() method. The ClassWizard's finish() method begins by getting a reference to a Velocity Context by using the TemplateFactory. The Context reference is using to keep track of the key/value mapping that will ultimately be referred to as variables in the template. Here the context is initialized using the values set by the user through the wizard dialog as well as the project comments set during the execution of the Project Wizard. Next the class and package names are used to derive the output file name for initializing the FileWriter. To simplify acquiring the template reference use the TemplateFactory's getTemplate() method telling it the specific template file. The Template reference is responsible for combining the context and template using the merge() method (See Listing 15).

  protected void finish() throws VetoException {
    super.finish();
    try {
      Context context = TemplateFactory.getContext();

      // copy the properties from the dialog to the template engine.
      Properties props = classPage.getProperties();

      String key = null;
      for (Enumeration enum = props.propertyNames(); enum.hasMoreElements(); ) {
        key = (String) enum.nextElement();
        context.put(key, props.getProperty(key));
      }

      // copy comments to properties
      context.put("Author", jbproject.getProperty("sys", "Author", ""));
      context.put("Title", jbproject.getProperty("sys", "Title", ""));
      context.put("Description", jbproject.getProperty("sys", "Description", ""));
      context.put("Version", jbproject.getProperty("sys", "Version", ""));
      context.put("Copyright", jbproject.getProperty("sys", "Copyright", ""));
      context.put("Company", jbproject.getProperty("sys", "Company", ""));

      if (props.getProperty("Abstract").equals("true")) {
        context.put("Methods", getAbstractMethods(props.getProperty("SuperClass")));
      }

      String classname = props.getProperty("Class", "Untitled");
      String packagename = props.getProperty("Package", "");
      Url[] sourcePaths = jbproject.getPaths().getSourcePath();
      String outputFile = sourcePaths[0].getFullName() + "/" + packagename.replace('.', '/') + "/" + classname + ".java";
      Template template = TemplateFactory.getTemplate("class.template");

      FileWriter fw = new FileWriter(outputFile);

      template.merge(context, fw);
      fw.close();

      // Refresh Project Pane
      host.getBrowser().getProjectView().refreshTree();

      // Open node in Content Pane
      Node node = jbproject.getNode(new Url(new File(outputFile)));
      Node[] nodes = new Node[1];
      nodes[0] = node;
      Browser.getActiveBrowser().openNodes(nodes, node);

    } catch (Exception ex) {
      if (PrimeTime.isVerbose()) {
        System.out.println(ex.getMessage());
        ex.printStackTrace();
      }
    }
  }
Listing 15 - ClassWizard's finish method (the real work)

For the wizard to appear in the Object Gallery, com.borland.primetime.wizard.WizardAction must be extended. The subclass can be implemented as a static inner class of the wizard to prevent managing an additional Java file. Create an instance of a WizardAction using one of the many overloaded constructors to define the short text, long text, mnemonic, small icon, large icon, whether it is a gallery wizard and its category. The short text is the label displayed beneath the icon. The long text is displayed in the tool tip when the mouse pointer hovers over the icon. For a gallery wizard, the small icon is unnecessary because only the large icon is displayed. Gallery Wizards need to identify themselves as such using the galleryWizard parameter otherwise they are added to the Wizards menu. The category identifies which tab the wizard appears on. The default tab is 'New. JBuilder creates and adds custom tabs if the category does not exist. The only other requirement is to override the createWizard() method returning an instance of a class implementing the Wizard interface such as the ClassWizard.

  public final static Icon ICON_CLASSWIZARD = Icons.getIcon(com.actsi.ot.wizard.classes.ClassWizard.class, "ClassWizard.gif");

  /**
   *  Display icon in Object Gallery
   */
  public final static WizardAction WIZARD_Gallery =
    new WizardAction("Velocity Template Engine Class", 'V', "Creates a new Java class",
        BrowserIcons.ICON_BLANK,
        ICON_CLASSWIZARD,
        true) {

      protected Wizard createWizard() {
        return new ClassWizard();
      }
    };
Listing 16 - Wizard Action

To complete the wizard classes, extend BasicWizardPage to create the UI a developer will interact with. Because the BasicWizardPage is a subclass of JPanel, JBuilder's UI designer can be used to layout UI controls. Overriding the checkPage() method can be useful for validating data.

Registering

Once an OpenTool is complete, JBuilder must be told about it. Registering an OpenTool is a two step process. First, a class must implement a static initOpenTool method that calls the appropriate manager's registration method. The initOpenTool is analogous to the main method of an executable class in that it is the main entry point into the OpenTool. To register a wizard, the WizardManager's registerWizardAction must be called for JBuilder to know it needs to be added to the Object Gallery or the Wizard menu (see Listing 17).

  public static void initOpenTool(byte majorVersion, byte minorVersion) {
    if (majorVersion == PrimeTime.CURRENT_MAJOR_VERSION) {
      WizardManager.registerWizardAction(ClassWizard.WIZARD_Gallery);
    }
  }
Listing 17 - Main entry point of the Class Wizard OpenTool

Step two tells JBuilder which class or classes have the initOpenTool() method. This step is analogous to creating an executable jar by setting the Main-class attribute in the manifest file to a class that contains a main() method. Using the Main-class attribute tells the virtual machine which class to execute. There are two differences between the manifest of an executable and OpenTool jar. An executable jar can have only one Main-class attribute and a single class associated with it. While an OpenTool jar uses different attributes to indicate when the initOpenTool() method should be called and may have more then one class in the category. For a wizard the best attribute to use is OpenTools-UI. The OpenTools-UI classes have their initOpenTool() method called the first time a JBuilder user invokes the Object Gallery or selects the Wizards menu. This lazy loading prevents objects from unnecessarily being instantiated and makes JBuilder load faster. Listing 18 shows the two wizards in this project separated by spaces being initialized in the OpenTools-UI attribute. For additional manifest attributes see the JBuilder OpenTool documentation. Don't forget, as with all manifest files the last line must be a new line character. Typically, the manifest file in an OpenTool project is named classes.opentools.

OpenTools-UI: com.actsi.ot.wizard.classes.jbte.ClassWizard com.actsi.ot.wizard.classes.ClassWizard

Listing 18 - classes.opentools file

Packaging

Now that the classes, images and manifest file are complete they need to be packaged in a jar that can be easily distributed. If JBuilder Professional or Enterprise is available use the Wizards | Archive Builder... to create the jar. Otherwise, use the Jakarta Ant script that accompanies this project. To find out more about Jakarta Ant see Resources.

NOTE: The Jakarta Ant script that accompanies this article is a very generic OpenTool build script that can be use for other OpenTool projects by changing the project and classpath property in build.properties.

NOTE: Use ant ? to list the available targets in the accompanying build script.

Installation

Installing an OpenTool involves copying the OpenTool jar to JBuilder's lib/ext directory. For this project the velocity-1.1.jar file must also be copied to JBuilder's lib/ext directory. Additionally, the templates must be copied to JBuilder's templates directory. Alternatively, the Jakarta Ant script that accompanies this article contains a deploy target that performs the installation if jbuilder.dir property in build.properties is set.

Conclusion

The combination of JBuilder OpenTool wizards and templates can be a powerful method of generating other types of code besides a basic Java class. An exercise left to the reader is creating wizards that generate code for text, property, XML or other relevant files that make Java development more productive.

Resources:

By Christopher M. Judd, Judd Solutions, LLC.

Server Response from: SC2