Extending VisualAge for Java: Tool Integration

Note: This article is the first draft of a chapter in my upcoming book, Effective VisualAge for Java. Please let me know if you think something is missing or incorrect! scott@javadude.com

For version 2.0 of VisualAge for Java, IBM added a tool integration API. This API allows you to write java code that controls VisualAge for Java, exporting and importing code, generating new methods, deleting others, versioning and displaying code. While the Tool API is not yet complete, the current implementation can provide hours of good clean fun, and save you quite a bit of work while developing other applications.

This chapter covers two aspects of extension in VisualAge for Java. The largest of these is the Tool API, so we will discuss that last. The simpler, feature integration, provides you a way to distribute applications and libraries with an easy setup inside VisualAge for Java. Using features and tool integration, you can provide greater functionality inside VisualAge for Java, and distribute that functionality to coworkers and friends.

Features - Bundles of Joy

Simply put, features are repository export files with a nice installation description. You can export any projects you would like as a repository file, describe where any contained beans should be placed in the beans palette, and zip it up. You deliver it to a friend who unzips it, starts VisualAge for Java, and asks to install the feature. Poof! The project appears in his workspace, and the beans appear in the palette.

Creating features

Creating features is very simple. We have already discussed exporting code as a repository in chapter 6: Integrating with the File System. You will use this skill to create much of the feature package. As an example, we look at the Box Beans feature. We developed the classes for the Box Beans in project "JavaDude Box Beans" (under domain name javadude.com), and placed the resource files in the appropriate project resources directory.

When developing feature beans, it is very important to provide useful information in the BeanInfo for the beans, and you should provide icons for the beans. (The icons are 16x16 or 32x32 GIFs.) You should also provide adequate documentation on the feature, usually in the form of javadoc comments. We will walk through the steps necessary to create the feature bundle, assuming the code, BeanInfo, and documentation comments are in place.

Step 1: Export the Projects

The first thing you need to do is export the projects that you require for this feature. These projects should be ones that are unique to this feature; if you have a common project that several features require, you should treat that common project as a separate feature, listing it as a pre-requisite for this feature. We will discuss how to do that later. You need to export the projects as a repository file.

NOTE
Recall that in order to perform a repository export, the project must be versioned!

The feature will exist in a special directory in the VisualAge for Java installation. IBM calls this directory the feature's base directory, naming it c:\IBMVJava\ide\features\<feature-name> (assuming you installed VisualAge foe Java 3.0 on your C drive. The path is similar for other platforms.)

The <feature-name> part of the directory must be unique, so a good choice is the name of one of the packages in the feature, replacing dots with dashes. For this example, we called the main package com.javadude.boxbeans, so the feature base directory is c:\IBMVJava\ide\features\com-javadude-boxbeans.

We must export the required projects to a file named projects.dat in the feature's base directory. You cannot use any other name or VisualAge will ignore your feature! For this example, we export project JavaDude Box Beans to repository file c:\IBMVJava\ide\features\com-javadude-boxbeans\projects.dat. You can only have a single version of any project in this repository file! Be careful; you should delete the file before performing another export to it!

WARNING
If you uninstall VisualAge for Java and delete the product directory, your feature bundle will be gone! We strongly recommend that you keep a zip of each feature outside of the VisualAge for Java installation directory so you can easily add the feature to a new installation of the product. Otherwise, you will need to recreate the feature bundle.

Step 2: Copy the Project Resources

Next, we need to ensure that all the resources the feature requires are present. The easiest way to do this is to copy the appropriate project_resources directories to the right directory. We create a new directory called project_resources under the base directory, then, copy the resource directories for each project to it. For this example, we create directory c:\IBMVJava\ide\features\com-javadude-boxbeans\project_resources and copy c:\IBMVJava\ide\project_resources\JavaDude Box Beans into that directory. Note that we copy the entire directory, not its contents. The result is directory c:\IBMVJava\ide\features\com-javadude-boxbeans\project_resources\JavaDude Box Beans and its contents.

You must copy each of the project resource directories to the feature's project resources directory. When you add the feature to your workspace, VisualAge copies the contents of these directories into the corresponding project resource directories. Make sure these directories are current under the feature or you may lose changes to your resource files!

Step 3: Create Help HTML

The easiest way to do this is to run javadoc on the projects, specifying <feature base>/doc as the target directory. After generating the HTML, you should copy the c:\IBMVJava\ide\javadoc\images directory into your <feature base>/doc directory.

If you do not have appropriate javadoc comments, or have better documentation available as a separate page, you can simply add your HTML documentation to the <feature base>/doc directory.

NOTE
Features do not require help pages, but we strongly recommend them to help the folks who use your bundled beans. You were nice enough to bundle them, you might as well go the extra step to document them, eh?
In addition, you can put the HTML into any directory under the <feature base>, you will just need to use that directory when specifying the help page location.

As of the general release of version 3.0, VisualAge for Java does not list the help page for features. We have reported this bug to IBM and they are working on it. We recommend that you do provide the help pages, as a patch may correct the problem. Just make sure you tell your users where they can find the documentation (in the feature base directory.)

Step 4: Create the Control File

Each feature must have a control file. The control file describes the feature and how to install it in VisualAge for Java. You must always name this file default.ini and must reside directly in the feature base directory. The file has the following format. Note that the order in which these entries appear is not important.

Name=feature name

Version=version id string

Help-Item=menu text,HTML file name

Palette-Items=category1,class1,class2,...,classN ; category2,class1,...

Prereq-Features=base dir1,base dir2,...

Note that VisualAge for Java only requires the name and version lines. For this example, the control file looks as follows:

Name=Box Beans

Version=1.2.2

Help-Item=Box Beans,doc/packages.html

Palette-Items=AWT,com.javadude.boxbeans.BoxAdapter,       \

                  com.javadude.boxbeans.HorizontalStrut,  \

                  com.javadude.boxbeans.VerticalStrut,    \

                  com.javadude.boxbeans.HorizontalGlue,   \

                  com.javadude.boxbeans.VerticalGlue;     \

              Swing,com.javadude.boxbeans.BoxAdapter,     \

                    com.javadude.boxbeans.HorizontalStrut,\

                    com.javadude.boxbeans.VerticalStrut,  \

                    com.javadude.boxbeans.HorizontalGlue, \

                    com.javadude.boxbeans.VerticalGlue

The Palette-Items must either all appear on a single line or be followed by backslash characters as above. Note the grouping of the palette items. In this example, we add the same four beans to the AWT and Swing palettes. We do this to allow the user to easily use them from either palette page.

Step 5: Delete the Projects from Your Workspace

Delete the feature projects from your workspace. This enables you to truly test the feature, and see if it properly modifies the palette. If you do not delete the projects from your workspace, VisualAge for Java does not allow you to add the features.

Step 6: Shutdown and Restart VisualAge for Java

Note that you should see a small dialog stating that VisualAge is installing your new feature as it starts up again. This does not mean that it added the feature to your workspace. It simply means the feature is available for inclusion.

VisualAge for Java now knows about the new feature, so were ready to add it to the workspace!

Step 7: Add the Feature

There are two ways to add the feature in VisualAge for Java. You can use the Quick Start menu (available from the File menu or by pressing F2), or you can choose the Available... page of the beans palette. If you choose Quick Start, you select the Add Feature option from the Features category. Selecting Add Feature from Quick Start or Available... from the beans palette presents a list of features that you can add. Any features that you have already added do not appear in the list.

For this example, we select "Box Beans 1.2.2" from the list. VisualAge adds the appropriate projects to your workspace, adds the beans to the palette, and adds any prerequisite features.

Step 8: I Say Zip it! Zip it Good!

Finally, you should zip the feature base directory so you can keep a safe copy of it and distribute it to other users. Some people like to zip just the base directory, and instruct users to unzip it into c:\IBMVJava\ide\features, while others like to make a copy of the entire directory structure in the zip file (just the paths leading to the features directory, not their contents) and instruct the users to unzip it to the C:\ drive.

Both methods are acceptable, just be sure to tell the user exactly what to do! Make sure that the entire directory is in the zip, including the projects.dat, default.ini,.help files, and any resources the feature requires.

Features -- Making Lives Easier Since 1998

Feature bundling is an easy and convenient way to distribute your applications to other users. It is a nice touch, making it easier for your users to install your library of beans.

Note that if you plan for others to use your beans in other environments, you must provide alternate packaging. We recommend that you package the beans in a JAR file for other IDE usage.

Tool Integration

Features are the simplest form of VisualAge for Java code integration. While they provide additional beans that you can use when designing new applications, they cannot control the IDE or modify your code. To provide this control, VisualAge for Java includes a Tool API that gives your Java code a good deal of control.

Using the Tool API, you can version, export, import, execute, delete, and generate code. You can also add objects to the workspace, and ask the user to select which objects to work with. The interface is flexible, but not complete. While it exposes a great deal of control, there are still several missing functions, like being able to modify the visual design of an application. Nonetheless, the Tool API is quite powerful and you can create some very interesting plug-ins for VisualAge for Java without much effort.

We will start by explaining how you create, use, and install tools. Pretend for a moment that you have already written a tool, and just need to set it up. After that, we explain how to perform several tasks in your code.

Setting up Tools

Tool distribution is very similar to feature distribute. Each tool has a base directory, which contains the code for the tool, resources, and a control file. The biggest difference is that instead of providing a repository file, you export the code to a directory. You cannot use a JAR file or repository here!

For this example, we look at the AutoGut tool. AutoGut is a simple tool that creates an interface by gutting an existing class. It essentially copies the method declarations, gutting the methods bodies (hence the name of the tool.) Suppose AutoGut exists in the WorkSpace as project "JavaDude AutoGut". We follow a series of steps, similar to those for creating a feature.

Step 1: Add the Tool API Feature

To run tools inside VisualAge for Java, you must add the Tool API Feature to the workspace. You do this by selecting File->Quick Start (or pressing F2) to see the Quick-Start dialog. Then select Features in the left pane, and Add Feature in the right pane. After you press enter, you will see a list of available features including the Tool API (unless you have already installed it.) The Tool API feature appears as IBM IDE Utility class libraries in this list.

This will add the tool libraries to your workspace. You need these to develop tools in the IDE. Note that you do not need to have these libraries in your workspace to execute tools from the menus or quick-start dialog.

Step 2: Export the Code

Perform a directory export of at least the class files of your tool code, as well as any necessary resource files. You can export the source if you like, but it is not required. The code must reside in a tool-specific base directory in c:\IBMVJava\ide\tools\<tool base>. We use the same convention for tool base-directory names as we did for features: pick a package in the feature and replace its dots with dashes. For this example, our directory looks like c:\IBMVJava\ide\tools\com-javadude-vaj-autogut. Use this base directory as the target of the export. VisualAge for Java will create the appropriate subdirectories under it.

Steps 3: Copy Resources and Help

Copy the resource and help files into the tool's base directory as you would for a feature. The only difference here is that the tool help link actually works.

Step 4: Create the Control File

The tool's control file is similar to that for a feature, with a bit more information. Like a feature's control file, you must name the file default.ini and place it in the tool's base directory. The file has the following format. Note that the order in which these entries appear is not important.

Name=feature name

Version=version id string

Help-Item=menu text,HTML file name

Menu-Group=menu group name

Menu-Items=menu item text,class,context ; menu item text, ...

Action-Items=menu op name,class,context; menu op name, ...

Quick-Start-Group=quick start category

Quick-Start-Items=quick start text,app class; quick start text, ...
NOTE
If you want separators between menu items, use a hyphen ("-") as the menu name and class to execute.

Note that VisualAge for Java only requires the name and version, but you must specify locations to run the tool to make it useful.

NOTE
Your tool can run from any or all of these locations, and you can use different classes for different contexts if you like.

Sample control files appear after we discuss tool contexts.

Tool Contexts

A user can run your tool under many contexts. A context is the set of projects, packages, or classes that the user had selected when they execute the tool. Depending on the context options you specify in your control file, the tool may or may not be available from the Tools menus of projects, packages, classes, the Workspace menu, or the Quick-Start dialog.

When you specify Menu-Items, you list the name of the menu entry, the application class, and the context in which it runs. The context can be one of the following:

If you want the tool to appear in more than one context, you must repeat the Menu-Items specification for each context. Note that there is still only a single Menu-Items line in the control file, just multiple tool specifications.

When your tool executes, VisualAge for Java passes context information as parameters to the main() method of the specified class. The first parameter is the context indicator, -c, -p, or -P. The rest of the parameters are the names of the selected classes, packages or projects. Note that the tool can only execute in one of these contexts! If the user runs the tool from the Workspace->Tools menu or from the Quick-Start dialog, VisualAge passes it no parameters. Make sure your tool checks in which context it executes by looking for its first argument.

If your tool executes against a resource file, you can add Action-Items for it as well Menu-Items. If you specify Action-Items, these actions replace the behavior of the Open command for the resource file. When a tool is run for a resource, VisualAge passes -R as the first argument, followed by pairs of strings, project name followed by resource file name.

NOTE
Your tool must handle all of the listed objects passed to it! VisualAge for Java does not invoke your tool separately for each selected object!

Some examples of the Menu-Items lines follow.

Menu-Items=Explode,com.foo.Exploder,;Explode,com.foo.Exploder,-c

This specification provides a Workspace->Tools->Explode item in the menu bar and a Tools->Explode item in a class' popup menu. Both places execute the same class, though the parameters differ. Note

Menu-Group=External Fun

Menu-Items=Configure,com.foo.SetUp,;     \

           Configure,com.foo.Setup,-c;   \

           Run in JDK,com.foo.Runner,-c; \

           Configure,com.foo.Setup,-P;   \

           Add to ClassPath JDK,com.foo.ClassPathSetter,-P

This specification creates the following options:

It will often be necessary to repeat information to make the tool available in multiple contexts.

Sample Control Files

As an example of general control files, we present the control file for the AutoGut tool.

Name=AutoGut

Version=1.0

Quick-Start-Group=Scott Stanchfield's Tools

Quick-Start-Items=Create an interface based on a class

Help-Item=Create an interface based on a class,doc/index.html

Menu-Items=\

  Create interface from a class,           \

      com.javadude.vaj.autogut.AutoGut,;   \

  Create interface from these classes,     \

      com.javadude.vaj.autogut.AutoGut,-c

Note that in this control file the text for the menu and quick-start items does not simply list the name of the tool. This makes it easier for the user to determine the available options, without needing to know the tool names. Note the subtle difference between the text for the workspace-level tool execution and that for running with a class. Try to be descriptive, using words like "these" or "a" to help the user see if the selected object is involved.

Suppose we wrote a tool that opens a graphics editor on various graphical resource files. Its control file might look as follows.

Name=Graphic Editor

Version=1.0

Quick-Start-Group=Resource File Tools

Quick-Start-Items=Edit Graphical Resources

Menu-Items=\

  Edit this JPG,com.foo.GraphicsEditor,-R.JPG;\

  Edit this GIF,com.foo.GraphicsEditor,-R.GIF;\

  Edit all JPG & GIFs in this dir,com.foo.GraphicsEditor,-R/;\

  Give this file a suffix,com.foo.SuffixGiver,-R.

Action-Items=\

  OPEN, com.foo.GraphicsEditor,-R.JPG;\

  OPEN, com.foo.GraphicsEditor,-R.GIF;\

  OPEN, com.foo.GraphicsEditor,-R./

The menu and actions are only available from the resources panel of the project browser.

Step 6: Shutdown and Restart VisualAge for Java

To finish the tool installation, you must shutdown and restart VisualAge for Java. Note that we do not need to delete the tool project from the workspace. Tools are executed from their base directory and are never brought into the workspace (unless you explicitly import them.)

Step 7: Run it!

You run the tool by selecting it from the appropriate contexts. Normally, this will be from the Tools menu of a selected class, package, or project, but you could also have placed it in the Workspace->Tools menu or in the quick start dialog. Your tool may perform different functions based on its execution context.

Step 8: When a New Tool Comes Along, You Must Zip It!

Once again, we strongly recommend that you zip the tool directory and store it safely. This allows you an easy way to reinstall the tool on other machines or if you uninstall and reinstall VisualAge for Java.

Internal Tool Setup for Resource Files

<<< TBD -- talk about options dialog settings for resource file associations >>>

VCE Tool Setup

Sometimes it is useful to run a tool in a Property Editor or Customizer. For example, you may want to obtain a list of all classes that implement a specific interface, or allow the user to select a class using VisualAge's class selection dialog. Using VisualAge's Tool API inside a property editor or customizer provides a huge advantage to your beans' users, as they won't need to type class or package names, and your customizer could even add new methods to the class!

WARNING
If you access the Tool API inside your property editor or customizer, your property editor or customizer will only run properly inside VisualAge for Java. Other IDEs do not support this API. To take advantage of these extensions, we recommend that you have your property editor or customizer check to see if running under VisualAge for Java before using these extensions. If not, you should provide an alternate way to process input if you want a portable bean. We discuss how to perform this check later.

To use the Tool API in a property editor or customizer, you need to know tow things.

Setting the Design-Time CLASSPATH

To make your Tool API calls work in the VCE, you must add the following directories to your design-time CLASSPATH.

c:\IBMVJava\ide\project_resources\IBM IDE Utility class libraries\

c:\IBMVJava\ide\project_resources\IBM IDE Utility local implementation\

You can modify the design-time CLASSPATH setting by choosing Window->Options to access the options dialog, then choosing the Visual Composition page. You can press the Edit button to add these directories. We recommend you press the Add Directory... button to choose these. Note that they will appear as relative names in the displayed design-time CLASSPATH.

Testing and Execution

If you want to test your tool inside VisualAge, without exporting and creating the control file, you can do so, but you must make sure the Tool API libraries are available. You can either add the two project directories mentioned above to the Workspace CLASSPATH (available via Window->Options->Resources), or add the IBM IDE Utility class libraries project to the tool application's CLASSPATH. We recommend you set you set up the Workspace CLASSPATH, as it makes testing several tools much easier.

You can then test your tool by running its application class. You can pass different parameters to it by setting the arguments in the Program tab of the class' Properties dialog. Make sure the first argument is the context (-c, -p, -P, or -R), followed by the names of the objects.

We recommend that you first try your tool in the proper place and only try to run it inside VisualAge if you need to debug it. Getting the command-line arguments correct can be tricky.

Writing a Tool

Now we come to the fun part. At least the fun part for this author to type: the Tool API.

We divide this section by task. The current Tool API documentation provided with the product is rather light. The product provides good concept descriptions of tool integration, but it can be difficult to really determine how to use all of those methods. We describe most of the API in this section, providing you the knowledge to write some nifty tools of your own.

Version Considerations

First a little note on mucking with other people's code...

The VisualAge for Java Tool API allows you to create, delete, and modify a user's code. You should not take this power lightly. Yes, VisualAge does automatically version methods for you, but in general, you should try to make "undoing" code changes easy for your user.

We recommend that you only work on versioned code. You can do this three ways. For all three methods, you start by checking to see if the type is an open edition. If not, the user has versioned it and you can simply continue. Otherwise, choose one of the following ways to deal with the open edition.

No matter how you handle open editions, you must deal with cases where the containing object is not an open edition. If you want to edit a class, the containing package must be either a scratch edition or an open edition. If it is versioned, you should ask the user what they want to do. In general, you should not simply try to create an open edition of the package or project in question. Many times the user will not be the owner of the containing object and cannot create that open edition.

Finally, be aware that enterprise users have additional constraints. This is the toughest thing to do when creating a tool. The biggest problems this can cause are creating open editions of containing objects, and versioning a package or project that contains unreleased objects. Keep in mind that a user may not have authority to create an open edition or release an object. Write your tool's error handling accordingly.

The Tool API Packages

IBM provides the Tool API in two packages.

You should normally include these two files in your tool class as follows:

import com.ibm.ivj.util.base.*;

import com.ibm.ivj.util.builders.*;
NOTE
Package com.ibm.ivj.util.builders contains interfaces BuilderFactory, TypeBuilder, and MethodBuilder. All other classes mentioned in this chapter are in package com.ibm.ivj.util.base.

Are We Running in VisualAge for Java?

The first thing we should check is if we are running inside VisualAge for Java. Because the Tool API runs your class' main() method, the class could be run on its own outside of VisualAge for Java. It would be nice to exit gracefully in cases like this.

The Tool API provides a method that tells you whether you are running inside VisualAge for Java. Static method ToolEnv.hostedByVAJava() returns true when running in VisualAge for Java, false otherwise.

Ummmmmm... Something is not quite right...

The problem with using this method is that it assumes the presence of the Tool API libraries. A better method of determining where you are running is to use the following code.

boolean inVisualAge = false;



try {

  Class.forName("com.ibm.ivj.util.base.ToolEnv");

  inVisualAge = true;

}

catch (ClassNotFoundException ignoreMe) {}

This method ensures that you do not try to use classes that do not exist. However, if your class includes references to the Tool API (instance variables, for example), the JVM cannot load it. If you intend to write a tool that works with or without VisualAge for Java you must separate the VisualAge parts. We recommend you write an interface that describes actions to take, and if using VisualAge, plug in a class that uses the Tool API and implements that interface. If not using VisualAge for Java, plug in a class that does not use VisualAge for Java.

A Simple Example

As a simple example, suppose you were writing a property editor that needed to ask the user for a class name. Using the Tool API, you could prompt the user to choose the class name from a list. Otherwise, you would need to ask the user to type the name. You can implement this as follows.

public interface Helper {

  public String askUserForClassName();

}



public class VisualAgeHelper implements Helper {

  public String askUserForClassName() {

    //  use Tool API to prompt the user for a class

    //    and return its name

  }

}



public class NonVisualAgeHelper implements Helper {

  public String askUserForClassName() {

    //  display a simple dialog asking user to type

    //    class name and return that class name

  }

}



public class MyPropertyEditor ... {

  private Helper helper;

  public MyPropertyEditor() {

    try {

      Class.forName("com.ibm.ivj.util.base.ToolEnv");

      helper = new VisualAgeHelper();

    }

    catch (ClassNotFoundException ignoreMe) {

      helper = new NonVisualAgeHelper();

    }

  }

  // in some method where needed...

  String name = helper.askUserForClassName();

}

Using a scheme like this allows you to provide the added benefit when using VisualAge for Java, but still allows the property editor in other IDEs.

Interfaces Everywhere!

Throughout the rest of this section, you will see that nearly all of the Tool API types are interfaces. Why? Because that is all you should care about! All you need to know is what features the Tool API provides. VisualAge for Java hides the implementation details, returning instance of some class that implements a requested interface via a factory method. This allows IBM to change the implementation of the Tool API at anytime, as long as the interface remains compatible. They can add new methods to the interfaces, but not remove them if they want to preserve existing tools. However, they can change how the tool performs its job quite easily, even using native methods where necessary.

Connecting to the Workspace

To use the Tool API, first you must (once you know you are actually inside VisualAge for Java) connect to the Workspace. The Tool API provides a bootstrapping class called ToolEnv. ToolEnv acts as a starting point, providing a factory method to obtain access to the workspace. You write code like

Workspace workspace = ToolEnv.connectToWorkspace();
NOTE
We omitted most exception handling from the examples for clarity. You will normally have to catch IvjException or one of its subclasses.

Workspace is an interface that describes how you can interact with VisualAge for Java. You access all other Tool API features from the returned Workspace-implementing instance.

Once you have a connection to the workspace, we recommend you access the workspace log. If you want to write messages about the status of your tool, you should write them to the log, not to System.out or System.err. The simplest way to write messages to the log is to use the logMessage() method of the Workspace:

workspace.logMessage("This is a message\n", true);

The logMessage() method writes a String to the log (you must provide new lines where you want them). The second parameter specifies whether you want to raise the log window in front of all other windows.

If you want more control over the output, or you need to pass an OutputStream to one of your methods for output, you can grab the log by executing the following code.

WorkspaceLog log = new WorkspaceLog(workspace);

WorkspaceLog is a subclass of java.io.OutputStream, which means you can use it wherever you would need an OutputStream. Note that output written to the log may not appear unless your call its flush() method!

NOTE
It is a good idea to write a newline ("\n") to the log before writing any other messages, as the previous tool may not have written one!

Are We Running Team Version Control?

One of the most difficult tasks when writing a tool is handling version control issues. The difficulty compounds when running in team mode. If you ever need to determine if VisualAge is running in team mode, use the following code.

if (workspace.isTeamMode()) {

  // we are running in team version mode!

}

Team mode can greatly affect versioning issues like whether or not you can version a project, package, or class, and if you can create open editions. Be aware that users of your tool may be running the Enterprise Edition of VisualAge for Java!

Workspace and Repository Contents

Now we get to the meat of the Tool API. We can use the API to inspect the repository and workspace, finding out available types, packages, and projects. Note that we intend this section to familiarize you with the available API, not to provide the full detail of which parameters each method takes. Please see the online help under "Reference" for the IBM Tool API class javadocs. We discuss the Workspace interface methods in each section that they manage.

Workspace and Repository Models

VisualAge for Java represents types, packages, and projects as Model Objects in the Tool API. Interface Model defines the identification of a type, package, or project. You can examine this identification information by using the following methods in any Model. (We will see the specific models in a moment.)

WorkspaceModel

You can use all of these methods from any model, whether that model is a WorkspaceModel or a RepositoryModel. WorkspaceModels represent your view of a type, package, or project that is in your Workspace. They provide extra methods to create open editions of a model, version an open edition, and allow us to save some extra data that the tool can access later. We will discuss this extra data later.

WorkspaceModels provide the following additional methods. We omit the tool data methods for now, as we will discuss them later.

Each of the subinterfaces of WorkspaceModel (Project, Package, Type) provides the methods you really need to interact with those types. WorkspaceModel and Model merely provide the identification and versioning methods.

RepositoryModel

As a subinterface of Model, all RepositoryModels have the same identifying information available to them. RepositoryModels represent a specific model version in the Repository. Thus, a class represented by a single WorkspaceModel in your Workspace might have several RepositoryModels representing it in the Repository. Your Workspace can only contain a single version of any object, while the Repository stores them all.

RepositoryModel adds a single method, isLoaded(), which allows you to check if a specific version of a model exists in the user's Workspace.

Each of the subinterfaces of RepositoryModel (ProjectEdition, PackageEdition, TypeEdition) provide the methods you will need to use to interact with the models.

The Workspace

So far, we have connected to the Workspace but done little else. The Workspace object is your main handle into the Tool API. Its primary use is a handle to the loaded types, packages, and projects, as well as keeping track of which Repository we use. The Workspace tracks the loaded types as WorkspaceModel objects. Throughout this section we will frequently come back to the Workspace object, so do not forget about it. Each object below will tell you how to access it from the Workspace.

The Repository

The Repository interface describes the Repository manager. You can access the Repository from the Workspace object as shown in the following code.

Repository repository = workspace.getRepository();

Other versions of getRepository() take a String or two to access alternative Repositories, either on the local machine or through a team server. Most often, however, you will use the current repository for the workspace.

The Repository interface contains methods getName() to identify the name of the repository, and isCurrentRepository() which returns true if the Repository object represents the current repository.

The Repository object also contains methods to access the contained ProjectEdition and PackageEdition objects. We will see more on this in a moment.

WARNING
The Repository interface also contains a compact() method. This method attempts to compact the repository. Use extreme care with this method, if you use it at all! You can use compact(String reposName) to specify a target directory for the compaction.

Working with Projects

A VisualAge for Java project is essentially a collection of packages. As such, the interfaces we use allow us to access those packages. There are two flavors of projects in the Tool API.

Project

Project is an interface that describes a version of a project that has been loaded into the Workspace. You can edit the project through this interface in the Tool API. You cannot edit a project through its repository view, ProjectEdition. This interface extends WorkspaceModel (which extends Model) providing identification information, and adds a few extra methods. You can obtain Project objects by asking the Workspace for them. Workspace provides the following methods to access Projects.

You can also access projects by grabbing a ProjectEdition from the repository, and asking it for the getLoaded() Project (see below).

Project defines the following methods.

As a simple example, we can print the name of all projects in the Workspace.

// connect to the workspace

Workspace workspace = ToolEnv.connectToWorkspace();



// get the projects in the workspace

Project[] projects = workspace.getProjects();



// log their names

workspace.logMessage("\n",false); // ensure we start on new line

for(int i=0; i< projects.length; i++)

  workspace.logMessage(projects[i].getName() + "\n", true);

ProjectEdition

ProjectEdition is an interface that describes a specific version of a project in the repository. You can access ProjectEditions from the Repository or from a Project in the workspace. The Repository defines the following methods to work with Project Editions.

The ProjectEdition interface extends RepositoryModel (which extends Model) providing identification information, and adds a few extra methods.

We can perform a similar example, listing the names and versions of all projects in the repository.

String versionName = "";



// connect to the workspace   

Workspace workspace = ToolEnv.connectToWorkspace();



// grab the current repository

Repository repository = workspace.getRepository();



// get a list of the names of all projects in the

//   repository

String[] projectNames = repository.getProjectNames();



// for each name...

workspace.logMessage("\n",false); // ensure we start on new line

for(int i = 0; i<projectNames.length; i++) {

  // report the name

  workspace.logMessage(projectNames[i] + "\n", true);



  // get a list of all editions of the project

  ProjectEdition[] editions = 

    repository.getProjectEditions(projectNames[i]);



  // for each edition

  for(int j=0; j< editions.length; j++) {

    // determine the version name - if not versioned, use 

    //   open edition format for the name

    if (editions[j].isVersion())

      versionName = editions[j].getVersionName();

    else

      versionName = "(" + editions[j].getVersionStamp() + ")";



    // print the edition name

    workspace.logMessage("  " + versionName + "\n", true);

  }

}

Working with Packages

A VisualAge for Java package is simply a representation of a Java language Package. VisualAge represents packages using the Package and PackageEdition interfaces. VisualAge treats packages as essentially groups of classes and interfaces.

Package

Package is the WorkspaceModel view of a Java Language package. You can use it to edit the contents of a package, specifying which classes and interfaces it contains, as well as performing version control operations. A Package is a specific version of a Java language package that is currently loaded into the Workspace. The Package object contains references to the specific versions of classes and interfaces that its Java language package contains.

You can obtain Package instances by asking for them in the Workspace, or by asking their containing Project instance. The Workspace interface provides the following methods to access packages.

You can also access packages using methods in the Project class, or by calling getLoaded() from the PackageEdition object that is currently loaded into the Workspace.

Package defines the following methods.

We add to our earlier project example, displaying the packages contained within each project. The bold code is what we have added for this example.

// connect to the workspace

Workspace workspace = ToolEnv.connectToWorkspace();



// get the projects in the workspace

Project[] projects = workspace.getProjects();



// log their names

workspace.logMessage("\n",false); // ensure we start on new line

for(int i=0; i< projects.length; i++) {

  workspace.logMessage(projects[i].getName() + "\n", true);

  // write names of all packages in this project

  Package[] packages = projects[i].getPackages();

  for(int j=0; j< packages.length; j++)

    workspace.logMessage("  " + packages[j].getName() + "\n", true);

}

PackageEdition

A PackageEdition describes a specific version of a package in the repository. You can access package editions from a Package object's getEdition() method, or from a ProjectEditions getPackagEditions() method. The Repository interface provides the following methods to work with PackageEditions.

The PackageEdition interface contains the following methods.

We extend our previous example to print the names and editions of all packages inside the project versions. The additional code appears in bold text.

String versionName = "";



// connect to the workspace   

Workspace workspace = ToolEnv.connectToWorkspace();



// grab the current repository

Repository repository = workspace.getRepository();



// get a list of the names of all projects in the

//   repository

String[] projectNames = repository.getProjectNames();



// for each name...

workspace.logMessage("\n",false); // ensure we start on new line

for(int i = 0; i<projectNames.length; i++) {

  // report the name

  workspace.logMessage(projectNames[i] + "\n", true);



  // get a list of all editions of the project

  ProjectEdition[] editions = 

    repository.getProjectEditions(projectNames[i]);



  // for each edition

  for(int j=0; j< editions.length; j++) {

    // determine the version name - if not versioned, use 

    //   open edition format for the name

    if (editions[j].isVersion())

      versionName = editions[j].getVersionName();

    else

      versionName = "(" + editions[j].getVersionStamp() + ")";



    // print the edition name

    workspace.logMessage("  " + versionName + "\n", true);



    // for each package edition in the project edition

    PackageEdition[] packageEditions = 

                       editions[j].getPackageEditions();

    if (packageEditions != null) 

      for(int k=0; k < packageEditions.length; k++) {

        // determine the version name

        // note the necessary try-catch!

        try {

          if (packageEditions[k].isVersion())

            versionName = packageEditions[k].getVersionName();

          else

            versionName = 

              "(" + packageEditions[k].getVersionStamp() + ")";

        }

        catch(IvjUnresolvedException e) {

          versionName = "UNRESOLVED: " +

                        packageEditions[k].getVersionName();

        }



        // print the package info}

        workspace.logMessage(

          "    " + packageEditions[k].getName() +

          "  " + versionName + "\n", true);

      }

  }

}

Working with Types

Types are VisualAge's way to refer generically to classes and interfaces. The type of data stored by VisualAge for Java for classes and interfaces is nearly identical. As with projects and packages, types come in two flavors: Type and TypeEdition.

Type

A Type is a representation of a class or interface in the workspace. You can use it for versioning and editing types (described later under Generating Code). You can obtain a Type from its containing Package or Project via their getTypes() method. However, the Workspace object provides the most effective means to access Types.

Type provides the following methods.

As a simple example, suppose we want a tool that lists all classes that implement selected interfaces. We can do this through the following code.

import com.ibm.ivj.util.base.*;

import com.ibm.ivj.util.builders.*; 



/** A Simple VisualAge for Java tool that displays 

 * all classes that implement the selected interfaces 

 */ 

public class ListImplementers { 

  /** The main tool processing */

  public static void main(String[] args) throws IvjException { 

    // connect to the workspace 

    Workspace workspace = ToolEnv.connectToWorkspace(); 

    workspace.logMessage("\n", true); 



    // we only run in context -c so 

    // there will always be at least two args 

    // first is "-c", second and after are the list 

    // of selected type names 

    for(int i=1; i<args.length; i++) {

      String interfaceToFind = args[i]; 



      // find the selected type in the workspace 

      Type interfaceType = workspace.loadedTypeNamed(interfaceToFind); 



      // if it's not an interface, report, but continue 

      if (!interfaceType.isInterface()) 

        workspace.logMessage(interfaceToFind + " is not an interface!\n", true); 



      // otherwise, it's an interface 

      else { 

        // write a small header 

        workspace.logMessage("Classes that implement interface " + interfaceToFind + "\n", true); 



        // get a list of all implementers and sub-interfaces 

        Type[] implementers = interfaceType.getAllSubtypes(); 



        // walk the list 

        for(int j=0; j<implementers.length; j++) 

          // check if it's an implementing class 

          if (implementers[j].isClass()) 

            // if so, report it! 

            workspace.logMessage( " " + implementers[j].getQualifiedName() + "\n", true);

      }

    }

  }

}

The control file for the above tool looks as follows.

Name=List Interface Implementers

Version=0.2 Menu-Items=\ List implementers,\ effectivevaj.under.toolapi.ListImplementers,-c

We set this tool up outside VisualAge for Java (in the tools directory, as explained above), shutdown and restart VisualAge. We now have a simple tool that displays the names of all classes that implement the selected interfaces. Note that you could later add a GUI to allow users to select interfaces from a list and display the results in a window instead of the log.

TypeEdition

VisualAge tracks classes and interfaces in the repository using TypeEditions. A TypeEdition represents a specific version of a class or interface. You can access TypeEditions from a Type's getEdition() method or a PackageEdition's getTypeEditions() method. There is no direct way to access a TypeEdition from the Repository object.

TypeEdition provides the following methods.

We can extend our edition example again to dump all versions of all classes in all versions of all packages in all projects in the repository. Note that this is just a silly example and can take quite a while to run. We only provide this example here to help show the relationship between the various edition objects. Once again, the additional code appears in bold text.

String versionName = "";

// connect to the workspace 

Workspace workspace = ToolEnv.connectToWorkspace(); 



// grab the current repository 

Repository repository = workspace.getRepository(); 



// get a list of the names of all projects in the repository 

String[] projectNames = repository.getProjectNames(); 



// for each name... log it 

workspace.logMessage("\n",false); // ensure we start on new line

for(int i = 0; i<projectNames.length; i++) {

  // report the name 

  workspace.logMessage(projectNames[i] + "\n", true);



  // get a list of all editions of the project 

  ProjectEdition[] editions = repository.getProjectEditions(projectNames[i]); 



  // for each edition 

  for(int j=0; j< editions.length; j++) {

    // determine the version name - if not versioned, use 

    // open edition format for the name 

    if (editions[j].isVersion()) 

      versionName = editions[j].getVersionName(); 

    else 

      versionName = "(" + editions[j].getVersionStamp() + ")"; 



    // print the edition name 

    workspace.logMessage(" " + versionName + "\n", true); 



    // for each package edition in the project edition 

    PackageEdition[] packageEditions = editions[j].getPackageEditions(); 



    if (packageEditions != null) 

      for(int k=0; k < packageEditions.length; k++) {

        boolean validEdition = true; 

        // determine the version name 

        // note the necessary try-catch! 

        try {

          if (packageEditions[k].isVersion()) 

            versionName = packageEditions[k].getVersionName(); 

          else 

            versionName = "(" + packageEditions[k].getVersionStamp() + ")"; 

        }

        catch(IvjUnresolvedException e) {

          versionName = "UNRESOLVED: " + packageEditions[k].getVersionName(); 

          validEdition = false; 

        }



        // print the package info 

        workspace.logMessage( " " + packageEditions[k].getName() + " " + versionName + "\n", true); 

        if (validEdition) {

          TypeEdition[] typeEditions = packageEditions[k].getTypeEditions(); 

          if (typeEditions != null) 

            for(int l=0; l < typeEditions.length; l++) {

              if (typeEditions[l].isVersion()) 

                versionName = typeEditions[l].getVersionName(); 

              else 

                versionName = "(" + typeEditions[l].getVersionStamp() + ")"; 

              workspace.logMessage( " " + typeEditions[l].getName() + " " + versionName + "\n", true); 

            }

        }

    }

  }

}

Running a Class

Inside your tool, you may need to run other classes or applications. The Tool API provides a method to invoke another class' main() method.

The Workspace interface's runMain() method provides the functionality to invoke another class' main method from within our tool. This allows you to ask the user for a class to run and run it, possibly with a set of arguments. The runMain() method takes two arguments: a Class object that represents the class you want to execute, and an array of Strings to pass as arguments. A sample invocation might look as follows.

// the following string for the class name could come from a 

// parameter, or could be selected by the user while the 

// tool is running 

String className = "effectivevaj.SomeClass"; 



// get a class object for the named class 

try { 

  Class theClass = Class.forName(className); 

  // run the application 

  workspace.runMain(theClass, new String[] {"Larry", "Curly", "Moe"});

}

catch(ClassNotFoundException e) { 

  // deal with the bad class name 

}

Sometimes these other applications require different CLASSPATH settings to find resources, and you may not know which resource directories you need until runtime. You can view and modify the current CLASSPATH setting using the following methods, defined in the Workspace interface.

As an example, suppose we want to add a jar named c:\jars\cookie.jar to the CLASSPATH, as well as the Swing class libraries.

// connect to the workspace

Workspace workspace = ToolEnv.connectToWorkspace(); 



// get the entries that VisualAge requires 

Object[] requiredStuff = workspace.getDefaultClassPathEntries(); 



// create the new (bigger) class path array 

Object[] newClassPath = new Object[requiredStuff.length + 2]; 



// copy the required entries 

System.arraycopy(requiredStuff, 0, newClassPath, 2, requiredStuff.length); 



// set the new entries 

newClassPath[0] = "c:\\jars\\cookie.jar"; 

newClassPath[1] = workspace.loadedProjectNamed("JFC class libraries"); 



// set the CLASSPATH in the workspace 

workspace.setClassPath(newClassPath);

Helping the User Pick an Object

Often during the execution of the tool, you need to ask your user which project, package, class, file, or directory they want to work with. Rather than just presenting them with a TextField where they need to type the entire name, you can ask VisualAge to present them with the same dialogs it uses when asking users for these types of data.

Common Parameters

Most of the methods we discuss in this section accept the same parameters. We explain these parameters once before even talking about what the methods do.

Title and Prompt

The title parameter is the String that appears in the title bar of the dialog that VisualAge displays. This String should be a very short description of the activity. "Choose Class" or "Select Directory" might be good choices when choosing a title.

The prompt parameter is the actual request displayed for the user. This String should be a full sentence, and can be as descriptive as you like. "Please select the target directory" or "Please select the class you want to delete" might be appropriate prompts.

Scope and Mask

Scope and mask help decide what items actually appear in the list. The scope limits items to those contained with a given WorkspaceModel. If you pass null as the value, the entire workspace is the scope. If you pass a Project or Package as the scope, the user will only see entries that reside under that Project or Package. Generally, using a Type as the scope is not very useful, as you could only ever see a single entry in the list! The reasoning is similar for choosing a Project: specifying a Project as the scope is not useful, nor is specifying a Package as the scope when prompting for a Package.

The mask parameter is the initial String that helps whittle the list to just those starting with a certain sequence of letters. Normally you would leave this parameter null or set it to the previously selected name (not fully-qualified of course.)

The user can change the mask while interacting with the dialog, but they cannot change the scope.

Workspace Prompting Methods

The Workspace interface provides the following methods for prompting the user for an object. We divide them into two groups based on the types of parameters they take.

The first group has four parameters:

The methods that use these parameters are:

Workspace provides variations on these methods that provide more control. The following methods allow you to specify an array of WorkspaceModels, and these are the only choices given to the user. This is extremely useful for cases where you might want to allow only classes that implement a certain interface, for example.

Finally, the Workspace interface provides two methods for file-system interaction.

You can use these just as though you were asking a question. If the user presses Cancel, the methods return null. For example

// connect to the workspace

Workspace workspace = ToolEnv.connectToWorkspace(); 



// Find the example project for this book 

Project book = workspace.loadedProjectNamed("Effective VisualAge for Java"); 



// Ask the user to choose a class. Only classes 

// in this book's package appear in the selection list 

String className = workspace.promptForClass("Select Class", "Please select the class to use!", book, null);

Exporting and Importing

Often you tools work with code inside VisualAge for Java. Sometimes, however, you may want to send the code outside (perhaps to check in to an external version control tool) or bring external code into VisualAge for Java. The Tool API provides export and import capabilities to allow you to move code in and out of the IDE.

The Workspace interface provides methods importData() and exportData() to perform the import and export, respectively. These methods each take import and export specification objects, describing what is being imported/exported and how.

Import and Export Specifications

The import and export specifications are objects that describe what to export and provide parameters to how that export occurs. There are two types of specifications: code and interchange. Code specifications describe directory exports and imports. Interchange specifications describe repository file exports and imports. You create instances of the specification classes and call some methods to set the parameters of the export or import. We describe each of the classes in the following sections.

ExportCodeSpec

Class ExportCodeSpec defines the details needed to export code to a directory. Note that if you want to create a JAR file, you must run the jar tool yourself after performing the export. You create an ExportCodeSpec using its default constructor.

ExportCodeSpec exportData = new ExportCodeSpec();

ExportCodeSpec provides the following methods.

Workspace workspace = null;

try {

  // connect to the workspace 

  workspace = ToolEnv.connectToWorkspace(); 



  // create a list of packages to export 

  Package[] packages = new Package[2]; 

  packages[0] = workspace.loadedPackageNamed("effectivevaj.gencode"); 

  packages[1] = workspace.loadedPackageNamed("effectivevaj.vce.factory"); 



  // set up the export specification 

  ExportCodeSpec exportData = new ExportCodeSpec(); 

  exportData.includeClass(false); 

  exportData.includeJava(true); 

  exportData.includeResources(true); 

  exportData.overwriteFiles(true); 

  exportData.setPackages(packages); 

  exportData.setExportDirectory("c:\\targetdir"); 



  // perform the export 

  workspace.exportData(exportData); 

}



// if we had an export problem, list the errors 

catch(IvjExportException e) {

  String[] errors = e.getErrors();

  workspace.logMessage("\nErrors during export!\n", true); 

  for(int i=0; i<errors.length; i++) 

    workspace.logMessage(errors[i]+"\n", true); 

}

ExportInterchangeSpec

The ExportInterchangeSpec class describes an export into another repository. Recall that repository format exports only export projects or packages from the repository, not from the workspace. If you use these methods you should make sure the user has versioned the objects being exported, or perform the versioning in your tool. You create an instance of ExportInterchangeSpec using its default constructor, then, set the parameters on that instance.

ExportInterchangeSpec exportData = new ExportInterchangeSpec();

ExportInterchangeSpec provides the following methods.

As an example, suppose we want to export all versions of project "Big Bad VooDoo Scotty" to repository file c:\sample\transfer.dat. We could do so with the following code.

Workspace workspace = null;

try { 

  // connect to the workspace 

  workspace = ToolEnv.connectToWorkspace(); 



  // get the current repository 

  Repository myRepository = workspace.getRepository(); 



  // specify the target repository 

  Repository targetRepository = workspace.getRepository("c:\\sample\\transfer.dat"); 



  // Find all versions of the project to export. 

  ProjectEdition[] projects = myRepository.getProjectEditions("Big Bad VooDoo Scotty");   



  // fill in the export details 

  ExportInterchangeSpec exportData = new ExportInterchangeSpec(); 

  exportData.allowRepositoryCreation(true); 

  exportData.setProjectEditions(projects); 

  exportData.setRepository(targetRepository); 



  // perform the export 

  workspace.exportData(exportData); 

}



// report any errors 

catch(IvjExportException e) {

  String[] errors = e.getErrors(); 

  workspace.logMessage("\nErrors during export!\n", true); 

  for(int i=0; i<errors.length; i++) 

    workspace.logMessage(errors[i]+"\n", true); 

}

ImportCodeSpec

The ImportCodeSpec describes the parameters needed when importing source, .class, or resource files from a directory.

Class ImportCodeSpec defines the following methods.

As a sample we import several .java files into a new Project in the workspace.

Workspace workspace = null;

try { 

  // connect to the workspace 

  workspace = ToolEnv.connectToWorkspace();   



  // create an import specification 

  ImportCodeSpec importData = new ImportCodeSpec(); 

  

  // create a new project to import into 

  Project targetProject = workspace.createProject("Sample Project", true); 



  // list the files to import 

  String[] javaFiles = { "C:\\sample\\test\\FirstClass.java", 

                         "C:\\sample\\test\\SecondClass.java",

                         "C:\\sample\\test\\AnotherClass.java", 

                         "C:\\sample\\test\\StillAnotherClass.java" 

                       };



  // set up the parameters for the import 

  importData.setDefaultProject(targetProject); 

  importData.setJavaFiles(javaFiles); 



  // perform the import 

  workspace.importData(importData); 

}



// report any errors 

catch(IvjImportException e) {

  String[] errors = e.getErrors();

  workspace.logMessage("\nErrors during import!\n", true);

  for(int i=0; i<errors.length; i++) 

    workspace.logMessage(errors[i]+"\n", true); 

}

ImportInterchangeSpec

Class ImportInterchangeSpec provides the information necessary to import project or package versions from another repository. ImportInterchangeSpec provides the following methods.

As a simple example, we import all versions of the JavaDude Box Beans project in an external repository file.

Workspace workspace = null;

try { 

  // connect to the workspace 

  workspace = ToolEnv.connectToWorkspace(); 

 

  // get the source repository 

  Repository sourceRepository = workspace.getRepository("c:\\sample\\boxbeans.dat"); 

  

  // get all versions to import 

  ProjectEdition[] projects = sourceRepository.getProjectEditions("JavaDude Box Beans");   



  // setup the import parameters 

  ImportInterchangeSpec importData = new ImportInterchangeSpec(); 

  importData.setProjectEditions(projects); 

  importData.setRepository(sourceRepository); 



  // perform the import 

  workspace.importData(importData); 

}



// report any errors 

catch(IvjImportException e) {

  String[] errors = e.getErrors(); 

  workspace.logMessage("\nErrors during import!\n", true);

  for(int i=0; i<errors.length; i++) 

    workspace.logMessage(errors[i]+"\n", true); 

}

Generating Code

One of the most useful things a tool can do is code generation. You can add, delete, and modify methods and classes, even providing your own user code blocks!

The Code Generation Interfaces

The Tool API provides code generation classes in the com.ibm.ivj.util.builders package. This package contains three interfaces that provide code generation capabilities.

BuilderFactory

BuilderFactory is your handle into the code generation capabilities, proving the functionality to create MethodBuilders, TypeBuilders and user code blocks. All code generation starts here. BuilderFactory provides the following methods.

TypeBuilder

TypeBuilder represents an interface or class that you want to add or modify. Note that if you want to delete a type you can just go ahead and delete it from the workspace. You access TypeBuilders via the BuilderFactory createTypeBuilder() methods.

The concept of contained MethodBuilders bears some explanation. TypeBuilder uses MethodBuilders to describe the actions you want to take on methods. MethodBuilders are not the methods themselves. If you, for example, call removeAllMethodBuilder(), it does not remove all methods in the class; it removes the actions you wanted to take on those methods. Each MethodBuilder allows you to change source code or specify deletion of that method. When you save() or saveMerge() a TypeBuilder, the tool API executes the actions you specified via MethodBuilders.

When you first access a TypeBuilder, it contains no MethodBuilders. You can ask it to create MethodBuilders for its existing methods, but unless you explicitly add those MethodBuilders to the TypeBuilder via its addMethodBuilder() method, the TypeBuilder will not contain the MethodBuilders. This allows you to choose which methods to modify or delete, and add only those MethodBuilders to the TypeBuilder.

NOTE
Accessing an existing method builder for a particular method is very difficult. Usually the only reason you would want to do so is to see its source or mark it for deletion. If you want to modify or delete a method, just create a new MethodBuilder and set the source or mark it for deletion. If you need to access the old source code, we describe how to do that in Viewing the Source of an Existing Method. We describe the methods of MethodBuilder in the next section.

TypeBuilder provides the following methods.

MethodBuilder

MethodBuilder represents a method that you want to add, modify, or delete. If you add a MethodBuilder to a TypeBuilder, you indicate that you want to perform an action on that method, adding it, replacing its contents or deleting it.

MethodBuilder defines the following methods.

Code Generation Tasks

Throughout the rest of this section, we describe several tasks that you may want to perform. With each task is an example of the code you would write to perform that task.

Create a New Class or Interface

The first basic task you may want to do is define a class. There are two main ways to do this. The easiest is to define the entire source for the class in a TypeBuilder and save that TypeBuilder.

String projectName = "Generated Code Samples";

String packageName = "sample.generated.code"; 



// connect to the workspace 

Workspace workspace = ToolEnv.connectToWorkspace(); 



// get a builderfactory to start the generation 

BuilderFactory factory = workspace.createBuilderFactory(); 



// find or create the specified project 

Project targetProject = workspace.loadedProjectNamed(projectName); 

if (targetProject == null) 

  targetProject = workspace.createProject(projectName, true); 



// find or create the specified package in that project 

Package targetPackage = workspace.loadedPackageNamed(packageName); 

if (targetPackage == null) 

  targetPackage = targetProject.createPackage(packageName, true);   



// if the package is in the wrong project, complain! 

else if (targetPackage.getProject() != targetProject) 

  workspace.logMessage("The target package " + packageName + " is already in another project!!!", true); 



// create a TypeBuilder for the new Class 

TypeBuilder typeBuilder = factory.createTypeBuilder("HelloWorld", targetPackage); 



// Use a StringBuffer for building the code -- it's much 

// faster than concatenating Strings 

StringBuffer b = new StringBuffer(); 



// Write the code for the class 

b.append("public class HelloWorld {\n" );

b.append("\tpublic static void main(String[] args) {\n" );

b.append("\t\tSystem.out.println(\"Hello, World!\");\n" );

b.append("\t}\n" ); 

b.append("}\n" );



// set that code as the source for the new class 

typeBuilder.setSource(b.toString()); 



// save the class 

typeBuilder.save();

The other method is similar to the next section. We create the TypeBuilder and add MethodBuilders to it for each of the methods we need in that class.

Add a Method to a Class or Interface

To add a method to a type, you create a new MethodBuilder for that method, set its source, add the MethodBuilder to the TypeBuilder for your type and save it. The following code demonstrates how to do this.

NOTE
There is currently a minor bug in the tool API that removes any leading space in front of the first line of your method. There is no workaround for this; the Tool API removes all leading whitespace characters.
// connnect to the workspace

Workspace workspace = ToolEnv.connectToWorkspace(); 



// find the specified class 

Type helloWorld = workspace.loadedTypeNamed(args[1]); 



// create a builder factory to start code generation 

BuilderFactory factory = workspace.createBuilderFactory(); 



// find the type builder for the existing type 

TypeBuilder typeBuilder = factory.createTypeBuilder(helloWorld); 



// create a new method builder for the new method 

MethodBuilder mainMethod = factory.createMethodBuilder("main");   



// Use a string buffer for efficiency 

StringBuffer b = new StringBuffer(); 



// write the code for the new method 

b.append("\tpublic void foo() {\n" ); 

b.append("\t\tSystem.out.println(\"We're doing something\");\n" ); 

b.append("\t}\n"); 



// set the source for the new method 

mainMethod.setSource(b.toString()); 



// tell the type builder to add this method when save()d 

typeBuilder.addMethodBuilder(mainMethod); 



// save the type 

typeBuilder.save();

Modify the Code in a Method

To modify code in a method, you generally ignore the old code and just replace it with new code. This is very similar to adding a method. In this example, we simply change the code in the main() method.

Note that if you want to allow the user a safe place to put their own code, you can define user blocks for them and merge your changes with theirs. We show how to do this in the next sections.

If you want to obtain the previous source and modify it, see Obtaining the Source of an Existing Method below to access the source as a String, then make some changes, then save it using this technique.

// connnect to the workspace

Workspace workspace = ToolEnv.connectToWorkspace(); 



// find the specified class 

Type helloWorld = workspace.loadedTypeNamed(args[1]); 



// create a builder factory to start code generation 

BuilderFactory factory = workspace.createBuilderFactory(); 



// find the type builder for the existing type 

TypeBuilder typeBuilder = factory.createTypeBuilder(helloWorld); 



// create a new method builder for the new method 

MethodBuilder mainMethod = factory.createMethodBuilder("main");   



// Use a string buffer for efficiency 

StringBuffer b = new StringBuffer(); 



// define the new code

b.append("\tpublic static void main(String[] args) {\n" ); 

b.append("\t\tSystem.out.println(\"Goodbye, World!\");\n" ); 

b.append("\t}\n" ); 



// save the changes

mainMethod.setSource(b.toString()); 

typeBuilder.addMethodBuilder(mainMethod); 

typeBuilder.save();

Defining User Code Blocks

Sometimes users may want to add their own code inside one of your generated methods. You can provide blocks inside the generated code that are "safe" for them to add code. When you later regenerate the code, the code in their safe blocks remains in the block in the new code, while the rest of the generated method could change.

In general, you should provide a user code block before or after actions in any methods that you want to have this feature, and between actions where you think the user might want more control. BuilderFactory provide a convenience method, createUserCodeBlock() that creates the block comments for you. These comments are merged with existing code when you call saveMerge() in a TypeBuilder.

You can use any String as the identifier for user code blocks. We recommend you pick textual names rather than numbers like the VCE uses. This gives you more flexibility if you want to add more user blocks later.

// connnect to the workspace

Workspace workspace = ToolEnv.connectToWorkspace(); 



// find the specified class 

Type helloWorld = workspace.loadedTypeNamed(args[1]); 



// create a builder factory to start code generation 

BuilderFactory factory = workspace.createBuilderFactory(); 



// find the type builder for the existing type 

TypeBuilder typeBuilder = factory.createTypeBuilder(helloWorld); 



// create a new method builder for the new method 

MethodBuilder mainMethod = factory.createMethodBuilder("main");   



// Use a string buffer for efficiency 

StringBuffer b = new StringBuffer(); 



// Define the source for the method, including some 

// user code blocks before and after the action 

b.append("\tpublic static void main(String[] args) {\n" ); 

b.append(factory.createUserCodeBlock("before action","\t\t")); 

b.append("\n"); 

b.append("\t\tSystem.out.println(\"Hello, World!\");\n" ); 

b.append(factory.createUserCodeBlock("after action","\t\t")); 

b.append("\n"); 

b.append("\t}\n" ); 



// save the changes

mainMethod.setSource(b.toString()); 

typeBuilder.addMethodBuilder(mainMethod); 

typeBuilder.saveMerge();

Modify Method with Embedded User Code

As long as you keep specifying where the user blocks are, you can keep the user's code by calling saveMerge() ofr your TypeBuilder. You only need to use saveMerge() when at least one of your added MethodBuilders contains a user code block. If none of the changes you are making modify methods with user code blocks, you can still use save()/

// connnect to the workspace

Workspace workspace = ToolEnv.connectToWorkspace(); 



// find the specified class 

Type helloWorld = workspace.loadedTypeNamed(args[1]); 



// create a builder factory to start code generation 

BuilderFactory factory = workspace.createBuilderFactory(); 



// find the type builder for the existing type 

TypeBuilder typeBuilder = factory.createTypeBuilder(helloWorld); 



// create a new method builder for the new method 

MethodBuilder mainMethod = factory.createMethodBuilder("main");   



// Use a string buffer for efficiency 

StringBuffer b = new StringBuffer(); 



// Create the new code with user blocks. In this 

// example we add a new user block 

b.append("\tpublic static void main(String[] args) {\n" );

b.append(factory.createUserCodeBlock("before action","\t\t")); 

b.append("\n"); 

b.append("\t\tSystem.out.println(\"This is different!\");\n" ); 

b.append(factory.createUserCodeBlock("after action","\t\t")); 

b.append("\n"); 

b.append("\t\tSystem.out.println(\"So is this!\");\n" ); 

b.append(factory.createUserCodeBlock("final action","\t\t")); 

b.append("\n"); 

b.append("\t}\n" );



// set the source for the method builder 

mainMethod.setSource(b.toString()); 



// tell the type builder that we want to change 

typeBuilder.addMethodBuilder(mainMethod); 



// merge the changes, keeping the user-entered code 

typeBuilder.saveMerge();

Checking if a Method Exists

Sometimes you may want to check if a method exists. What you really want to check is if a method exists with a specific signature. To do this, you create a new MethodBuilder and set the source to an empty method. You do this just so you have a new method with the signature you want to find. You pass this method to checkMethodExists, which compares its method signature with those in the type.

NOTE
When specifying parameters to the test method, you should fully-qualify all types, other than those in package java.lang. This ensures the Tool API will be able to match the parameter types even if the necessary imports are not present in the type.

Note that you do not need to specify the access modifier for the method, or any code within the method.

// connect to the workspace

Workspace workspace = ToolEnv.connectToWorkspace(); 

Type type = workspace.loadedTypeNamed(args[1]);   



// get the type builder for the class 

BuilderFactory factory = workspace.createBuilderFactory(); 

TypeBuilder typeBuilder = factory.createTypeBuilder(type); 



// create a new method builder for the search 

// note that it just needs to define the signature 

// -- no method body needed 

// -- no access modifier needed 

// -- parameter types should be fully-qualified 

//    in case imports are not present 

MethodBuilder test = factory.createMethodBuilder("test"); 

test.setSource("void paint(java.awt.Graphics i) {}"); 



// check to see if we have a method with the signature 

if (typeBuilder.checkMethodExists(test)) 

  System.out.println("FOUND!!!");

Delete a Method

Method deletion is actually very simple. First, you create a new MethodBuilder with the same signature as the method you want to delete. Then, you mark that MethodBuilder for deletion. Finally, add it to the TypeBuilder. When the TypeBuilder save()s or saveMerge()s, the method is deleted.

// connect to the workspace

Workspace workspace = ToolEnv.connectToWorkspace(); 



// find the specified type 

Type helloWorld = workspace.loadedTypeNamed(args[1]); 



// get a builder factory to start code generation 

BuilderFactory factory = workspace.createBuilderFactory(); 



// get the type builder to change the specified class 

TypeBuilder typeBuilder = factory.createTypeBuilder(helloWorld); 

MethodBuilder mainMethod = factory.createMethodBuilder("main"); 



// specify enough source for the method signature 

mainMethod.setSource("void main(java.lang.String[] args) {}");



// check if it exists... 

if (typeBuilder.checkMethodExists(mainMethod)) 

  // ... if so, mark it for deletion 

  mainMethod.markForDeletion(true); 



// tell the type builder that we'd like to make changes 

typeBuilder.addMethodBuilder(mainMethod); 



// save the type, deleting the method. 

typeBuilder.save();

Obtaining the Source of an Existing Method

If you need to work with the existing method source, you cannot simply grab a MethodBuilder from the TypeBuilder and call getSource(). Instead, you should create a new MethodBuilder that has the proper signature of the method you want. Then, check that it exists in the type. Finally, call getExistingMethodSource() for that MethodBuilder in the TypeBuilder. This finds the method with the same signature and returns its code as a String.

// connect to the workspace

Workspace workspace = ToolEnv.connectToWorkspace();



// find the specified type 

Type helloWorld = workspace.loadedTypeNamed(args[1]);



// get a builder factory to start code generation

BuilderFactory factory = workspace.createBuilderFactory();



// get the type builder to change the specified class

TypeBuilder typeBuilder = factory.createTypeBuilder(helloWorld);



// create a method builder to help us find main

MethodBuilder mainMethod = factory.createMethodBuilder("test");



// set enough source so we have the signature

mainMethod.setSource("void main(java.lang.String[] args) {}");



// check to see if a method with that signature exists

if (typeBuilder.checkMethodExists(mainMethod)) {

  String source = typeBuilder.getExistingMethodSource(mainMethod);

  workspace.logMessage("Source code for main:\n" + source, true);

}

Storing Tool Options

A nice touch to add to your tool is persistence of tool options. Most tools require the user provide some input, such as classes to export, and some output, such as where to export the classes. Users can quickly grow to hate a tool if they must input the same data every time they use the tool.

To resolve this situation, tools can save ToolData objects. ToolData acts as a keyed piece of information that VisualAge for Java tracks for you. Your tool accesses it via a key, which usually maps uniquely to the tool name. The data stored within it can be any Serializable object, allowing you to store any amount of necessary data. ToolData is stored per project by default, but we can have more control over its behavior.

Setting Tool Data

You set tool data by creating an instance of some Serializable class, storing your data within that class, then calling setToolOptions() with that data. As an example, suppose we want to store the last-used class, package and project names. We start by defining a bean that holds the data for us.

import java.io.*;



public class LastUsedOptions implements Serializable {

  private String lastClass;

  private String lastPackage; 

  private String lastProject; 



  public String getLastClass() {

    return lastClass;

  } 

  public String getLastPackage() {

    return lastPackage;

  } 

  public String getLastProject() {

    return lastProject;

  } 

  public void setLastClass(String lastClass) {

    this.lastClass = lastClass; 

  } 

  public void setLastPackage(String lastPackage) {

    this.lastPackage = lastPackage; 

  } 

  public void setLastProject(String lastProject) {

    this.lastProject = lastProject; 

  }

}

We then write the code to store the current values from out tool in this object and save it as tool data.

// connect to the workspace

Workspace workspace = ToolEnv.connectToWorkspace(); 



// set up the options for later retrieval 

LastUsedOptions options = new LastUsedOptions();

options.setLastClass("SomeClassName");

options.setLastPackage("some.package");

options.setLastProject("A Sample Project"); 



// store the options, keyed by this tool name 

ToolData toolData = new ToolData("my.class.ThisToolName", options); 

workspace.setToolOptions(toolData);

Checking to See If Tool Data Exists

If you need to check to see if a ToolData exists for a project, simply call testToolOptions() in the Workspace interface. For example,

// connect to workspace

Workspace workspace = null;

workspace = ToolEnv.connectToWorkspace();



// check to see if the tool options exist

if (workspace.testToolOptions("my.class.ThisToolName")) {

  // get the options...

}

Reading the Tool Data

Once you know the tool data exists you can read it via the getToolOptions() method in the Workspace interface.

// connect to the workspace

Workspace workspace = ToolEnv.connectToWorkspace();



// grab the tool data

ToolData toolData = workspace.getToolOptions("my.class.ThisToolName");



// get the data we want from it

LastUsedOptions options = (LastUsedOptions)toolData.getData();



// use the data

workspace.logMessage(options.getLastClass() + "\n", true);

workspace.logMessage(options.getLastPackage() + "\n", true);

workspace.logMessage(options.getLastProject() + "\n", true);

Deleting the Tool Data

If you no longer need the data, or perhaps your user requests so, you can delete the tool data by calling clearToolOptions().

// connect to the workspace

Workspace workspace = ToolEnv.connectToWorkspace();



// delete the tool data

workspace.clearToolOptions("my.class.ThisToolName");

Storing Other Files

You can also get a tool-specific directory in which you can place whatever you want. Note that there is a single instance of this directory, as opposed to a tool directory per project. This is a good place to store other files and data that you want to apply to the tool no matter on which project you invoke it. You can get this name by calling the getToolDataDirectory() method of the Workspace class.

Workspace and Repository Data

You can store ToolData for any Workspace model. Using the above methods, the ToolData is associated with the project in which you are working. If you want, you can store other ToolData with any Project, Package, or Type.

You can do this by calling the following methods, available from Project, Package, or Type. The Tool API defined these methods in interface WorkspaceModel.

Using these methods, you can store information for the user in their workspace. Think of these as personalized tool options.

You can also store data in the repository instead of the Workspace. This makes the settings available to all users who connect to that repository. The repository versions of these methods are:

clearToolWorkspaceData(String key) -- Removes the data from the Project, Package, or Type has data for the specified key.

Usage for all of these methods is similar to the getToolOptions() and setToolOptions() methods.

Miscellaneous Features

There are still a few features left in the API. They do not seem to fit in with any of the above topics, so we lump them together here.

Temporary Directories

Sometimes you need a temporary directory while processing a tool. You might use it to export code that you need to process outside VisualAge for Java, for example. The Tool API can create a temporary directory for you via the Workspace's getTempDirectory() method. This method returns a String, the name of the temporary directory. After your tool exits, VisualAge deletes the temporary directory and its contents.

Users

You may need to know which users can access this workspace, as well as name of the current workspace owner. You can access this information via the following methods in the Workspace class.

Shutting Down VisualAge for Java

You can shut down VisualAge for Java by invoking the shutdown() method of the Workspace interface. This is normally only used in tool servlets (see Remote Tool Access below), but if you have a need you can use it in a normal tool as well. If you do use it, make sure the user is well aware that you plan to shutdown, or they may think the IDE has crashed.

Removing Features and Tools

To remove features, open the Quick-Start dialog (press F2 or select File->Quick Start) and choose Features->Remove Feature. Select the feature in question and VisualAge will remove that feature from your workspace as well as any additions for it in your palette.

NOTE
This does not remove it from the features directory! If you want to completely delete it, remove the feature as above, exit VisualAge for Java and delete the feature's base directory. If you do not delete the directory, you can add the feature back any time you want.

To remove tools, exit VisualAge and delete the tool base directory. The next time you start VisualAge for Java, the tool menu options disappear.

Internationalization and Features/Tools

If you want your control file to act differently base on the user's locale, you can simply define a .ini file for the desired locale. The default.ini file is just that: a set of defaults. Specific locale files override the default's behavior.

Suppose you wanted to create a British English control file. You would simply define en_GB.ini in the feature or tool's base directory. If you wanted to define a set of general French control file settings, you would define fr.ini. To specify French in Canada, define fr_CA.ini. You only need to define the settings that differ from the default.ini file in each of these. The more specific the locale in the file name, the higher its precedence. For example, entries in en_GB.ini take precedence over any in en.ini which takes precedence over default.ini (assuming the locale it British English, of course.)

NOTE
All control files must be ASCII files. If you need to use non-ASCII characters, you must use UNICODE escapes (\uxxx) to specify those characters.

Sample Tools

The CDROM that accompanies this book contains several VisualAge for Java tools. Some of the tools describe their tool design in their documentation, and most provide source code for you to examine.

Summary

The VisualAge for Java Tool API provides a powerful means for you to extend your development environment. This chapter has provided all the information you need to create some very useful tools, helping you to generate commonly-used code and automate tasks such as exports, imports, and repository maintenance.