    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     */
    017    package org.apache.commons.jexl2.internal;
    018
    019    import java.lang.reflect.Array;
    020    import java.lang.reflect.InvocationTargetException;
    021    import org.apache.commons.jexl2.internal.introspection.MethodKey;
    022
    023    /**
    024     * Specialized executor to invoke a method on an object.
    025     * @since 2.0
    026     */
    027    public final class MethodExecutor extends AbstractExecutor.Method {
    028        /** Whether this method handles varargs. */
    029        private final boolean isVarArgs;
    030        /**
    031         * Creates a new instance.
    032         * @param is the introspector used to discover the method
    033         * @param obj the object to find the method in
    034         * @param name the method name
    035         * @param args the method arguments
    036         */
    037        public MethodExecutor(Introspector is, Object obj, String name, Object[] args) {
    038            super(obj.getClass(), discover(is, obj, name, args));
    039            isVarArgs = method != null && isVarArgMethod(method);
    040        }
    041
    042        /**
    043         * Invokes the method to be executed.
    044         * @param o the object to invoke the method upon
    045         * @param args the method arguments
    046         * @return the result of the method invocation
    047         * @throws IllegalAccessException Method is inaccessible.
    048         * @throws InvocationTargetException Method body throws an exception.
    049         */
    050        @Override
    051        public Object execute(Object o, Object[] args)
    052            throws IllegalAccessException, InvocationTargetException  {
    053            if (isVarArgs) {
    054                Class<?>[] formal = method.getParameterTypes();
    055                int index = formal.length - 1;
    056                Class<?> type = formal[index].getComponentType();
    057                if (args.length >= index) {
    058                    args = handleVarArg(type, index, args);
    059                }
    060            }
    061            if (method.getDeclaringClass() == ArrayListWrapper.class && o.getClass().isArray()) {
    062                return method.invoke(new ArrayListWrapper(o), args);
    063            } else {
    064                return method.invoke(o, args);
    065            }
    066        }
    067
    068        /** {@inheritDoc} */
    069        @Override
    070        public Object tryExecute(String name, Object obj, Object[] args) {
    071            MethodKey tkey = new MethodKey(name, args);
    072            // let's assume that invocation will fly if the declaring class is the
    073            // same and arguments have the same type
    074            if (objectClass.equals(obj.getClass()) && tkey.equals(key)) {
    075                try {
    076                    return execute(obj, args);
    077                } catch (InvocationTargetException xinvoke) {
    078                    return TRY_FAILED; // fail
    079                } catch (IllegalAccessException xill) {
    080                    return TRY_FAILED;// fail
    081                }
    082            }
    083            return TRY_FAILED;
    084        }
    085
    086
    087        /**
    088         * Discovers a method for a {@link MethodExecutor}.
    089         * <p>
    090         * If the object is an array, an attempt will be made to find the
    091         * method in a List (see {@link ArrayListWrapper})
    092         * </p>
    093         * <p>
    094         * If the object is a class, an attempt will be made to find the
    095         * method as a static method of that class.
    096         * </p>
    097         * @param is the introspector used to discover the method
    098         * @param obj the object to introspect
    099         * @param method the name of the method to find
    100         * @param args the method arguments
    101         * @return a filled up parameter (may contain a null method)
    102         */
    103        private static Parameter discover(Introspector is,
    104                Object obj, String method, Object[] args) {
    105            final Class<?> clazz = obj.getClass();
    106            final MethodKey key = new MethodKey(method, args);
    107            java.lang.reflect.Method m = is.getMethod(clazz, key);
    108            if (m == null && clazz.isArray()) {
    109                // check for support via our array->list wrapper
    110                m = is.getMethod(ArrayListWrapper.class, key);
    111            }
    112            if (m == null && obj instanceof Class<?>) {
    113                m = is.getMethod((Class<?>) obj, key);
    114            }
    115            return new Parameter(m, key);
    116        }
    117
    118        /**
    119         * Reassembles arguments if the method is a vararg method.
    120         * @param type   The vararg class type (aka component type
    121         *               of the expected array arg)
    122         * @param index  The index of the vararg in the method declaration
    123         *               (This will always be one less than the number of
    124         *               expected arguments.)
    125         * @param actual The actual parameters being passed to this method
    126         * @return The actual parameters adjusted for the varargs in order
    127         * to fit the method declaration.
    128         */
    129        protected Object[] handleVarArg(Class<?> type, int index, Object[] actual) {
    130            final int size = actual.length - index;
    131            // if no values are being passed into the vararg, size == 0
    132            if (size == 1) {
    133                // if one non-null value is being passed into the vararg,
    134                // make the last arg an array of the expected type
    135                if (actual[index] != null) {
    136                    // create a 1-length array to hold and replace the last argument
    137                    Object lastActual = Array.newInstance(type, 1);
    138                    Array.set(lastActual, 0, actual[index]);
    139                    actual[index] = lastActual;
    140                }
    141                // else, the vararg is null and used as is, considered as T[]
    142            } else {
    143                // if no or multiple values are being passed into the vararg,
    144                // put them in an array of the expected type
    145                Object lastActual = Array.newInstance(type, size);
    146                for (int i = 0; i < size; i++) {
    147                    Array.set(lastActual, i, actual[index + i]);
    148                }
    149
    150                // put all arguments into a new actual array of the appropriate size
    151                Object[] newActual = new Object[index + 1];
    152                System.arraycopy(actual, 0, newActual, 0, index);
    153                newActual[index] = lastActual;
    154
    155                // replace the old actual array
    156                actual = newActual;
    157            }
    158            return actual;
    159        }
    160
    161       /**
    162         * Determines if a method can accept a variable number of arguments.
    163         * @param m a the method to check
    164         * @return true if method is vararg, false otherwise
    165         */
    166        private static boolean isVarArgMethod(java.lang.reflect.Method m) {
    167            Class<?>[] formal = m.getParameterTypes();
    168            if (formal == null || formal.length == 0) {
    169                return false;
    170            } else {
    171                Class<?> last = formal[formal.length - 1];
    172                // if the last arg is an array, then
    173                // we consider this a varargs method
    174                return last.isArray();
    175            }
    176        }
    177    }
    178
    179