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.
NOTERecall 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!
WARNINGIf 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.
NOTEFeatures 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,...
- Name -- A unique name for the feature. Note that it is very important that you make this name as unique as possible. As more and more people provide features, distinguishing them will become more difficult. Do not use feature names like "Buttons"; rather, use names like "JavaDude's Fancy Image Buttons". This name need not be related to any of the project or package names contained within it.
- Version -- A version string that helps you identify the release number of the feature. This version is independent of the versions of any of the contained projects or packages.
- Help-Item -- An optional entry that provides help on using your feature. The specified help text appears in the Help menu. Note that this feature does not currently work in version 3.0 (or in version 2.0).
- Palette-Items -- An optional list of which beans inside the feature VisualAge should add to the beans palette. You specify to which category to add beans, followed by the fully qualified class names of the beans to add. Make sure that the beans you add specify icons, and that those icons reside in the appropriate project resources directory in step 2 above. These icons appear at the bottom of the palette, preceded by a separator.
- Prereq-Features -- An optional entry that automatically adds other required features. VisualAge makes sure it adds the other features before this one if possible. Note that prerequisites do not affect unloading features from the workspace. If you delete a feature, you will need to delete prerequisites by hand if you wish.
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, ...
- Name -- A unique name for the tool. Note that it is very important that you make this name as unique as possible. As more and more people provide tools, distinguishing them will become more difficult. This name need not be related to any of the project or package names contained within it.
- Version -- A version string that helps you identify the release number of the tool. This version is independent of the versions of any of the contained projects or packages.
- Help-Item -- An optional entry that provides help on using your tool. The specified help text appears in the Help->Tools menu. If the user selects the tool option, the tool help page appears in the registered web browser.
- Menu-Group -- An optional name of a submenu to add to the Tools menu of projects, packages, classes, or the Workspace menu. If you have more than one menu item, you should define a menu group for your tool. The menu group only appears in the contexts specified by the menu items (see below for details on the contexts.) If specified, all menu items appear in this menu group. If not specified, menu items appear directly in the Tools menu(s).
- Menu-Items -- Optional items that are added to the Tools menu(s) of projects, packages, classes, or the main Workspace menu. Each item has three parts: a name that VisualAge displays in the menu, the name of the main tool class to execute when the user selected that name, and the context in which the command appears. If you specify a Menu-Group entry for the tool, the menu items appear underneath that Menu-Group in the appropriate Tools menu.
If you want separators between menu items, use a hyphen ("-") as the menu name and class to execute.
- Action-Items -- An optional list of tool classes to run when the user selects "Open" for various types of resource files. Resource files only appear in Resources tab of project browsers, and you can provide your own editors and viewers for resource files using these options. The context entries for these items specify which types of resource files the tool classes affect. The context can be -R. (dash-R-dot), running the tool application for resource files without a suffix (ones that do not contain a dot in their name), or -R.suffix, which affects only the resources ending with the specified suffix.
- Quick-Start-Group -- By specifying an optional Quick-Start-Group and Quick-Start-Items, the user can run your tool from the Quick-Start dialog. The Quick-Start-Group specifies the group name that appears in the left side of the Quick-Start dialog. You must provide both Quick-Start-Group and Quick-Start-Items if you want your tool to appear in the Quick-Start dialog.
- Quick-Start-Items -- An optional lists the items that should appear in the specified Quick-Start-Group in the Quick-Start dialog. Note that you do not specify a context for these items.
Note that VisualAge for Java only requires the name and version, but you must specify locations to run the tool to make it useful.
NOTEYour 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:
- -c -- The menu item appears on the Tools menu for selected classes.
- -p -- The menu item appears on the Tools menu for selected packages. Note this is a lower-case letter p.
- -P -- The menu item appears on the Tools menu for selected projects. Note that this is an upper-case letter P.
- -R.ext --The menu item appears on the Tools menu for selected resource files that have the specified extension.
- -R. -- The menu item appears on the Tools menu for selected resources that have no file extension.
- -R/ -- The menu item only appears on the Tools menu for resource directories.
- -R -- The menu item appears on the Tools menu for any resource file or directory.
- <nothing> -- If you leave the context blank, the tool appears on the Tools menu of the Workspace menu in all browers.
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.
NOTEYour 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:
- Tools->External Fun->Run in JDK for classes, running com.foo.Runner.
- Tools-> External Fun->Configure for classes, running com.foo.Setup.
- Tools-> External Fun->Add to ClassPath for projects, running com.foo.ClassPathSetter.
- Tools-> External Fun->Configure for projects, running com.foo.Setup.
- Workspace->Tools-> External Fun->Configure, running com.foo.Setup.
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!
WARNINGIf 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.
- The tool code can reside directly in the property editor or as a separate class in the workspace. You do not place these types of tools in the external tools directory. This also means you do not need a control file for this type of use.
- You must add the Tool API libraries to your design-time CLASSPATH.
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.
- Refuse to process the code and exit. This is the easiest way to handle the situation, but the least user-friendly. Many existing tools, including functions like the Fix/Migrate command (found on the Reorganize menu) perform their processing this way.
- Version it before proceeding. The problem with this approach is that the user has no input for the version name. We do not recommend the approach.
- Ask the user what they want to do. Always the best option, though it involves more work. The main problem is that the user may not have the authority to version an object if they are using the Enterprise Edition. This is the ideal handling of the problem, but you need to be very careful in your implementation.
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.
- com.ibm.ivj.util.base -- the main Tool API, providing all non-code manipulation.
- com.ibm.ivj.util.builders -- the builder API, providing code generation, deletion, and modification.
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!
NOTEIt 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.)
- getName() -- Perhaps the most important method in all models. This method allows you to see the name of the type, package, or project. Note that default packages use the name you see for them in the Workspace, similar to "Default package for My Funky Project".
- getOwnerName() -- Returns the name of the owner of the model (useful if running with Enterprise Edition Team Support.)
- getVersionName() -- Returns the name of this version of the model. If the model is an open edition, this returns null.
- getVersionStamp() -- Returns a timestamp representing when this model was versioned, or when the open edition was created.
- isEdition() -- Returns true if this model is an open edition. If the model is versioned, or a scratch edition, this method returns false.
- isPackage(), isProject(), isType() -- These methods return true or false to tell you which type of model this one is.
- isVersion() -- Returns true if this model has been versioned. Be careful with this method! If you have deleted a package and a project references it, and you try to call isVersion() on that package version, you will get an IvjUnresolvedException. You should watch for this when using this method!
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.
- createNewEdition() -- Creates a new open edition of this model.
- createVersion(String versionString) -- Versions this model, using the passed-in String as the new version name.
- delete() -- Removes the model from the Workspace. Remember, removing something from the Workspace does not delete it from the Repository!
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.
WARNINGThe 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.
- getProjects() -- Returns an array of Project objects, listing all projects in the Workspace.
- loadedProjectNamed(String name) -- Returns a Project object that matches the specified name.
- loadedProjectsNamed(String[] names) -- Returns an array of Project objects that match the specified names.
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.
- createDefaultPackage(boolean ignoreRepository) -- creates a default package for this project if one does not already exist. Note that this method takes a single Boolean parameter, ignoreRepository. If false, and some version of the default package exists in the repository, it throws an IvjException. If true, the processing ignores the fact that another default package already exists for the project in the repository.
- createPackage(String name, Boolean ignoreRepository) -- creates a package with the specified name in the project in the workspace.
- getAllEditions() -- returns an array of ProjectEdition objects to tell you which editions are available in the repository.
- getComment() -- returns the comment string that was saved with this edition of the project.
- getDefaultPackage() -- return the default package for the project, or null is one does not exist in the workspace.
- getEdition() -- returns a ProjectEdition representing the edition of the project that is in the workspace. Note that if this is a scratch edition of the project, you will see the edition on which the scratch edition is based. Remember, scratch editions do not appear in the repository.
- getPackages() -- returns an array of Package objects that tell you which packages are in this project in the workspace.
- getTypes() -- returns an array of Type objects that tell you which classes and interfaces are in this version of the project.
- isScratchEdition() -- returns true if the version of the project in the workspace is a scratch edition.
- openBrowser() -- opens a project browser for the project.
- setComment(String comment) -- allows you to change the comment associated with the project.
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.
- getProjectCount() -- returns the number of projects (not project editions) in the repository.
- getProjectNames() -- returns an array of Strings containing the names of all projects in the repository.
- getProjectEditions(String name) -- returns an array of ProjectEdition objects that describe versions of the specified project in the repository.
The ProjectEdition interface extends RepositoryModel (which extends Model) providing identification information, and adds a few extra methods.
- getAllEditions() -- Returns an array of ProjectEdition objects, allowing you to access any version of a project, no matter which you start with.
- getComment() -- Returns the String comment that the user saved with the project.
- getLoaded() -- Returns the Project object that represents the view of this project edition in the workspace. Note this this method returns null if this edition is not the one that is loaded into the workspace! Use the isLoaded() method (inherited from RepositoryModel) to determine if you have the loaded edition.
- getPackageEditions() -- Returns an array of the repository models (PackageEdition) that represent the specific versions of packages contained in this specific version of the project.
- getTypeEditions() -- Returns an array of TypeEditions (the repository models for the specific versions of classes and interfaces) that appear in this project. Note that the Repository does not directly track this information. Instead, it obtains it by asking its PackageEditions for their lists of classes and merges them together. If you only need class or interface information, but not a list of packages, this method can make your life much easier.
- loadIntoWorkspace() -- Places this edition of the project into the Workspace, replacing any previous edition that happened to be there. Be very careful if you use this method, as it can cause a great deal of confusion to the user.
- purge() -- Attempts to mark this version of the project for deletion. This will not work if the user does not own the project! Use this method with care!
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.
- getPackages() -- Returns an array of Package objects for all packages that are currently loaded in the user's workspace.
- loadedDefaultPackageFor(String name) -- Returns a Package object for the default package in the named project.
- loadedDefaultPackagesFor(String[] names) -- Returns an array of Package objects that represent the default package in the named projects.
- loadedPackageNamed(String name) -- Returns the Package object for the named package.
- loadedPackagesNamed(String[] names) -- Returns an array of Package objects for the named 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.
- createVersion(String versionString, boolean releaseMe) -- Versions the package using the new versionString. If you specify true for the releaseMe parameter, VisualAge will attempt to release the package into an open project edition. Note that releasing can fail for several reasons, such as the project is not an open edition, or the user does not own the package.
- getAllEditions() -- Returns an array of PackageEdition objects that represent all versions of the package in the repository.
- getComment() -- Returns the String comment that the user entered with this version of the package.
- getEdition() -- Returns a PackageEdition for this specific version of the package.
- getProject() -- Returns the Project that contains this package in the workspace. Note that a package could appear in several different project versions in the repository, but only in a single project in the workspace. Even the same version of a package could appear in different projects in the repository.
- getTypes() -- Returns an array of Type objects that represent the classes and interfaces defined in this package in the workspace.
- isDefaultPackage() -- Returns true if this is the default package for its project in the workspace, false if it's a named package.
- isReleased() -- Returns true if this package has been released, associating it with its parent project version in the repository.
- isScratchEdition() -- Returns true if this is a scratch edition of the package in the workspace.
- openBrowser() -- Opens a package browser for the package in the workspace.
- release() -- Attempts to release the package into its containing project version. This will fail if the project is not an open edition or if the user does not own the package.
- setComment(String comment) -- Sets the comment String for the package.
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.
- getPackageCount() -- Returns the number of packages (not the number of PackageEditions) in the repository.
- getPackageNames() -- Returns an array of Strings containing the names of the packages contained in the repository.
- getPackageEditions(String name) -- Returns an array of PackageEdition objects that represent the versions of the specified package in the repository.
The PackageEdition interface contains the following methods.
- getAllEditions() -- Returns an array of all PackageEditions in the repository for this package.
- getComment() -- Returns the comment String stored by the user for this package version.
- getLoaded() -- Returns true if this PackageEdition represents the version of the package that is currently loaded into the Workspace. Otherwise, it returns false.
- getTypeEditions() -- Returns an array of TypeEdition objects representing the classes and interfaces contained in this edition of the package in the repository.
- isDefaultPackage() -- Returns true if this package is the default package for its containing project version in the repository.
- loadIntoWorkspace(Project project) -- Adds the version of the package to the workspace, replacing any existing version in the specified project. If a different version of the same package exists in a different project in the workspace, the Tool API throws an IvjException.
- purge() -- Attempts to mark this version of the project for deletion. This will not work if the user does not own the project! Use this method with care!
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.
- getTypes() -- Returns an array of all Type objects loaded into the workspace.
- loadedTypeNamed(String name) -- Returns the Type object matching the package-qualified name in the workspace.
- loadedTypesNamed(String[] names) -- Returns an array of Type objects matching the package-qualified names in the workspace.
Type provides the following methods.
- createVersion(String versionString, boolean releaseMe) -- Versions the type using the new versionString. If you specify true for the releaseMe parameter, VisualAge will attempt to release the type into an open package edition. Note that releasing can fail for several reasons, such as the package is not an open edition, or the user does not own the type.
- getAllEditions() -- Returns an array of TypeEdition objects that represent all of the versions of this type in the repository.
- getAllSubtypes() -- Returns an array of Type objects that extend this class or interface, and if this is an interface, all of the classes that implement this interface. This is a complete list of all extensions or implementations, direct or indirect (meaning subclasses of subclasses are included). This can be an incredibly useful method, especially in property editors and customizers. Note that if this type is an interface and you are only interested in subinterfaces or implementing classes, you must check each returned item to see if it isClass() or isInterface().
- getDeveloperName() -- Returns the name of the user who developed this version of the type. Remember, any user can develop and version a type, but only the owner can release it.
- getEdition() -- Returns the TypeEdition that represents the specific version of the type in the workspace.
- getPackage() -- Returns the Package object that represents the package containing this type in the workspace.
- getProject() -- Returns the Project object that represents the project containing this type in the workspace.
- getQualifiedName() -- Returns the package-qualified name of the type. Note that getName() only returns the type name itself without package qualification.
- isApplet() -- Returns true if this type is an applet (a class that extends java.applet.Applet).
- isApplication() -- Returns true if this type is an application (a class that has a main() method).
- isClass() -- Returns true if this type is a class.
- isInDefaultPackage() -- Returns true if this type is in the default package for the containing project.
- isInterface() -- Returns true if this type is an interface.
- isReleased() -- Returns true if this version of the type has been released.
- openBrowser() -- Opens a class or interface browser for the type.
- release() -- Attempts to release the type into its containing package version. This will fail if the package is not an open edition or if the user does not own the type.
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.
- getAllEditions() -- Returns an array of TypeEdition objects that represent all versions of this type that exist in the repository.
- getDeveloperName() -- Returns the name of the user that developed this version of this type.
- getLoaded() -- Returns the Type object that represents this version of the type as loaded into the Workspace. This method returns null if this version of the type is not currently in the workspace.
- getSimpleName() -- Returns in the unqualified name of the type (no package name in front of it).
- loadIntoWorkspace() -- Attempts to load this type into the workspace.
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.
- getClassPathEntries() -- Returns an array of "entries" that represent the current CLASSPATH. These entries are either Strings or Project objects. If Strings, they contain the full filesystem path of the CLASSPATH entry. This could be a directory name or the name of a JAR or ZIP file. If this is a Project, it represents the project_resources directory of the indicated project.
- getDefaultClassPathEntries() -- Returns an array of "entries" that represent the required VisualAge for Java CLASSPATH entries. Note that these must appear on the CLASSPATH! If you use setClassPath() to change the CLASSPATH you must ensure that these entries appear in your new CLASSPATH. This method exists to allow you to determine where you want to place the required VisualAge for Java libraries, before or after your own libraries. Entries are either Strings or Projects as described in getClassPathEntries() above.
- setClassPath(Object[] entries) -- Allows you to set the CLASSPATH for the current execution. The entries are Projects and Strings, as described in getClassPathEntries() above. Note that you must include the entries required by VisualAge for Java, obtained via getDefaultClassPathEntries().
- removeClassPathEntries(Object[] enties) -- Removes the specified entries from the current CLASSPATH. Entries can either be Strings or Projects, as described in getClassPathEntries above.
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:
- String title -- The text displayed in the title bar of the dialog.
- String prompt -- The request asked of the user.
- WorkspaceModel scope -- The model under which to look, or null for the entire workspace.
- String mask -- The initial mask to limit the search, or null for none.
The methods that use these parameters are:
- promptForApplet() -- Only displays classes that extend java.applet.Applet.
- promptForApplication() -- Only displays classes that have a main() method.
- promptForClass() -- Only displays classes.
- promptForInterface() -- Only displays interfaces.
- promptForPackage() -- Only displays packages
- promptForProject() -- Only displays projects. Note that this method does not have (or need) a scope parameter.
- promptForType() -- Only displays classes or interfaces.
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.
- promptForModel(WorkspaceModel[] models, String title, String prompt, String mask) -- Only displays the models you pass in. Note that the list can contain a mix of Projects, Packages, and Types.
- promptForPackage(Package[] models, String title, String prompt, String mask) -- Only the specified packages appear for the user selection.
- promptForProject(Project[] models, String title, String prompt, String mask) -- Only the specified projects appear for the user selection.
- promptForType(Type[] models, String title, String prompt, String mask) -- Only the specified Types appear for the user selection.
Finally, the Workspace interface provides two methods for file-system interaction.
- promptForDirectoryName(String title, String initialDir) -- Allows the user to browse for a directory.
- promptForFileName(String title, String initialFile, String extension) -- Allows the user to choose a file, possibly limited to a specific extension (null for any extension).
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.
- getExportDirectory() -- Returns a String value containing the current target directory for the export.
- getPackages() -- Returns an array of Package objects that are to be exported to the target directory.
- getProjects() -- Returns an array of Project objects that are to be exported to the target directory.
- getTypes() -- Returns an array of Type objects that are to be exported to the target directory.
- includeClass() -- Returns true if we are set to export .class files.
- includeClass(boolean value) -- Allows us to specify that you want to export .class files.
- includeClassDebugInfo() -- Returns true if we are set to include debug attributes in the exported .class files.
- includeClassDebugInfo(boolean value) -- Allows us to specify that we want to include debugging attributes in the exported .class files.
- includeJava() -- Returns true if we are set to export .java source files.
- includeJava(boolean value) -- Allows us to specify that we want to export .java source files.
- includeResources() -- Returns true if we are set to export resource files from the projects' project_resources directory.
- includeResources(boolean value) -- Allows us to specify that we want to export resource files from the projects' project_resources directory.
- overwriteFiles() -- Returns true if we are set to overwrite existing files without warning. If false, VisualAge asks the user if they want to overwrite any existing files.
- overwriteFiles(boolean value) -- Allows us to specify that we want to overwrite existing files without warning. If we pass in false, the user must verify each file to overwrite.
- setExportDirectory(String directory) -- Allows us to specify the target directory for the export. VisualAge for Java will create this directory if it does not already exist.
- setPackages(Package[] packages) -- Allows us to specify which packages we want to export.
- setProjects(Project[] projects) -- Allows us to specify which projects we want to export.
- setTypes(Type[] types) -- Allows us to specify which types we want to export.
- useSubdirectories() -- Returns true if we are set to create the proper subdirectory structure in the target directory.
- useSubdirectories(booelan value) -- Allows us to specify that we want VisualAge for Java to automatically create the proper directory structure for exported classes. Each class resides in a directory structure built from its package's name.
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.
- allowRepositoryCreation() -- Returns true if this export specification will allow VisualAge for Java to create a new repository (if the specified repository does not exist).
- allowRepositoryCreation(boolean value) -- Allows us to specify that we want to allow VisualAge for Java to create a new repository as the target of the export if the specified repository does not already exist.
- getPackageEditions() -- Returns an array of PackageEdition objects that represent the versions of packages to be exported.
- getProjectEditions() -- Returns an array of ProjectEdition objects that represent the versions of projects to be exported.
- getRepository() -- Returns the target repository.
- setPackageEditions(PackageEdition[] packages) -- Allows us to specify which package versions to export. You can export several versions of the same package.
- setProjectEditions(ProjectEdition[] projects) --Allows us to specify which project versions to export. You can export several versions of the same project.
- setRepository(Repository repository) -- Sets the target repository. You will need to use the getRepository() methods in Workspace that take String(s) to specify the target repository. Note that although VisualAge for Java can create the target repository, the directory containing that target repository must already exist.
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.
- getClassFiles() -- Returns an array of Strings, the fully-qualified names of the .class files to be imported.
- getDefaultProject() -- Returns the Project object that represents the project into which any new packages and types within those packages are imported.
- getJavaFiles() -- Returns an array of Strings, the fully-qualified names of the .java files to be imported.
- getResourceFiles() -- Returns an array of Strings, the names of the resource files to be imported. These names are relative to the resource path.
- getResourcePath() -- Returns the base path of resources to be imported.
- setClassFiles(String[] fileNames) -- Sets the names of the .class files to import. These names are all fully qualified.
- setDefaultProject(Project project) -- Sets the Project into which all new packages and their types are imported. Types within existing packages are imported into their project in the workspace.
- setJavaFiles(String[] fileNames) -- Sets the names of the .java files to import. These names are all fully qualified.
- setResourceFiles(String[] fileNames) -- Sets the names of the resource files to import. These names are all relative to the resource path. This is necessary so VisualAge for Java knows how to copy the resources into the project_resources directory. You need to choose a resource path that maps to the project_resources directory, and VisualAge will set up the appropriate directory structure under the project_resources directory that matches the names of these files.
- setResourcePath(String path) -- Sets the name of the base directory for resources. All resource files are relative to this directory.
- useSharedResDir() -- Returns true if set to import resources into the shared project_resource directory.
- useSharedResDir(boolean value) -- Specifies whether to import resources into the shared project_resource directory.
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.
- getPackageEditions() -- Returns an array of PackageEdition objects that represent the versions of packages that we are set to import.
- getProjectEditions() -- Returns an array of ProjectEdition objects that represent the versions of projects that we are set to import.
- getRepository() -- Returns the source Repository for the import.
- setPackageEditions(PackageEdition[] packages) -- Sets the package versions that we want to import.
- setProjectEditions(ProjectEdition[] projects) -- Sets the project versions that we want to import.
- setRepository(Repository repository) -- Sets the source repository.
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.
- createMethodBuilder(String builderName) -- Creates a new MethodBuilder that you can use to add a new method. If you want to modify or delete an existing method, you should ask the TypeBuilder to find the appropriate MethodBuilder for you. The name passed in to this method is simply a unique name for you to work with; it has no effect on the name of the method you are creating. Each MethodBuilder must have a unique name, so it is probably a good idea to use a name that encodes the signature of the method you want to create. For example, if you are creating method callDoctor(Child, Symptoms) you may want the string to be "callDoctor(Child,Symptoms)" to make it easy to distinguish from other MethodBuilders. See Add a Method to a Class or Interface below for an example of creating a new Method Builder.
- createTypeBuilder(String name, Package pkg) -- Creates a new TypeBuilder for adding a new class or interface. You cannot use this method to work with an existing type! See the Create a New Class or Interface below for an example of creating a TypeBuilder for a new class.
- createTypeBuilder(Type type) -- Creates a TypeBuilder to modify an existing type in the workspace. You cannot use this method to work with types that do not exist yet! See Add a Method to a Class or Interface below for an example.
- createUserCodeBlock(String identifier, String indent) -- Creates a special block comment that delimits a safe place for the user to add their own code. The identifier can be any String you would like, but must be unique within the block of code you are generating. You can use the saveMerge() method in the TypeBuilder interface to merge code containing user code block comments with existing user code, keeping the user code. The indent parameter is a spacing String, allowing you to line up the comment blocks with the rest of the generated code. See Modify Method with Embedded User Code below for an example.
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.
NOTEAccessing 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.
- addMethodBuilder(MethodBuilder methodBuilders) -- Adds a MethodBuilder to the TypeBuilder to represent the change we want to make to a method in this type. When you save() or saveMerge() this TypeBuilder, the Tool API executes the actions specified by your added MethodBuilders.
- checkMethodExists(MethodBuilder methodBuilder) -- Checks to see if this TypeBuilder contains a method with the same signature as the specified method builder. This method only compares the name and parameter types of the specified MethodBuilder to the methods in this type, not the method body, return type or access modifier. Note that if this method returns true, it does not mean that the specified MethodBuilder is in the return list from getExistingMethods(). It just means that a method with that signature exists. Note that when specifying the signature of the MethodBuilder, you should fully-qualify the type names you use to describe the parameters. See Delete a Method below for an example.
- getBuilderName() -- Returns the name of this builder, which is not necessarily the same as the name of the type. This should only be used for internal distinction between TypeBuilders.
- getExistingMethods() -- Returns an array of MethodBuilder objects, describing the methods contained in this type.
- getExistingMethodSource(MethodBuilder methodBuilder) -- Returns a String containing the source code of a method that matches the signature of the passed-in MethodBuilder. See Obtaining the Source of an Existing Method below for an example.
- getExistingSource() -- Returns a String that contains the entire source of this type, which does not contain the method source (as in the Workspace view of a class or interface definition.)
- getMethodBuilders() -- Returns an array of MethodBuilder objects that represent the actions we want to take against methods. Note this is very different from getExistingMethods(), which creates MethodBuilders for each method in this type.
- getSource() -- Returns a String representing the current set source. This will be the source that replaces the existing source if this type is save()d.
- removeAllMethodBuilders() -- Removes the MethodBuilders that you added to make changes. Note that this does not delete any methods from the type!
- removeMethodBuilder(MethodBuilder methodBuilder) -- Removes a specific MethodBuilder from the list of those you had added to make changes to the type. Note that this does not delete the actual method from the type.
- save() -- Saves the type, setting its source in the workspace to the source you set by setSource(), and executing the actions of all MethodBuilders added to this type. This save ignores any user code in changed methods.
- saveMerge() -- Same as save(), except user code is merged with the new versions of methods that define user code blocks.
- setSource(String source) -- Overwrites the source of this type. If this source contains any methods they are extracted from the source as their own new MethodBuilders.
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.
- getBuilderName() -- Returns the builder name for this MethodBuilder. As mentioned several times above, remember that the builder name might not be related to the method signature.
- getSource() -- Returns the source that you have set for the method. If you obtain an array of MethodBuilders from a TypeBuilder's getExistingMethods() method, calling getSource() on those MethodBuilders will return null! If you want the source you must pass the MethodBuilders to the getExistingMethodSource() method of the TypeBuilder.
- isMarkedForDeletion() -- Returns true if you have marked the method for deletion.
- markForDeletion(boolean deleteMe) -- Allows you to specify that this method should be deleted.
- setSource(String source) -- Allows you to set the new source for this method. When you add the MethodBuilder to a TypeBuilder and save() or saveMerge() the TypeBuilder, the new source is updated in the workspace.
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.
NOTEThere 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.
NOTEWhen 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 = (LastUsedOption