JBuilder OpenTools loader concepts

General description

JBuilder uses a dynamic discovery mechanism to load itself rather than a hard coded startup process.  There are actually only two class files at the heart of JBuilder: the PrimeTime class and the Command interface.

The PrimeTime class provides a variety of methods for loading OpenTools and interpreting a command line.  The actual process of discovering OpenTools is entirely automatic and is described in detail in the next section.

The Command interface defines a self-describing command line option.  Each implementation of the interface provides a one-line description of the option, brief online help, and the actual command processor that handles requests.

There is no single class or interface that embodies the concept of an OpenTool.  Each OpenTool can inherit from any class, though it must be public and it must define a single method used to initialize the tool.  The OpenTool initialization method must have the following signature:

public static void initOpenTool(byte, byte)

This approach provides a very open ended foundation for extensibility with minimal additional complexity.  An extension to JBuilder need only be added to the classpath and it will automatically be initialized at the appropriate time.

Detailed description

OpenTool discovery

OpenTools are discovered by searching the current classpath for special manifest entries.  An OpenTools entry in the JAR's manifest starts with the string "OpenTools-" and is followed by an OpenTools category.  The value of the manifest entry must be a space delimited list of fully qualified class names, each of which must be a valid OpenTool.

The following manifest file describes a single OpenTool class in the "Core" category:

OpenTools-Core: com.borland.primetime.vfs.FileFilesystem

There is an alternative mechanism for finding OpenTools that is designed to be used only during development.  This mechanism is described below under Defining OpenTools during development.

The initOpenTool() method

A valid OpenTool need only be a public class that implements a single static method matching the required signature:
public static void initOpenTool(byte, byte)

This method is invoked once when JBuilder is loading, giving the OpenTool a chance to do whatever initialization is appropriate. The two parameters passed to the initOpenTool() method describe the version number of the OpenTools API implemented by JBuilder.

OpenTools API versions

The two parameters passed to an OpenTool are the major and minor version numbers. The major version number is incremented when the OpenTools API changes in a way that breaks compatibility with existing OpenTools, and the minor version number is incremented when features are added to the OpenTools API.

Each OpenTool must check the major version number before performing any registration tasks. The following template illustrates the standard implementation technique for this requirement:

public class examples.opentools.Sample {
  public static void initOpenTool(byte majorVersion,
      byte minorVersion) {

    // Make sure the OpenTools API is compatible
    if (majorVersion != PrimeTime.CURRENT_MAJOR_VERSION)
      return;

    // Perform OpenTool initialization
  }
}

Writing and registering an OpenTool

The following is an example of a trivial OpenTool that does nothing but greet the user at load time:
public class examples.opentools.GreetUser {
  public static void initOpenTool(byte majorVersion,
      byte minorVersion) {

    // Make sure the OpenTools API is compatible
    if (majorVersion != PrimeTime.CURRENT_MAJOR_VERSION)
      return;

    // Perform OpenTool initialization
    String userName = System.getProperties().getProperty(
        "user.name");
    System.out.println("Greetings, " + userName + ".");
  }
}
OpenTools must be compiled and added to the classpath before starting JBuilder, and the OpenTools discovery process needs to be able to find the class name at startup.  To accomplish this you'll need to create a manifest file that looks something like this:
OpenTools-Core: examples.opentools.GreetUser

Note that manifest file must conform to the guidelines set forth by Sun.  Entries are case sensitive, the manifest file cannot include blank lines, each line must end with a line terminator, and only the last entry found with a given name is used.

To initialize more than one OpenTool in a single category using a single manifest file you must use a space delimited list of class names.  The manifest file format will allow a single entry to span more than one line, however each line continuation must begin with a space character and this character does not count as a space between entries.  As a result, each new line with a class name should start with two spaces.  The following example uses the text "<space>" to indicate the presence of a space character for clarity:

OpenTools-Core:<space>examples.opentools.OpenToolOne
<space><space>examples.opentools.OpenToolTwo

Suppressing existing OpenTools

There are times when you may wish to replace existing functionality by suppressing an existing OpenTool and providing a replacement that serves the same purpose.  Any entry in the OpenTools list that starts with a minus character is treated as a request to suppress the OpenTool of the specified class name:
OpenTools-Core:<space>-examples.opentools.OpenToolOne

The category associated with the suppression request is ignored.  The presence of the above entry in any manifest file on the classpath would prevent the OpenTool "examples.opentools.OpenToolOne" from ever being initialized.

Registering command-line handlers

OpenTools in the "Core" category can extend the set of command line options recognized by JBuilder. The static method registerCommand() must be called once for each command line option.  It is important to remember that OpenTools in categories other than "Core" are typically loaded after the command line has been parsed; any commands they register will arrive too late to be recognized by the command line handler.

Each command registration requires two parameters:

Note that the option name must be supplied without a leading hyphen, as shown in the following example:

PrimeTime.registerCommand("example", new ExampleCommand());

The preceding statement would allow JBuilder to react to a command line that uses a leading hyphen to indicate the presence of an option. The remainder of the option name is case-sensitively matched to the appropriate command:

jbuilder -example
Each option on the command line results in a call to the associated Command instance's invokeCommand() method.

Commands that can accept one or more arguments should override takesCommandArguments() and return true.  These commands will be passed everything between their command line option and the following option as a parameter when invoked.  In the following example the handler for "example" will receive the three command arguments "one", "two", and "three".

 jbuilder -example one two three -example2 four
Additional methods defined by the Command interface require each command to return a brief one line self description from getComandDescription(), and to print a more detailed description when printCommandHelp() is called.  The textual descriptions provided are available to the end user via the built-in "-help" command line option.

The default command-line handler

After invoking all other command line handlers JBuilder will always invoke the default command line handler.  This is the command most recently registered with a null option.

The OpenTools provided with JBuilder will automatically register a default command handler when the "Core" OpenTools are initialized that starts the full graphical IDE.  Third-party OpenTools can take action during their invokeCommand() method if the normal IDE load process needs to be circumvented.  The following statement is sufficient to prevent the graphical IDE from loading:

PrimeTime.registerCommand(null, null);

Defining OpenTools during development

Manifest entries can easily be included in JAR or ZIP archives, which is convenient for delivery but far from convenient during development.  JBuilder also supports the notion of an "override" manifest which can be used in conjunction with a directory structure or an archive as an alternative to the true manifest.  The override file has the same format as the normal manifest file, but JBuilder looks for it as an independent file in the same directory as each classpath entry. The override file is derived from the classpath entry by adding the suffix ".opentools".  For example:
 
CLASSPATH entry Override manifest filename
c:\classes\ c:\classes.opentools
c:\classes.zip c:\classes.zip.opentools

If found, the "override" manifest is used and the actual manifest file is ignored.

The JBuilder startup process

JBuilder's main method performs the following steps on startup:
  1. Searches the classpath for OpenTools
  2. Loads all initializes all OpenTools in the "Core" category
  3. Parses the command line and invokes appropriate Command instances
  4. Invokes the default Command instance

One of JBuilder's own core OpenTools registers a default command which, unless overridden while parsing and invoking commands from the command line, continues to load the remainder of the IDE as follows:

  1. Displays the splash screen
  2. Loads and initialize all OpenTools in the "UI" category
  3. Initializes the Properties System
  4. Creates and displays a Browser instance
  5. Hides the splash screen 

Debugging the startup process

JBuilder performs a special search for the command line switch "-verbose" before starting the OpenTools discovery process.  If present, details of the search process and initialization of each OpenTool are printed to System.out.  An abbreviated example of this output appears as follows:
Scanning manifest from X:\jbuilder30\jdk.2\lib\jpda.jar
Scanning manifest from X:\jbuilder30\lib\AwtMotifPatch.jar
Scanning manifest from X:\jbuilder30\lib\beandt.jar
Scanning manifest from X:\jbuilder30\lib\DBCSpatch.jar
Scanning manifest from X:\jbuilder30\lib\dx.jar
Scanning manifest from X:\jbuilder30\lib\jbcl.jar
Scanning manifest from X:\jbuilder30\lib\jbuilder.jar
Scanning manifest from X:\jbuilder30\lib\ext\PalmDeployer.jar
OpenTools discovered (110ms)
--- Initializing OpenTools-Core
OpenTool com.borland.jbuilder.JBuilderToolkit (170+701ms)
OpenTool com.borland.primetime.node.FolderNode (371+0ms)
OpenTool com.borland.primetime.node.ImageFileNode (20+20ms)
OpenTool com.borland.primetime.node.TextFileNode (0+0ms)
OpenTool com.borland.jbuilder.node.PropertiesFileNode (20+10ms)
OpenTool com.borland.jbuilder.node.ClassFileNode (10+0ms)
OpenTool com.borland.jbuilder.node.CPPFileNode (10+0ms)
OpenTool com.borland.jbuilder.node.HTMLFileNode (10+0ms)
--- OpenTools-Core initialized (1493ms total)

The first eight lines above describe the discovery process, detailing exactly which files are being used to gather a complete list of OpenTools available.  The next line reports how long the complete discovery process took.

The next nine lines describe the process of loading each defined Core OpenTool in turn.  The paired times shown in parenthesis describe time to load the class and the time spent running the initOpenTool method.  These times should typically be less than 100ms combined, performing absolutely minimal initialization during startup. Time-consuming initialization should be deferred until the first time the feature is actually used.

The last line summarizes the combined time spent loading and initializing every OpenTool in a particular category.  This value is the measured elapsed time rather than a sum of the times reported for individual OpenTools.

Defining additional OpenTools categories

Complex OpenTools may wish to provide hooks for other OpenTools to customize their behavior even further. The text editor in JBuilder is one such example, allowing OpenTools in the "Editor" to register keymaps and other specialized behavior.

Why create a new category instead of just registering everything in the "UI" category?

Choosing a category name

Inprise reserves the right to use any category name that does not start with a package-like domain prefix. Third parties should use a category name based on one of their domain registrations to avoid name collisions. For example: Amazon.com might choose to use "com.amazon.Book" as a category.

Initializing a category

The static initializeOpenTools() method can be used to initialize all OpenTools belonging to a specified category. The method will return once each tool registered in the category has been initialized.