package org.obo.app.swing; import java.awt.Component; import java.awt.KeyboardFocusManager; import java.awt.event.ActionEvent; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import javax.swing.AbstractAction; import javax.swing.Icon; import javax.swing.JComponent; import org.apache.log4j.Logger; /** * An action which traverses the component hierarchy, beginning with the currently focused component, * until it finds a object implementing a method with the name of the action's actionCommand. The * method must accept no arguments. If an object in the component hierarchy implements the method * "getResponderDelegate" or is a JComponent with a client property "responderDelegate", the object * returned will also be queried for an implementation of the actionCommand. * @author Jim Balhoff */ @SuppressWarnings("serial") public class ResponderChainAction extends AbstractAction { private String actionCommand; private Object finalResponder = null; public static String DELEGATE_METHOD = "getResponderDelegate"; public static String CLIENT_PROPERTY = "responderDelegate"; /** * Creates a ResponderChainAction object which will try to invoke a method named by the given actionCommand. * The ResponderChainAction will use the actionCommand as the description string and a default icon. * @param actionCommand The name of the method this action invokes. */ public ResponderChainAction(String actionCommand) { this(actionCommand, actionCommand, null); } /** * Creates a ResponderChainAction object which will try to invoke a method named by the given actionCommand. * The ResponderChainAction will use the specified description string and a default icon. * @param actionCommand The name of the method this action invokes. * @param name The action's description string. */ public ResponderChainAction(String actionCommand, String name) { this(actionCommand, name, null); } /** * Creates a ResponderChainAction object which will try to invoke a method named by the given actionCommand. * The ResponderChainAction will use the specified description string and the specified icon. * @param actionCommand The name of the method this action invokes. * @param name The action's description string. * @param icon The action's icon. */ public ResponderChainAction(String actionCommand, String name, Icon icon) { super(name, icon); this.setActionCommand(actionCommand); } /** * Invoked when an action occurs. The action searches the component hierarchy for a object * which can respond to the action's actionCommand, and if successful invokes that method on the object. */ @Override public void actionPerformed(ActionEvent event) { final Object target = this.getValidResponder(); if (target == null) { log().debug("No valid targets"); return; } final Method method = this.getMethodForName(target, this.getActionCommand()); this.callMethod(method, target); } public String getActionCommand() { return actionCommand; } public void setActionCommand(String actionCommand) { this.actionCommand = actionCommand; } public Object getFinalResponder() { return this.finalResponder; } public void setFinalResponder(Object anObject) { this.finalResponder = anObject; } private Component getFocusOwner() { return KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner(); } /** * Returns the first found object which implements a method named by the action's * actionCommand, or null if none is found. */ private Object getValidResponder() { if (this.getActionCommand() == null) return null; Object responder = this.getNextResponder(null); while (responder != null) { if (this.objectRespondsToMethod(responder, this.getActionCommand())) { return responder; } else if (this.objectHasDelegateForMethod(responder, this.getActionCommand())) { return this.getDelegateForObject(responder); } else { responder = this.getNextResponder(responder); } } return responder; } /** * Returns the next object which should be queried for an implementation of the * actionCommand. This is typically the parent Component of an existing Component, * but could also be an object returned by getResponderDelegate or the client property * "responderDelegate". If null is passed, the currently focused Component is returned. */ private Object getNextResponder(Object currentResponder) { if (currentResponder == null) return this.getFocusOwner(); if (currentResponder instanceof Component) { final Component parent = ((Component)currentResponder).getParent(); if (parent != null) { return parent; } } return this.getFinalResponder(); } private boolean objectRespondsToMethod(Object anObject, String methodName) { return this.getMethodForName(anObject, methodName) != null; } private boolean objectHasDelegateForMethod(Object anObject, String methodName) { final Object delegate = this.getDelegateForObject(anObject); if (delegate != null) { return this.objectRespondsToMethod(delegate, methodName); } else { return false; } } private Method getMethodForName(Object anObject, String methodName) { try { final Method method = anObject.getClass().getMethod(methodName); return method; } catch (SecurityException e) { return null; } catch (NoSuchMethodException e) { return null; } } private Object getDelegateForObject(Object anObject) { if (this.objectRespondsToMethod(anObject, DELEGATE_METHOD)) { final Method method = this.getMethodForName(anObject, DELEGATE_METHOD); return this.callMethod(method, anObject); } else if (anObject instanceof JComponent) { return ((JComponent)anObject).getClientProperty(CLIENT_PROPERTY); } else { return null; } } private Object callMethod(Method method, Object anObject) { try { return method.invoke(anObject); } catch (IllegalArgumentException e) { log().error("Unable to invoke method on target", e); } catch (IllegalAccessException e) { log().error("Unable to invoke method on target", e); } catch (InvocationTargetException e) { log().error("Unable to invoke method on target", e); } return null; } private Logger log() { return Logger.getLogger(this.getClass()); } }