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