/** * Copyright (c) 2003-2005, Simone Bordet * All rights reserved. * * This software is distributable under the BSD license. * See the terms of the BSD license in the documentation provided with this software. */ package foxtrot.utils; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.EventListener; /** * This class wraps an EventListener subclass (and thus any AWT/Swing event listener such as * <code>ActionListener</code>s, <code>MouseListener</code>s and so on) making sure that if * a wrapped listener is executing, another wrapped listener (even of different type) it is * not executed. * <br> * For example, if a user clicks very quickly on a button, it may trigger the execution of * the associated listener more than once. When the listener contains Foxtrot code, the * second click event is dequeued by Foxtrot and processed again, invoking the listener * again. Using this class to wrap the listener avoids this problem: the second event will * be dequeued and processed by Foxtrot as before, but the wrapped listener will not be * called. * <br> * Example Usage: * <pre> * final JButton apply = new JButton("Apply"); * apply.addActionListener((ActionListener)EventListenerProxy.create(ActionListener.class, new ActionListener() * { * public void actionPerformed(ActionEvent e) * { * apply.setEnabled(false); * Worker.post(new Job() * { * public Object run() * { * // Lenghty apply code * } * }); * } * })); * * JButton cancel = new JButton("Cancel"); * cancel.addActionListener((ActionListener)EventListenerProxy.create(ActionListener.class, new ActionListener() * { * public void actionPerformed(ActionEvent e) * { * // For example, dispose a dialog * } * })); * </pre> * Without using EventListenerProxy, when a user clicks on the apply button and immediately * after on the cancel button, it happens that apply's button listener is executed; in there, * usage of Foxtrot's Worker will dequeue and execute the event associated to the cancel * button click, that will - for example - dispose the dialog <strong>before</strong> the * apply operation is finished. <br /> * When using EventListenerProxy instead, the second event - the cancel button click - will * not be executed: the event will be processed without invoking the wrapped listener. <br /> * The overhead in the code is to change plain listeners: * <pre> * button.addActionListener(new ActionListener() {...}); * </pre> * to this: * <pre> * button.addActionListener((ActionListener)EventListenerProxy.create(ActionListener.class, new ActionListener() {...})); * </pre> * @version $Revision: 1.3 $ */ public class EventListenerProxy implements InvocationHandler { /** * This flag signals the fact that the wrapped listener is in execution. * It is static since event listeners are never processed in parallel, * but one after the other in the Event Dispatch Thread. * Only after one listener finishes another one can execute. */ private static boolean working; private EventListener listener; /** * Creates an instance that wraps the given listener */ protected EventListenerProxy(EventListener listener) { if (listener == null) throw new NullPointerException("EventListener cannot be null"); this.listener = listener; } /** * Creates a proxy for the given listener. <br> * The listener must implement the given listener interface * @param listenerInterface The interface used to create the proxy * @param listener The listener to proxy * @return A proxy for the given listener * @throws NullPointerException When the interface or the listener is null * @throws IllegalArgumentException When the listener does not implement the interface */ public static EventListener create(Class listenerInterface, EventListener listener) { if (!listenerInterface.isInstance(listener)) throw new IllegalArgumentException("EventListener " + listener + " must implement " + listenerInterface.getName()); return (EventListener)Proxy.newProxyInstance(listenerInterface.getClassLoader(), new Class[]{listenerInterface}, new EventListenerProxy(listener)); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (working) return null; try { working = true; return method.invoke(listener, args); } finally { working = false; } } }