    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 o, 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(o.getClass()) && tkey.equals(key)) {
    075                try {
    076                    return execute(o, 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            // if no values are being passed into the vararg
    131            if (actual.length == index) {
    132                // create an empty array of the expected type
    133                actual = new Object[]{Array.newInstance(type, 0)};
    134            } else if (actual.length == index + 1) {
    135                // if one value is being passed into the vararg
    136                // make sure the last arg is an array of the expected type
    137                if (MethodKey.isInvocationConvertible(type,
    138                        actual[index].getClass(),
    139                        false)) {
    140                    // create a 1-length array to hold and replace the last param
    141                    Object lastActual = Array.newInstance(type, 1);
    142                    Array.set(lastActual, 0, actual[index]);
    143                    actual[index] = lastActual;
    144                }
    145            } else if (actual.length > index + 1) {
    146                // if multiple values are being passed into the vararg
    147                // put the last and extra actual in an array of the expected type
    148                int size = actual.length - index;
    149                Object lastActual = Array.newInstance(type, size);
    150                for (int i = 0; i < size; i++) {
    151                    Array.set(lastActual, i, actual[index + i]);
    152                }
    153
    154                // put all into a new actual array of the appropriate size
    155                Object[] newActual = new Object[index + 1];
    156                for (int i = 0; i < index; i++) {
    157                    newActual[i] = actual[i];
    158                }
    159                newActual[index] = lastActual;
    160
    161                // replace the old actual array
    162                actual = newActual;
    163            }
    164            return actual;
    165        }
    166
    167       /**
    168         * Determines if a method can accept a variable number of arguments.
    169         * @param m a the method to check
    170         * @return true if method is vararg, false otherwise
    171         */
    172        private static boolean isVarArgMethod(java.lang.reflect.Method m) {
    173            Class<?>[] formal = m.getParameterTypes();
    174            if (formal == null || formal.length == 0) {
    175                return false;
    176            } else {
    177                Class<?> last = formal[formal.length - 1];
    178                // if the last arg is an array, then
    179                // we consider this a varargs method
    180                return last.isArray();
    181            }
    182        }
    183    }
    184
    185