Welcome!

Java Authors: Jeremy Geelan, Liz McMillan, Hari Gottipati, Tad Anderson, Yakov Fain

Related Topics: Java

Java: Article

Coding with Java Swing

Patterns for designing scalable and robust user-interfaces

Even for many seasoned developers, Swing code can be notoriously difficult to organize. Where is the right place to put parsing and validation logic? How do you prevent those threading issues that cause lockups or repainting glitches? Is it possible to unit test GUI logic? Can the code somehow be shared with other user-interfaces, like a web front-end? If these questions sound familiar, the solutions presented here may revolutionize the way you code with Swing.

Two-Layer Separation
Suppose you want to offer your end-users a Swing-based product built on top of a homegrown API, such as a mathematics package, that they can actually license for their own development purposes or perhaps even use to extend your product through a plugin mechanism. To achieve that goal, the API must be fully decoupled from all Swing code. Let's take a look at a simple example of such an API.

In the class Divider, I defined the following method:

public DivisionResult divide(
     int dividend, int divisor, IDivisionListener divisionListener)
     throws ArithmeticException { ... }

Given a dividend and a divisor it returns a DivisionResult, a simple bean containing quotient and remainder. If divisor is 0, it throws an ArithmeticException. I'll discuss the divisionListener parameter below.

Now, let's slap on a JFrame to exploit this API. DivisionFrame (see Figure 1) contains 2 JTextFields that enable the user to enter a dividend and a divisor. When the user presses the Divide button, the resultant quotient and remainder are displayed in a JLabel.

The simplest attempt at a separation of concerns is a GUI layer built directly on top of the API layer. The GUI layer consists of JFrames, JDialogs, Swing components and their data models. The API layer contains the business logic and as mentioned above, it should be possible to use the API layer without a GUI.

Since there are only 2 layers in this approach, user inputs must be prepared for the API layer in the GUI layer. In this case, Divider.divide() accepts integers, not strings. The ActionListener bound to the Divide button parses the values provided by the JTextFields and it either calls divide() or it changes the text color red to indicate invalid input.

Keep in mind that Divider actually represents an advanced mathematics package. Its methods may take several seconds or minutes to complete. I decided to simulate that effect by sleeping for 5 seconds inside of divide(). If divide() is called directly by the ActionListener, the GUI will appear frozen and possibly grayed-out for that time because of Swing's one-threaded nature (see the sidebar, The Event Queue).

The general solution is a request-response model. The GUI layer makes a request into the API layer on a new thread. That thread takes as long as is needed to complete the operation, freeing the event-dispatching thread to continue servicing the event queue. When the result of the operation is ready, the thread uses one of the static invokeXX() methods of the EventQueue class to safely update the GUI by requesting that the event-dispatching thread handle the update on its behalf.

In this model, a thread boundary exists between the layers: the event-dispatch thread is the only thread that runs within the GUI layer, worker threads run within the API layer, and neither type is allowed to cross the boundary. Unfortunately, there is no mechanism to automatically prevent a rogue thread from slipping through. Also, the code required to keep switching threads tends to be voluminous and ugly even with the aid of SwingWorker (see Resources).

Three-Layer Separation
Consider introducing a layer between the GUI and API layers to mediate data between them. This mediation layer is responsible for converting user input into a format acceptable by the API layer and for converting resultant values and other data from the API layer back into a format acceptable by the GUI layer. Ideally, the boundaries between the 3 layers should be formalized with interfaces; though, that may not be entirely possible if the API was provided by a third-party vendor. Finally, the event-dispatching thread is not permitted to cross into the mediation layer. Worker threads can pass back-and-forth between the mediation and API layers; however, they cannot enter the GUI layer.

For our simple example, I created a single class, aptly named Mediator, to serve as the entire mediation layer. To formalize the layers, Mediator implements IDivisionFrameMediator, which allows DivisionFrame to pass the values in the JTextFields directly as strings:

public interface IDivisionFrameMediator {
    public void divide(String dividendStr, String divisorStr);
}

For the reverse direction, DivisionFrame implements IDivisionFrame. It enables Mediator to push a DivisionResult back to DivisionFrame in the form of a string and to mark the input fields as valid or invalid:

public interface IDivisionFrame {
public void showDivisionResult(String divisionResult);
public void showValid(boolean dividend, boolean divisor);
}

Mediator is loosely-coupled to DivisionFrame; they each hold a handle to each other as an interface type. However, the handle cannot be a direct reference to the object in the opposing layer because that would break the thread-layer separation rules discussed above.

We could solve the problem by creating 2 proxy classes (see the sidebar, Proxies). Let's call these hypothetical classes MediatorProxy and DivisionFrameProxy. MediatorProxy implements IDivisionFrameMediator and contains a reference to Mediator. Whenever you invoke a method of MediatorProxy, it spawns off a new thread and uses it to call the corresponding method in Mediator. Similarly, DivisionFrameProxy implements IDivisionFrame and contains a reference to DivisionFrame. Calling a method of DivisionFrameProxy delegates the invocation to DivisionFrame using EventQueue.invokeLater().

With such proxies, the code in DivisionFrame and Mediator appears immaculate. DivisionFrame calls directly into the methods of its IDivisionFrameMediator and Mediator does the same with its IDivisionFrame. It's still a request-response model, but the proxies hide the thread switching details and there's no chance of a thread inadvertently slipping by. However, creating the proxies themselves is a tedious and repetitive task you shouldn't have to do yourself.

SwingProxy
To obviate the need to code the proxies by hand, I created a utility class called SwingProxy that dynamically generates them for you. SwingProxy provides a static method, newSwingProxy(), that accepts the target object and returns a new proxy that can be cast to any of the interface types that the target implements. For example, for an instance of DivisionFrame, which implements IDivisionFrame, you can create a proxy for it as follows:

IDivisionFrame divisionFrameProxy =
   (IDivisionFrame)SwingProxy.newSwingProxy(divisionFrame);

The proxy automatically takes care of the thread switching. In this case, if a method of divisionFrameProxy is invoked by a worker thread, it will call the corresponding method of divisionFrame with the event-dispatching thread. Similarly, a call by the event-dispatching thread will turn into a call by a worker thread.

SwingProxy (see Listing 1) is built around java.lang.reflect.Proxy, a class that is capable of producing a proxy of a specified type at runtime. The Proxy utility produces proxies that resemble funnels; to create the proxy, you supply an implementation of the InvocationHandler interface and when you invoke any method of the proxy, it automatically gets funneled down to InvocationHandler's single method, invoke():

public interface InvocationHandler {
   public Object invoke(Object proxy, Method method,
     Object[] args) throws Throwable;
}

The first parameter is a reference to the proxy. The second is the method that was called in the form of a reflected method type. The third is an object array containing the arguments passed to the method.

SwingProxy contains 2 private inner classes. The first, CallHandler, implements InvocationHandler. Its invoke() method inspects the calling thread and switches accordingly. When it needs to delegate a call from a worker thread to the event-dispatching thread, it examines the return type of the method. If it returns void, invokeLater() is used. Otherwise, invokeAndWait() is called and the return value is passed back to the worker thread that called the proxy. When the proxy is called by the event-dispatching thread, the call is delegated to a worker thread and nothing is ever returned. The interface methods specifying calls from the GUI layer to the mediation layer should always return void. The worker threads that CallHandler use originate from a thread-pool supplied by java.util.concurrent.Executors.

The second inner class, TargetInvoker, implements Runnable, which allows it to be executed on a different thread. It performs the actual method invocation using reflection in its run() method.

Pushing Data to the GUI
The three-layer design described above is not limited a request-response model. The API can stream data to the GUI by pushing it through the mediation layer. To demonstrate this, I created a dialog to display the (artificially prolonged) progress of the division computation (see Figure 2).

ProgressDialog contains a JProgressBar to show completion percentage, a JLabel for status messages, and a button that allows the user to abort the computation. ProgressDialog lives within the GUI layer and to formally separate it from the other layers, it implements IProgressDialog:

public interface IProgressDialog {
    public void start();
    public void setProgress(String message, int progress);
    public void end();
}

Mediator holds a proxy to ProgressDialog as that type. start() makes the dialog appear, setProgress() updates the status label and the progress bar, and end() hides the dialog. In turn, ProgressDialog holds a proxy to Mediator in the form of an IProgressDialogMediator, which contains a method that is called when the Cancel button is pressed:

public interface IProgressDialogMediator {
    public void requestCancel();
}

Divider.divide() was written to abort if the executing thread is interrupted. Mediator obtains a reference to that thread before invoking Divider and requestCancel() simply interrupts it.

The third parameter of Divider.divide() is an IDivisionListener, which is repeatedly called back during the 5 second pause to simulate progress notifications:

public interface IDivisionListener {
public void computationPerformed(int percentage);
}

Mediator implements IDivisionListener and it delegates the call to IProgressDialog.setProgress(). For time consuming methods that don't provide such a listener, JProgressBar can be put into indeterminate mode. That mode shows an animation conceptually similar to the moving logo in the corner of a web browser.

Mediator as the Controller
It is important to recognize that the mediation layer consists of more than just bidirectional adapters that convert data between formats. It contains the control logic that governs when and how the parts from the other layers are used. The control logic should not be intermingled with the adaptation logic that performs the parsing and the static validation.

In this simple example, Mediator.divide() contains most of the control logic (refer to Listing 2). As talked about above, divide() is invoked by the Divide button and it passes the user input fields as strings. Instead of attempting to parse the strings directly within divide(), they are used to create instances of a class called DivisionFrameParser. The constructor of DivisionFrameParser accepts a string field and parses it. The class provides methods to check if the parsing was successful and to retrieve the field as an integer. In this way, Mediator is focused on the interaction of classes across the layers and less on processing the shuttled data.

Alternate UIs
Mediator, DivisionFrame and ProgressDialog expose setters to enable their dependencies to be injected prior to use. The Main class, which serves as the entry point of the application, wires everything up. However, with Mediator loosely-coupled to the GUI layer, it's possible to completely replace the user-interface. To prove it, if you launch the application with a "-t" command-line argument, it will run in text-mode. Text-mode prompts the user for a dividend and divisor and it prints results back to the console. It was made possible by providing Mediator with alternate implementations of IDivisionFrame and IProgressDialog.

What about a web front-end? Web applications serve data on demand; data is not easily pushed to the browser. This makes showing progress updates, for instance, fairly tricky. As with text-mode, implementing a web front-end entails creating a new GUI layer, but it will also require additions to the mediation layer. The control logic discussed above was designed with pushing data in mind. New controller classes will be required for the request-response nature of the web; however, since we separated the parsing and validation logic from the control logic, it can be reused in a web application.

Testing
The GUI classes do not need to be connected to Mediator to launch them. To demonstrate this, I inserted a main() method into DivisionFrame that uses a mock implementation of IDivisionFrameMediator. In this case, the division request is simply printed out to the console.

Using mock implementations to represent the rest of the system is an especially useful technique if you are developing without the aid of a GUI builder because it will enable you to quickly view your efforts without launching the complete application. Mock implementations can allow you to fully exercise the features provided by the GUI with much less effort. For example, you do not need to induce a problem in the real application just to make sure that error messages get displayed correctly.

Mock implementations are also the hallmark of automated unit testing. The online code that supplements this article includes JUnit tests for Divider, Mediator and DivisionFrameParser. To make Mediator easier to test, I made Divider implement IDivider, an interface that contains its single method. The complete execution cycle for Mediator is tested with mock implementations of IDivider, IDivisionFrame and IProgressDialog.


More Stories By Michael Birken

Michael Birken is actively involved in the design and research of emerging trading technologies at a Manhattan-based financial software company. He's a Sun Certified Java programmer and developer. Michael holds a BS in computer engineering from Columbia University.

Comments (2) View Comments

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


Most Recent Comments
Java News 02/12/07 04:34:40 PM EST

Even for many seasoned developers, Swing code can be notoriously difficult to organize. Where is the right place to put parsing and validation logic? How do you prevent those threading issues that cause lockups or repainting glitches? Is it possible to unit test GUI logic? Can the code somehow be shared with other user-interfaces, like a web front-end? If these questions sound familiar, the solutions presented here may revolutionize the way you code with Swing.

TJ Herring 02/12/07 02:31:20 PM EST

Hi.. excellent article. Although, I cannot seem to find the source code that is referenced within. Can you provide a simple URL to the location to download the source? Thanks!