Chaining Stateless Properties
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);
}
}
}
...
}
Implementation Plan for Chaining Observers- Observe the bean for each property in the chain.
- When a property changes, action is required:
- If it's the last property whose value has changed, notify the chain's listener
- For other properties, change the observed bean for the next property in the chain
Each time a new listener is registered on the chain, a new chain of
SubListeners
is created. Each SubListener
observes a single property of a single bean. And so the chain of SubListeners
observes the chain of properties. We use a custom SubListener
at the end of the chain who notifies the PropertyChangeListener
: 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 ObserversJust as with my previous solution, I can support removing the chain of listeners with a carefully crafted
equals()
method. Two SubListeners
are equal if they model the same property, have the same chain of SubListener
s downstream, and notify the same PropertyChangeListener
at the chain's end.ChainedProperty.java source listing