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 */
017package org.apache.commons.jexl2.internal;
018
019import java.lang.reflect.Array;
020import java.lang.reflect.InvocationTargetException;
021import org.apache.commons.jexl2.internal.introspection.MethodKey;
022
023/**
024 * Specialized executor to invoke a method on an object.
025 * @since 2.0
026 */
027public 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