/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.jorphan.reflect;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.jorphan.util.JMeterError;
import org.apache.log.Logger;
/**
* Implements function call-backs.
*
* Functors may be defined for instance objects or classes.
*
* The method is created on first use, which allows the invokee (class or instance)
* to be omitted from the constructor.
*
* The class name takes precedence over the instance.
*
* If a functor is created with a particular instance, then that is used for all future calls;
* if an object is provided, it is ignored.
* This allows easy override of the table model behaviour.
*
* If an argument list is provided in the constructor, then that is ignored in subsequent invoke() calls.
*
* Usage:
* f = new Functor("methodName")
* o = f.invoke(object) - OR -
* o = f.invoke(object,params)
*
* f2 = new Functor(object,"methodName");
* o = f2.invoke() - OR -
* o = f2.invoke(params)
*
* f3 = new Functor(class,"methodName");
* o = f3.invoke(object) - will be ignored
* o = f3.invoke() - OR -
* o = f3.invoke(params)
* o = f3.invoke(object,params) - object will be ignored
*
*/
public class Functor {
private static final Logger log = LoggingManager.getLoggerForClass();
/*
* If non-null, then any object provided to invoke() is ignored.
*/
private final Object invokee;
/*
* Class to be used to create the Method.
* Will be non-null if either Class or Object was provided during construction.
*
* Can be used instead of invokee, e.g. when using interfaces.
*/
private final Class clazz;
// Methondname must always be provided.
private final String methodName;
/*
* If non-null, then any argument list passed to invoke() will be ignored.
*/
private Object[] args;
/*
* Argument types used to create the method.
* May be provided explicitly, or derived from the constructor argument list.
*/
private final Class[] types;
/*
* This depends on the class or invokee and either args or types;
* it is set once by doCreateMethod(), which must be the only method to access it.
*/
private Method methodToInvoke;
Functor(){
throw new IllegalArgumentException("Must provide at least one argument");
}
/**
* Create a functor with the invokee and a method name.
*
* The invokee will be used in all future invoke calls.
*
* @param _invokee object on which to invoke the method
* @param _methodName method name
*/
public Functor(Object _invokee, String _methodName) {
this(null, _invokee, _methodName, null, null);
}
/**
* Create a functor from class and method name.
* This is useful for methods defined in interfaces.
*
* The actual invokee must be provided in all invoke() calls,
* and must be an instance of the class.
*
* @param _clazz class to be used
* @param _methodName method name
*/
public Functor(Class _clazz, String _methodName) {
this(_clazz, null, _methodName, null, null);
}
/**
* Create a functor with the invokee, method name, and argument class types.
*
* The invokee will be ignored in any invoke() calls.
*
* @param _invokee object on which to invoke the method
* @param _methodName method name
* @param _types
*/
public Functor(Object _invokee, String _methodName, Class[] _types) {
this(null, _invokee, _methodName, null, _types);
}
/**
* Create a functor with the class, method name, and argument class types.
*
* Subsequent invoke() calls must provide the appropriate ivokee object.
*
* @param _clazz the class in which to find the method
* @param _methodName method name
* @param _types
*/
public Functor(Class _clazz, String _methodName, Class[] _types) {
this(_clazz, null, _methodName, null, _types);
}
/**
* Create a functor with just the method name.
*
* The invokee and any parameters must be provided in all invoke() calls.
*
* @param _methodName method name
*/
public Functor(String _methodName) {
this(null, null, _methodName, null, null);
}
/**
* Create a functor with the method name and argument class types.
*
* The invokee must be provided in all invoke() calls
*
* @param _methodName method name
* @param _types parameter types
*/
public Functor(String _methodName, Class[] _types) {
this(null, null, _methodName, null, _types);
}
/**
* Create a functor with an invokee, method name, and argument values.
*
* The invokee will be ignored in any invoke() calls.
*
* @param _invokee object on which to invoke the method
* @param _methodName method name
* @param _args arguments to be passed to the method
*/
public Functor(Object _invokee, String _methodName, Object[] _args) {
this(null, _invokee, _methodName, _args, null);
}
/**
* Create a functor from method name and arguments.
*
* The class will be determined from the first invoke call.
* All invoke calls must include a target object;
* which must be of the same type as the initial invokee.
*
* @param _methodName method name
* @param _args
*/
public Functor(String _methodName, Object[] _args) {
this(null, null, _methodName, _args, null);
}
/**
* Create a functor from various different combinations of parameters.
*
* @param _clazz class containing the method
* @param _invokee invokee to use for the method call
* @param _methodName the method name (required)
* @param _args arguments to be used
* @param _types types of arguments to be used
*
* @throws IllegalArgumentException if:
* - methodName is null
* - both class and invokee are specified
* - both arguments and types are specified
*/
private Functor(Class _clazz, Object _invokee, String _methodName, Object[] _args, Class[] _types) {
if (_methodName == null){
throw new IllegalArgumentException("Methodname must not be null");
}
if (_clazz != null && _invokee != null){
throw new IllegalArgumentException("Cannot provide both Class and Object");
}
if (_args != null && _types != null){
throw new IllegalArgumentException("Cannot provide both arguments and argument types");
}
// If class not provided, default to invokee class, else null
this.clazz = _clazz != null ? _clazz : (_invokee != null ? _invokee.getClass() : null);
this.invokee = _invokee;
this.methodName = _methodName;
this.args = _args;
// If types not provided, default to argument types, else null
this.types = _types != null ? _types : (_args != null ? _getTypes(_args) : null);
}
//////////////////////////////////////////
/*
* Low level invocation routine.
*
* Should only be called after any defaults have been applied.
*
*/
private Object doInvoke(Class _class, Object _invokee, Object[] _args) {
Class[] argTypes = getTypes(_args);
try {
Method method = doCreateMethod(_class , argTypes);
if (method == null){
final String message = "Can't find method "
+_class.getName()+"#"+methodName+typesToString(argTypes);
log.error(message, new Throwable());
throw new JMeterError(message);
}
return method.invoke(_invokee, _args);
} catch (Exception e) {
final String message = "Trouble functing: "
+_class.getName()
+"."+methodName+"(...) : "
+" invokee: "+_invokee
+" "+e.getMessage();
log.warn(message, e);
throw new JMeterError(message,e);
}
}
/**
* Invoke a Functor, which must have been created with either a class name or object.
*
* @return the object if any
*/
public Object invoke() {
if (invokee == null) {
throw new IllegalStateException("Cannot call invoke() - invokee not known");
}
// If invokee was provided, then clazz has been set up
return doInvoke(clazz, invokee, getArgs());
}
/**
* Invoke the method on a given object.
*
* @param p_invokee - provides the object to call; ignored if the class or object were provided to the constructor
* @return the value
*/
public Object invoke(Object p_invokee) {
return invoke(p_invokee, getArgs());
}
/**
* Invoke the method with the provided parameters.
*
* The invokee must have been provided in the constructor.
*
* @param p_args parameters for the method
* @return the value
*/
public Object invoke(Object[] p_args) {
if (invokee == null){
throw new IllegalStateException("Invokee was not provided in constructor");
}
// If invokee was provided, then clazz has been set up
return doInvoke(clazz, invokee, args != null? args : p_args);
}
/**
* Invoke the method on the invokee with the provided parameters.
*
* The invokee must agree with the class (if any) provided at construction time.
*
* If the invokee was provided at construction time, then this invokee will be ignored.
* If actual arguments were provided at construction time, then arguments will be ignored.
*
*/
public Object invoke(Object p_invokee, Object[] p_args) {
return doInvoke(clazz != null ? clazz : p_invokee.getClass(), // Use constructor class if present
invokee != null ? invokee : p_invokee, // use invokee if provided
args != null? args : p_args);// use argumenrs if provided
}
/*
* Low-level (recursive) routine to define the method - if not already defined.
* Synchronized to protect access to methodToInvoke.
*/
private synchronized Method doCreateMethod(Class p_class, Class[] p_types) {
if (log.isDebugEnabled()){
log.debug("doCreateMethod() using "+this.toString()
+"class="
+ p_class.getName()
+ " types: " + Arrays.asList(p_types));
}
if (methodToInvoke == null) {
try {
methodToInvoke = p_class.getMethod(methodName, p_types);
} catch (Exception e) {
for (int i = 0; i < p_types.length; i++) {
Class primitive = getPrimitive(p_types[i]);
if (primitive != null) {
methodToInvoke = doCreateMethod(p_class, getNewArray(i, primitive, p_types));
if (methodToInvoke != null) {
return methodToInvoke;
}
}
Class[] interfaces = p_types[i].getInterfaces();
for (int j = 0; j < interfaces.length; j++) {
methodToInvoke = doCreateMethod(p_class,getNewArray(i, interfaces[j], p_types));
if (methodToInvoke != null) {
return methodToInvoke;
}
}
Class parent = p_types[i].getSuperclass();
if (parent != null) {
methodToInvoke = doCreateMethod(p_class,getNewArray(i, parent, p_types));
if (methodToInvoke != null) {
return methodToInvoke;
}
}
}
}
}
return methodToInvoke;
}
/**
* Check if a read Functor method is valid.
*
* @deprecated ** for use by Unit test code only **
*
* @return true if method exists
*/
public boolean checkMethod(Object _invokee){
Method m = null;
try {
m = doCreateMethod(_invokee.getClass(), getTypes(args));
} catch (Exception e){
// ignored
}
return null != m;
}
/**
* Check if a write Functor method is valid.
*
* @deprecated ** for use by Unit test code only **
*
* @return true if method exists
*/
public boolean checkMethod(Object _invokee, Class c){
Method m = null;
try {
m = doCreateMethod(_invokee.getClass(), new Class[]{c});
} catch (Exception e){
// ignored
}
return null != m;
}
public String toString(){
StringBuffer sb = new StringBuffer(100);
if (clazz != null){
sb.append(clazz.getName());
}
if (invokee != null){
sb.append("@");
sb.append(System.identityHashCode(invokee));
}
sb.append(".");
sb.append(methodName);
typesToString(sb,types);
return sb.toString();
}
private void typesToString(StringBuffer sb,Class[] _types) {
sb.append("(");
if (_types != null){
for(int i=0; i < _types.length; i++){
if (i>0) {
sb.append(",");
}
sb.append(_types[i].getName());
}
}
sb.append(")");
}
private String typesToString(Class[] argTypes) {
StringBuffer sb = new StringBuffer();
typesToString(sb,argTypes);
return sb.toString();
}
private Class getPrimitive(Class t) {
if (t==null) {
return null;
}
if (t.equals(Integer.class)) {
return int.class;
} else if (t.equals(Long.class)) {
return long.class;
} else if (t.equals(Double.class)) {
return double.class;
} else if (t.equals(Float.class)) {
return float.class;
} else if (t.equals(Byte.class)) {
return byte.class;
} else if (t.equals(Boolean.class)) {
return boolean.class;
} else if (t.equals(Short.class)) {
return short.class;
} else if (t.equals(Character.class)) {
return char.class;
}
return null;
}
private Class[] getNewArray(int i, Class replacement, Class[] orig) {
Class[] newArray = new Class[orig.length];
for (int j = 0; j < newArray.length; j++) {
if (j == i) {
newArray[j] = replacement;
} else {
newArray[j] = orig[j];
}
}
return newArray;
}
private Class[] getTypes(Object[] _args) {
if (types == null)
{
return _getTypes(_args);
}
return types;
}
private static Class[] _getTypes(Object[] _args) {
Class[] _types;
if (_args != null) {
_types = new Class[_args.length];
for (int i = 0; i < _args.length; i++) {
_types[i] = _args[i].getClass();
}
} else {
_types = new Class[0];
}
return _types;
}
private Object[] getArgs() {
if (args == null) {
args = new Object[0];
}
return args;
}
}