This is a little technique I came up with a few days ago that makes Async Callbacks a lot easier. It also adds something a lot like Property Bindings to GWT with very little work.
public interface PropertyChangeListener {
void propertyChanged(SourcesPropertyChangeEvents source, String propertyName, Object oldValue, Object newValue);
}
public interface SourcesPropertyChangeEvents {
void addPropertyChangeListener(PropertyChangeListener listener);
void addPropertyChangeListener(String propertyName, PropertyChangeListener listener);
void removePropertyChangeListener(PropertyChangeListener listener);
void removePropertyChangeListener(String propertyName, PropertyChangeListener listener);
}
public class PropertyChangeListenerCollection extends AbstractCollection<PropertyChangeListener> {
private final Map<String, Set<PropertyChangeListener>> listeners = new HashMap<String, Set<PropertyChangeListener>>();
private Set<PropertyChangeListener> getListenersForProperty(final String name) {
Set<PropertyChangeListener> set = listeners.get(name);
if(set == null) {
set = new HashSet<PropertyChangeListener>();
listeners.put(name, set);
}
return set;
}
// this is a simple utility method that avoids duplicate copies of the same
// PropertyChangeListener
private Set<PropertyChangeListener> getAllListeners() {
final Set<PropertyChangeListener> all = new HashSet<PropertyChangeListener>();
for(final Set<PropertyChangeListener> set : listeners.values()) {
all.addAll(set);
}
return all;
}
public void add(final PropertyChangeListener listener) {
add(null, listener);
}
public void add(final String property, final PropertyChangeListener listener) {
if(listener != null) {
getListenersForProperty(property).add(listener);
}
}
public void remove(final PropertyChangeListener listener) {
if(listener != null) {
for(final Set<PropertyChangeListener> set : listeners.values()) {
set.remove(listener);
}
}
}
public void remove(final String property, final PropertyChangeListener listener) {
if(listener != null) {
getListenersForProperty(property).remove(listener);
}
}
// although unused I've provided a simple implementation of the size method
public int size() {
return getAllListeners().size();
}
public Iterator<PropertyChangeListener> iterator() {
return getAllListeners().iterator();
}
public void firePropertyChangeEvent(final SourcesPropertyChangeEvents source, final String name,
final Object oldValue, final Object newValue) {
final Set<PropertyChangeListener> propertyListeners = new HashSet<PropertyChangeListener>();
propertyListeners.addAll(getListenersForProperty(null));
propertyListeners.addAll(getListenersForProperty(name));
for(final PropertyChangeListener l : propertyListeners) {
l.propertyChanged(source, name, oldValue, newValue);
}
}
}
public class Property<T> implements SourcesPropertyChangeEvents, PropertyChangeListener, AsyncCallback<T> {
private final String name;
private T value;
private PropertyChangeListenerCollection listeners;
public Property(final String name) {
this(name, null);
}
public Property(final String name, final T initialValue) {
this.name = name;
this.value = initialValue;
}
public void set(final T newValue) {
final T oldValue = value;
value = newValue;
if(listeners != null) {
listeners.firePropertyChangeEvent(this, name, oldValue, newValue);
}
}
public T get() {
return value;
}
public void onSuccess(final T newValue) {
set(newValue);
}
public void onFailure(final Throwable error) {
if(GWT.getUncaughtExceptionHandler() != null) {
GWT.getUncaughtExceptionHandler().onUncaughtException(error);
}
}
public void propertyChanged(final SourcesPropertyChangeEvents source, final String propertyName,
final Object oldValue, final Object newValue) {
set(newValue);
}
public void addPropertyChangeListener(final PropertyChangeListener listener) {
if(listeners != null) {
listeners = new PropertyChangeListenerCollection();
}
listeners.add(listener);
}
public void addPropertyChangeListener(final String propertyName, final PropertyChangeListener listener) {
if(listeners != null) {
listeners = new PropertyChangeListenerCollection();
}
listeners.add(propertyName, listener);
}
public void removePropertyChangeListener(final PropertyChangeListener listener) {
if(listeners != null) {
listeners.remove(listener);
}
}
public void removePropertyChangeListener(final String propertyName, final PropertyChangeListener listener) {
if(listeners != null) {
listeners.remove(propertyName, listener);
}
}
}
Instead of storing your bindable properties as normal fields: you simply wrap them in Property objects. You can then use the fact that Property objects both produce and consume propertyChangeEvents to bind them together, and even pass them into RPC methods to be set when the server hands the data back to you.
They rely on a PropertyChangeListenerCollection class that I haven’t given here, but it’s a simple enough class to write.
11-November-2008: I added in an implementation of the PropertyChangeListenerCollection class.
Give it a try, it makes life a surprising amount easier considering it’s size.