    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,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.jexl3.internal.introspection;
    018
    019import org.apache.commons.jexl3.JexlEngine;
    020import java.lang.reflect.Array;
    021import java.lang.reflect.InvocationTargetException;
    022import org.apache.commons.jexl3.JexlException;
    023
    024/**
    025 * Specialized executor to invoke a method on an object.
    026 * @since 2.0
    027 */
    028public final class MethodExecutor extends AbstractExecutor.Method {
    029    /** If this method is a vararg method, vaStart is the last argument index. */
    030    private final int vaStart;
    031    /** If this method is a vararg method, vaClass is the component type of the vararg array. */
    032    private final Class<?> vaClass;
    033
    034    /**
    035     * Discovers a {@link MethodExecutor}.
    036     * <p>
    037     * If the object is an array, an attempt will be made to find the
    038     * method in a List (see {@link ArrayListWrapper})
    039     * </p>
    040     * <p>
    041     * If the object is a class, an attempt will be made to find the
    042     * method as a static method of that class.
    043     * </p>
    044     * @param is the introspector used to discover the method
    045     * @param obj the object to introspect
    046     * @param method the name of the method to find
    047     * @param args the method arguments
    048     * @return a filled up parameter (may contain a null method)
    049     */
    050    public static MethodExecutor discover(final Introspector is, final Object obj, final String method, final Object[] args) {
    051        final Class<?> clazz = obj.getClass();
    052        final MethodKey key = new MethodKey(method, args);
    053        java.lang.reflect.Method m = is.getMethod(clazz, key);
    054        if (m == null && clazz.isArray()) {
    055            // check for support via our array->list wrapper
    056            m = is.getMethod(ArrayListWrapper.class, key);
    057        }
    058        if (m == null && obj instanceof Class<?>) {
    059            m = is.getMethod((Class<?>) obj, key);
    060        }
    061        return m == null? null : new MethodExecutor(clazz, m, key);
    062    }
    063
    064    /**
    065     * Creates a new instance.
    066     * @param c the class this executor applies to
    067     * @param m the method
    068     * @param k the MethodKey
    069     */
    070    private MethodExecutor(final Class<?> c, final java.lang.reflect.Method m, final MethodKey k) {
    071        super(c, m, k);
    072        int vastart = -1;
    073        Class<?> vaclass = null;
    074        if (MethodKey.isVarArgs(method)) {
    075            // if the last parameter is an array, the method is considered as vararg
    076            final Class<?>[] formal = method.getParameterTypes();
    077            vastart = formal.length - 1;
    078            vaclass = formal[vastart].getComponentType();
    079        }
    080        vaStart = vastart;
    081        vaClass = vaclass;
    082    }
    083
    084    @Override
    085    public Object invoke(final Object o, Object... args) throws IllegalAccessException, InvocationTargetException {
    086        if (vaClass != null && args != null) {
    087            args = handleVarArg(args);
    088        }
    089        if (method.getDeclaringClass() == ArrayListWrapper.class && o.getClass().isArray()) {
    090            return method.invoke(new ArrayListWrapper(o), args);
    091        }
    092        return method.invoke(o, args);
    093    }
    094
    095    @Override
    096    public Object tryInvoke(final String name, final Object obj, final Object... args) {
    097        final MethodKey tkey = new MethodKey(name, args);
    098        // let's assume that invocation will fly if the declaring class is the
    099        // same and arguments have the same type
    100        if (objectClass.equals(obj.getClass()) && tkey.equals(key)) {
    101            try {
    102                return invoke(obj, args);
    103            } catch (IllegalAccessException | IllegalArgumentException xill) {
    104                return TRY_FAILED;// fail
    105            } catch (final InvocationTargetException xinvoke) {
    106                throw JexlException.tryFailed(xinvoke); // throw
    107            }
    108        }
    109        return JexlEngine.TRY_FAILED;
    110    }
    111
    112
    113    /**
    114     * Reassembles arguments if the method is a vararg method.
    115     * @param actual The actual arguments being passed to this method
    116     * @return The actual parameters adjusted for the varargs in order
    117     * to fit the method declaration.
    118     */
    119    @SuppressWarnings("SuspiciousSystemArraycopy")
    120    private Object[] handleVarArg(Object[] actual) {
    121        final Class<?> vaclass = vaClass;
    122        final int vastart = vaStart;
    123        // variable arguments count
    124        final int varargc = actual.length - vastart;
    125        // if no values are being passed into the vararg, size == 0
    126        if (varargc == 1) {
    127            // if one non-null value is being passed into the vararg,
    128            // and that arg is not the sole argument and not an array of the expected type,
    129            // make the last arg an array of the expected type
    130            if (actual[vastart] != null) {
    131                final Class<?> aclazz = actual[vastart].getClass();
    132                if (!aclazz.isArray() || !vaclass.isAssignableFrom(aclazz.getComponentType())) {
    133                    // create a 1-length array to hold and replace the last argument
    134                    final Object lastActual = Array.newInstance(vaclass, 1);
    135                    Array.set(lastActual, 0, actual[vastart]);
    136                    actual[vastart] = lastActual;
    137                }
    138            }
    139            // else, the vararg is null and used as is, considered as T[]
    140        } else {
    141            // if no or multiple values are being passed into the vararg,
    142            // put them in an array of the expected type
    143            final Object varargs = Array.newInstance(vaclass, varargc);
    144            System.arraycopy(actual, vastart, varargs, 0, varargc);
    145            // put all arguments into a new actual array of the appropriate size
    146            final Object[] newActual = new Object[vastart + 1];
    147            System.arraycopy(actual, 0, newActual, 0, vastart);
    148            newActual[vastart] = varargs;
    149            // replace the old actual array
    150            actual = newActual;
    151        }
    152        return actual;
    153    }
    154}
    155
    156