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                // and that arg is not the sole argument and not an array of the expected type,
135                // make the last arg an array of the expected type
136                if (actual[index] != null) {
137                    Class<?> aclazz = actual[index].getClass();
138                    if (!aclazz.isArray() || !aclazz.getComponentType().equals(type)) {
139                        // create a 1-length array to hold and replace the last argument
140                        Object lastActual = Array.newInstance(type, 1);
141                        Array.set(lastActual, 0, actual[index]);
142                        actual[index] = lastActual;
143                    }
144                }
145                // else, the vararg is null and used as is, considered as T[]
146            } else {
147                // if no or multiple values are being passed into the vararg,
148                // put them in an array of the expected type
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 arguments into a new actual array of the appropriate size
155                Object[] newActual = new Object[index + 1];
156                System.arraycopy(actual, 0, newActual, 0, index);
157                newActual[index] = lastActual;
158    
159                // replace the old actual array
160                actual = newActual;
161            }
162            return actual;
163        }
164    
165       /**
166         * Determines if a method can accept a variable number of arguments.
167         * @param m a the method to check
168         * @return true if method is vararg, false otherwise
169         */
170        private static boolean isVarArgMethod(java.lang.reflect.Method m) {
171            Class<?>[] formal = m.getParameterTypes();
172            if (formal == null || formal.length == 0) {
173                return false;
174            } else {
175                Class<?> last = formal[formal.length - 1];
176                // if the last arg is an array, then
177                // we consider this a varargs method
178                return last.isArray();
179            }
180        }
181    }
182    
183