====== 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: public abstract class EventMulticaster 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 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) l).removeInternal(oldl); } else { return l; // it's not here } } } public class ActionListenerEventMulticaster extends EventMulticaster 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); } } 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. public class EventDispatcher implements InvocationHandler { private final Collection listeners = new ArrayList(); private final T proxy; public EventDispatcher(Class 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; } } EventDispatcher eventDispatcher = new EventDispatcher(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: public class EventDispatcher implements InvocationHandler { private EventListenerList listeners = new EventListenerList(); public T createInterfaceInstance(Class 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 listenerClass = (Class) 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 listenerClass, T listener) { listeners.add(listenerClass, listener); } public void removeListener(Class listenerClass, T listener) { listeners.remove(listenerClass, listener); } } {{tag>generics}}