001: package abbot.script;
002:
003: import java.lang.reflect.*;
004: import java.util.*;
005:
006: import abbot.Log;
007: import abbot.i18n.Strings;
008:
009: /** Class for script steps that want to invoke a method on a class.
010: * Subclasses may override getMethod and getTarget to customize behavior.
011: * <blockquote><code>
012: * <call method="..." args="..." class="..."><br>
013: * </code></blockquote>
014: */
015: public class Call extends Step {
016: private String targetClassName = null;
017: private String methodName;
018: private String[] args;
019:
020: private static final String USAGE = "<call class=\"...\" method=\"...\" args=\"...\" [property=\"...\"]/>";
021:
022: public Call(Resolver resolver, Map attributes) {
023: super (resolver, attributes);
024: methodName = (String) attributes.get(TAG_METHOD);
025: if (methodName == null)
026: usage(Strings.get("call.method_missing"));
027: targetClassName = (String) attributes.get(TAG_CLASS);
028: if (targetClassName == null)
029: usage(Strings.get("call.class_missing"));
030: String argList = (String) attributes.get(TAG_ARGS);
031: if (argList == null)
032: argList = "";
033: args = ArgumentParser.parseArgumentList(argList);
034: }
035:
036: public Call(Resolver resolver, String description,
037: String className, String methodName, String[] args) {
038: super (resolver, description);
039: this .targetClassName = className;
040: this .methodName = methodName;
041: this .args = args != null ? args : new String[0];
042: }
043:
044: public String getDefaultDescription() {
045: return getMethodName() + getArgumentsDescription();
046: }
047:
048: public String getUsage() {
049: return USAGE;
050: }
051:
052: public String getXMLTag() {
053: return TAG_CALL;
054: }
055:
056: /** Convert our argument vector into a single String. */
057: public String getEncodedArguments() {
058: return ArgumentParser.encodeArguments(args);
059: }
060:
061: protected String getArgumentsDescription() {
062: return "("
063: + ArgumentParser.replace(getEncodedArguments(),
064: ArgumentParser.ESC_COMMA, ",") + ")";
065: }
066:
067: /** Set the String representation of the arguments for this Call step. */
068: public void setArguments(String[] args) {
069: if (args == null)
070: args = new String[0];
071: this .args = args;
072: }
073:
074: /** Designate the arguments for this Call step. The format of this String
075: is a comma-separated list of String representations. See the
076: abbot.parsers package for supported String representations.
077: <p>
078: @see ArgumentParser#parseArgumentList for a description of the format.
079: @see #setArguments(String[]) for the preferred method of indicating
080: the argument list.
081: */
082: public void setArguments(String encodedArgs) {
083: if (encodedArgs == null)
084: args = new String[0];
085: else
086: args = ArgumentParser.parseArgumentList(encodedArgs);
087: }
088:
089: public void setMethodName(String mn) {
090: if (mn == null) {
091: throw new NullPointerException(
092: "Method name may not be null");
093: }
094: methodName = mn;
095: }
096:
097: /** Method name to save in script. */
098: public String getMethodName() {
099: return methodName;
100: }
101:
102: public String getTargetClassName() {
103: return targetClassName;
104: }
105:
106: public void setTargetClassName(String cn) {
107: if (cn == null)
108: usage(Strings.get("call.class_missing"));
109: targetClassName = cn;
110: }
111:
112: /** Attributes to save in script. */
113: public Map getAttributes() {
114: Map map = super .getAttributes();
115: map.put(TAG_CLASS, getTargetClassName());
116: map.put(TAG_METHOD, getMethodName());
117: if (args.length != 0) {
118: map.put(TAG_ARGS, getEncodedArguments());
119: }
120: return map;
121: }
122:
123: /** Return the arguments as an array of String.
124: @deprecated use getArguments().
125: */
126: public String[] getArgs() {
127: return getArguments();
128: }
129:
130: /** Return the arguments as an array of String. */
131: public String[] getArguments() {
132: return args;
133: }
134:
135: protected void runStep() throws Throwable {
136: try {
137: invoke();
138: } catch (Exception e) {
139: Log.debug(e);
140: throw e;
141: }
142: }
143:
144: protected Object evaluateParameter(Method m, String param,
145: Class type) throws Exception {
146: return ArgumentParser.eval(getResolver(), param, type);
147: }
148:
149: /** Convert the String representation of the arguments into actual
150: * arguments.
151: */
152: protected Object[] evaluateParameters(Method m, String[] params)
153: throws Exception {
154: Object[] args = new Object[params.length];
155: Class[] types = m.getParameterTypes();
156: for (int i = 0; i < args.length; i++) {
157: args[i] = evaluateParameter(m, params[i], types[i]);
158: }
159: return args;
160: }
161:
162: /** Make the target method invocation. This uses
163: <code>evaluateParameters</code> to convert the String representation
164: of the arguments into actual arguments.
165: Tries all matching methods of N arguments.
166: */
167: protected Object invoke() throws Throwable {
168: boolean retried = false;
169: Method[] m = getMethods();
170: for (int i = 0; i < m.length; i++) {
171: try {
172: Object[] params = evaluateParameters(m[i], args);
173: try {
174: Object target = getTarget(m[i]);
175: Log.debug("Invoking " + m[i] + " on " + target
176: + getEncodedArguments() + "'");
177: if (target != null
178: && !m[i]
179: .getDeclaringClass()
180: .isAssignableFrom(target.getClass())) {
181: // If the class loader mismatches, try to resolve it
182: if (retried) {
183: String msg = "Class loader mismatch? target "
184: + target.getClass()
185: .getClassLoader()
186: + " vs. method "
187: + m[i].getDeclaringClass()
188: .getClassLoader();
189: throw new IllegalArgumentException(msg);
190: }
191: retried = true;
192: m = resolveMethods(m[i].getName(), target
193: .getClass(), null);
194: i = -1;
195: continue;
196: }
197: if ((m[i].getModifiers() & Modifier.PUBLIC) == 0
198: || (m[i].getDeclaringClass().getModifiers() & Modifier.PUBLIC) == 0) {
199: Log
200: .debug("Bypassing compiler access restrictions on "
201: + "method " + m[i]);
202: m[i].setAccessible(true);
203: }
204: return m[i].invoke(getTarget(m[i]), params);
205: } catch (java.lang.reflect.InvocationTargetException ite) {
206: throw ite.getTargetException();
207: }
208: } catch (IllegalArgumentException e) {
209: if (i == m.length - 1)
210: throw e;
211: }
212: }
213: throw new IllegalArgumentException("Can't invoke method "
214: + m[0].getName());
215: }
216:
217: /** Return matching methods to be used for invocation. */
218: public Method getMethod() throws ClassNotFoundException,
219: NoSuchMethodException {
220: return resolveMethod(getMethodName(), getTargetClass(), null);
221: }
222:
223: /** Return matching methods to be used for invocation. */
224: protected Method[] getMethods() throws ClassNotFoundException,
225: NoSuchMethodException {
226: return resolveMethods(getMethodName(), getTargetClass(), null);
227: }
228:
229: /** Get the class of the target of the method invocation. This is public
230: * to provide editors access to the class being used (for example,
231: * providing a menu of all available methods).
232: */
233: public Class getTargetClass() throws ClassNotFoundException {
234: return resolveClass(getTargetClassName());
235: }
236:
237: /** Return the target of the invocation. The default implementation
238: * always returns null for static methods; it will attempt to instantiate
239: * a target for non-static methods.
240: */
241: protected Object getTarget(Method m) throws Throwable {
242: if ((m.getModifiers() & Modifier.STATIC) == 0) {
243: try {
244: return getTargetClass().newInstance();
245: } catch (Exception e) {
246: setScriptError(new InvalidScriptException(
247: "Can't create an object instance of class "
248: + getTargetClassName()
249: + " for non-static method "
250: + m.getName()));
251: }
252: }
253: return null;
254: }
255:
256: /** Look up all methods in the given class with the given name and return
257: type, having the number of arguments in this step.
258: @see #getArguments()
259: @throws NoSuchMethodException if no matching method is found
260: */
261: protected Method[] resolveMethods(String name, Class cls,
262: Class returnType) throws NoSuchMethodException {
263: // use getDeclaredMethods to include class methods
264: Log.debug("Resolving methods on " + cls);
265: Method[] mlist = cls.getMethods();
266: ArrayList found = new ArrayList();
267: for (int i = 0; i < mlist.length; i++) {
268: Method m = mlist[i];
269: Class[] params = m.getParameterTypes();
270: if (m.getName().equals(name)
271: && params.length == args.length
272: && (returnType == null || m.getReturnType().equals(
273: returnType))) {
274: found.add(m);
275: }
276: }
277: if (found.size() == 0) {
278: throw new NoSuchMethodException(Strings.get(
279: "call.no_matching_method", new Object[] {
280: name,
281: (returnType == null ? "*" : returnType
282: .toString()),
283: String.valueOf(args.length), cls }));
284: }
285:
286: // TODO Now sort according to restrictiveness of method arguments
287: Method[] list = (Method[]) found.toArray(new Method[found
288: .size()]);
289: //Arrays.sort(list);
290:
291: return list;
292: }
293:
294: /** Look up the given method name in the given class with the requested
295: return type, having the number of arguments in this step.
296: @throws IllegalArgumentException if not exactly one match exists
297: @see #getArguments()
298: */
299: protected Method resolveMethod(String name, Class cls,
300: Class returnType) throws NoSuchMethodException {
301: Method[] methods = resolveMethods(name, cls, returnType);
302: if (methods.length != 1) {
303: return disambiguateMethod(methods);
304: }
305: return methods[0];
306: }
307:
308: /** Try to distinguish betwenn the given methods.
309: @throws IllegalArgumentException indicating the appropriate target
310: method can't be distinguished.
311: */
312: protected Method disambiguateMethod(Method[] methods) {
313: String msg = Strings.get("call.multiple_methods", new Object[] {
314: methods[0].getName(), methods[0].getDeclaringClass() });
315: throw new IllegalArgumentException(msg);
316: }
317: }
|