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