blob: fb36d006d485f994e0a4064a2b3562916069f1c1 [file] [log] [blame]
/*
* 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.commons.jexl2.internal;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import org.apache.commons.jexl2.internal.introspection.MethodKey;
/**
* Specialized executor to invoke a method on an object.
* @since 2.0
*/
public final class MethodExecutor extends AbstractExecutor.Method {
/** Whether this method handles varargs. */
private final boolean isVarArgs;
/**
* Creates a new instance.
* @param is the introspector used to discover the method
* @param obj the object to find the method in
* @param name the method name
* @param args the method arguments
*/
public MethodExecutor(Introspector is, Object obj, String name, Object[] args) {
super(obj.getClass(), discover(is, obj, name, args));
isVarArgs = method != null && isVarArgMethod(method);
}
/**
* Invokes the method to be executed.
* @param o the object to invoke the method upon
* @param args the method arguments
* @return the result of the method invocation
* @throws IllegalAccessException Method is inaccessible.
* @throws InvocationTargetException Method body throws an exception.
*/
@Override
public Object execute(Object o, Object[] args)
throws IllegalAccessException, InvocationTargetException {
if (isVarArgs) {
Class<?>[] formal = method.getParameterTypes();
int index = formal.length - 1;
Class<?> type = formal[index].getComponentType();
if (args.length >= index) {
args = handleVarArg(type, index, args);
}
}
if (method.getDeclaringClass() == ArrayListWrapper.class && o.getClass().isArray()) {
return method.invoke(new ArrayListWrapper(o), args);
} else {
return method.invoke(o, args);
}
}
/** {@inheritDoc} */
@Override
public Object tryExecute(String name, Object o, Object[] args) {
MethodKey tkey = new MethodKey(name, args);
// let's assume that invocation will fly if the declaring class is the
// same and arguments have the same type
if (objectClass.equals(o.getClass()) && tkey.equals(key)) {
try {
return execute(o, args);
} catch (InvocationTargetException xinvoke) {
return TRY_FAILED; // fail
} catch (IllegalAccessException xill) {
return TRY_FAILED;// fail
}
}
return TRY_FAILED;
}
/**
* Discovers a method for a {@link MethodExecutor}.
* <p>
* If the object is an array, an attempt will be made to find the
* method in a List (see {@link ArrayListWrapper})
* </p>
* <p>
* If the object is a class, an attempt will be made to find the
* method as a static method of that class.
* </p>
* @param is the introspector used to discover the method
* @param obj the object to introspect
* @param method the name of the method to find
* @param args the method arguments
* @return a filled up parameter (may contain a null method)
*/
private static Parameter discover(Introspector is,
Object obj, String method, Object[] args) {
final Class<?> clazz = obj.getClass();
final MethodKey key = new MethodKey(method, args);
java.lang.reflect.Method m = is.getMethod(clazz, key);
if (m == null && clazz.isArray()) {
// check for support via our array->list wrapper
m = is.getMethod(ArrayListWrapper.class, key);
}
if (m == null && obj instanceof Class<?>) {
m = is.getMethod((Class<?>) obj, key);
}
return new Parameter(m, key);
}
/**
* Reassembles arguments if the method is a vararg method.
* @param type The vararg class type (aka component type
* of the expected array arg)
* @param index The index of the vararg in the method declaration
* (This will always be one less than the number of
* expected arguments.)
* @param actual The actual parameters being passed to this method
* @return The actual parameters adjusted for the varargs in order
* to fit the method declaration.
*/
protected Object[] handleVarArg(Class<?> type, int index, Object[] actual) {
// if no values are being passed into the vararg
if (actual.length == index) {
// create an empty array of the expected type
actual = new Object[]{Array.newInstance(type, 0)};
} else if (actual.length == index + 1) {
// if one value is being passed into the vararg
// make sure the last arg is an array of the expected type
if (MethodKey.isInvocationConvertible(type,
actual[index].getClass(),
false)) {
// create a 1-length array to hold and replace the last param
Object lastActual = Array.newInstance(type, 1);
Array.set(lastActual, 0, actual[index]);
actual[index] = lastActual;
}
} else if (actual.length > index + 1) {
// if multiple values are being passed into the vararg
// put the last and extra actual in an array of the expected type
int size = actual.length - index;
Object lastActual = Array.newInstance(type, size);
for (int i = 0; i < size; i++) {
Array.set(lastActual, i, actual[index + i]);
}
// put all into a new actual array of the appropriate size
Object[] newActual = new Object[index + 1];
for (int i = 0; i < index; i++) {
newActual[i] = actual[i];
}
newActual[index] = lastActual;
// replace the old actual array
actual = newActual;
}
return actual;
}
/**
* Determines if a method can accept a variable number of arguments.
* @param m a the method to check
* @return true if method is vararg, false otherwise
*/
private static boolean isVarArgMethod(java.lang.reflect.Method m) {
Class<?>[] formal = m.getParameterTypes();
if (formal == null || formal.length == 0) {
return false;
} else {
Class<?> last = formal[formal.length - 1];
// if the last arg is an array, then
// we consider this a varargs method
return last.isArray();
}
}
}