001: package abbot.script;
002:
003: import java.lang.reflect.Method;
004: import java.util.*;
005:
006: import abbot.tester.*;
007: import abbot.util.AWT;
008:
009: /** Encapsulate an action. Usage:<br>
010: * <blockquote><code>
011: * <action method="..." args="..."><br>
012: * <action method="..." args="component_id[,...]" class="..."><br>
013: * </code></blockquote>
014: * An Action reproduces a user semantic action (such as a mouse click, menu
015: * selection, or drag/drop action) on a particular component. The id of the
016: * component being operated on must be the first argument, and the class of
017: * that component must be identified by the class tag if the action is not
018: * provided by the base {@link abbot.tester.ComponentTester} class<p>
019: * Note that the method name is the name of the actionXXX method,
020: * e.g. to click a button (actionClick on
021: * AbstractButtonTester), the XML would appear thus:<p>
022: * <blockquote><code>
023: * <action method="actionClick" args="My Button" class=javax.swing.AbstractButton><br>
024: * </code></blockquote>
025: * Note that if the first argument is a Component, the class tag is required.
026: * Note also that the specified class is the <i>tested</i> class, not the
027: * target class for the method invocation.
028: * <p>
029: * The target class for the method invocation is always a
030: * ComponentTester-derived class.
031: */
032: // Any reason for the tested class to be saved and not the target class? the
033: // tester class gets looked up dynamically, but is there any reason that would
034: // be required?
035: // The component reference class should be used; that way the component can
036: // change class w/o affecting the action; also the action class should *not*
037: // be saved, and the component tester looked up dynamically.
038: public class Action extends Call {
039:
040: private static final String USAGE = "<action method=\"...\" args=\"...\" [class=\"...\"]/>";
041:
042: // Account for deprecated methods which use String representations of
043: // modifier masks.
044: private static Set stringModifierMethods = new HashSet(Arrays
045: .asList(new String[] { "actionKeyStroke", "actionKeyPress",
046: "actionKeyRelease" }));
047:
048: private static Set optionalFocusMethods = new HashSet(Arrays
049: .asList(new String[] { "actionKeyStroke", "actionKeyPress",
050: "actionKeyRelease", "actionKeyString", }));
051:
052: private static final String DEFAULT_CLASS_NAME = "java.awt.Component";
053:
054: /** Provide a default value for the target class name, so that the Call
055: * parent class won't choke.
056: */
057: private static Map patchAttributes(Map map) {
058: if (map.get(TAG_CLASS) == null) {
059: map.put(TAG_CLASS, DEFAULT_CLASS_NAME);
060: }
061: return map;
062: }
063:
064: public Action(Resolver resolver, Map attributes) {
065: super (resolver, patchAttributes(attributes));
066: patchMethodName();
067: }
068:
069: /** Action for a method in the ComponentTester base class. */
070: public Action(Resolver resolver, String description,
071: String methodName, String[] args) {
072: super (resolver, description, DEFAULT_CLASS_NAME, methodName,
073: args);
074: patchMethodName();
075: }
076:
077: public Action(Resolver resolver, String description,
078: String methodName, String[] args, Class targetClass) {
079: super (resolver, description, targetClass.getName(), methodName,
080: args);
081: patchMethodName();
082: }
083:
084: private void patchMethodName() {
085: // account for deprecated usage
086: String mn = getMethodName();
087: if (!mn.startsWith("action"))
088: setMethodName("action" + mn);
089: }
090:
091: /** Ensure the default class name is DEFAULT_CLASS_NAME
092: * The target class <i>must</i> be a subclass of java.awt.Component.
093: */
094: public void setTargetClassName(String cn) {
095: if (cn == null || "".equals(cn))
096: cn = DEFAULT_CLASS_NAME;
097: super .setTargetClassName(cn);
098: }
099:
100: /** Return the XML tag for this step. */
101: public String getXMLTag() {
102: return TAG_ACTION;
103: }
104:
105: /** Return custom attributes for an Action. */
106: public Map getAttributes() {
107: Map map = super .getAttributes();
108: // Only save the class attribute if it's not the default
109: map.remove(TAG_CLASS);
110: if (!DEFAULT_CLASS_NAME.equals(getTargetClassName())) {
111: map.put(TAG_CLASS, getTargetClassName());
112: }
113: return map;
114: }
115:
116: /** Return the proper XML usage for this step. */
117: public String getUsage() {
118: return USAGE;
119: }
120:
121: /** Return a default description for this action. */
122: public String getDefaultDescription() {
123: // strip off "action", if it's there
124: String name = getMethodName();
125: if (name.startsWith("action"))
126: name = name.substring(6);
127: return name + getArgumentsDescription();
128: }
129:
130: public Class getTargetClass() throws ClassNotFoundException {
131: return resolveTester(getTargetClassName()).getClass();
132: }
133:
134: /** Convert the String representation of the arguments into actual
135: * arguments.
136: */
137: protected Object evaluateParameter(Method m, String param,
138: Class type) throws Exception {
139: // Convert ComponentLocation arguments
140: if (ComponentLocation.class.isAssignableFrom(type)) {
141: ComponentTester tester = (ComponentTester) getTarget(m);
142: String arg = ArgumentParser
143: .substitute(getResolver(), param);
144: return tester.parseLocation(arg);
145: }
146: // Convert virtual key codes and modifier masks into integers
147: else if ((type == int.class || type == Integer.class)
148: && (param.startsWith("VK_") || param.indexOf("_MASK") != -1)) {
149: if (param.startsWith("VK_"))
150: return new Integer(AWT.getKeyCode(param));
151: return new Integer(AWT.getModifiers(param));
152: } else {
153: return super .evaluateParameter(m, param, type);
154: }
155: }
156:
157: /** Return the target of the invocation. */
158: protected Object getTarget(Method m) throws ClassNotFoundException {
159: return resolveTester(getTargetClassName());
160: }
161:
162: /** Remove deprecated methods from those looked up. */
163: protected Method[] resolveMethods(String name, Class cls,
164: Class returnType) throws NoSuchMethodException {
165: Method[] methods = super .resolveMethods(name, cls, returnType);
166: if (stringModifierMethods.contains(name)) {
167: // still have some key methods hanging around which expect a
168: // string representation of the VK_ code and/or modifiers.
169: // ignore them here.
170: ArrayList list = new ArrayList(Arrays.asList(methods));
171: for (int i = 0; i < methods.length; i++) {
172: Class[] ptypes = methods[i].getParameterTypes();
173: for (int j = 0; j < ptypes.length; j++) {
174: if (ptypes[j] == String.class) {
175: list.remove(methods[i]);
176: break;
177: }
178: }
179: }
180: methods = (Method[]) list.toArray(new Method[list.size()]);
181: }
182: return methods;
183: }
184:
185: /** Resolve the method name into its final form. */
186: public Method getMethod() throws ClassNotFoundException,
187: NoSuchMethodException {
188: return resolveMethod(getMethodName(), getTargetClass(),
189: void.class);
190: }
191:
192: protected Method disambiguateMethod(Method[] methods) {
193: // Try to find the right one by examining some of the parameters
194: // Nothing fancy, just explicitly picks between the variants.
195: if (methods.length == 2) {
196: // pick between key action variants
197: if (optionalFocusMethods.contains(methods[0].getName())) {
198: Class[] params = methods[0].getParameterTypes();
199: Method kcMethod, crefMethod;
200: if (params[0] == int.class) {
201: kcMethod = methods[0];
202: crefMethod = methods[1];
203: } else {
204: kcMethod = methods[1];
205: crefMethod = methods[0];
206: }
207: String[] args = getArguments();
208: if (args.length > 0 && args[0].startsWith("VK_")) {
209: return kcMethod;
210: }
211: return crefMethod;
212: }
213: }
214: return super.disambiguateMethod(methods);
215: }
216: }
|