Development

Announcer

When registering event listeners it seems that there are many different and duplicate implementations of iterating over the listeners and broadcasting your event. This broadcaster takes the strain out of many uses of this pattern, by reflectively calling the notification method(s) on all registered listeners.

Usage

To use, simply define an interface that extends EventListener, and put the notification methods on it. Use an announcer in the class that wishes to send notifications, and delegate the register and unregister methods to it.

Then, from the caller, say announcer.announce().notificationMethod();

Notes

As this uses an iterator over an unsynchronized collection, it won’t work when registration and unregistration happens while notifications are being notified. A simple variant could use a CopyOnWriteArrayList.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.List;
 
public class Announcer<T extends EventListener> {
    private final T proxy;
    private final List<T> listeners = new ArrayList<T>();
          
    public Announcer(Class<? extends T> listenerType) {
        proxy = listenerType.cast(Proxy.newProxyInstance(
            getClass().getClassLoader(),
            new Class<?>[]{listenerType},
            new InvocationHandler() {
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    announce(method, args);
                    return null;
                }
            }));
    }
     
    public void addListener(T listener) {
        listeners.add(listener);
    }
     
    public void removeListener(T listener) {
        listeners.remove(listener);
    }
     
    public T announce() {
        return proxy;
    }
     
    private void announce(Method m, Object[] args) {
        try {
            for (T listener : listeners) {
                m.invoke(listener, args);
            }
        }
        catch (IllegalAccessException e) {
            throw new IllegalArgumentException("could not invoke listener", e);
        }
        catch (InvocationTargetException e) {
            Throwable cause = e.getCause();
             
            if (cause instanceof RuntimeException) {
                throw (RuntimeException)cause;
            }
            else if (cause instanceof Error) {
                throw (Error)cause;
            }
            else {
                throw new UnsupportedOperationException("listener threw exception", cause);
            }
        }
    }
     
    public static <T extends EventListener> Announcer<T> to(Class<? extends T> listenerType) {
        return new Announcer<T>(listenerType);
    }
}
comments powered by Disqus