FileDocCategorySizeDatePackage
PropertyChangeSupport.javaAPI DocExample11236Sun Dec 14 22:47:38 GMT 2003oreilly.hcj.references

PropertyChangeSupport

public class PropertyChangeSupport extends Object
Implements a beans like property change support utility that stores the references to the listeners in a weak set and thus are memory-safe.

By storing the references to the listeners in weak references instead of strong references, we can be sure that if the listener is no longer used by other classes, it will be garbage collected with this class. Since the producer of messages should not have the responsability of maintaining the garbage collection status of its listeners, this is needed.

Note that this class implements the identical interface to java.lang.PropertyChangeSupport. This means that it is completely plug in compatible, enabling users to switch out current support in favor of the safe version.

This object is not serializable because it uses a WeakHashSet to hold the listeners. Refer to that class for reasons for this decision. Users must declare this attribute transient.

This class is thread safe.

author
$author$
version
$Revision: 1.8 $
see
java.lang.ref.WeakReference
see
java.beans.PropertyChangeSupport

Fields Summary
private static final Logger
LOGGER
Holds the logger instance for the class.
private static final String
ALL_PROPERTIES
Holds the value used as the key for general listeners in the listener map. This value is chosen because it would be illegal to name a property the same as this value which guarantees that there will be no name clashes.
private final Map
listenerMap
Holds the Listener map. The map is held as an index of WeakHashSet objects. Each property will appear as a key once in the set and when a property change event is fired on a property, the event will be fired to the union of the specific listeners set and the general listeners set. The general listeners are also held in the set with the key set by ALL_PROPERTIES
private final Object
producer
Stores the producer of the events.
Constructors Summary
public PropertyChangeSupport(Object producer)
Creates a new instance of SafePropertyChangeSupport.

param
producer This is the object that is producing the property change events.
throws
RuntimeException If there is an introspection problem.


	                            	 
	    
		try {
			final BeanInfo info = Introspector.getBeanInfo(producer.getClass());
			final PropertyDescriptor[] props = info.getPropertyDescriptors();
			for (int idx = 0; idx < props.length; idx++) {
				listenerMap.put(props[idx].getName(), new WeakHashSet());
			}
			listenerMap.put(ALL_PROPERTIES, new WeakHashSet());
			this.producer = producer;
		} catch (IntrospectionException ex) {
			throw new RuntimeException(ex);
		}
	
Methods Summary
public voidaddPropertyChangeListener(java.beans.PropertyChangeListener pcl)
Add a non-specific PropertyChangeListener. Adds a listener that will recieve events on all properties.

param
pcl The PropertyChangeListener add.

		synchronized (listenerMap) {
			((WeakHashSet)listenerMap.get(ALL_PROPERTIES)).add(pcl);
		}
	
public voidaddPropertyChangeListener(java.lang.String property, java.beans.PropertyChangeListener pcl)
Add a PropertyChangeListener for a specific property.

param
property The name of the relevant property.
param
pcl The listener to add.

		validateNamedProperty(property);

		synchronized (listenerMap) {
			((WeakHashSet)listenerMap.get(property)).add(pcl);
		}
	
public voidfirePropertyChange(java.lang.String property, java.lang.Object old, java.lang.Object neo)
Notify listeners that an object type property has changed

param
property the name of the property which changed.
param
old The old value.
param
neo The new value.

		PropertyChangeEvent event = new PropertyChangeEvent(producer, property, old, neo);
		firePropertyChange(event);
	
public voidfirePropertyChange(java.lang.String property, int old, int neo)
Shortcut for firing an event on boolean properties.

param
property the name of the property which changed.
param
old The old value.
param
neo The new value.

		PropertyChangeEvent event =
			new PropertyChangeEvent(producer, property, new Integer(old), new Integer(neo));
		firePropertyChange(event);
	
public voidfirePropertyChange(java.lang.String property, boolean old, boolean neo)
Shortcut for firing an event on boolean properties.

param
property the name of the property which changed.
param
old The old value.
param
neo The new value.

		PropertyChangeEvent event =
			new PropertyChangeEvent(producer, property, new Boolean(old), new Boolean(neo));
		firePropertyChange(event);
	
public voidfirePropertyChange(java.beans.PropertyChangeEvent event)
Fire a property change event to all of the listeners.

This method is called by all the fire methods to perform the firing of the events.

The firing will go to the listeners that are registered for the specific property as well as general purpose listeners.

If the old and new values for the event are the same, by the equals() method, the event will not be fired.

param
event The event to fire to the listeners.

		validateNamedProperty(event.getPropertyName());

		if (event.getOldValue() == null) {
			if (event.getOldValue() == null) {
				return;
			}
		} else if (event.getOldValue()
		                .equals(event.getNewValue())) {
			return;
		}

		// validated that an event must be thrown; now throw it.
		synchronized (listenerMap) {
			// First gets the list of listeners and stores them in strong
			// references by copying them into a new set.
			Set targets = new HashSet((Set)listenerMap.get(ALL_PROPERTIES));
			targets.addAll((Set)listenerMap.get(event.getPropertyName()));

			// Fire events at the listeners.
			Iterator iter = targets.iterator();

			while (iter.hasNext()) {
				((PropertyChangeListener)iter.next()).propertyChange(event);
			}

			// Release the strong set so that the weak refs take over again.
		}
	
public java.beans.PropertyChangeListener[]getPropertyChangeListeners()
Returns an array of all the listeners that were added to the PropertyChangeSupport object with addPropertyChangeListener(). The array is computed as late as possible to give as much cleanout time as neded.

return
An array of all listeners.

		Set all = new WeakHashSet();
		PropertyChangeListener[] pcls = new PropertyChangeListener[0];

		synchronized (listenerMap) {
			Iterator iter = listenerMap.values()
				                       .iterator();

			while (iter.hasNext()) {
				all.addAll((Set)iter.next());
			}
		}

		return (PropertyChangeListener[])all.toArray(pcls);
	
public java.beans.PropertyChangeListener[]getPropertyChangeListeners(java.lang.String property)
Returns an array of all the listeners which have been associated with the named property.

param
property The name of the relevant property.
return
An array of listeners listening to the specified property.

		validateNamedProperty(property);

		PropertyChangeListener[] pcls = new PropertyChangeListener[0];
		Set namedListeners = null;

		synchronized (listenerMap) {
			namedListeners = new HashSet((Set)listenerMap.get(property));
		}

		return (PropertyChangeListener[])namedListeners.toArray(pcls);
	
public booleanhasListeners(java.lang.String property)
Check if there are any listeners interested in a specific property.

param
property The relvant property name.
return
true If there are listeners for the given property

		validateNamedProperty(property);

		synchronized (listenerMap) {
			return (!((Set)listenerMap.get(property)).isEmpty());
		}
	
public voidremovePropertyChangeListener(java.beans.PropertyChangeListener pcl)
Remove a PropertyChangeListener from the listener list. This removes a PropertyChangeListener that was registered for all properties.

param
pcl The PropertyChangeListener to be removed

		synchronized (listenerMap) {
			((WeakHashSet)listenerMap.get(ALL_PROPERTIES)).remove(pcl);
		}
	
public voidremovePropertyChangeListener(java.lang.String property, java.beans.PropertyChangeListener pcl)
Remove a PropertyChangeListener for a specific property.

param
property The name of the relevant property.
param
pcl The listener to remove.

		validateNamedProperty(property);

		synchronized (listenerMap) {
			((WeakHashSet)listenerMap.get(property)).remove(pcl);
		}
	
private voidvalidateNamedProperty(java.lang.String property)
Validate that a property name is a member of the producer object.

This is a helper method so that all methods that must validate this need not replicate the code.

param
property The name of the property to validate.
throws
IllegalArgumentException DOCUMENT ME!

		if (!listenerMap.containsKey(property)) {
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug("Key Set: " + listenerMap.keySet());  //$NON-NLS-1$
			}

			throw new IllegalArgumentException("The property '" + property  //$NON-NLS-1$
			                                   + "' is not a valid property of "  //$NON-NLS-1$
			                                   + producer.getClass()
			                                   + ". Valid values = "  //$NON-NLS-1$
			                                   + listenerMap.keySet().toString());
		}