In a recent post, I described how I implemented the observers for stateless properties. In this follow up I describe how to do observers for chained properties.
What's a chained property?
Suppose I'd like to bind a combo box to a customer's country. If the Customer bean has a getCountry()
property, that's easy. But if the Country is only exposed via a series of getters like getBillingAddress().getCountry()
, I bind to Customer#address and address#country in series. A chained property is created by composing multiple properties into one.
Implementing get() and set() is easy
I resolve each property in the chain using the value of one property as the bean for the next:
public class ChainedProperty<B,V> implements Property<B,V> {
private final List<Property> propertyChain;
public ChainedProperty(...) { ... }
public V get(B bean) {
_Object current = bean;_
for (Property property : propertyChain) {
_current = property.get(current);_
}
return (V)current;
}
public void set(B bean, V value) {
_Object current = bean;_
for (Iterator<Property> m = propertyChain.iterator(); m.hasNext(); ) {
Property property = m.next();
if (m.hasNext()) {
_current = property.get(current);_
} else {
_ property.set(current, value);_
}
}
}
...
}`</pre>**Implementation Plan for Chaining Observers**
-
Observe the bean for each property in the chain.
-
When a property changes, action is required:
Each time a new listener is registered on the chain, a new chain ofSubListeners
is created. EachSubListener
observes a single property of a single bean. And so the chain ofSubListeners
observes the chain of properties. We use a customSubListener
at the end of the chain who notifies thePropertyChangeListener
:` public _abstract_ class SubListener<B, V> implements PropertyChangeListener { private final Property<B, V> property; private B bean; private V currentValue; public SubListener(...) { ... }
void _setBean(B bean)_ { property.removePropertyChangeListener(bean, this); this.bean = bean; property.addPropertyChangeListener(bean, this); valueChanged(); } public void propertyChange(PropertyChangeEvent evt) { valueChanged(); } private void valueChanged() { V oldValue = currentValue; currentValue = property.get(bean); fireValueChanged(oldValue, currentValue); } abstract void _fireValueChanged(V oldValue, V currentValue)_;
}
public class ChainingSubListener<B, V> extends SubListener<B, V> {
private final SubListener<V, ?> next;
public ChainingSubListener(...) { ... }@Override public void fireValueChanged(V oldValue, V currentValue) { _next.setBean(currentValue);_ }
}
public class LastSubListener<B, V> extends SubListener<B, V> {
private final Object rootBean;
private final String chainName;
private final PropertyChangeListener delegate;
public LastSubListener(...) { ... }@Override public void fireValueChanged(V oldValue, V currentValue) { _delegate.propertyChange( new PropertyChangeEvent(rootBean, chainName, oldValue, currentValue));_ }
}
Removing Observers
Just as with my previous solution, I can support removing the chain of listeners with a carefully craftedequals()
method. TwoSubListeners
are equal if they model the same property, have the same chain ofSubListener
s downstream, and notify the samePropertyChangeListener
at the chain's end.
ChainedProperty.java source listing