001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
013: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014: * License for the specific language governing permissions and limitations
015: * under the License.
016: *
017: */
018:
019: package org.apache.jorphan.reflect;
020:
021: import java.lang.reflect.Method;
022: import java.util.Arrays;
023:
024: import org.apache.jorphan.logging.LoggingManager;
025: import org.apache.jorphan.util.JMeterError;
026: import org.apache.log.Logger;
027:
028: /**
029: * Implements function call-backs.
030: *
031: * Functors may be defined for instance objects or classes.
032: *
033: * The method is created on first use, which allows the invokee (class or instance)
034: * to be omitted from the constructor.
035: *
036: * The class name takes precedence over the instance.
037: *
038: * If a functor is created with a particular instance, then that is used for all future calls;
039: * if an object is provided, it is ignored.
040: * This allows easy override of the table model behaviour.
041: *
042: * If an argument list is provided in the constructor, then that is ignored in subsequent invoke() calls.
043: *
044: * Usage:
045: * f = new Functor("methodName")
046: * o = f.invoke(object) - OR -
047: * o = f.invoke(object,params)
048: *
049: * f2 = new Functor(object,"methodName");
050: * o = f2.invoke() - OR -
051: * o = f2.invoke(params)
052: *
053: * f3 = new Functor(class,"methodName");
054: * o = f3.invoke(object) - will be ignored
055: * o = f3.invoke() - OR -
056: * o = f3.invoke(params)
057: * o = f3.invoke(object,params) - object will be ignored
058: *
059: */
060: public class Functor {
061: private static final Logger log = LoggingManager
062: .getLoggerForClass();
063:
064: /*
065: * If non-null, then any object provided to invoke() is ignored.
066: */
067: private final Object invokee;
068:
069: /*
070: * Class to be used to create the Method.
071: * Will be non-null if either Class or Object was provided during construction.
072: *
073: * Can be used instead of invokee, e.g. when using interfaces.
074: */
075: private final Class clazz;
076:
077: // Methondname must always be provided.
078: private final String methodName;
079:
080: /*
081: * If non-null, then any argument list passed to invoke() will be ignored.
082: */
083: private Object[] args;
084:
085: /*
086: * Argument types used to create the method.
087: * May be provided explicitly, or derived from the constructor argument list.
088: */
089: private final Class[] types;
090:
091: /*
092: * This depends on the class or invokee and either args or types;
093: * it is set once by doCreateMethod(), which must be the only method to access it.
094: */
095: private Method methodToInvoke;
096:
097: Functor() {
098: throw new IllegalArgumentException(
099: "Must provide at least one argument");
100: }
101:
102: /**
103: * Create a functor with the invokee and a method name.
104: *
105: * The invokee will be used in all future invoke calls.
106: *
107: * @param _invokee object on which to invoke the method
108: * @param _methodName method name
109: */
110: public Functor(Object _invokee, String _methodName) {
111: this (null, _invokee, _methodName, null, null);
112: }
113:
114: /**
115: * Create a functor from class and method name.
116: * This is useful for methods defined in interfaces.
117: *
118: * The actual invokee must be provided in all invoke() calls,
119: * and must be an instance of the class.
120: *
121: * @param _clazz class to be used
122: * @param _methodName method name
123: */
124: public Functor(Class _clazz, String _methodName) {
125: this (_clazz, null, _methodName, null, null);
126: }
127:
128: /**
129: * Create a functor with the invokee, method name, and argument class types.
130: *
131: * The invokee will be ignored in any invoke() calls.
132: *
133: * @param _invokee object on which to invoke the method
134: * @param _methodName method name
135: * @param _types
136: */
137: public Functor(Object _invokee, String _methodName, Class[] _types) {
138: this (null, _invokee, _methodName, null, _types);
139: }
140:
141: /**
142: * Create a functor with the class, method name, and argument class types.
143: *
144: * Subsequent invoke() calls must provide the appropriate ivokee object.
145: *
146: * @param _clazz the class in which to find the method
147: * @param _methodName method name
148: * @param _types
149: */
150: public Functor(Class _clazz, String _methodName, Class[] _types) {
151: this (_clazz, null, _methodName, null, _types);
152: }
153:
154: /**
155: * Create a functor with just the method name.
156: *
157: * The invokee and any parameters must be provided in all invoke() calls.
158: *
159: * @param _methodName method name
160: */
161: public Functor(String _methodName) {
162: this (null, null, _methodName, null, null);
163: }
164:
165: /**
166: * Create a functor with the method name and argument class types.
167: *
168: * The invokee must be provided in all invoke() calls
169: *
170: * @param _methodName method name
171: * @param _types parameter types
172: */
173: public Functor(String _methodName, Class[] _types) {
174: this (null, null, _methodName, null, _types);
175: }
176:
177: /**
178: * Create a functor with an invokee, method name, and argument values.
179: *
180: * The invokee will be ignored in any invoke() calls.
181: *
182: * @param _invokee object on which to invoke the method
183: * @param _methodName method name
184: * @param _args arguments to be passed to the method
185: */
186: public Functor(Object _invokee, String _methodName, Object[] _args) {
187: this (null, _invokee, _methodName, _args, null);
188: }
189:
190: /**
191: * Create a functor from method name and arguments.
192: *
193: * The class will be determined from the first invoke call.
194: * All invoke calls must include a target object;
195: * which must be of the same type as the initial invokee.
196: *
197: * @param _methodName method name
198: * @param _args
199: */
200: public Functor(String _methodName, Object[] _args) {
201: this (null, null, _methodName, _args, null);
202: }
203:
204: /**
205: * Create a functor from various different combinations of parameters.
206: *
207: * @param _clazz class containing the method
208: * @param _invokee invokee to use for the method call
209: * @param _methodName the method name (required)
210: * @param _args arguments to be used
211: * @param _types types of arguments to be used
212: *
213: * @throws IllegalArgumentException if:
214: * - methodName is null
215: * - both class and invokee are specified
216: * - both arguments and types are specified
217: */
218: private Functor(Class _clazz, Object _invokee, String _methodName,
219: Object[] _args, Class[] _types) {
220: if (_methodName == null) {
221: throw new IllegalArgumentException(
222: "Methodname must not be null");
223: }
224: if (_clazz != null && _invokee != null) {
225: throw new IllegalArgumentException(
226: "Cannot provide both Class and Object");
227: }
228: if (_args != null && _types != null) {
229: throw new IllegalArgumentException(
230: "Cannot provide both arguments and argument types");
231: }
232: // If class not provided, default to invokee class, else null
233: this .clazz = _clazz != null ? _clazz
234: : (_invokee != null ? _invokee.getClass() : null);
235: this .invokee = _invokee;
236: this .methodName = _methodName;
237: this .args = _args;
238: // If types not provided, default to argument types, else null
239: this .types = _types != null ? _types
240: : (_args != null ? _getTypes(_args) : null);
241: }
242:
243: //////////////////////////////////////////
244:
245: /*
246: * Low level invocation routine.
247: *
248: * Should only be called after any defaults have been applied.
249: *
250: */
251: private Object doInvoke(Class _class, Object _invokee,
252: Object[] _args) {
253: Class[] argTypes = getTypes(_args);
254: try {
255: Method method = doCreateMethod(_class, argTypes);
256: if (method == null) {
257: log.error("Can't find method " + _class
258: + typesToString(argTypes));
259: throw new JMeterError("Can't find method " + _class
260: + typesToString(argTypes));
261: }
262: return method.invoke(_invokee, _args);
263: } catch (Exception e) {
264: final String message = "Trouble functing: "
265: + _class.getName() + "." + methodName + "(...) : "
266: + " invokee: " + _invokee + " " + e.getMessage();
267: log.warn(message, e);
268: throw new JMeterError(message, e);
269: }
270: }
271:
272: /**
273: * Invoke a Functor, which must have been created with either a class name or object.
274: *
275: * @return the object if any
276: */
277: public Object invoke() {
278: if (invokee == null) {
279: throw new IllegalStateException(
280: "Cannot call invoke() - invokee not known");
281: }
282: // If invokee was provided, then clazz has been set up
283: return doInvoke(clazz, invokee, getArgs());
284: }
285:
286: /**
287: * Invoke the method on a given object.
288: *
289: * @param p_invokee - provides the object to call; ignored if the class or object were provided to the constructor
290: * @return the value
291: */
292: public Object invoke(Object p_invokee) {
293: return invoke(p_invokee, getArgs());
294: }
295:
296: /**
297: * Invoke the method with the provided parameters.
298: *
299: * The invokee must have been provided in the constructor.
300: *
301: * @param p_args parameters for the method
302: * @return the value
303: */
304: public Object invoke(Object[] p_args) {
305: if (invokee == null) {
306: throw new IllegalStateException(
307: "Invokee was not provided in constructor");
308: }
309: // If invokee was provided, then clazz has been set up
310: return doInvoke(clazz, invokee, args != null ? args : p_args);
311: }
312:
313: /**
314: * Invoke the method on the invokee with the provided parameters.
315: *
316: * The invokee must agree with the class (if any) provided at construction time.
317: *
318: * If the invokee was provided at construction time, then this invokee will be ignored.
319: * If actual arguments were provided at construction time, then arguments will be ignored.
320: *
321: */
322: public Object invoke(Object p_invokee, Object[] p_args) {
323: return doInvoke(clazz != null ? clazz : p_invokee.getClass(), // Use constructor class if present
324: invokee != null ? invokee : p_invokee, // use invokee if provided
325: args != null ? args : p_args);// use argumenrs if provided
326: }
327:
328: /*
329: * Low-level (recursive) routine to define the method - if not already defined.
330: * Synchronized to protect access to methodToInvoke.
331: */
332: private synchronized Method doCreateMethod(Class p_class,
333: Class[] p_types) {
334: if (log.isDebugEnabled()) {
335: log.debug("doCreateMethod() using " + this .toString()
336: + "class=" + p_class.getName() + " types: "
337: + Arrays.asList(p_types));
338: }
339: if (methodToInvoke == null) {
340: try {
341: methodToInvoke = p_class.getMethod(methodName, p_types);
342: } catch (Exception e) {
343: for (int i = 0; i < p_types.length; i++) {
344: Class primitive = getPrimitive(p_types[i]);
345: if (primitive != null) {
346: methodToInvoke = doCreateMethod(p_class,
347: getNewArray(i, primitive, p_types));
348: if (methodToInvoke != null)
349: return methodToInvoke;
350: }
351: Class[] interfaces = p_types[i].getInterfaces();
352: for (int j = 0; j < interfaces.length; j++) {
353: methodToInvoke = doCreateMethod(p_class,
354: getNewArray(i, interfaces[j], p_types));
355: if (methodToInvoke != null) {
356: return methodToInvoke;
357: }
358: }
359: Class parent = p_types[i].getSuperclass();
360: if (parent != null) {
361: methodToInvoke = doCreateMethod(p_class,
362: getNewArray(i, parent, p_types));
363: if (methodToInvoke != null) {
364: return methodToInvoke;
365: }
366: }
367: }
368: }
369: }
370: return methodToInvoke;
371: }
372:
373: /**
374: * Check if a read Functor method is valid.
375: *
376: * @deprecated ** for use by Unit test code only **
377: *
378: * @return true if method exists
379: */
380: public boolean checkMethod(Object _invokee) {
381: Method m = null;
382: try {
383: m = doCreateMethod(_invokee.getClass(), getTypes(args));
384: } catch (Exception e) {
385: // ignored
386: }
387: return null != m;
388: }
389:
390: /**
391: * Check if a write Functor method is valid.
392: *
393: * @deprecated ** for use by Unit test code only **
394: *
395: * @return true if method exists
396: */
397: public boolean checkMethod(Object _invokee, Class c) {
398: Method m = null;
399: try {
400: m = doCreateMethod(_invokee.getClass(), new Class[] { c });
401: } catch (Exception e) {
402: // ignored
403: }
404: return null != m;
405: }
406:
407: public String toString() {
408: StringBuffer sb = new StringBuffer(100);
409: if (clazz != null) {
410: sb.append(clazz.getName());
411: }
412: if (invokee != null) {
413: sb.append("@");
414: sb.append(System.identityHashCode(invokee));
415: }
416: sb.append(".");
417: sb.append(methodName);
418: typesToString(sb, types);
419: return sb.toString();
420: }
421:
422: private void typesToString(StringBuffer sb, Class[] _types) {
423: sb.append("(");
424: if (_types != null) {
425: for (int i = 0; i < _types.length; i++) {
426: if (i > 0)
427: sb.append(",");
428: sb.append(_types[i].getName());
429: }
430: }
431: sb.append(")");
432: }
433:
434: private String typesToString(Class[] argTypes) {
435: StringBuffer sb = new StringBuffer();
436: typesToString(sb, argTypes);
437: return sb.toString();
438: }
439:
440: private Class getPrimitive(Class t) {
441: if (t == null)
442: return null;
443: if (t.equals(Integer.class)) {
444: return int.class;
445: } else if (t.equals(Long.class)) {
446: return long.class;
447: } else if (t.equals(Double.class)) {
448: return double.class;
449: } else if (t.equals(Float.class)) {
450: return float.class;
451: } else if (t.equals(Byte.class)) {
452: return byte.class;
453: } else if (t.equals(Boolean.class)) {
454: return boolean.class;
455: } else if (t.equals(Short.class)) {
456: return short.class;
457: } else if (t.equals(Character.class)) {
458: return char.class;
459: }
460: return null;
461: }
462:
463: private Class[] getNewArray(int i, Class replacement, Class[] orig) {
464: Class[] newArray = new Class[orig.length];
465: for (int j = 0; j < newArray.length; j++) {
466: if (j == i) {
467: newArray[j] = replacement;
468: } else {
469: newArray[j] = orig[j];
470: }
471: }
472: return newArray;
473: }
474:
475: private Class[] getTypes(Object[] _args) {
476: if (types == null) {
477: return _getTypes(_args);
478: }
479: return types;
480: }
481:
482: private static Class[] _getTypes(Object[] _args) {
483: Class[] _types;
484: if (_args != null) {
485: _types = new Class[_args.length];
486: for (int i = 0; i < _args.length; i++) {
487: _types[i] = _args[i].getClass();
488: }
489: } else {
490: _types = new Class[0];
491: }
492: return _types;
493: }
494:
495: private Object[] getArgs() {
496: if (args == null) {
497: args = new Object[0];
498: }
499: return args;
500: }
501: }
|