package abbot.script; import java.awt.Component; import java.lang.reflect.Method; import java.util.*; import abbot.tester.*; import abbot.util.AWT; /** Encapsulate an action. Usage:<br> * <blockquote><code> * <action method="..." args="..."><br> * <action method="..." args="component_id[,...]" class="..."><br> * </code></blockquote> * An Action reproduces a user semantic action (such as a mouse click, menu * selection, or drag/drop action) on a particular component. The id of the * component being operated on must be the first argument, and the class of * that component must be identified by the class tag if the action is not * provided by the base {@link abbot.tester.ComponentTester} class<p> * Note that the method name is the name of the actionXXX method, * e.g. to click a button (actionClick on * AbstractButtonTester), the XML would appear thus:<p> * <blockquote><code> * <action method="actionClick" args="My Button" class=javax.swing.AbstractButton><br> * </code></blockquote> * Note that if the first argument is a Component, the class tag is required. * Note also that the specified class is the <i>tested</i> class, not the * target class for the method invocation. * <p> * The target class for the method invocation is always a * ComponentTester-derived class. */ // Any reason for the tested class to be saved and not the target class? the // tester class gets looked up dynamically, but is there any reason that would // be required? // The component reference class should be used; that way the component can // change class w/o affecting the action; also the action class should *not* // be saved, and the component tester looked up dynamically. public class Action extends Call { private static final String USAGE = "<action method=\"...\" args=\"...\" [class=\"...\"]/>"; // Account for deprecated methods which use String representations of // modifier masks. private static Set stringModifierMethods = new HashSet(Arrays.asList(new String[] { "actionKeyStroke", "actionKeyPress", "actionKeyRelease" })); private static Set optionalFocusMethods = new HashSet(Arrays.asList(new String[] { "actionKeyStroke", "actionKeyPress", "actionKeyRelease", "actionKeyString", })); private static final String DEFAULT_CLASS_NAME = "java.awt.Component"; /** Provide a default value for the target class name, so that the Call * parent class won't choke. */ private static Map patchAttributes(Map map) { if (map.get(TAG_CLASS) == null) { map.put(TAG_CLASS, DEFAULT_CLASS_NAME); } return map; } public Action(Resolver resolver, Map attributes) { super(resolver, patchAttributes(attributes)); patchMethodName(); } /** Action for a method in the ComponentTester base class. */ public Action(Resolver resolver, String description, String methodName, String[] args) { super(resolver, description, DEFAULT_CLASS_NAME, methodName, args); patchMethodName(); } public Action(Resolver resolver, String description, String methodName, String[] args, Class targetClass) { super(resolver, description, targetClass.getName(), methodName, args); patchMethodName(); } private void patchMethodName() { // account for deprecated usage String mn = getMethodName(); if (!mn.startsWith("action")) setMethodName("action" + mn); } /** Ensure the default class name is DEFAULT_CLASS_NAME * The target class <i>must</i> be a subclass of java.awt.Component. */ public void setTargetClassName(String cn) { if (cn == null || "".equals(cn)) cn = DEFAULT_CLASS_NAME; super.setTargetClassName(cn); } /** Return the XML tag for this step. */ public String getXMLTag() { return TAG_ACTION; } /** Return custom attributes for an Action. */ public Map getAttributes() { Map map = super.getAttributes(); // Only save the class attribute if it's not the default map.remove(TAG_CLASS); if (!DEFAULT_CLASS_NAME.equals(getTargetClassName())) { map.put(TAG_CLASS, getTargetClassName()); } return map; } /** Return the proper XML usage for this step. */ public String getUsage() { return USAGE; } /** Return a default description for this action. */ public String getDefaultDescription() { // strip off "action", if it's there String name = getMethodName(); if (name.startsWith("action")) name = name.substring(6); return name + getArgumentsDescription(); } public Class getTargetClass() throws ClassNotFoundException { return resolveTester(getTargetClassName()).getClass(); } /** Convert the String representation of the arguments into actual * arguments. */ protected Object evaluateParameter(Method m, String param, Class type) throws Exception { // Convert ComponentLocation arguments if (ComponentLocation.class.isAssignableFrom(type)) { ComponentTester tester = (ComponentTester)getTarget(m); return tester.parseLocation(param); } // Convert virtual key codes and modifier masks into integers else if ((type == int.class || type == Integer.class) && (param.startsWith("VK_") || param.indexOf("_MASK") != -1)) { if (param.startsWith("VK_")) return new Integer(AWT.getKeyCode(param)); return new Integer(AWT.getModifiers(param)); } else { return super.evaluateParameter(m, param, type); } } /** Return the target of the invocation. */ protected Object getTarget(Method m) throws ClassNotFoundException { return resolveTester(getTargetClassName()); } /** Remove deprecated methods from those looked up. */ protected Method[] resolveMethods(String name, Class cls, Class returnType) throws NoSuchMethodException { Method[] methods = super.resolveMethods(name, cls, returnType); if (stringModifierMethods.contains(name)) { // still have some key methods hanging around which expect a // string representation of the VK_ code and/or modifiers. // ignore them here. ArrayList list = new ArrayList(Arrays.asList(methods)); for (int i=0;i < methods.length;i++) { Class[] ptypes = methods[i].getParameterTypes(); for (int j=0;j < ptypes.length;j++) { if (ptypes[j] == String.class) { list.remove(methods[i]); break; } } } methods = (Method[])list.toArray(new Method[list.size()]); } return methods; } /** Resolve the method name into its final form. */ public Method getMethod() throws ClassNotFoundException, NoSuchMethodException { return resolveMethod(getMethodName(), getTargetClass(), void.class); } protected Method disambiguateMethod(Method[] methods) { // Try to find the right one by examining some of the parameters // Nothing fancy, just explicitly picks between the variants. if (methods.length == 2) { // pick between key action variants if (optionalFocusMethods.contains(methods[0].getName())) { Class[] params = methods[0].getParameterTypes(); Method kcMethod, crefMethod; if (params[0] == int.class) { kcMethod = methods[0]; crefMethod = methods[1]; } else { kcMethod = methods[1]; crefMethod = methods[0]; } String[] args = getArguments(); if (args.length > 0 && args[0].startsWith("VK_")) { return kcMethod; } else { return crefMethod; } } } return super.disambiguateMethod(methods); } }