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