476
|
Chapter 12, Miscellany
#94 Fire Events and Stay Bug Free
HACK
The obvious question here is: why didn’t D get called? Well, think about it:
you iterate over the listeners by index, from 0 to 4. On index 2, the code
calls
C
’s
handleEvent( )
, which removes itself from the
ArrayList
. As a result,
D
, which was at index 3, is now at index 2. But having serviced index 2
(which was
C
), the
for
loop moves on to index 3, which is now listener
E
.
Thus,
D
never gets called.
That was a lot of fun the first time I got to debug it.
Hacking a Solution
Consider an alternative approach. Counting up gets you in trouble because a
listener that removes itself shifts the indices of all subsequent listeners. But if
you counted down—from the last listener to the first—then a listener could
remove itself safely.
In these examples, this would mean counting from index 4 down to 0. On
index 2, listener
C removes itself, but that doesn’t change the indices of the
listeners that haven’t been called yet, which are at indices 0 and 1.
All you have to do is change the
fireEvent( ) method in the
BackwardsForLoopEventSource class:
public void fireEvent (EventObject o) {
for (int i=listeners.size( )-1; i>=0; i--) {
TestEventListener l = (TestEventListener) listeners.get(i);
l.handleEvent (o);
}
}
Run this modified code and you get the output shown here:
[tonberry] cadamson% java BackwardsForLoopEventSource
E called
D called
C called
B called
A called
Woo hoo! It works! All five listeners get called.
Does the event order matter? Do you need to require that listeners are called
in the order they were added? If so, you might do something else, like going
back to the
Iterator approach and making a clone that you iterate over. But
that’s not really necessary.
Fire Events and Stay Bug Free #94
Chapter 12, Miscellany
|
477
HACK
Surprisingly, the “count backward” approach is how Swing’s classes handle
this problem. If you look in the source of Swing objects that have
fireXXX( )
methods, you’ll see they generally use
javax.swing.event.EventListenerList
.
This class maintains a list in which each pair of elements defines a listener:
each even-numbered entry is the
Class
of a listener, and each odd-num-
bered entry is the listener itself. This means you have to go through the list
two entries at a time, checking the class and then firing the event to the lis-
tener. It’s kind of weird, but the JavaDoc says this provides more thread-
safety and serialization support…yeah, great, I’m sure I’ll appreciate that the
next time I buy some JavaBeans off the shelf at Fry’s.
Anyways, take a look at the JavaDoc and you’ll see that Sun provides a
prototype event-firing method to use with the
EventListenerList, and like
the previous example, it uses a backward
for loop. Example 12-15 shows a
simple implementation as a
TestEventSource.
Example 12-15. Using an EventListenerList to fire events
import java.util.*;
import javax.swing.event.*;
public class EventListenerListEventSource
extends TestEventSource {
EventListenerList listenerList = new EventListenerList( );
public void addListener (TestEventListener l) {
listenerList.add (TestEventListener.class, l);
}
public void removeListener (TestEventListener l) {
listenerList.remove (TestEventListener.class, l);
}
public void fireEvent (EventObject o) {
Object[] listeners = listenerList.getListenerList( );
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i] == TestEventListener.class) {
((TestEventListener) listeners[i+1]).handleEvent(o);
}
}
}
public static void main (String[] args) {
EventListenerListEventSource bfles =
new EventListenerListEventSource( );
bfles.test( );
}
}

Get Swing Hacks now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.