XUL2Swing - A Program to translate XUL to Java Swing

A Historical Synopsis

On my first Java project, which I just completed recently, I had developed a fairly complex XML configuration file, which provided for a number of user options. The program was written for a limited customer base, and I hoped that I could get away with a good manual describing how to use a text editor to edit the configuration file. My boss didn't see it that way, and wanted a GUI editor built into the program. I didn't know Swing.

Years ago, before I knew about XML, I played with the idea of using text within a hierarchical format to describe a window structure; i.e., a GUI for a program. I thought it would be very cool to write a program that could convert my textual description into code that I could incorporate as a class or a set of classes into a program. I went far enough with the idea as to write some of the conversion program, but I never finished it.

Download.


Structure vs Content

But that set me up for XUL, and I immediately appreciated what XUL has to offer: a text language that I could use to describe complex window structures easily, and actually see them on-screen without writing any code other than the XML to describe them.

Writing

<window/>

seemed a much easier way to display a window than to try to write the equivalent code in, say, C++, or even Java. And, in XUL I could describe much more complex window structures using XUL elements named by the window parts that I wanted; such as "label", "textbox", "button", etc.

<window orient="vertical"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <hbox>
    <label value="Enter File Name: "/>
    <textbox id="file_name"/>
    <button label="Browse..."/>
  </hbox>
</window>

That's pretty simple. It describes a window with a label, a textbox, and a button arranged horizontally. But it isn't my goal to teach XUL here; there are online tutorials and references. And there are plenty of samples as well. I have included one, called Password.xul, in this package.

So I put together a XUL description of the editor, which took a bit over 300 lines of XUL, and, because that had been so easy, spent some of my commute time wishing I could translate that directly into Java Swing, and skip the step where I would have to hand code it.

I could see no way around it. But I decided that, rather than hand coding the interface using the XUL description as my guide, I would try to write a XUL2Swing converter in Java that would use the XUL file as input, and would output a Java class that implemented the interface in Swing. Not knowing Swing, I figured this was a simple matter of a routine to recursively traverse the XML, and, for each XUL element, generate the equivalent Swing code. And likely, this would be a good way to learn Swing, too.

This worked out. Pretty well. At least the generated code compiled, and the window, along with its children components, appeared when I ran the test program. But I had no actual placement and size information in the XUL, so the Swing layout was not ideal. To say the least.


The Size of It

Firefox has a growing family of extensions, and one that I had recently installed, called FireBug, is especially ingenious and useful. One of its features is a console window that I could actually write into using the console.log() command in JavaScript.

Another fact about Firefox is that it maintains all the placement and sizing information of the XUL objects in properties that you can access from JavaScript.

So, putting these two facts together, I wrote a short JavaScript routine to dump my XUL template, along with the left, top, width, and height of every XUL object in my template, into the FireBug console as a XUL document. And then I connected my JavaScript routine to the Ok button using the oncommand attribute.

Now, I could load my XUL document into Firefox, start up FireBug, resize the window to taste, and then click the Ok button. The new XUL document appeared in FireBug's console window, which I could copy-and-paste into a new file exactly like my original XUL file, but with the placement and size information added as attributes in the XUL elements. A few lines of code added to my XUL2Swing converter used the new attributes to flesh out a setBounds() command for each Swing component.


Leaner is Meaner

Again, while commuting, I realized that it would be detrimental to require that a user had to have FireBug, as cool as it is, to use this package, and so I started wondering how hard it would be to write some XUL that would allow to load the XUL file I was working on into Firefox, size it to taste, and then output the modified XUL to a file. It turned out that most of that could be easily done with a little XUL and some JavaScript all in a single file, which I cleverly call XUL2Swing.xul in this package. Regrettably, you can't actually write the output to a file from FireFox, due to built-in protections, so you still have to use copy-and-paste to get the text out of Firefox and into a file.


How to Use This Toolset

There are several steps:

1. Use a text editor or an XML editor to create and edit your XUL source document. An open-source XUL editor is available, and it is being improved as we speak. It's called XUL Explorer. It currently has some issues, but it is being improved.

Note: you can read about these steps in a different form in Password.xul, XUL2Swing.xul, and Password-info.xwk.

2. Start Firefox, and select the File/Open File menu item to load XUL2Swing.xul. This is the XUL template included with this tool set, and mentioned earlier, that provides a toolbar and a tabbed area. The toolbar provides a textbox and a Load XUL button to let you load your XUL source document into the panel associated with the Preview tab. It also provides a second button, called Output XUL, to generate the modified XUL document in the panel associated with the Output tab. Use the little sizer tab at the lower-right corner of your browser to size your GUI. When you are satisfied that the window is as small as you can get it without truncating any GUI object in your window, click the Output XUL button. Then click the Output tab to see the output document. Use your mouse to drag-select the entire document in the output panel. And then press control-C to copy your marked text into the clipboard.

3. Paste the contents of the clipboard into an empty file using a text editor, and then save the copied file into the directory where you have installed the XUL2Swing program. Be sure to name the file the same as the name of the class you want to create from the file. Save the file with an ".xwk" extension. For example, the name of the sample set is Password.xul and Password.xwk. When you complete the conversion process, the names of the Java files will be Password.java and PasswordListener.java.

4. Run the XUL2Swing program using the following command line:

XUL2Swing <classname> <outdirname> [STRING] [FOLD] [LOG | DEBUG | DEEP | VERBOSE] [LISTENER] [MAIN]

The XUL2Swing is a Java jar file, so to make it easier to use, a batch file, called XUL2Swing.bat comes in the zip-file. The batch file uses the first parameter and appends ".xwk" to it to find the input file in the execution directory. It names the output file by appending ".java" to the first parameter, and writes the file to the directory specified by the second parameter.

The remaining parameters are not positional. You simply type the actual words in whatever order they occur to you.

The STRING parameter, if present, collects quoted strings together at the top of the generated module.

The FOLD parameter, if present, removes duplicate strings when the STRING parameter is also specified.

The  logging parameter (choose one or none of LOG, DEBUG, DEEP, or VERBOSE) specifies the logging depth of the generated code. None will log errors only. Each of the choices subsumes the choices to its left. E.g., DEEP includes errors, LOG, and DEBUG.

The LISTENER parameter generates a module that implements an ActionListener class for the GUI. Be careful not to wipe out a good version by not removing this parameter at the appropriate time!

See the change list for a description of the MAIN parameter.

BTW: XUL2Swing requires JRE 6 or better. I am cutting edge, or maybe naive, and I use JRE 6. The reason why I use 6 or later for this project is because the DOM and XPath are finally integrated into rt.jar in those versions. XUL2Swing uses those tools heavily. Also, the isEmpty() method is implemented as of 1.6. I can't believe they overlooked this essential String method before. What were they thinking?

If you are at all like me, you will actually go through many permutations of these steps a number of times before you are through. Is it worth it? I think so, because you will be focused almost entirely on aesthetics and usability, and not on implementation details.

This process works well if you are working in the Eclipse environment. Nothing in the conversion process generates the import statements that you need, but you can add them in Eclipse with a simple control-shift O. If you aren't using Eclipse, you will have to add them manually, I guess. You could add the appropriate imports to XUL2Swing if you feel like it.


How to Interface to Your XUL2Swing-generated class

The XUL2Swing tool generates the java window class according to the following structure: (see Password.java for details). In fact, it would be good at this point to display Password.java in an editor to match what I describe here with what is in an actual generated file.



public class <className>  {

private JFrame mFrame = null;
private Container mWindow = null;

// Other private variables for the generated Swing objects go here. The
// XUL2Swing program derives the name of each variable by appending the
// id  attribute of the XUL object to "m". For example, mTrayImageName,
// mVbox0007, etc.

// If you didn't code the XUL object with an id attribute, the program
// makes up the name. If you want to access the object whose reference is
// stored in the variable, you had better be sure to give it a unique
// id attribute in your XUL source document.

// The ActionListener.actionPerformed method that you provide to handle
// events also needs the id attribute to identify the source of each event.

// A minor point: XML people like to use dashes in their names.
// XUL2Swing converts dashes into underlines, so that "topic-name" will
// become "topic_name" in the generated Java code.

// Here are some private variable declarations generated by XUL2Swing.
// There will be one variable for each window object. Note that four of the
// variable names are made-up names, and two of them are derived from
// the object's XUL id attribute.

private Box mGroupbox0006 = null;
private Box mVbox0007 = null;
private Box mHbox0008 = null;
private JTextField mTrayImageName = null;
private Box mHbox0010 = null;
private JTextField mTemplateName = null;

//-----------------------------------------------------------------------
// Public getter methods are added for all window objects whose names
// were created from the id attribute after all the variables have been declared.
// The converter generates the getter name by appending the id of the
// object to "get_".

// Not all variables get a getter. Here are the getters for the two JTextField
// variables above.

public JTextField getTrayImageName() { return mTrayImageName; }
public JTextField getTemplateName() { return mTemplateName; }

// No getters are generated for the other four objects since their names would not be reliable.

//-----------------------------------------------------------------------
// XUL2Swing supports several attributes that you can use in the XUL
// that are not standard XUL attributes. Since FireFox does not validate
// XUL, this works ok. Two of these attributes, method and xpath,
// affect whether load and save methods are generated at this point in the
// class. Load and save methods are always public, and you can choose
// to have XUL2Swing generate them if the data for the window class is in
// an XML document.

// Use a method attribute in a container-type XUL element, such as
// a groupbox element, and an xpath attribute in each of the components
// of the container to construct a load and a save method for the container
// and its components. (One exception to this is that you use the xpath
// attribute in the the radiogroup element rather than in the  radiobutton
//
elements it contains.)

// A caveat: you must use only one method attribute for a given XML path.

// Lets say that you want to create a load and a save method for the
// groupbox had the following short XUL:

<groupbox method="basics">
<caption label="Basics"/>
<label value="Enter image name: "/>
<textbox id="tray-image-name" xpath="image/@name" value="image.gif"/>
<textbox id="template-name" xpath="template/@name" value="template.xml"/>
</groupbox>

// This will cause XUL2Swing to generate both a load method and a save
// method

public void loadBasics(XMLDoc _data, Node _node) {
mTrayImageName.setText(_data.getValue(_node, "image/@name", "image.gif");
mTemplateName.setText(_data.getValue(_node, "template/@name", "template.xml");
} // loadBasics

public void saveBasics(XMLDoc _data, Node _node) {
_data.setValue(_node,  "image/@name", mTrayImageName.getText());
_data.setValue(_node, "template/@name", mTemplateName.getText());
} // saveBasics

// The program that you are developing can call these methods, passing an
// XMLDoc object and the node that you want to use as the context node for
// the xpath expressions. The load and save methods will do the rest
// using the xpath attribute values to locate the values you specify.

// Note that the XMLDoc module is included in this release. It contains the sole
// interface between your application and the Java runtime. If you want to use an
// older version of the Java runtime, you can by making the appropriate changes
// to XMLDoc.

// If you have menus in your XUL file, then a public method is added here to include
// them in your JFrame object. Note that the JDialog and JApplete object types
// do not support menus, so don't bother to code them in your XUL. And don't call
// it because it won't be there.

public void createMenus(JFrame _frame, ActionListener _client) {...}

// Following the createMenus method is the method that adds the components to the
// contentPane of your main window object. Using the contentPane rather than the
// JFrame lets the code support any top-level window object.

public void createWindow(Container _window, ActionListener _client) {...}

// These last two methods contain the code to initialize all the Swing objects that
// comprise your translated XUL window. Your code must instantiate the class
// and then call the createWindow method and createMenus method (if you
// support it) to instantiate the contents of your frame. The sample user code in the
// file, called XULTest.java demonstrates this. This file is included in this release.

} // <classname>

The XUL2Swing toolset can also construct a skeleton class that implements the ActionListener class for your window class. You can examine sample code in the PasswordListener.java file included in this release.


The Help

XMLDoc

This class provides wrappers around all of the DOM and XPath calls to allow the XUL2Swing program to be written more succinctly. It also allows you to adapt to other environments, since it is the only interface to the DOM and XPath. You will find it described in the appropriate JavaDoc included in this release.

Logger

The XUL2Swingprogram uses the simple-minded Logger class to log strings to a log file and to the console. The class supports a level filter so that debug messages can be filtered out of the log stream when they are not desired. Again, you will find it described in the appropriate JavaDoc included in this release. Feel free to adapt to your own standard logger.

XUL2Swing

This Java module traverses your XUL "layout" file to generate the Java files. Yeah, it's in the JavaDocs too.

XULTest

This is a one-page Java module that you can quickly adapt (change two line, and possibly a third) to use for your own project. There's not much to it.

Supported XUL Objects

XUL Elements
Note that not every possible attribute that can be associated with the elements listed below are supported. Feel free to add more as you need them.

    button
    caption
    checkbox
    deck
    description
    groupbox
    hbox
    label
    listbox
    listitem
    menubar
    menu
    menulist
    menupopup
    menuitem
    menuseparator
    radiogroup
    radio
    script
    spacer
    tab
    tabbox
    tabpanel
    tabpanels
    tabs
    textbox
    vbox
    window (dialog should be added by someone:-)
XUL attributes
Note that these attributes are not applied for every XUL element listed above.

    checked
    cols
    control
    disabled
    editable
    height
    id
    label
    left
    orient
    rows
    selected
    selectedIndex
    style
    title
    tooltiptext
    top
    value
    width
CSS properties
    text-align (in labels only)

Additional XUL2Swing attributes   

    actioname - allows to set the name of the action handler for the corresponding XUL element.
    capacity    - allows to set the maximum number of JList items in a XUL <listbox> element.
    method     - allows to create a load and save method for a group of components.
    xpath        - identifies XML data relative to a specified context node for the corresponding XUL element.
    scale         - allows to enlarge an object to accomodate text that Swing would otherwise truncate.


FAQ

How do I handle XUL2Swing events?

When you translate the XWK-file, XUL2Swing can generate two files:  the file containing the Swing translation of your XUL window, and a file containing the actionPerformed method corresponding to the events that could be generated by the Swing code. You control whether XUL2Swing generates this second file by setting a fourth command-line parameter to "true". It defaults to "false". The actionPerformed method is a standard Swing method. For each event, XUL2Swing generates code to detect the event, and to direct control to an event handler method that it also generates within the same file. You can either use the action file as a separate module, or you can stitch it into your own code wherever it would be appropriate.

Here is a very short actionPerformed method:
public void actionPerformed(ActionEvent _e) {
if (false);
else if (_e.getActionCommand().equals("sig_onfile"))
on_sig_onfile(_e);
else if (_e.getActionCommand().equals("load_button"))
on_load_button(_e);
else if (_e.getActionCommand().equals("save_button"))
on_save_button(_e);
else
Logger.log(false, 0, _e.getActionCommand() + " is not handled");
} // actionPerformed
The handlers that XUL2Swing generates are stubs that you, as the programmer, populate with code to handle the event.

Here are two sample methods. The first (on_sig_onfile) is as it is generated by XUL2Swing, and the second includes some code that I added to load information into my patient GUI when the user clicks the Load button on the interface.
private void on_sig_onfile(ActionEvent _e) {
Logger.log(true, 0, "sig_onfile");
} // on_sig_onfile

private void on_load_button(ActionEvent _e) {
Logger.log(true, 0, "load_button");
// Load the patient information from disk
patient = new XMLDoc();
patient.load("patient.xml");

// Display the patient information
win.load_NameAddr(patient, patient.getDocumentElement());
win.load_PatientData(patient, patient.selectSingleNode("/*/info"));
} // on_load_button

How do I control the objects that XUL2Swing generated?

Each object that XUL2Swing translated from your XUL code has a private reference variable within the GUI class module. XUL2Swing also generates a getter for that variable. If you provide an id-attribute for the XUL element, XUL2Swing generates a name for the getter using the id. If you don't provide an id-attribute, XUL2Swing generates a name that you probably can't predict, and, therefore, can't use.

Let's say that your XUL element for a textbox is simply <textbox/>. XUL2Swing uses a counter to generate the name for this object. The counter could have any value, so it will likely not be the same from one run to the next. Consequently, you won't be able to access this object.

But let's say that your XUL element looks like
<textbox id="edit-notes" multiline="true" rows="9"/>
XUL2Swing generates a name based on the id, and the getter method now has a predictable name of get_edit_notes. So, if you would like to disable this Swing object, for example, you would simply write
gui.get_edit_notes().setEnable(false);

How do I select a subpanel in a XUL2Swing deck object?

The XUL <deck> element is translated into a JPanel, an integer variable, and a Vector object to contain its children objects as in the following example:
private JPanel mMainDeck = null;
private int mMainDeckNdx = 0;
private Vector<Container> mMainDeckChild = null;
Unlike other XUL2Swing objects, when you get the deck object, instead of getting the object, itself, you get the current value of the index variable. Also unlike other XUL2Swing objects, you can set the deck object, or rather, the index variable. By changing the value of the index value, you select which deck page to display.

Here is a sample of the code that XUL2Swing generates to implement this feature:
public int getMainDeck() {
return mMainDeckNdx;
} // getMainDeck

public void setMainDeck(int _mMainDeckNdx) {
mMainDeckChild.elementAt(mMainDeckNdx).setVisible(false);
mMainDeckNdx = _mMainDeckNdx >= mMainDeckChild.size() ? 0 :
_mMainDeckNdx < 0 ? mMainDeckChild.size()-1 : _mMainDeckNdx;
mMainDeckChild.elementAt(mMainDeckNdx).setVisible(true);
} // setMainDeck
You can page through each panel in the example deck by repeatedly performing the following line of code:
gui.setMainDeck(getMainDeck()+1);
Note that the XUL2Swing code automatically restricts the value of the index within the bounds of the number of panels in the deck. Also note that the index is zero-based.

How do I handle JList/listbox events?

When XUL2Swing translates the <listbox> element, it extends the JList class translation to redirect all JList navigation events to the same actionPerformed method as other Swing events are directed. The extension class is local to the main GUI class, and is called JIdList.

How do I load information into the JList?

Here is some sample code showing how to load the names of a list of XML topic elements into the topic_list.(Note that XUL2Swing currently does not translate <listitem> elements.)
// Locate the list of topic elements in the data document, and
// then load their names into the JList.
//
public void loadTopics(int _selectedTopic) {
NodeList nodes = config.selectNodes("/*/topics/topic");
int len = nodes.getLength();
DefaultListModel listModel = (DefaultListModel)(gui.getTopicList().getModel());
listModel.clear();
for (int i = 0; i < len; i++)
listModel.addElement(config.getValue(nodes.item(i), "@name", ""));
gui.getTopicList().setSelectedIndex(_selectedTopic);
} // loadTopics

How do I instantiate the GUI?

A test module, called XULTest, included in this project, contains a method that demonstrates how to make your Swing GUI display. It is a bit large to include here, so I'll simply refer you to the source file. The method is called createAndShowGUI. It's pretty standard Swing. It calls the gui.createWindow() method, passing a reference to the content pane of your JFrame, JDialog, or JApplet object, and a reference to your ActionListener object.

How do I instantiate the menubar if my XUL code has one?

The createAndShowGUI method also shows how to instantiate the menubar. It's a simple call to gui.createMenus(), passing a reference to your JFrame object, and a reference to your ActionListener object. Note that the Swing JDialog and JApplet classes do not support a menubar.

Additional questions yet to be answered:

How do I use the method and xpath attributes to load and save information in the GUI? TBD. For now, look for examples in the sample files.

How do I keep Swing from truncating a label? TBD. This often happens to labels for buttons and textboxes. Swing and XUL are not a perfect match. Sometimes I've had to play with the scale attribute (not a XUL attribute), or by enlarging the Firefox browser window to something a bit wider than the smallest XUL size.

How do I keep Swing from clipping the contents of a box? This affects neighboring objects. TBD. Same as above.

Got more questions, suggestions, or coding improvements? Let me know. paulatmedlock.com (at = @)

Download.


Log

2008/03/19 pjm

A major upgrade.

* Added a method to the window create class to generate the content pane based on the XUL window element information before the createWindow method is called.
* Added support to translate the XUL listbox to the Swing JTable when the listbox element contains listheader elements. If no listheader elements, then generates a JList object.
* Added the MAIN command-line parameter to select to append code to the main listener class to make the XUL2Swing output a complete program.
* Added a file, called XUL2Swing.class, to contain material that XUL2Swing needs to generate several derived classes. This allows to add canned code to the output Java more easily. The copy function includes the ability to substitute for string variables found in the XUL2Swing.class file. Variables are recognized by having the form ^name^, where "name" is the name of the variable. The value of the variable is substituted for the ^name^ string in the output.

2007/10/11 pjm

A flurry of activity here.

* Changed the command-line parameters to use position to identify the first three - i.e., they are required, and keywords for the remaining options. I changed the manual to reflect the new command line. The parameters in question are
[STRING] [FOLD] [LOG | DEBUG | DEEP | VERBOSE] [LISTENER]
Each of these new parameter options is explained in the manual.

* Improved the string constant generator in the code to use prefixes on the generated string names when the STRING parameter is specified and the FOLD parameter is not specified. There are four prefixes: TIP_ for tooltips, GUI_ for other GUI strings, ID_ for identifiers, and XP_ for XPath expressions. I use the id-attributes of the XUL elements to generate the names of the strings, or I make the names up if you don't provide id attributes. It's better for you if you do.

Note that XUL2Swing requires that some XUL elements have id-attributes: viz., elements that generate Swing events. If you overlook any, you will get an exception, and the code generation will cease.

2007/10/08 pjm

* Added branchName attribute to support multiple ActionListeners.

If you want to create multiple ActionListeners, each responsible for a different branch of the XUL window tree, then add a branchName attribute to the root element of the XUL branch you want to assign to the ActionListener. XUL2Swing generates a method to create the components for the branch, and a class/file implementing the ActionListener interface for the branch. To generate the two names, XUL2Swing appends the value of the branchName attribute to "create" for the name of the branch create method, and prepends the value of the branchName attribute to "Listener" for the name of the branch action listener module/file.

Create methods that you generate using the branchName attribute have the following API:
 public final void <createBranchName>(final ActionListener _client);
For example, if branchName="publisher", then the create method name is "createPublisher" and the ActionListener class name is "PublisherListener."

When you use this option, you must instantiate each branch of your window tree with separate calls to set the action listener for that branch. And the order of instantiation is important: To instantiate the branches of the XUL tree in your Java code, you must call the create methods from root to leaf so that each branch has an instantiated connection point in the Java object.

The PubSubConfig file set shows a sample of this feature. Note the TopicsListener.java and ServersListener.java files in the XULTest/src folder. Also note the createTopics() and createServers() methods in PubSubConfig.java.

2007/09/18 pjm

* Added code to isolate all strings into a group of private static final String constants to help make it easier to internationalize the module. This feature is implemented for both the Swing window class and the actionListener class. You also have the option to eliminate duplicate strings. You get these features with two new parameters on the command line.
* Recoded label generator to use camelCase form of names instead of leaving the underlines in the names. E.g., "m_topic_deck_child" is now "mTopicDeckChild".

* Added support for a code to indicate that you don't want to export a particular Swing component, even if it has an id-attribute. Just put a "?" on the front of the id-attribute. Or, if you don't mind my generating numbered variable names (such as groupbox0056), then you can simply omit the id-attribute on XUL elements you don't want to export.

* Added code to generate boilerplate comments for javadoc for all public methods and classes. You can flesh them out or leave them as they are, up to you. You would have to add some code and yet another command-line parameter if you want to leave them out, however. (Pretty soon we have to have a configuration file, which I don't think would be particularly good. Maybe good for a shop-oriented use of the program, though.