package abbot.script.swt; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Vector; import abbot.Log; import abbot.i18n.Strings; import abbot.script.InvalidScriptException; import abbot.swt.Resolver; /** Class for script steps that want to invoke a method on a class. * Subclasses may override getMethod and getTarget to customize behavior. * <blockquote><code> * <call method="..." args="..." class="..." [property="..."]><br> * </code></blockquote> * <p> * If a property is indicated, the stringified result of the call will be * stored in a property for later retrieval into any string as ${property}. */ public class Call extends Step { public static final String copyright = "Licensed Materials -- Property of IBM\n"+ "(c) Copyright International Business Machines Corporation, 2003\nUS Government "+ "Users Restricted Rights - Use, duplication or disclosure restricted by GSA "+ "ADP Schedule Contract with IBM Corp."; private String targetClassName = null; private String methodName; private String[] args; private String propertyName = null; private static final String USAGE = "<call class=\"...\" method=\"...\" args=\"...\" [property=\"...\"]/>"; public Call(Resolver resolver, HashMap attributes) { super(resolver, attributes); setMethodName((String)attributes.get(TAG_METHOD)); setTargetClassName((String)attributes.get(TAG_CLASS)); String argList = (String)attributes.get(TAG_ARGS); if (argList == null) argList = ""; args = ArgumentParser.parseArgumentList(argList); } public Call(Resolver resolver, String description, String className, String methodName, String[] args) { super(resolver, description); this.targetClassName = className; this.methodName = methodName; this.args = args != null ? args : new String[0]; } protected String getDefaultDescription() { return getMethodName() + "(" + getEncodedArguments() + ")"; } public String getUsage() { return USAGE; } public String getXMLTag() { return TAG_CALL; } /** Convert our argument vector into a single String. */ public String getEncodedArguments() { String argList = ""; for (int i=0;i < args.length;i++) { if (i != 0) argList += ","; argList += encode(args[i]); } return argList; } public void setArguments(String argList) { args = ArgumentParser.parseArgumentList(argList); } public void setMethodName(String mn) { if (mn == null) usage(Strings.get("MethodNameMissing")); methodName = mn; } /** Method name to save in script. */ public String getMethodName() { return methodName; } public String getTargetClassName() { return targetClassName; } public void setTargetClassName(String cn) { if (cn == null) usage(Strings.get("ClassNameMissing")); targetClassName = cn; } /** Attributes to save in script. FIXME use a hash table */ public HashMap getAttributes() { HashMap map = super.getAttributes(); map.put(TAG_CLASS, getTargetClassName()); map.put(TAG_METHOD, getMethodName()); if (args.length != 0) { map.put(TAG_ARGS, getEncodedArguments()); } return map; } /** Return the arguments as an array of String. */ public String[] getArgs() { return args; } protected void runStep() throws Throwable { invoke(); } /** Deferred evaluation of arguments allows us to refer to components that * don't necessarily exist when the script is read in. */ // NOTE: the default invocation is expected to be performed on a // ComponentTester target. protected Object invoke() throws Throwable { try { Method m = getMethod(); Log.debug("Invoking " + m + " with " + getEncodedArguments()); Object[] params = (Object[])ArgumentParser.eval((Resolver)getResolver(), (String[])args, (Class[])m.getParameterTypes()); return m.invoke(getTarget(), params); } catch(java.lang.reflect.InvocationTargetException ite) { throw ite.getTargetException(); } } /** Return the method to be used for invocation. The concrete * implementation of this method should invoke the method * resolveMethod with the appropriate arguments. */ protected Method getMethod() throws InvalidScriptException { return resolveMethod(getMethodName(), getTargetClass(), null); } /** Get the class of the target of the method invocation. This is public * to provide editors access to the class being used (for example, * providing a menu of all available methods). */ public Class getTargetClass() throws InvalidScriptException { return resolveClass(getTargetClassName()); } /** Return the target of the invocation. The default implementation * always returns null for static methods; it will attempt to instantiate * a target for non-static methods. */ protected Object getTarget() throws Throwable { Method m = getMethod(); if ((m.getModifiers() & Modifier.STATIC) == 0) { try { return getTargetClass().newInstance(); } catch(Exception e) { setScriptError(new InvalidScriptException("Can't create an object instance of class " + getTargetClassName() + " for non-static method " + m.getName())); } } return null; } /** Look up the given method name in the given class. */ protected Method resolveMethod(String name, Class cls, Class returnType) { Method method = null; // use getDeclaredMethods to include class methods Method[] mlist = cls.getMethods(); Vector found = new Vector(); for (int i=0;i < mlist.length;i++) { Method m = mlist[i]; Class[] params = m.getParameterTypes(); if (m.getName().equals(name) && params.length == args.length && (returnType == null || m.getReturnType().equals(returnType))) { found.add(m); } } if (found.size() == 0) { throw new IllegalArgumentException(Strings.get("NoMatchingMethod", new Object[] { name, (returnType == null ? "void" : returnType.toString()), String.valueOf(args.length), cls })); } else if (found.size() != 1) { throw new IllegalArgumentException(Strings.get("MultipleMethods", new Object[] { name, cls })); } Log.debug("found '" + name + "' in " + cls); return (Method)found.get(0); } static String encode(String arg) { if (arg == null) return "(null)"; arg = ArgumentParser.replace(arg, "\\,", "--ESCAPED-COMMA--"); arg = ArgumentParser.replace(arg, ",", "\\,"); return ArgumentParser.replace(arg, "--ESCAPED-COMMA--", "\\,"); } }