Replace 1.1 event adapters to build better apps

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

Page 2 of 3

import java.awt.*; import java.awt.event.*;

/** * This class provides most of the infrastructure and default behavior * for the adapter classes provided. It does _not_ provide the * infrastructure for the asynchronous handling. */ public abstract class AbstractSophisticatedAdapter { // An single object implementing a policy for handling exceptions // and errors for all adapters. // static private ThrowableHandlerIFC throwableHandler = new DefaultThrowableHandler();

/** * This class defines the interface required for installable * Error and Exception handler objects. All Exceptions are * handled by one method. Errors are separated into * those Errors that might be recoverable, and all the rest. * The breakout for Error classes is practical, not theoretical. */ public static interface ThrowableHandlerIFC { public void handleException (Exception e);

public void handleOutOfMemoryError (OutOfMemoryError error);

public void handleExceptionInInitializerError (ExceptionInInitializerError error);

public void handleNoClassDefFoundError (NoClassDefFoundError error);

public void handleUnsatisfiedLinkError (UnsatisfiedLinkError error);

public void handleStackOverflowError (StackOverflowError error);

public void handleOtherError (Error error); }

/** * This class provides an implementation of the ThrowableHandlerIFC * that handles all errors and exceptions by displaying a modal dialog * box. More sophisticated handlers are easily imaginable. */ public static class DefaultThrowableHandler implements ThrowableHandlerIFC { // This is the dialog class. // static class ErrorDialog extends Dialog { // We need a parent Frame object for the Dialog object, // but since we make a modal Dialog object it doesn't // matter what Frame object we use. Since it doesn't matter, // it is easier to just make // our own Frame rather than passing in a Frame as a parameter. // static Frame baseFrame = new Frame();

public ErrorDialog (Throwable throwable) { super (baseFrame, "Ooops! " + throwable.getClass().getName(), true);

setLayout (new BorderLayout (1, 1)); add (new Label (throwable.getLocalizedMessage()), "Center");

Button closeButton = new Button ("Drat!"); closeButton.addActionListener (new CloseAction()); add (closeButton, "South"); setSize (300, 100); }

private class CloseAction implements ActionListener { public void actionPerformed (ActionEvent event) { setVisible (false); dispose(); } } }

public void handleException (Exception e) { new ErrorDialog (e).show(); }

public void handleOutOfMemoryError (OutOfMemoryError error) { new ErrorDialog (error).show(); }

public void handleExceptionInInitializerError (ExceptionInInitializerError error) { new ErrorDialog (error).show(); }

public void handleNoClassDefFoundError (NoClassDefFoundError error) { new ErrorDialog (error).show(); }

public void handleUnsatisfiedLinkError (UnsatisfiedLinkError error) { new ErrorDialog (error).show(); }

public void handleStackOverflowError (StackOverflowError error) { new ErrorDialog (error).show(); }

public void handleOtherError (Error error) { new ErrorDialog (error).show(); } }

/** * This method allows for the installation of a new handler * if the default handler provided is not acceptable. All * adapter objects in an application share the same default * handler. */ static public void installDefaultExceptionHandler (ThrowableHandlerIFCnewHandler) { if (newHandler == null) throw new NullPointerException("Default throwable handler cannot throwableHandler = newHandler; }

/** * Dispatch Exceptions to the installed handler. * This method is protected so that subclasses can override * this behavior if desired. */ protected void handleException (Exception exception) { throwableHandler.handleException (exception); }

protected void handleOutOfMemoryError (OutOfMemoryError error) { throwableHandler.handleOutOfMemoryError (error); }

protected void handleExceptionInInitializerError (ExceptionInInitializerError error) { throwableHandler.handleExceptionInInitializerError (error); }

protected void handleNoClassDefFoundError (NoClassDefFoundError error) { throwableHandler.handleNoClassDefFoundError (error); }

protected void handleUnsatisfiedLinkError (UnsatisfiedLinkError error) { throwableHandler.handleUnsatisfiedLinkError (error); }

protected void handleStackOverflowError (StackOverflowError error) { throwableHandler.handleStackOverflowError (error); }

protected void handleOtherError (Error error) { throwableHandler.handleOtherError (error); }

/** * Concrete implementations can override this method to provide * behavior to execute after any errors and exceptions have * been dealt with. An example of this might be code to * re-enable GUI components that were disabled while the * action was executing. This method is especially useful * for asynchronously executing adapters. */ protected void finallyImpl () { }

/** * This function takes an Error object and calls the appropriate * handleXXXError() method. */ protected void dispatchError (Error error) { if (error instanceof OutOfMemoryError) handleOutOfMemoryError ((OutOfMemoryError)error); else if (error instanceof OutOfMemoryError) handleOutOfMemoryError ((OutOfMemoryError)error); else if (error instanceof ExceptionInInitializerError) handleExceptionInInitializerError ((ExceptionInInitializerError)error); else if (error instanceof NoClassDefFoundError) handleNoClassDefFoundError ((NoClassDefFoundError)error); else if (error instanceof UnsatisfiedLinkError) handleUnsatisfiedLinkError ((UnsatisfiedLinkError)error); else if (error instanceof StackOverflowError) handleStackOverflowError ((StackOverflowError)error); else handleOtherError (error); } }

SophisticatedActionAdapter.java

You can't use classes that don't exist, so, to get the benefits of the sophisticated adapters presented in this article, each JDK-provided listener interface must have a related sophisticated adapter class. Because ActionListener is one of the simplest and most useful listener interfaces, we'll start by presenting the sophisticated adapter for it. The SophisticatedActionAdapter presented below demonstrates how to use AbstractSophisticatedAdapter, outlined above, as a base class, and illustrates adding asynchronous behavior to specific adapter classes.

import java.awt.event.*;

/** * This class should be used in place of java.awt.ActionListener. * It provides robust error and exception handling as well as * the ability to run the action on a thread other than the * AWT thread. */ public abstract class SophisticatedActionAdapter extends AbstractSophisticatedAdapter implements ActionListener { // This is null if actions should be handled on // their own threads and non-null if events // should be handled on the AWT thread. See actionPerformed() // and setAsynchronousHandlingEnabled() to see how this works. // private AsynchThread awtThreadObject;

/** * Construct a new SophisticatedActionAdapter object that * handles ActionEvents on the AWT thread. */ public SophisticatedActionAdapter () { awtThreadObject = new AsynchThread(null); }

/** * Construct a new SophisticatedActionAdapter object that * handles ActionEvents on the AWT thread if 'runAsynchronously' * is false, and on its own thread if 'runAsynchronously' is true. */ public SophisticatedActionAdapter (boolean runAsynchronously) { setAsynchronousHandlingEnabled (runAsynchronously); }

/** * Set whether the ActionEvents are processed on the AWT thread * or asynchronously on their own threads. */ public void setAsynchronousHandlingEnabled (boolean runAsynchronously) { if (runAsynchronously == false) awtThreadObject = new AsynchThread(null); else awtThreadObject = null; }

/** * Specific child classes should override actionPerformedImpl() * instead of this method. This method is final so that * the compiler will detect the common mistake of overriding * this method. */ public final void actionPerformed (ActionEvent event) { if (awtThreadObject != null) awtThreadObject.run(event); else new AsynchThread (event).start(); }

/** * Client code should override this method instead of actionPerformed(). * If the ActionEvent is to be handled on its own thread, the subclass * is responsible for calling SwingUtilities.invokeLater() and * SwingUtilities.invokeAndWait() as needed. */ protected void actionPerformedImpl (ActionEvent event) throws Exception { }

// This class handles the asynchronous event dispatching. // For convenience, all event dispatching -- synchronous // and asynchronous -- goes through an object of this class. // See the run() method in this class and the actionPerformed() // in SophisticatedActionAdapter to see how this works. // private class AsynchThread extends Thread { private ActionEvent event;

public AsynchThread (ActionEvent event) { this.event = event; }

private void run (ActionEvent event) { this.event = event; run(); }

public void run () { try { actionPerformedImpl (event); } catch (Exception exception) { handleException (exception); } catch (Error error) { dispatchError (error); } finally { finallyImpl(); } } } }

SophisticatedKeyAdapter.java

KeyListener is a more complicated listener interface with multiple methods. SophisticatedKeyAdapter is the appropriate sophisticated adapter class for KeyListener. Furthermore, it can serve as a template for other adapters implementing multiple event methods.

SophisticatedKeyAdapter, presented below, incorporates two important differences from SophisticatedActionAdapter. First, multiple event methods funnel though the run() method in the AsynchThread inner class. Second, the asynchronous behavior is turned on or off for the entire object, not for each method in the class.

import java.awt.event.*;

public class SophisticatedKeyAdapter extends AbstractSophisticatedAdapter implements KeyListener { // These values are used to route events properly // through the run() method in the AsynchThread inner class. // static private final int KEY_PRESSED = 0; static private final int KEY_RELEASED = 1; static private final int KEY_TYPED = 2;

private AsynchThread awtThreadObject;

public SophisticatedKeyAdapter () { awtThreadObject = new AsynchThread(null); }

public SophisticatedKeyAdapter (boolean runAsynchronously) { setAsynchronousHandlingEnabled (runAsynchronously); }

public void setAsynchronousHandlingEnabled (boolean runAsynchronously) { if (runAsynchronously == false) awtThreadObject = new AsynchThread(null); else awtThreadObject = null; }

public final void keyPressed (KeyEvent event) { if (awtThreadObject != null) awtThreadObject.run(event, KEY_PRESSED); else new AsynchThread (event).start(); }

public final void keyReleased (KeyEvent event) { if (awtThreadObject != null) awtThreadObject.run(event, KEY_RELEASED); else new AsynchThread (event).start(); }

public final void keyTyped (KeyEvent event) { if (awtThreadObject != null) awtThreadObject.run(event, KEY_TYPED); else new AsynchThread (event).start(); }

/** * Client code should override this method instead of keyPressed(). */ protected void keyPressedImpl (KeyEvent event) throws Exception { }

/** * Client code should override this method instead of keyReleased(). */ protected void keyReleasedImpl (KeyEvent event) throws Exception { }

/** * Client code should override this method instead of keyTyped(). */ protected void keyTypedImpl (KeyEvent event) throws Exception { }

private class AsynchThread extends Thread { private KeyEvent event; private int eventType; public AsynchThread (KeyEvent event) { this.event = event; }

private void run (KeyEvent event, int eventType) { this.event = event; this.eventType = eventType; run(); }

public void run () { try { if (eventType == KEY_PRESSED) keyPressedImpl (event); else if (eventType == KEY_RELEASED) keyReleasedImpl (event); else keyTypedImpl (event); } catch (Exception exception) { handleException (exception); } catch (Error error) { dispatchError (error); } finally { finallyImpl(); } } } }

Obviously, there are more listener classes beyond KeyListener and ActionListener. With this in mind, you should provide each additional listener class with an appropriate SophisticatedXXXAdapter class, as needed.

Implications

The adoption of sophisticated adapter classes could have several potential drawbacks:

  • The GUI runs more slowly: The GUI code actually runs more slowly using the sophisticated adapter classes. This extra time should not matter too much, since adding a few microseconds overhead to a GUI action probably won't be noticed.
  • Small applets download more slowly: The sophisticated adapters add code, which means that applets download more slowly. The good news is that, if an applet needs the sophisticated adapter capability, providing it in a base class should result in less total code to download than writing try/catch blocks inside each adapter. However, if the applet is small and simple enough not to need this capability, these classes may be unnecessary. For small applets, the adapters provided in the JDK 1.1 are often sufficient.
  • Using the asynchronous capability is tricky: Using the asynchronous capability correctly and safely is tricky. Because the JDK 1.1 adapters run their code on the AWT thread, programmers can write GUIs without worrying about multithreaded programming. However, once a program uses the sophisticated adapters' asynchronous capabilities, the specific adapter subclasses must be written with multithreading in mind. While multithreaded programming is often necessary to keep the GUI from appearing locked up, this necessity does not make the multithreaded programming work any easier.
  • Running out of memory is still dangerous: The sophisticated adapters provided above do not always deal well with running out of memory. In fact, they might fail while trying to report that the program is out of memory because there isn't even enough memory to report the problem! Various strategies can be attempted with installable error handling policies to address this, but none solve the problem completely.

Integrating the classes with IDEs

Drag and drop IDE environments like Visual Café and JBuilder don't use this article's sophisticated adapter classes. To use these adapter classes with an IDE, let the IDE generate code using the standard adapters, then edit the generated code. This is annoying but not difficult.

What about JDK 1.0?

The problems solved by these sophisticated adapter classes are still problems for JDK 1.0 applets and applications. Unfortunately, the adapter classes shown above don't work with JDK 1.0 because it has a different event model. Even worse, there are no good solutions using the 1.0 event model.

A possible workaround: SophisticatedButton.java

To ensure that Errors and RuntimeExceptions in JDK 1.0 applets do not go unreported, one can subclass each AWT component and implement the correct try/catch behavior in the subclass. Client code should then use these subclasses instead of the raw AWT classes. An incomplete example for Button is illustrated here:

import java.awt.*;

/** * This button subclass catches all Errors and Exceptions. * Use this class only if you are running in a JDK 1.0 environment. */ public class SophisticatedButton extends Button { public final boolean handleEvent(Event evt) { try { handleEventImpl (evt); } catch (Exception exception) { handleException (exception); } catch (Error error) { dispatchError (error); } finally { finallyImpl(); }

return true; }

/** * Client code overrides this method instead of handleEvent(). */ protected void handleEventImpl (Event evt) { }

protected void handleException (Exception e) { // Dispatch to a global class like // AbstractSophisticatedAdapter, but rewritten // to work with JDK 1.0. }

protected void dispatchError (Error error) { // Dispatch to a global class like // AbstractSophisticatedAdapter, but rewritten // to work with JDK 1.0 (so no inner classes...) }

protected void finallyImpl () { // Dispatch to a global class like // AbstractSophisticatedAdapter, but rewritten // to work with JDK 1.0 (so no inner classes...) } }

To add more functionality, you can provide a Thread subclass for each SophisticatedXXX class, then rewrite the try block inside the handleEvent() method to dispatch to a new Thread object, if appropriate. This code would look similar to the dispatch code in the SophisticatedActionAdapter class above.

| 1 2 3 Page 2