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