People frequently ask us why we designed Xeddy as a JBuilder tool. The reason is very simple. We've been using JBuilder in our everyday work for years and over time have become familiar with its Open Tool API. So when it came to the question of how to structure our intended XML editor to be available for regular work as soon as possible, JBuilder Open Tool appeared to be the most natural solution.
Xeddy uses JBuilder's structure view, editor and message pane. The integration of the two products is so tight that the end user will probably not be able to find the border between Xeddy and JBuilder. (You could rewrite this as follows: The integration of the two products is so tight that to the end user it will appear seamless.) Working on Xeddy we found JBuilder's API as open and extendible as declared on Blake Stone's update website. Its architecture is very close to human thinking; you find things where you expect to. Of course, we had been working with Borland's products years before, acquiring the look and feel of their products, and maybe that is why we felt so comfortable here. For this reason we found their way of thinking about the development environment very straightforward and sometimes we guessed the solution to things not yet considered.
OK. Let us go into more detail to show you how to use JBuilder OpenTool API.
Once you've completed the basic tutorial, you are able to create a project with the OpenTools library, prepare an entry point for the loader (initOpenTool method) and perform some basic tasks. Now you can continue and perform another basic OpenTools' writer task: create a viewer.
Assume you've just invented a new, exciting image file format and are thinking about how to make money on it. First, you have to create a viewer module for JBuilder. Fortunately, the PPM image format is not our invention, and so we were able to use the driver from http://www.acme.com instead of making it from scratch. Having the driver in place we could start teaching JBuilder how to work with the PPM format. You can follow the process using the complete archive containing all classes here or check the results using the compiled version. An example of PPM file is here. Ready?
The first thing to do is to create a particular FileNode subclass representing a PPM image file. The project view of JBuilder displays a list of files currently assigned to the project. Each file listed has a relevant Node object attached to it. Here I register the "ppm" extension with a short description.
Then we register the NodeViewer itself. This class will read a ppm file using acme's decoder and prepare a viewer component that physically displays the image. Just due to my laziness we will subclass an abstract implementation of NodeViewer, the AbstractNodeViewer.
Finally we prepare a NodeViewerFactory implementation which, being registered, makes decisions about the viewers used for nodes. We will use our viewer whenever any instance of our node arrives.
Each FileNode represents nodes within a project that are associated with storage outside the project file. FileNode has two superclasses: UrlNode which keeps a Url representing resource (not necessary File) and abstract Node (direct Object subclass) which is an abstract parent of all nodes in the whole system; including project nodes and package/folder helper nodes (displaying structure of project). We have chosen FileNode as the superclass, but the choice was not obvious. There is also the ImageFileNode class with a nice icon, so why not subclass from here? I will answer below, but now try for yourself: change the superclass to ImageFileNode (no additional import clause required), compile and run!
Now look at the code. The most interesting method here is initOpenTool:
public static void initOpenTool(byte major, byte minor) {
if (major == PrimeTime.CURRENT_MAJOR_VERSION) {
FileNode.registerFileNodeClass("ppm", "PPM File",
PpmFileNode.class, ImageFileNode.ICON);
} else System.out.println("Unsupported version:"+major+",
expected "+PrimeTime.CURRENT_MAJOR_VERSION);
}
The two parameters passed to the method 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, therefore we need to check it first. The minor version number is incremented when features are added to the OpenTools API. The registerFileNodeClass method will add a new node extension and relevant node class to the registry of known types and their associated extensions, including a short description and an icon.
Fortunately there is a public field ICON in the ImageFileNode, so we will use it. Otherwise we should load our own icon and present it here (in getDisplayIcon) as well as during the registration of the node class. It could be done in a couple of ways, using IconLoader for instance:
Icon icon = IconLoader.getImageIcon("resource/PpmFileNode.gif", PpmFileNode.class);
Assume we have a project with a bunch of files. Now, open any one of them. A big viewer or editor window appears on the right side of the application frame with a tab with a node's name on top of it. At the bottom of the view is another set of tabs: NodeViewers that have been created by registered NodeViewerFactories that 'accept' the corresponding node. The view contains as many viewer's tabs as have been registered for the current type; if none, a source view is being used. If opening an image (GIF or JPEG), only a viewer appears with a few controls (only viewer has been registered; create an editor and register it, another tab will appear):

Now we have to create the same viewer for PPM file format. First, we must read the PPM file and create an Image instance. Then we will use a viewer component. So, read the file: there is a free implementation of PPM decoder, take a look at PpmDecoder and its superclass. You do not need to fetch the classes, they are included in the project. Use the decoder like this:
InputStream stream = ... // Assume it exists and contains data from PPM image file ImageProducer decoder = new PpmDecoder(stream); Image image = Toolkit.getDefaultToolkit().createImage(decoder);
The PPM file format is a disk hungry format, especially its ascii version -- the 400x200 splash image costs 235kB in binary format and 894kB in ascii format. Therefore it seems to be a good idea to detach the loading of the image into a separate thread which should be started as soon as possible (we need only a Node to display). It means, in constructor:
public PpmNodeViewer(Context context) {
super(context);
new Thread(this).start();
}
and perform the read:
public synchronized void run() {
Node node = context.getNode();
if (node instanceof PpmFileNode) try {
PpmFileNode ppmnode = (PpmFileNode)node;
InputStream stream = ppmnode.getInputStream();
ImageProducer producer = new PpmDecoder(stream);
image = Toolkit.getDefaultToolkit().createImage(producer);
} catch (Exception ex) {
}
}
The read (or Reading the method - not sure) method asks the context for a node. Context class encapsulates the Browser and node in a single class. This is frequently used by NodeViewerFactory implementations that need to provide a new instance of a NodeViewer per unique Browser/Node pairing. If Node conforms to our requirements, it is asked for the InputStream and a new image is prepared.
Every NodeViewer should implement the NodeViewer interface. It defines support for the viewer lifecycle:
void viewerActivated(boolean requestFocus); void viewerDeactivating(); void viewerDeactivated(); void releaseViewer();
, some accessories:
String getViewerDescription(); Icon getViewerIcon(); String getViewerTitle();
and (important for us) support for viewer and structure component. The Viewer component is physically displaying the image, it could be JLabel, for example. The Structure component displays the structure of the viewed file. It makes no sense for an image, but in the case of a Java source file it could display parsed information about fields and methods, in the case of an XML file it will display the whole parsed XML structure. There are a few additional methods supporting property change events in cooperation with the Browser (the main window with a menu, project window...).
It is always comfortable to use a predefined abstract class with some methods implemented instead of writing it all from scratch. BTW we used AbstractNodeViewer, filled the required accessory information and returned null when asked for structure component. When returning the viewer component, we have two choices: use minimal implementation of the viewer with JLabel or use a predefined class commonly used for viewing images.
Any JComponent can be the return type of getViewerComponent(), therefore we can simply return something like this:
return new JScrollPane(new JLabel(new ImageIcon(image)));
but we have to wait for the image loading thread first:
while (!loaded) wait(500); if (image != null) return new JScrollPane(new JLabel(new ImageIcon(image))); else return null;

This viewer displays the image at 100% only. It is useful, but Borland's viewer is better...
Use the ImageViewerComponent (package com.borland.primetime.viewer) instead of JLabel:
return new ImageViewerComponent(image);
or, when using JB4:
ImageViewerComponent component = new ImageViewerComponent(); component.setImage(image); return component;
Please note that ImageViewerComponent is not a documented class and using it is not recommended. It causes incompatibility between version 3.x and 4. But it looks good, doesn't it?
Well, we have a nice viewer displaying an image. But how do we tell JBuilder to use this viewer for the PpmFileNode? The answer is NodeViewerFactory. The NodeViewerFactory interface defines a factory that creates instances of NodeViewer for displaying the contents of Node objects. Each NodeViewer must be created using an implementation of this simple interface, which requires only two answers: whether a given node could be viewed using the current factory (each factory serves for one or more viewers); if so, the NodeViewer will be asked. And that is all: now you can create a special implementation of NodeViewerFactory, which will operate PpmFileNodes and will recommend PpmNodeViewers to everyone who asks about them. Finally, register this factory in the Browser and you're done!
Take a look at PpmFileNodeViewerFactory, a NodeViewerFactory implementation dedicated to handling PpmFileNode and its viewer.
Xeddy consists of four basic parts: a structure view and its model, an inspector and its table model, an editor's document and a background thread parsing xml data. These components are interconnected with a number of listeners, as shown here:

There are four important blocks on the picture: the structure view with StructureTreeModel (top-left), the inspector view with NodeTableModel, the editor with NodeDocument and the ParserThread without visual representation. These four create one part of the system, the second one being jbuilder-related objects: actions, options, wizards and, of course, a bunch of file nodes and related viewers as described above.
| PLEASE NOTE |
|---|
| ...that parser thread waits a predefined amount of time of user's inactivity [2s] before starting the parser. We hope it prevents conflicts between user and thread. |
The parser thread uses an implementation of the SAX/2 parser. The system defines its own ContentHandler which produces the DOM objects including location information (used e.g. in "Show in code" action). The thread operates on a list of NodeListeners and informs them about changes in XML code. For the inquisitive, there is a ParserStatusListener handling information about parser status (waiting, toggled, started, finished and aborted).
Everyone is welcome to register as a NodeListener to receive information about changes of parsed XML. Currently, all mentioned blocks are as visible on the image.
The system has been designed as extremely open for XML parsers. It is very easy to plug a new, just-arrived, parser into the system and use it. Unfortunatelly, not all parsers implement at least SAX/1 fully, to say nothing of SAX/2. Excluding the Apache Xerces; we have found this parser an excellent piece of software. Therefore, hardy guys could try to plug their own or new parser into Xeddy, but at their own risk. There is a parser property file com.amaio.xed.parser.resource.ParserThreadFactory.properties. It looks like this:
/** * Copyright (c) 2000 Amaio, Inc., All rights reserved. * This file may be copied, modified and distributed only in accordance with the * terms of the limited licence contained in the accompanying file LICENSE.TXT. * * $Id: ParserThreadFactory.properties,v 1.4 2000/07/26 15:28:54 slavekp Exp $ */ xed.parser.xerces=Apache Xerces xed.parser.xerces.parser=com.amaio.xed.parser.xerces112.ValidatingSAXParser;checkingValidity=true xed.parser.xerces.modeller=com.amaio.xed.parser.Modeller$Default xed.parser.xerces.document=org.apache.xerces.dom.DocumentImpl
You can find several keys here. First, xed.parser.xerces is an as yet unused parser name. We plan to add a parser chooser to the option page, where these names will be listed in a popup list. The second key, a xed.parser.xerces.parser, is more interesting. It is a parser name. Here you can add several properties followed by a semicolon to the end of the class name: it's a javabean-like property name. System will prepare relevant method calls from them, e.g. setCheckingValidity(true). You can specify more calls, if used, delimited by colons. Please note that you can use this method calling mechanism for all specified class names: parser name, content handler name (modeller) and document name.
Modeller takes care of constructing the DOM from SAX calls. There is no reason to replace it but if you find one, replace it with your own implementation of com.amaio.xed.parser.Modeller. The last entry represents name of parser's Document implementation.
Example:
Assume Sun have finished their parser and it works properly. Then you can use it like that:
xed.parser.sun=Sun JAXP xed.parser.sun.parser=com.sun.xml.parser.Parser xed.parser.sun.modeller=com.amaio.xed.parser.Modeller$Default xed.parser.sun.document=com.sun.xml.tree.XmlDocument
Structure View consists of two objects: the StructureTreeModel, a TreeModel implementation and its visual representation, and a StructureTree subclass. StructureTreeModel receives events about DOM updates and forces StructureTree to update.
Inspector displays detailed information about the selected Node. It uses JTable as a visual component and NodeTableModel as a model. It uses several custom renderers and editors to provide functionality as you can see.
This is a Document interface implementation without special features.
1. Blake Stone's Update website and the OpenTools Overview
The online resource for up-to-the-minute JBuilder news straight from the R&D team and the basic steps required to create, compile, and install a JBuilder Foundation OpenTool are unveiled.
2. Download the complete archive containing all classes here or check the results using the compiled version. An example of PPM file is here.
3. ACME Laboratories
ACME Labs is proud to make available a variety of software, all free, some trivial, some massive, all high-quality.