Introducing builders

In the previous sections some of the key concepts of the JGUIraffe library have been described. Allthough there have been some code examples, there has not yet been a complete example that constructs a full user interface and combines the elements discussed so far. The reason for this is that the single elements of a JGUIraffe application are typically not created by hand. Rather, this is the task of so-called builders.

The JGUIraffe library uses the term builder for a component that is capable of interpreting a builder script and producing objects from it. The results of such a builder operation can be graphical components which are part of the user interface. They can also be plain old Java objects (POJOs) implementing business logic.

For instance, if an application wants to display a dialog box, there is no code that creates the dialog window, initializes the graphical components, associates them with a Form object, and so on. Instead the application has a builder script that contains all relevant definitions. This script is passed to the builder. When the builder completes the fully initialized dialog window with all its helper objects is available and can be displayed.

In this section we give a high-level overview over builders and how they are used to bring the concepts discussed so far together to form a fully functional application. Subsequent sections will then deal with specific builder types and provide detailed information about constructing different kinds of elements.

Why builders

If you have ever implemented a user interface - e.g. a Swing UI - directly in Java, you will certainly agree that the resulting Java code can be, well, problematic. It is hard to read as it is not obvious which parts of the code are responsible for which parts of the UI. And it is even harder to maintain or to incorporate changes of the user interface. Therefore the authors of this library belief that there should be a separation between Java code and the definition of the user interface. This can lead to the following advantages:

  • A declarative style for defining the UI can be used. This can be much more readable than Java code because it is free of boiler-plate code and focuses on a very specific use case.
  • The format for defining the UI can resemble the hierarchical structure of the components that form the user interface. Thus it is easier to see which parts of the definition correspond to which parts of the user interface. This makes changes of the user interface easier.
  • Changes at the UI can be performed without having to recompile the application. It is even possible to create UI definitions dynamically and interprete them at runtime.
  • A declarative syntax does not hard-wire UI elements to specific Java classes. This is an additional layer of abstraction which can be used to create different UIs from a single definition - depending on the configuration or type of the builder used.
  • This approach can also lead to a cleaner overall programming model. Naive UI programming often makes use of many anonymous inner classes defined in window or other UI classes acting as event listeners. If there is a well-defined interface between the user interface definition and controller logic, there is no need to create lots of inner classes, and business logic can be much more centralized.

In the first line builders make the construction of a user interface element like a dialog box very convenient and reduce the amount of Java code required for this purpose significantly. They also ensure that all the helper components provided by the JGUIraffe library to simplify UI programming are correctly created, initialized, and wired together. For instance, when a window for a dialog box is constructed, the builder automatically creates a corresponding Form object which can be used to read or write the contents of the graphical input components. A controller object for the dialog box can be created, too.

So builders link the different parts of a JGUIraffe application together. The result of a builder operation can be used directly by an application without additional initialization or configuration.

Builder scripts

A builder script contains definitions for the objects (graphical components or POJOs) to create. The default builder implementation in JGUIraffe is based on the Apache Commons Jelly project. This means that builder scripts are actual Jelly scripts, i.e. XML documents.

Jelly provides a powerful and flexible way of processing XML documents. The basic idea is that XML elements are mapped to Java classes - so-called tag handler classes. The Jelly engine processes the XML document, inspects the single XML elements, creates the corresponding tag handler objects and invokes them. This basically means that the XML document is executed as if it was a program written in a specialized XML-based programming language.

Jelly ships with many standard tag handler classes organized in different tag libraries. A tag library contains several related tag handler classes and associates them with an XML namespace. (The XML elements in a Jelly script are all prefixed with a namespace indicating the tag library they belong to. This makes it possible to have tags with identical names in different tag libraries.) There are tag libraries defining core programming constructs (like conditional execution, loops, or including other scripts) and other, very specialized tag libraries as well. The latter include tag libraries for XML processing, thread handling, accessing HTML pages, sending e-mails, and many more. All these standard tag libraries can be used in builder scripts.

Writing a custom Jelly tag handler class is not complicated. The class can extend the base class org.apache.commons.jelly.TagSupport. Then it has to define the doTag() method. Here arbitrary logic of the tag can be implemented.

The JGUIraffe library contains some custom tag libraries that deal with the creation of several objects. The following table lists these specific tag libraries and provides a short description for each. They are discussed in detail in later sections.

Name space Library name Description
di Dependency injection The tags in this library allow the creation of arbitrary Java objects. Java beans can be declared, and their properties can be set. References to other beans can be defined which are automatically resolved by the framework. Thus complex networks of objects can be defined and accessed in a convenient way.
f Form components This tag library contains tags for creating typical UI controls to be used in forms, e.g. labels, text fields, lists, panels, check boxes, etc. It is the most comprehensive tag library provided by JGUIraffe. This is due to the amount of available UI controls.
a Actions The Action tag library supports the definition of actions, i.e. elements the user can interact with to trigger a functionality of the application. The declaration of menus and tool bars is also covered.
w Windows Here tags for creating several kinds of windows - e.g. frames or dialog boxes - can be found. This library also supports the creation of controller objects - Java classes that are associated with windows and control their life-cycle.

The following listing shows a simple (incomplete) builder script. This is just to give the reader an impression how such a script looks like. Details will be discussed in later chapters.

<?xml version="1.0" encoding="ISO-8859-1"?>
<j:jelly xmlns:j="jelly:core" xmlns:di="diBuilder" xmlns:f="formBuilder"
  xmlns:a="actionBuilder" xmlns:w="windowBuilder">

  <!-- The dialog window -->
  <w:dialog titleres="newfile_title" center="true">
    <f:borderlayout/>
    <!-- The main panel -->
    <f:panel>
      <f:borderconstr name="CENTER"/>
      <f:percentlayout columns="4dlu start/preferred 3dlu full/preferred(6cm)/100 4dlu"
        rows="4dlu preferred 3dlu preferred full/preferred(5cm)/100 4dlu"/>
      <f:label textres="newfile_lab_name">
        <f:percentconstr col="1" row="1"/>
      </f:label>
      <f:textfield name="fileName" displayNameres="newfile_disp_name"
        maxlength="200" tooltipres="newfile_txt_name_tip">
        <f:percentconstr col="3" row="1"/>
        <f:validators phase="syntax">
          <f:validator
            class="net.sf.jguiraffe.examples.tutorial.createfile.UniqueFileNameValidator"/>
        </f:validators>
      </f:textfield>
      <f:label textres="newfile_lab_content">
        <f:percentconstr col="1" row="3"/>
      </f:label>
      <f:textarea name="fileContent" displayNameres="newfile_disp_content">
        <f:percentconstr col="1" row="4" spanx="3" targetCol="3"/>
      </f:textarea>
    </f:panel>

    <!-- The button bar -->
    <f:panel>
      <f:borderconstr name="SOUTH"/>
      <f:buttonlayout/>
      <f:button name="btnOk" textres="newfile_btn_create"
        mnemonicres="newfile_btn_create_mnemo" default="true"/>
      <f:button name="btnCancel" textres="newfile_btn_cancel"
        mnemonicres="newfile_btn_cancel_mnemo" cancel="true"/>
    </f:panel>
  </w:dialog>
</j:jelly>
    

This script defines a simple dialog window with a text field for entering a file name and a text area for the file content. The PercentLayout manager is used for laying out the components. The dialog also has two buttons for creating the file or for canceling the operation.

Real-world builder scripts tend to be a bit larger because typically some helper objects (e.g. controllers, actions, validators, etc.) are defined, too. This is actually one disadvantage of this approach: builder scripts can become pretty verbose. Jelly provides some means for addressing this problem. For instance, it is possible to devide the functionality in multiple sub scripts which are then included in the main script.

Builder interfaces

Access to a builder is possible through the Builder interface. This interface defines the following methods for creating objects:

  • build() is a generic builder method. It processes a builder script that may define arbitrary objects. Access to the objects created by the builder is possible through the data objects passed to the method (see below).
  • buildContainer() can be used for constructing a UI if the root container (e.g. a panel) is already available. The root container is passed to the builder. Graphical components defined in the builder script are then added to this container.
  • buildWindow() is a convenience method for creating a complete window. The method expects that the builder script to be processed defines a kind of window. This window is returned.
buildContainer() and buildWindow() are merely convenience methods which can be implemented in terms of the generic build() method. In addition to these methods there is a release() method which can be used to free all resources obtained by objects created by the script. Usually the developer does not have to call this method explicitly because this is done by the framework.

A builder needs a lot of parameters to fulfill its task. When invoking the builder these parameters have to be passed in form of a BuilderData object. The BuilderData object is pretty complex. It does not only define input parameters for the builder but also allows access to the builder results. Fortunately the major part of the input parameters can be inferred from global application data. Therefore it is not necessary to create and populate a BuilderData object by hand. Rather, there is a convenience method in the ApplicationContext class that returns an initialized object. We will show this later in an example.

Another important information required by the builder is of course the script to be processed. Typically these scripts are located in the file system or in the class path of the application. JGUIraffe provides an abstraction for the concrete location of a builder script in form of the Locator interface. There are multiple concrete Locator implementations, including a locator for files, for URLs, or for scripts in the application's class path. Another interesting Locator implementation is ByteArrayLocator. This class allows passing a script to a builder that was created in memory, e.g. as a string.

These are the most important interfaces related to builders. There are a couple of other interfaces involved. These are discussed in the details section.

Invoking a builder

To invoke a builder in order to execute a builder script the following steps have to be performed:

  • A reference to the Builder object has to be obtained.
  • The BuilderData object required by the builder has to be created and initialized..
  • A Locator object pointing to the builder script has to be created.
  • The appropriate build() method of the builder has to be called.

All these steps can be done manually. For instance, the various parameters managed by a BuilderData object could be set by hand. Fortunately, this is not necessary because ApplicationContext provides methods that do the major part of the work already. So just a few lines of code are needed as shown in the following code fragment:

Builder builder = application.getApplicationContext().newBuilder();
ApplicationBuilderData builderData = application
        .getApplicationContext().initBuilderData();

Locator locator = ClassPathLocator.getInstance("mywindow.jelly");
Window window = builder.buildWindow(locator, builderData);
    

In this example application refers to the global Application object. First a new Builder object is obtained using the newBuilder() method of the application context. Then the initBuilderData() method of ApplicationContext returns an initialized object of the ApplicationBuilderData class which is a default implementation of the BuilderData interface. The object returned by initBuilderData() contains already all information required by the builder. So it can be used directly. Alternatively it is possible to set further properties or change some of the default settings before the builder is actually invoked.

Next a Locator object is created pointing to the builder script. In this case a script named mywindow.jelly is to be executed which is located in the application's class path. If the application is packaged in a jar archive, the script is probably also part of this jar. The ClassPathLocator class searches the whole class path for a resource with the specified name.

Now all information required for executing the builder script is available. In the last line the builder is called. Here the buildWindow() method is called which returns the main window defined in the builder script. (We assume that the script named mywindow.jelly actually defines a window.) The window returned by the builder can now be used. If something goes wrong - for instance, if the script cannot be found or contains invalid instructions -, a BuilderException is thrown.

For creating and opening a window - e.g. a dialog box - there is an even more convenient way. Because this is a use case needed frequently by a typical application the JGUIraffe library provides a convenience class which does exactly the steps outlined above: OpenWindowCommand.

As the name implies, OpenWindowCommand is a command object. Therefore it can be executed in a background thread. (Executing a builder script should normally be very fast. However, it does not hurt to do this in a background thread.) When an instance is created the Locator object defining the builder script must be provided. Then the command can be executed, and the newly created window is displayed automatically.