Replace 1.1 event adapters to build better apps

Sophisticated event adapters can replace JDK 1.1's adapters, improving your applications

T he event adapter classes introduced with JDK 1.1 are simple and lightweight, but they do not provide enough capability for medium or large Java applications. Their shortcomings are legion: too many problems go unreported to the user; GUI responsiveness is missing when actions take a long time to complete; and an application's error-handling policy cannot be changed easily. Given all this, what's a developer to do?

This article demonstrates how to address these issues by using sophisticated event adapter classes designed to improve on those provided with JDK 1.1.

The following applet demonstrates this article's sophisticated adapters in action. The top row of the applet uses the sophisticated adapters, with the result that various errors are caught and the user is notified. The Long Asynch Action button shows the asynchronous behavior in action; notice that the GUI is not hung while it sleeps.

The bottom row uses standard adapters; note that the errors detected by the top components go undetected. The Long AWT-Blocking Action button shows the GUI hung.

You need a JDK 1.1-enabled browser to see this applet.

Note: Not all browsers have complete JDK 1.1 support. If the applet does not appear in a pop-up window in the upper left-hand corner of your browser window, you will need to download the applet. To do so, just click on this handy zip file.

Adapter classes in JDK 1.1 -- benefits and limitations

First, let's look at the simple event adapters you get with the JDK 1.1. They aren't very complicated because each method in each JDK 1.1 adapter does absolutely nothing. There is no ActionAdapter class for the simple ActionListener interface, but if an ActionAdapter class existed, it would look like this:

    public class ActionAdapter implements ActionListener
    {
        public void actionPerformed (ActionEvent e)
        {
        }
    }

The other adapter classes provided by the JDK are larger, but no more complicated. These simple adapters do have good features:

  • They are very lightweight.
  • Their "do nothing" methods are as efficient as possible.
  • They do not enforce a specific policy on programs. Since any policy is likely to be wrong for some programs, this safe decision allows different programs to implement different policies. In fact, this article's adapters might have been more difficult to write had the JDK-provided adapters been more complex than they are.
  • They ship with the Java runtime environment, so applets don't need to download them.

Unfortunately, the JDK 1.1-provided adapters also have four major limitations.

Limitation 1: The JDK 1.1 adapters do not catch RuntimeExceptions

Quick, answer this: Can this method throw any exceptions?

    public void niftyMethod ()   // Note: no throws clause!
    {
        // Do stuff.
    }

In fact, it can. The Java language defines an entire class hierarchy of Exception objects that can be thrown even without a throws XXXException clause. These exceptions descend from a base class named java.lang.RuntimeException. Examples include IndexOutOfBoundsException and NullPointerException.

A RuntimeException usually indicates that there is a bug in the program; it may be trying to use a null reference or indexing off the end of an array. Nevertheless, the standard adapters allow programming errors like this to go undetected by the program and unreported to the user of the program. The practical result is that programs can fail silently, preventing the user from discovering the problem until it's too late.

The following sequence illustrates this scenario:

            User: "Please save my work."
    Java Program: Hmmm. There's a null pointer exception so I can't save
                  the work ... but it isn't caught, so I don't need
                  to tell the user.
                  "OK!"
            User: "Exit."
    Java Program: "Bye!"
            User: "Hey, where's my file?!?"

Well-behaved programs should never fail silently. A better exchange would look something like this:

            User: "Please save my work."
    Java Program: Hmm. There's a null pointer exception so I can't save
                  the work. Ah, I'll report it.
                  "I can't save your work because I got a null pointer
                  exception!"
            User: "What the heck is a null pointer?  Well, just to be
                  safe, I think I'll save this to the clipboard and
                  dump it in a text file ..."

The user might still lose some information (formatting, in this example) but this is better than losing everything.

Limitation 2: The JDK 1.1 adapters do not catch Errors

Next question: Can anything be thrown from the following improved function?

    public void niftyMethodTwo ()
    {
        try
        {
            // Do stuff.
        }
        catch (Exception e)    // Note: We are catching all exceptions!
        {
            // Panic.
        }        
    }

Again, the question has an affirmative answer. The Java language defines an entire category of failures, called Errors, that are not exceptions. Errors usually indicate that something has gone wrong with the underlying virtual machine or with the program itself. Examples include OutOfMemoryError and StackOverflowError.

As with runtime exceptions, errors should not happen during program execution. Again, however, if they do happen, the user should receive notification. The following exchange illustrates the problem resulting from not dealing with Errors:

            User: "Please save my work."
    Java Program: Hmm. I'm out of memory so I can't save the work, 
                  but errors aren't caught so I don't need to tell
                  the user.
                  "OK!"
            User: "Exit."
    Java Program: "Bye!"
            User: "Did I lose my file again?!?"

Again, if users receive notification of the problem, they can at least try to recover their work:

            User: "Please save my work."
    Java Program: Hmm. I'm out of memory so I can't save
                  the work. Ah, I'll report it.
                  "I can't because I'm out of memory!"
            User: "Darn. Close these other files."
    Java Program: "OK."
            User: "Now please save my work."
    Java Program: Hmm, everything worked that time.
                  "OK!"
            User: "That worked well. I'm going to start a fan
                  club for the programmers who wrote this program."

To reiterate, well-behaved programs should not fail silently.

Limitation 3: The JDK adapters run on the AWT thread

All the methods in the default adapters run on the AWT thread. Both the AWT and Swing are single-threaded, so the entire GUI freezes while an action executes. Even the application's other frames will not respond to new GUI events while the application handles a prior event. Indeed, if the action runs for very long, the application can appear to be hung.

A practical example of this would be printing a large document:

            User: "Please print my huge document."
    Java Program: OK, here I go ... I'll format it first.
            User: "I'd like to edit another file." The user clicks
                  the mouse on the File menu. Nothing happens
    Java Program: (to itself) I'm still formatting ... la-de-dah ...
            User: "Hello? Are we locked up?"
    Java Program: (still to itself) I'm still formatting ... tum-tee-tum ...
            User: Starts clicking madly ... nothing happens ...
                  user starts getting upset.    

A better-behaved application would put the printing in the background. The sequence might then go like this:

            User: "Please print my huge document."
    Java Program: OK, here I go ... I'll do this in the background
                  so the user can edit other documents.
            User: "I'd like to edit another file." The user clicks
                  the mouse on the File menu. The File menu drops down
                  and the user selects a new file to edit
    Java Program: (smugly) I'm still formatting, but I can load a new
                  file at the same time because I'm so clever.
            User: The user starts typing.
    Java Program: (getting even more smug) I'm still formatting, but
                  I can edit and print because I'm so clever.

There are still complicated design problems that must be addressed before a program can safely do two things at once, but users don't care. Properly designed programs should never appear to be locked up, a situation the standard adapters do not help to avoid.

Limitation 4: The JDK 1.1 adapters provide no single point for an exception-handling policy

If the standard adapters are used, each handler must provide its own try/catch block and do something with the Exceptions and Errors that are caught. This process is both tedious and mistake prone. Worse, if the policy for handling Exceptions and Errors changes, there will be an enormous amount of cut-and-paste rework. Imagine, for example, that version 1 of an application does not log errors, but that we want to add this feature in version 2. It would be nice to make this change in one place rather than in hundreds. A class hierarchy sporting a single place to implement an error-handling policy makes this sort of change easy.

More complete adapters

Robust applications should use adapters that address all of these concerns.

To review:

  • All thrown Exceptions and Errors should be caught and reported. The application must not fail silently.
  • Adapters should be able to run on separate threads. This keeps the GUI from locking up while handling long-running actions.
  • There should be a single place to implement an error-handling policy. This reduces the amount of code and allows the policy to change easily.

Finally, we want programmers to use our new adapters just like the standard adapters. Indeed, our new adapters should not be more complicated to use than the standard adapters.

AbstractSophisticatedAdapter.java

The first step is to place most of the error handling and reporting into an abstract base class -- AbstractSophisticatedAdapter. All our other adapter classes will inherit from this base class. Let's take a look:

1 2 3 Page 1