Atom Feed SITE FEED   ADD TO GOOGLE READER

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
  1. Observe the bean for each property in the chain.
  2. 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 Observers
Just 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 SubListeners downstream, and notify the same PropertyChangeListener at the chain's end.

ChainedProperty.java source listing