[an error occurred while processing this directive]

Page One
Page Two
The Databank
What Is Swing?
Special Report
IDE Roundup
Swing and the Web
Swing Text
Tech Topics
Friends
Tips and Tricks
The PLAF Papers
Call 911
The Archive
JFC Home
Download Swing
Swing API Docs
Download JDK
JDK Docs
Java Tutorial
 
The Swing Connection Tech Topics

Generating Event Listeners Dynamically



Swing uses ActionListeners to add behavior to nearly all of its GUI controls -- and the anonymous classes that JavaBeans use to implement event listeners have been accused in some quarters of consuming more class file space than they should. This article shows how Swing can help you get around that problem. With the help of several code samples that you can download and experiment with, it offers some suggestions that may be of interest to developers of IDEs and other kinds of applications that generate listeners for Swing or AWT.


By Hans Muller

One charge that gets leveled against the Java Beans event model from time to time is that the anonymous classes used to implement event listeners consume more class file space than they should.

Event listener classes implement one of the many interfaces derived from java.util.EventListener. For example, to handle keyboard events, you can add an implementation of java.awt.event.KeyListener to a component's "key" listener list:

KeyListener myKeyListener = new KeyListener() {
    public void keyTyped(KeyEvent e) { handleKeyEvent(e); }
    public void keyPressed(KeyEvent e) { handleKeyEvent(e); }
    public void keyReleased(KeyEvent e) { handleKeyEvent(e); }
};
myComponent.addKeyListener(myKeyListener);

As you can see, the listener in this case simply dispatches the KeyEvent to another method, called handleKeyEvent(). Using a listener as a sort of "trampoline" to shuttle events from a listener method to some event-handling method in the outer class is a common idiom. In fact, some GUI builders generate boilerplate listener implementations that do exactly that. Today's JavaTM compilers produce a class file like "myListener" for each anonymous inner class. Usually the class file gets a funny name such as "MyClass$3".


The ActionListener Problem

Fortunately, most applications don't have many KeyListeners. However, Swing applications tend to be teeming with ActionListeners. ActionListeners are used to add behavior to nearly all of Swing's GUI controls -- notably buttons, combo boxes, and menu items.

This can be a serious concern in applets; applets should download as quickly as possible, and yet each inner class file used in an applet contains a block of potentially redundant information (beyond the few byte codes that represent the applet's listener method implementations themselves). If these listener classes aren't just trampolines but actually do significant work, then the cost of the extra class files probably isn't worth worrying about. However, when an IDE or other system unconditionally generates this kind of thing, it's worth considering the remedies presented in this article.


How the Remedies Work

To illustrate how these remedies work, this article presents several source files, titled:

You'll get a chance to see how all these parts fit together later in the article.


Using Reflection to Implement a Generic ActionListener

In the future, it's quite possible that separate class files will not be generated for each inner class, or that inner class files will be relatively small. However, in the short term, the problem does exist, so the solutions outlined in this article should be of interest to anyone developing an IDE or other application that generates listeners for Swing or AWT.

Many developers have observed that by using reflection to dispatch from a listener to some other handling method, one can get by with just one listener implementation per listener type. The following example (with exception handling omitted) shows how easy it is to create a generic ActionListener that invokes a method on some target object :

class GenericActionListener implements ActionListener {
    private final Object target;
    private final Method targetMethod;

    GenericActionListener(Object target, Method targetMethod) {
	this.target = target;
	this.targetMethod = targetMethod;
    }

    public void actionPerformed(ActionEvent e) {
	targetMethod.invoke(target, new Object[]{e});
    }
}

The one class created in this example -- GenericActionListener -- can be used for all of the ActionListeners in an application. To use GenericActionListener, you must look up the method object that the listener will invoke. For example, to create an ActionListener that calls an ActionEvent method called buttonAction, you could write a method like this (exception handling ommitted):

Method m = getClass().getMethod("myAction", new Class[]{ActionEvent.class});
myButton.addActionListener(new GenericActionListener(this, m));

This kind of solution works well enough for small GUI applications. In general, however, it requires the creation of a similar boilerplate class for each listener type, and that can get oppressive. As it turns out, it's possible to generate the bytecodes that define simple classes like GenericActionListener at runtime -- and they can be generated for any listener interface. The fact that such a class can be generated at runtime is of particular importance to developers of IDEs and other tools because it enables event links to be created between components (classes) that are loaded at run-time -- that is, those whose listener interfaces aren't known until runtime.


GenericListener: Dynamically Generating Listener Classes

So far, we've defined a new utility class called GenericActionListener that can be used to generate a listener interface implemention in which one listener method dispatches to a method on some target object. To see how this new class works, you can download and examine the source files supplied with this article:

The GenericListener.create() method code generates a "trampoline" listener class, if one doesn't exist already, and then loads the new class with a custom class loader. The trampoline class is configured to apply a method that you specify to a target object. If you take the black-box view of GenericListener.create(), then you provide a listener method, a target object, and a target object method, and the create() method returns a listener object where the specified listener method calls targetObject.targetMethod(event).

Here's an example (We're using the a convenient version of the create() method that allows us to specify string method names):

ActionListener l = (ActionListener)(GenericListener.create(
    ActionListener.class, 
    "actionPerformed", 
    myTargetObject, 
    "myTargetObjectMethod"));

myButton.addActionListener(l);

Here we've created an ActionListener whose actionPerformed() method calls myTargetObject.myTargetObjectMethod(anActionEvent). If the ActionListener interface doesn't contain a method named actionPerformed(), or if the target object doesn't contain a method named myTargetObjectMethod() that matches the signature of actionPerformed(), a runtime exception is thrown. Naturally, the value returned by the static GenericListener.create() method has to be cast to the correct listener type, since it can return any type of listener.

To see a complete program that creates a MouseListener and an ActionListener, see the Demo.java example.

This approach has two important advantages of this approach over the strategy of managing a set of trampoline classes that was described in the preceding section:

  • The internal listener "trampoline" classes are generated automatically and at runtime. They don't affect download time, and they have no more effect on the applications working set than a minimal set of similar hand-written classes would.

  • IDEs and other tools that load classes or components dynamically can use GenericListener to generate event bindings for listener interfaces (and event types) that aren't known until run-time.

WARNING: The GenericListener implementation takes about 2,000 lines of pure Java programming language code, and most developers will probably find it moderately difficult to read -- although developers who have spent quality time with Java compiler implementations, and who are familiar with the VM (virtual machine) and the class loader, shouldn't have too much trouble understanding it.

A variety of simple extensions can be applied to the GenericListener class itself; for example, it would be easy to add support for having all listener methods dispatch to the target method. However, our advice for the rest of the classes is: If you don't like Java compilers, don't look.


Acknowledgement

The GenericListener class is really just a thin veneer over a more general-purpose infrastructure for generating arbitrary interface implementations built by John Rose. John is a member of the Sun/Java Software Java compiler team. Without his help, we wouldn't have dared take this work farther than GenericActionListener.


Hans Muller

 

[an error occurred while processing this directive]