HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

With Java 1.1 rolling off the digital assembly line, it's time to take a look at Java's new AWT event delivery model. The nice thing about this new model is that the design pattern it uses, a Listener/Listenee model, is applicable to your Java 1.0.X applications and applets, not just Java 1.1. You will see that this new model will make your visual components easier to develop and more re-usable. In addition, you will know one of the key techniques you'll need to use to create Java Beans.

Luckily, there's nothing very difficult to understand about the new event delivery mechanism. I think that if you aren't already using something like it you'll be knocking yourself on the head wondering,"Why aren't I doing this now?!"

The Problems with the 1.0.X Model
I've recently designed a Web site that required having free-floating toolbars as part of the interface. Several different areas in the Web site needed different types of toolbars embedded in the pages. The user interacts with the toolbars to change the information displayed within the page, or to navigate through the Web site.

I could have designed this site using the traditional Java event delivery mechanism. In that case, I would have had to build a different applet for each toolbar that appears within the site. And the delivery of the toolbar events would require custom code for each toolbar on each page. For example, if Toolbar1 on a page is supposed to control a visual graph of data, the buttons of the toolbar controlling the specific data and format being displayed, then the event handling code of the Toolbar1 class would have to be specifically written to perform an action when the different toolbar components triggered events. That's because the eventual target of the events, the graph component, would only receive the event if a) the graph is a container of the toolbar; or b) I wrote custom code to hand the event to the graph. Well, the graph and the toolbar are neither a container of the other, nor do they share a common container. So if I was going to stick to the Java 1.0.X event delivery mechanism, I would have had to write custom applets for each of the toolbars.

Considering the site was non-trivial, and these toolbars were to be a major part of the site, the prospect of creating dozens of different applets classes was not appealing for two reasons:

  1. Non-reusable toolbars: Each toolbar in each page needs to be built as a different applet class, with new code and new .CLASS files. I'm just too lazy to do that.
  2. A slower site: With multiple different toolbars on each page, each toolbar would require at least one new .CLASS file to be downloaded from the Web server.
There is a more generic problem that this example illustrates: Unless the eventual target of an event is a parent (or ancestor) container of the UI device that collects the event, then you will have to write custom code to give the event to its eventual target. Even in the Java 1.1 technique we're about to explore this is true. But it seems that we should create a generic method of getting an event to its eventual handler. Then we won't have to write new code do pass events from their source to their eventual handlers. "Generic" here means "re-usable," and if you're as lazy a programmer as me, then the idea of just re-using code instead of writing it all the time is music to your ears.

Using Interfaces to Deliver Events
The problem seems to be rooted in the fact that controls always hand off events to only one other object (if the control itself doesn't handle the event), and that other object is the control's container. What would work well is if the control could hand off events to multiple other objects, and you could tell the control which objects to send events to instead of having a hard-coded list of targets.

It is obvious that this is a job for interfaces. The way to solve this problem is to define an interface of callback methods that the control can call when specific control events occur. For example, here's how you could define an interface of callback methods that a push button calls when it is pushed by the user:

public interface PushButtonListener {
public void push(PushButton source);
}

Then, any object that wants to be notified when a particular button is pushed need only implement this interface. In addition, the application must "register" the ButtonListener with the button itself. Listing 1 shows how we will implement a push-button so that it can notify any number of PushButtonListeners when it is pushed:

Now, any number of objects can register themselves as listeners of a PushButton object. When the user presses the button associated with that PushButton, it delivers an event notification to all of its listeners.

Of course, we could implement the PushButton class any number of different ways. More specifically, we could use several different methods for delivering events to the listeners. For the PushButton class itself, there might not be much of a reason to design anything more complex. But for more complex controls, especially those that have many different listeners, you may want to create a background thread for delivering events to the listeners instead of using the AWT-Callback thread. You may also need to use a priority mechanism to determine the order you want to send notifications to listeners.

The Generic Listener Design
The previous example demonstrates how to use interfaces to define a generic event deliverer/listener design. In a generalization of this technique, we say that these elements define what you have to have present in the event deliverer object class:

  • An addXXXListener method: Objects that have a listener interface register themselves with the event deliverer object through this method. This method need only take one parameter, which is a reference to the object cast as an interface. It is up to the event deliverer to maintain a list of objects to deliver events to.
  • A removeXXXListener method: This is the method that is called to unregister listeners from the event delivering object. A reference to the same interface that was passed to the addXXXListener method is passed as the only parameter to this method.

And in the listener object class we must declare that the object implements a listener interface. The methods of the listener interface should all follow the same model:

public void (Object deliverer[, Object ]);

That is, each method of this interface should have at least one parameter. That parameter is a reference to the object that is delivering the event notification. This parameter is necessary because, conceivably, the listener may register itself with multiple event deliverers that can deliver events of the same type.

The methods of the listener interface can optionally take one additional parameter. This parameter is a reference to an object that contains information about the event. It is within this object that you could, for example, indicate how many times the mouse was clicked for a mouse click event, or you could give the exact time that a timer event occurred.

Using Events with Java Beans
There's an added benefit to using this event delivery design pattern in Java 1.1. If you use this design pattern to design event deliverers and listeners then the Java Beans introspector can automatically detect the events that your controls fire off, and the events that your objects can receive.

Introspection is a new capability of Java 1.1. The Java 1.1 Introspector object is able to analyze your Java classes and divine certain class capabailities. Among those capabilities is the type of events your classes can fire and receive. This is one of the major parts of Java Beans. So if you use this new event delivery design pattern, you are not only gaining the benefits of creating re-usable classes, you are also making a Java Bean!

A Java Bean is Java's abstraction of a generic, cross-processes object. There are already a few different models for inter-process components that can receive and fire events across processes and network boundaries. For example, Microsoft's ActiveX controls, which are based on the Windows COM communications model, is an instance of a generic component model. ActiveX components can deliver and receive events (as well as a few other capabailities). The dream of Java Beans is a single executable Java class (or collection of Java classes) that can be used as an inter-process component on different operating systems. On machines that are running Microsoft Windows operating systems, a Java Bean is exposed to all other active processes as an ActiveX component. Similarly, on a Macintosh system the exact same Java object will be exposed to other processes as an OpenDoc part.

The Introspector object is responsible for figuring out the events your Java class objects can fire and receive, so that the Java Bean can act like an ActiveX object that fires off ActiveX events on Windows systems, or be an OpenDoc part that fires off OpenDoc events on the Macintosh.

The way the Introspector works is by going thropugh the public API of your Java class and analyzing the various methods. The Introspector is able to recognize when the event delivery design pattern, the same design described above, is being used in your class. Thus, the Introspector can make known to the Java Beans system that your Java object's fire or receive events are of a particular type.

Following the Java Beans Design Pattern to the Letter
In order for the Introspector to recognize when you are using a particular event delivery design pattern (the deliverer\listener design pattern), you must follow some specific rules in the naming and definition of your event delivery API and interfaces. This section describes those exact rules.

Note that by following these rules, and effectively making your class objects usable as Java Beans in Java 1.1, you are not precluded from using the class in Java 1.0.X. The classes will still work just fine in Java 1.0.X environments. The Java 1.0.X specification does not include an Introspector or the concept of a Java Bean. You must use a Java 1.1+ environment to gain the Java Beans "extras."

The first rule you must follow is in the naming of your listener interface. The interface name must look like this:

interface Listener extends EventListener{ ... }

That is, just tack the name of the Event-firing class to the "Listener" suffix, and derive the interface from the java.lang.EventListener interface. The EventListener interface actually has no members. It is necessary, however, for the Introspector to properly identify your listener interface as the target of an event delivery.

Next, you must define an event object class. Instances of this class are used to send extra information about each thrown event from the event deliverer to the event listener. In Java 1.1, this class should always be (ultimately) derived from the java.lang.EventObject class. This class can have any member methods or variables you want, but it must eventually be derived from the EventObject class. Here's an example of a Keyboard event class that will be used to deliver keyboard events to a listener:

public class KeyboardEvent extends java.lang.EventObject {
// member data
}

Then, each method of the listener interface should look like this (which looks very similar to the generic listener interface method pattern described previously):

public void <some name>(<bean class> obj, <event class> event);

For example, the keyboard event interface might have these methods in it:

public void keyPress(Keyboard kb, KeyboardEvent event);
public void keyRelease(Keyboard kb, KeyboardEvent event);

By defining the listener interface and and event object class, you can define any class as a listener of a particular event. In Java 1.1, objects of that class will correctly be recognized as Java Beans capabable of receiving the indicated type of event.

To define an object that can fire the event you only need to include public addXXXListener and removeXXXListener methods in your event-firing class. The name of the method and the type of single parameter argument to each method is all the Introspector needs to recognize that the class objects can through the type of event indicated. For example, a class that can fire keyboard events would have these two public methods:

public void addKeyboardListener(KeyboardListener kbl);
public void removeKeyboardListener(KeyboardListener kbl);

Summary
The new event delivery mechanism defined by Java 1.1 promotes re-usable classes and is completely backwards compatible with Java 1.0.X. In addition, it also makes for more readable and maintainable code. With these benefits alone, there's really no reason not to implement your custom controls and event delivery classes using this new paradigm.

An added benefit is that this design pattern is how you define one aspect of Java Beans. Automatically, the classes you define using the exact design pattern proscribed by this article are recognized as Java Beans by the Java 1.1 Introspector. Classes that implement event receiving interfaces are automatically recognized as event receivers, and classes that implement the addXXXListener and removeXXXListener public methods are automatically recognized as event firing methods.

In Java 1.0.X environments, the listener and delivery classes will still work perfectly well together. In Java 1.1 environments the classes will have the added benefit of being Java Beans, usable as inter-process controls without any modifications whatsoever.

About the Author
Brian Maso is a programming consultant working out of Portland, OR. He is the co-author of The Waite Group Press's upcoming release The Java API SuperBible. Before Java, Brian spent five years corralled in the MS Windows branch of programming, working for such notables as the Hearst Corp., First DataBank, and Intel. Readers are encouraged to contact Brian via e-mail at [email protected] with any comments or questions.

	

Listing 1

public class PushButton extends Panel {
    private Button m_button = new Button();
    private Vector m_vectListeners = new Vector();

    public PushButton() { this(""); }
    public PushButton(String title) {
        m_button = new Button(title);
        setLayout(new BorderLayout());
        add("Center", m_button);
    }

    public boolean action(Event evt, Object what) {
        Enumeration enum = m_vectListener.elements();
        While(enum.hasMoreElements())
            ((PushButtonListener)enum.nextElement()).push(this);
    }

 public addPushButtonListener(PushButtonListener target) {
        m_vectListener.addElement(target);
    }

    public removePushButtonListener(PushButtonListener target) {
        m_vectListener.removeElement(target);
    }
}

 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.