MethodExecutor.java
/*
* 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.jexl3.internal.introspection;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlException;
/**
* Specialized executor to invoke a method on an object.
* @since 2.0
*/
public final class MethodExecutor extends AbstractExecutor.Method {
/**
* Discovers 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)
*/
public static MethodExecutor discover(final Introspector is, final Object obj, final String method, final 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 m == null ? null : new MethodExecutor(clazz, m, key);
}
/** If this method is a vararg method, vaStart is the last argument index. */
private final int vaStart;
/** If this method is a vararg method, vaClass is the component type of the vararg array. */
private final Class<?> vaClass;
/**
* Creates a new instance.
* @param c the class this executor applies to
* @param m the method
* @param k the MethodKey
*/
private MethodExecutor(final Class<?> c, final java.lang.reflect.Method m, final MethodKey k) {
super(c, m, k);
int vastart = -1;
Class<?> vaclass = null;
if (MethodKey.isVarArgs(method)) {
// if the last parameter is an array, the method is considered as vararg
final Class<?>[] formal = method.getParameterTypes();
vastart = formal.length - 1;
vaclass = formal[vastart].getComponentType();
}
vaStart = vastart;
vaClass = vaclass;
}
/**
* Reassembles arguments if the method is a vararg method.
* @param args The actual arguments being passed to this method
* @return The actual parameters adjusted for the varargs in order
* to fit the method declaration.
*/
@SuppressWarnings("SuspiciousSystemArraycopy")
private Object[] handleVarArg(final Object[] args) {
final Class<?> vaclass = vaClass;
final int vastart = vaStart;
// variable arguments count
Object[] actual = args;
final int varargc = actual.length - vastart;
// if no values are being passed into the vararg, size == 0
if (varargc == 1) {
// if one non-null value is being passed into the vararg,
// and that arg is not the sole argument and not an array of the expected type,
// make the last arg an array of the expected type
if (actual[vastart] != null) {
final Class<?> aclazz = actual[vastart].getClass();
if (!aclazz.isArray() || !vaclass.isAssignableFrom(aclazz.getComponentType())) {
// create a 1-length array to hold and replace the last argument
final Object lastActual = Array.newInstance(vaclass, 1);
Array.set(lastActual, 0, actual[vastart]);
actual[vastart] = lastActual;
}
}
// else, the vararg is null and used as is, considered as T[]
} else {
// if no or multiple values are being passed into the vararg,
// put them in an array of the expected type
final Object varargs = Array.newInstance(vaclass, varargc);
System.arraycopy(actual, vastart, varargs, 0, varargc);
// put all arguments into a new actual array of the appropriate size
final Object[] newActual = new Object[vastart + 1];
System.arraycopy(actual, 0, newActual, 0, vastart);
newActual[vastart] = varargs;
// replace the old actual array
actual = newActual;
}
return actual;
}
@Override
public Object invoke(final Object o, final Object... oArgs) throws IllegalAccessException, InvocationTargetException {
Object[] args = oArgs;
if (vaClass != null && args != null) {
args = handleVarArg(args);
}
if (method.getDeclaringClass() == ArrayListWrapper.class && o.getClass().isArray()) {
return method.invoke(new ArrayListWrapper(o), args);
}
return method.invoke(o, args);
}
@Override
public Object tryInvoke(final String name, final Object obj, final Object... args) {
final 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(obj.getClass()) && tkey.equals(key)) {
try {
return invoke(obj, args);
} catch (IllegalAccessException | IllegalArgumentException xill) {
return TRY_FAILED; // fail
} catch (final InvocationTargetException xinvoke) {
throw JexlException.tryFailed(xinvoke); // throw
}
}
return JexlEngine.TRY_FAILED;
}
}