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