Listener notification approaches

The problem which is covered in this article, describes different approaches to implement the listener

List of listeners

This approach is implemented in java.beans.PropertyChangeSupport class. With minor addons this class implements the listener notification based on list of listeners.

Some other implementations also store the references to listeners together with listener classes in one array of objects (even positions store the class, odd positions the reference to the listener). This approach can be used when you need to store all listeners in one array (not one array per listener class).

Chain of listeners

The idea is based on chaining the listeners, which in our implementation is singular tree where the nodes are the special classes (called EventMulticaster) which contain two leaves, one of two is EventMulticaster instance and another is a listener instance. The last EventMulticaster in the chain has two listeners as leaves. The adding of new element is always O(1) complexity, the removal needs processing of all nodes with some tree mutations (O(n) complexity). The solution is perfect for the cases when it is known, that the consumer will use no, one or two listeners, which is quite common. Originally it is implemented in java.awt.AWTEventMulticaster class, and here is the generic version with usage examples:

EventMulticaster class (common functionality)

public abstract class EventMulticaster<T extends EventListener> implements EventListener {
 
    protected final T a, b;
 
    /**
     * @see java.awt.AWTEventMulticaster
     */
    protected EventMulticaster(T a, T b) {
        this.a = a;
        this.b = b;
 
        assert a != null && b != null;
    }
 
    /**
     * This method should be overridden to create new multicast objects of a given class.
     */
    protected abstract T addInternal(T a, T b);
 
    /**
     * @see java.awt.Toolkit$ToolkitEventMulticaster#remove(T oldl)
     */
    private T removeInternal(T oldl) {
        // Return the other listener if there is match:
        if (oldl == a) return b;
        if (oldl == b) return a;
 
        // Otherwise we process the left and right branch:
        final T a2 = removeInternal(a, oldl);
        final T b2 = removeInternal(b, oldl);
 
        if (a2 == a && b2 == b) {
            return (T) this; // it's not here
        }
 
        return addInternal(a2, b2);
    }
 
    /**
     * @see java.awt.AWTEventMulticaster#removeInternal(T l, T oldl)
     */
    protected static <T extends EventListener> T removeInternal(T l, T oldl) {
        if (l == oldl /* we have match */|| l == null /* all listeners have already been removed */) {
            return null;
        }
        else if (l instanceof EventMulticaster) {
            return ((EventMulticaster<T>) l).removeInternal(oldl);
        }
        else {
            return l; // it's not here
        }
    }
}

ActionListenerEventMulticaster class (concrete implementation of a given interface)

public class ActionListenerEventMulticaster extends EventMulticaster<ActionListener> implements ActionListener {
 
    public ActionListenerEventMulticaster(ActionListener a, ActionListener b) {
        super(a, b);
    }
 
    /**
     * @see org.epo.cassius.generic.guicomponent.component.event.EventMulticaster#add(T, T)
     */
    @Override
    protected ActionListener addInternal(ActionListener a, ActionListener b) {
        return add(a, b);
    }
 
    /**
     * @see java.awt.AWTEventMulticaster#addInternal(EventListener a, EventListener b)
     */
    public static ActionListener add(ActionListener a, ActionListener b) {
        if (a == null) return b;
        if (b == null) return a;
 
        return new ActionListenerEventMulticaster(a, b);
    }
 
    /**
     * @see java.awt.AWTEventMulticaster#removeInternal(EventListener l, EventListener oldl)
     */
    public static ActionListener remove(ActionListener l, ActionListener oldl) {
        return EventMulticaster.removeInternal(l, oldl);
    }
 
    /**
     * @see java.awt.event.ActionListener#actionPerformed(ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
        a.actionPerformed(e);
        b.actionPerformed(e);
    }
}

MyComponent class (consumer)

public class MyComponent {
 
    private ActionListener    actionListener;
 
...
 
    public void addActionListener(ActionListener l) {
        actionListener = ActionListenerEventMulticaster.add(actionListener, l);
    }
 
    public void removeActionListener(ActionListener l) {
        actionListener = ActionListenerEventMulticaster.remove(actionListener, l);
    }
 
    protected void fireActionPerformed(ActionEvent e) {
        if (actionListener != null) {
            actionListener.actionPerformed(e);
        }
    }
}

Proxying

This approach uses the Java ability to proxy calls of a given interface via a specific class, which is called a proxy. Proxy declares itself as a instance of a given interface and when some method is invoked on the class, it calls the same method via reflection on all listeners.

EventDispatcher class

public class EventDispatcher<T extends EventListener> implements InvocationHandler {
 
    private final Collection<T>    listeners = new ArrayList<T>();
 
    private final T                proxy;
 
    public EventDispatcher(Class<T> listenerClass) {
        proxy = listenerClass.cast(Proxy.newProxyInstance(listenerClass.getClassLoader(), new Class[]{listenerClass}, this));
    }
 
    public T getInterfaceInstance() {
        return proxy;
    }
 
    public void addListener(T listener) {
        listeners.add(listener);
    }
 
    public void removeListener(T listener) {
        listeners.remove(listener);
    }
 
    /**
     * @see java.lang.reflect.InvocationHandler#invoke(Object, Method, Object[])
     */
    public Object invoke(Object proxy, Method method, Object[] args) {
        for (final T listener : listeners) {
            try {
                method.invoke(listener, args);
            }
            catch (IllegalArgumentException e) {
                LogFactory.getLog(getClass()).error("invoke()", e);
            }
            catch (IllegalAccessException e) {
                LogFactory.getLog(getClass()).error("invoke()", e);
            }
            catch (InvocationTargetException e) {
                LogFactory.getLog(getClass()).error("invoke()", e);
            }
        }
 
        return null;
    }
}

Usage

EventDispatcher<ActionListener> eventDispatcher = new EventDispatcher<ActionListener>(ActionListener.class);
 
eventDispatcher.addListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        System.out.println("e=" + e);
    }
});
 
eventDispatcher.addListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        System.out.println("e is " + e);
    }
});
 
eventDispatcher.getInterfaceInstance().actionPerformed(new ActionEvent(eventDispatcher, 1, "command"));

You can extend this implementation and store the listeners of all classes in one list and then invoke a method only for those listeners, that are of a given class:

EventDispatcher class

public class EventDispatcher<T extends EventListener> implements InvocationHandler {
 
    private EventListenerList    listeners    = new EventListenerList();
 
    public T createInterfaceInstance(Class<T> listenerClass) {
        return listenerClass.cast(Proxy.newProxyInstance(listenerClass.getClassLoader(), new Class[]{listenerClass}, this));
    }
 
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
        final Class<T> listenerClass = (Class<T>) method.getDeclaringClass();
        final EventListener[] list = listeners.getListeners(listenerClass);
        for (int i = 0; i < list.length; i++) {
            method.invoke(list[i], args);
        }
 
        return null;
    }
 
    public void addListener(Class<T> listenerClass, T listener) {
        listeners.add(listenerClass, listener);
    }
 
    public void removeListener(Class<T> listenerClass, T listener) {
        listeners.remove(listenerClass, listener);
    }
}
programming/java/listener_notification.txt · Last modified: 2009/04/24 09:40 by dmitry
 
 
Recent changes RSS feed Driven by DokuWiki