| /* |
| * 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(); |
| } |
| } |
| } |
| |
| |