Typed and Targeted Property Change Events in Java

Listening for bound property changes of a JavaBean is simple enough, and
determining which bean fired the event is as straightforward as calling
PropertyChangeEvent.getSource(). But what to do if the JavaBean in
question has complex property values, each of which can have its own bound
properties? This article examines a method for representing targeted
eventsthose that may represent changes in objects other than the source. It
also introduces an adapter that allows type-aware property change events using
Java 5.0 generics. Examples are provided using the Guise Internet application
framework.

Typed Property Change Events

When Sun added the JavaBeans component architecture, Java acquired a standard way to
conceptualize properties of objects that were richer than mere data
fields. Besides the ability to perform custom actions when properties are read
and/or written, using so-called getter and setter methods,
respectively, Java allowed third-party observer objects to listen for property
changes and perform their own actions in response. Such properties which notify
listeners of changes are referred to as bound properties. The
foundational classes of this architecture are found in the java.beans
package.

The two fundamental classes that implement bound properties in JavaBeans are
PropertyChangeListener and PropertyChangeEvent. Sun
provided the PropertyChangeSupport class to assist developers in
creating classes with bound properties, obviating the need for a class to keep
track of its own property change listeners and to manually fire property change
events to those listeners. Using these classes, a simple Automobile
class could implement an engine bound property, assuming that an
Engine class exists. (Note that we require that an engine always be
non-null to simplify the logic. Without this requirement, the code
would need extra checks for null throughout.)

That's easy enough. But the old and new values of
PropertyChangeEvent are always expressed as Object,
forcing us to cast those values, even when we know ahead of time what type to
expect. When we listen for the "engine" property, we expect the
value to be an Engine. Wouldn't it be nice if
PropertyChangeEvent used generics, so that its values were
automatically returned as the expected types?

Unfortunately, PropertyChangeEvent was created long before
generics were added to the Java language in version 5.0, but using another Java
feature, covariance, it's possible to create an adapter class that
provides us with typed values while providing backwards-compatibility with
PropertyChangeEvent. This part of the task is surprisingly easy;
here is the basic structure of a class,
com.garretwilson.beans.GenericPropertyChangeEvent<V>, that
does just that:

So far the main functionality of the class is to cast the old and new values
to the generic type, V, before returning them. Because Java doesn't
keep track of generics at runtime, the real return types of the
getOldValue() and getNewValue() methods after erasure
are Object, anyway. The cast to the generic type isn't actually
performed here at runtime, so the @SuppressWarnings("unchecked")
annotation is needed to prevent the compiler from alerting us to this fact.
Whatever code uses these methods will perform the appropriate cast, however,
providing equivalence to the property change listener code we had earlier,
except without the need to code casts by hand. We've even provided a copy
constructor to allow creating generic property change events from standard
non-generic property change events.

There's a problem, however: the Automobile class fires a normal
PropertyChangeEvent, not a
GenericPropertyChangeEvent<V>. We could change the
Automobile class to throw a
GenericPropertyChangeEvent<V>, which is compatible with
PropertyChangeEvent, but then we'd have to cast the event inside
our PropertyChangeListener, which defeats the purpose of this
exercise. It looks like we'll have to create a custom
GenericPropertyChangeListener<V> as well:

Well, that was certainly easy enough. But more problems arise. First, the
java.beans.PropertyChangeSupport class keeps track of
PropertyChangeListeners, not
GenericPropertyChangeListeners, and therefore will call the
PropertyChangeListener.propertyChange() method rather than the
generic version. We could scrap PropertyChangeSupport and keep
track of the listeners ourselves, but there bigger problem: our
Automobile class would no longer be compatible with the standard
Java bound property contract, which requires registration of
PropertyChangeListeners and firing of property change events to the
non-generics-aware version of propertyChange(). Third party tools
would not be able to work with our JavaBean.

The trick is to use a special base listener class that is compatible with
both PropertyChangeListener and
GenericPropertyChangeListener<V> and that converts the
PropertyChangeEvent to a
GenericPropertyChangeEvent<V> as needed. That class,
com.garretwilson.beans.AbstractGenericPropertyChangeListener, is
presented below:

/**A Java Beans property change listener
retrofitted to use generics to cast to proper value type.
@param <V> The type of property value.
@author Garret Wilson
*/
public abstract class AbstractGenericPropertyChangeListener<V>
implements GenericPropertyChangeListener<V>
{
/**Called when a bound property is changed.
This non-generics version calls the generic version,
creating a new event if necessary.
No checks are made at compile time to ensure the given event
actually supports the given generic type.
@param propertyChangeEvent An event object describing
the event source, the property that has changed,
and its old and new values.
@see GenericPropertyChangeListener#propertyChange
(GenericPropertyChangeEvent)
*/
@SuppressWarnings("unchecked")
public final void propertyChange(final PropertyChangeEvent
propertyChangeEvent)
{
propertyChange((GenericPropertyChangeEvent<V>)
getGenericPropertyChangeEvent(propertyChangeEvent));
}
/**Converts a property change event to a generics-aware
property value change event.
@param propertyChangeEvent An event object describing
the event source, the property that has changed,
and its old and new values.
@return A generics-aware property change event,
either cast from the provided object
or created from the provided object's values as appropriate.
*/
@SuppressWarnings("unchecked")
public static <T> GenericPropertyChangeEvent<T>
getGenericPropertyChangeEvent(final PropertyChangeEvent
propertyChangeEvent)
{
if(propertyChangeEvent instanceof GenericPropertyChangeEvent)
{
return (GenericPropertyChangeEvent<T>)propertyChangeEvent;
}
else //if the event is a normal property change event
{
return new GenericPropertyChangeEvent<T>(
propertyChangeEvent); //create a copy of the event
}
}
}

Fulfilling the contract of PropertyChangeListener, this class
overrides propertyChange(PropertyChangeEvent), but then converts
the event to a GenericPropertyChangeEvent<T> and passes it to
the generic method version,
propertyChange(GenericPropertyChangeEvent<T>). The conversion
in the utility method <T> GenericPropertyChangeEvent<T>
getGenericPropertyChangeEvent(final PropertyChangeEvent
propertyChangeEvent) is efficient: if the event is already a
GenericPropertyChangeEvent<T>, there's no need to create a
new event. Otherwise, the method creates a new
GenericPropertyChangeEvent<T> from the plain
PropertyChangeEvent using the copy constructor we created
above.

To use this efficiency, we'll need to modify the Automobile
class to fire GenericPropertyChangeEvents. (The class would work
just fine without this, but would require the unnecessary creation of new
objects from generics-aware listeners.) It turns out that
PropertyChangeSupport.firePropertyChange(String, Object, Object)
just creates a PropertyChangeEvent and sends that to
PropertyChangeSupport.firePropertyChange(PropertyChangeEvent). The
Automobile class can create its own
GenericPropertyChangeEvent<Engine> and fire that object when
needed:

Furthermore, non-generics-aware PropertyChangeListener instances
can still register with an Automobile instance, and they will
function as normal. Even better, we can use the
AbstractGenericPropertyChangeEvent<V> registration pattern to
register with any JavaBean, whether or not it is generics-aware. Our code will
perform casts implicitly, obviating the need for us to perform this tedious and
error-prone process ourselves. This pattern additionally imposes consistency on
our codeif we copy part of this routine to another property change listener
that expects a type other than Engine, our code won't compile,
whereas an erroneous hand-coded cast to Engine in a
non-generics-aware property change listener would have gone unnoticed until
runtime.

Advertiser Disclosure:
Some of the products that appear on this site are from companies from which QuinStreet receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. QuinStreet does not include all companies or all types of products available in the marketplace.

Thanks for your registration, follow us on our social networks to keep up-to-date