Getting Listeners from JavaBeansTM
An Amendment to the JavaBeans Specification
Hans Muller and Mark Davidson
<!-- @@@ print version @@@ -->
Presently all of the state in an AWT component that can be written can also be read, e.g. there are no write-only properties in the component API. Event listeners are a notable exception. AWT event listeners are managed according to the JavaBeans conventions with a pair of methods: addFooListener() and removeFooListener() for a listener that implements the FooListener interface.
No access is provided to the listener lists themselves. The fields that contain the listener lists may be private, package private or protected and no methods are provided to return the contents of the listener lists. This has caused some problems for Swing and other AWT clients:
The Swing UI classes have to keep references to every listener they add, just to enable removing them if the UI is changed. Swing applications contain 1000's of references like this. In general, you can't clear any JavaBeans listener list unless you've kept a private copy.
Archiving systems have to resort to implementation dependent snooping to discover the contents of listener lists.
Externalization isn't an option for classes derived from Component, like the Swing components, because the listeners are inaccessible.
To mitigate the problem in Java 2 Standard Edition (J2SE) v 1.3 we added a getListeners(Class) method to Component and to the Swing classes that defined listener lists. The getListeners(Class) method uses a Class as its argument to specify a particular listener list. For example to get all of the listeners added with addFocusListener(), one would write: getListeners(FocusListener.class).
This particular approach to exposing listener lists was taken to minimize the overall change to the AWT/Swing public API. It was not indented to be a pattern for all JavaBeans and it did not handle PropertyChangeListeners - which can be added to a single property, as in addPropertyChangeListener("myProperty", myListener).
The specific implementation of this work in the AWT and Swing is covered by 4290704.
Solution
The JavaBeans Specification has been extended so that listener lists can optionally be read. Two extensions have been added:
To the add/remove pattern for listeners: add an optional get<ListenerType>s() method that returns an array of all of the listeners for a particular list.
Extend the EventSetDescriptor class to cover the new get<ListenerType>s() method. So that BeanInfos returned for classes by Introspection can recognize the new event pattern.
The rest of the document describes the changes to the JavaBeans Specification to support the new get<ListenerType>s() extension.
The additional methods and classes presented in this amendment have been implemented for Java 2 Standard Edition (J2SE) v 1.4. JavaBeans which have been written that extend Beans in the java.awt or javax.swing packages will automatically pick up the implementation of this amendment for existing listeners when J2SE 1.4 is used . If the extended Bean defines additional listeners, then the extended Bean should implement the appropriate get<ListenerType>s() methods.
Beans Specification Amendments
All additions or changes to the JavaBeans Specification are in red
6.5 Event Listener Registration...
public void add<ListenerType>(<ListenerType listener)
public void remove<ListenerType>(<ListenerType> listener)
public <ListenerType>[] get<ListenerType>s()
...
The add<ListenerType> method adds the given listener to the set of event listeners registered for events associated with the <ListenerType>. The get<ListenerType>s method returns the set of registered event listeners. Similarly, the remove<ListenerType> method removes the given listener from the set of event listeners registered for <ListenerType> events.
The get<ListenerType>s method is optional. It was added in the XX revision of this specification consequently beans defined per earlier revisions don't support it.
6.5.1 Event Registration Examplepublic abstract class Model {
...
private List listeners = new ArrayList(0);
public synchronized void ModelChangedListener[] getModelChangedListeners() {
return (ModelChangedListener[])(listeners.toArray());
}
...
}
7.4.1 Bound properties
...
The PropertyChangeListener event listener interface is used to report updates to simple bound properties. If a bean supports bound properties then it should support the set of multi-cast event listener registration methods for PropertyChangeListeners:
public void addPropertyChangeListener(PropertyChangeListener x);
public void removePropertyChangeListener(PropertyChangeListener x);
public PropertyChangeListener[] getPropertyChangeListeners();
...
The getPropertyChangeListeners method is optional. It was added in the XX revision of this specification and as a consequence beans defined per earlier revisions don't support it. Also, the getPropertyChangeListeners method may return a mixture of PropertyChangeListeners and PropertyChangeListenerProxy objects (which implement PropertyChangeListener) if the bean supports listening on named properties. See Section 7.4.5.1 (section number may change - msd) for details.
7.4.2 Constrained properties
...
... If a bean supports constrained properties then it should support a normal set of multi-cast event listener registration methods for VetoableChangeListeners:
void addVetoableChangeListener(VetoableChangeListener x);
void removeVetoableChangeListener(VetoableChangeListener x);
VetoableChangeListener[] getVetoableChangeListeners();
The getVetoableChangeListeners method is optional. It was added in the XX revision of this specification and as a consequence beans defined per earlier revisions don't support it. Also, the getVetoableChangeListeners method may return a mixture of VetoableChangeListeners and VetoableChangeListenerProxy objects (which implement VetoableChangeListener) if the bean supports listening on named properties. See Section 7.4.5.1 (section number may change - msd) for details.
7.4.5 Optional support for listening on named propertiesIn addition to supporting the standard design pattern for adding, retrieving and removing PropertyChangeListeners shown in Section 7.4.1 above, a bean that fires PropertyChangeEvents may also support an additional set methods that allow a PropertyChangeListener to be added, retrieved and removed for a named property: void addPropertyChangeListener(String propertyName,
PropertyChangeListener listener);
void removePropertyChangeListener(String propertyName,
PropertyChangeListener listener);
PropertyChangeListener[] getPropertyChangeListeners(String propertyName);
In this case, the bean should associate the given listener with the given property name, and only invoke it's propertyChange method when the given named property has been changed.
Similarly, in addition to supporting the standard additional registration methods for adding and removing VetoableChangeListeners, beans may also support addition registration methods that are tied to a specific named property:
void addVetoableChangeListener(String propertyName,
VetoableChangeListener listener);
void removeVetoableChangeListener(String propertyName,
VetoableChangeListener listener);
VetoableChangeListener[] getVetoableChangeListeners(String propertyName);
7.4.5.1 Retrieval of listeners for named properties
When listeners for named properties are added to a bean, the zero argument retrieval method getPropertyChangeListeners (or similarly, getVetoableChangeListeners) will return all the listeners added to the bean. For beans which support named properties, the definition of getListeners() has been extended to mean that it would return the real listener, or a subclass of an EventListenerProxy for listeners added with additional parameters. Subclasses of EventListenerProxys may be used to identify the listeners associated with specific named properties.
If the calling method is interested in distinguishing the listeners from the getXXXListeners() method, then it must check each element to see if its an EventListenerProxy or subclass, perform the appropriate cast and extract the additional parameters.
For example, if a PropertyChangeListener was added with a named property to a bean with the addPropertyChangeListener("propertyName", listener), then the no argument getPropertyChangeListeners() method may return a set of PropertyChangeListeners and PropertyChangeListenerProxys. If the calling method is interested in retrieving the name of the property, then it must test each element to see if its a PropertyChangeListenerProxy.
7.4.7 A bound and constrained example... We use instances of the PropertyChangeSupport and VetoableChangeSupport classes to help us keep track of the event listeners and to deliver the actual events. import java.awt.*;
import java.beans.*;
public class JellyBean {
...
public PropertyChangeListener[] getPropertyChangeListeners() {
return changes.getPropertyChangeListeners();
}
...
public VetoableChangeListener[] getVetoableChangeListeners() {
return vetos.getVetoableChangeListeners();
}
...
}
Javadoc for EventListenerProxy
package java.util;
/**
* An abstract wrapper class for an EventListener class which associates a set of
* additional parameters with the listener. Subclasses must provide the storage and
* accessor methods for the additional arguments or parameters.
*
* Subclasses of EventListenerProxy may be returned by getListeners() methods
* as a way of associating named properties with their listeners.
*
*
* @since 1.4
*/
public class EventListenerProxy implements EventListener {
private final EventListener listener;
/**
* @param listener The listener object
* @param parameters List of parameters associated with the listener.
*/
public EventListenerProxy(EventListener listener)
/**
* @return The listener associated with this proxy.
*/
public EventListener getListener()
}
/**
* A class which extends the EventListenerProxy specifically
* for adding a named PropertyChangeListener. Instances of
* this class can be added as PropertyChangeListener to
* an object.
*
* If the object has a getPropertyChangeListeners()
* method then the array returned could be a mixture of
* PropertyChangeListener and PropertyChangeListenerProxy
* objects.
*
* For example, a Bean which supports named properties would have a two argument
* method signature for adding a PropertyChangeListener for a property:
*
* public void addPropertyChangeListener(String propertyName,
* PropertyChangeListener listener);
*
* If the Bean also implemented the zero argument get listener method:
*
* public PropertyChangeListener[] getPropertyChangeListeners();
*
* then the array may contain PropertyChangeListeners which are also
* PropertyChangeListenerProxy objects.
*
* If the calling method is interested in retrieving the named property then it
* would have to test the element to see if its a proxy class.
*
* @see java.util.EventListenerProxy
* @since 1.4
*/
public class PropertyChangeListenerProxy extends EventListenerProxy
implements PropertyChangeListener {
/**
* Constructor which binds the PropertyChangeListener to a specific property.
*
* @param listener The listener object
* @param propertyName The name of the property to listen on.
// XXX - msd NOTE: I changed the order of the arguments so that it's similar to
// PropertyChangeSupport.addPropertyChangeListener(String, PropertyChangeListener)
*/
public PropertyChangeListenerProxy(String propertyName,
PropertyChangeListener listener)
/**
* @param listener The listener object
* @param parameters List of parameters associated with the listener.
*/
public PropertyChangeListenerProxy(PropertyChangeListener listener,
Object[] parameters)
/**
* Forwards the property change event to the listener delegate.
*
* @param evt the property change event
*/
public void propertyChange(PropertyChangeEvent evt)
/**
* Returns the name of the named property associated with the
* listener.
*/
public String getPropertyName()
}
/**
* A class which extends the EventListenerProxy specifically
* for adding a named VetoableChangeListener. Instances of
* this class can be added as VetoableChangeListener to
* an object.
*
* If the object has a getVetoableChangeListeners()
* method then the array returned could be a mixture of
* VetoableChangeListener and VetoableChangeListenerProxy
* objects.
*
* @see #EventListenerProxy
* @since 1.4
*/
public class VetoableChangeListenerProxy extends EventListenerProxy
implements VetoableChangeListener {
/**
* @param propertyName The name of the property to listen on.
* @param listener The listener object
// XXX - msd NOTE: I changed the order of the arguments so that it's similar to
// PropertyChangeSupport.addPropertyChangeListener(String, PropertyChangeListener)
*/
public VetoableChangeListenerProxy(String propertyName,
VetoableChangeListener listener)
/**
* @param listener The listener object
* @param parameters List of parameters associated with the listener.
*/
public VetoableChangeListenerProxy(VetoableChangeListener listener,
Object[] parameters)
/**
* Forwards the property change event to the listener delegate.
*/
public void vetoableChange(PropertyChangeEvent evt)
/**
* Returns the name of the named property associated with the
* listener.
*/
public String getPropertyName()
}
8.4 Design Patterns for Events
...
We look for a set of methods of the form:
public void add<EventListenerType>(<EventListenerType> a)
public void remove<EventListenerType>(<EventListenerType> a)
public <EventListenerType>[] get<EventListenerType>s()
where the first two methods take the same "<EventListenerType"> type argument,...., the third method returns an array of "<EventListenerType>" objects, starts with "get" and is the plural form....
...
The get<EventListenerType>s method is optional. It was added in the XX revision of this specification and as a consequence beans defined per earlier revisions don't support it. Also, the get<EventListenerType>s method may return a mixture of <EventListenerType>s and EventListenerProxy objects if the bean supports listening on named properties. See Section 7.4.5.1 (section number may change - msd) for details.
So for example:
public void addFredListener(FredListener t);
public void removeFredListener(FredListener t);
public FredListener[] getFredListeners();
Defines a multi-cast event source
8.4.1 Unicast event sources
Implement the same changes as the multi-cast example
JavaDoc for EventSetDescriptor (additional contructors and accessor method)
/**
* This constructor creates an EventSetDescriptor from scratch using
* string names.
*
* @param sourceClass The class firing the event.
* @param eventSetName The programmatic name of the event set.
*Note that this should normally start with a lower-case character.
* @param listenerType The Class of the target interface that events
*will get delivered to.
* @param listenerMethodNames The names of the methods that will get called
*when the event gets delivered to its target listener interface.
* @param addListenerMethodName The name of the method on the event source
*that can be used to register an event listener object.
* @param removeListenerMethodName The name of the method on the event source
*that can be used to de-register an event listener object.
* @param getListenerMethodName The name of the method on the event source
*that can be used to access the array of event listener objects.
* @exception IntrospectionException if an exception occurs during
* introspection.
* @since 1.4
*/
public EventSetDescriptor(Class sourceClass,
String eventSetName,
Class listenerType,
String listenerMethodNames[],
String addListenerMethodName,
String removeListenerMethodName,
String getListenerMethodName) throws IntrospectionException
/**
* This constructor creates an EventSetDescriptor from scratch using
* java.lang.reflect.Method and java.lang.Class objects.
*
* @param eventSetName The programmatic name of the event set.
* @param listenerType The Class for the listener interface.
* @param listenerMethods An array of Method objects describing each
*of the event handling methods in the target listener.
* @param addListenerMethod The method on the event source
*that can be used to register an event listener object.
* @param removeListenerMethod The method on the event source
*that can be used to de-register an event listener object.
* @param getListenerMethod The method on the event source
*that can be used to access the array of event listener object.
* @exception IntrospectionException if an exception occurs during
* introspection.
* @since 1.4
*/
public EventSetDescriptor(String eventSetName,
Class listenerType,
Method listenerMethods[],
Method addListenerMethod,
Method removeListenerMethod,
Method getListenerMethod) throws IntrospectionException
/**
* Gets the method used to access the event listeners.
*
* @return The method used to access the array of listeners at the
* event source or null if it doesn't exist
* @since 1.4
*/
public Method getGetListenerMethod()
Implementation Details
The get<ListenerType>s() methods have been added in J2SE v 1.4 for all classes in the javax.swing and java.awt packages and sub packages which implement listener registration.
Changes to java.beans.EventSetDescriptor
The EventSetDescriptor has been amended to support the get<ListenerType>s() method. This means that two new constructors and the accessor method have been added.
For backwards compatibility, the EventSetDescriptor will not fail if the getListener() methods do not exist. However, the getGetListenerMethod() will return null if that method doesn't exist.
Changes to java.beans.PropertyChangeSupport
PropertyChangeSupport has had the following methods added:
public PropertyChangeListener[] getPropertyChangeListeners();
public PropertyChangeListener[] getPropertyChangeListeners(String propertyName);
Changes to java.beans.VetoableChangeSupport
VetoableChangeSupport has had the following methods added:
public VetoableChangeListener[] getVetoableChangeListeners();
public VetoableChangeListener[] getVetoableChangeListeners(String propertyName);