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.jexl3.internal.introspection; 018 019import org.apache.commons.jexl3.JexlEngine; 020import java.lang.reflect.Array; 021import java.lang.reflect.InvocationTargetException; 022import org.apache.commons.jexl3.JexlException; 023 024/** 025 * Specialized executor to invoke a method on an object. 026 * @since 2.0 027 */ 028public final class MethodExecutor extends AbstractExecutor.Method { 029 /** If this method is a vararg method, vaStart is the last argument index. */ 030 private final int vaStart; 031 /** If this method is a vararg method, vaClass is the component type of the vararg array. */ 032 private final Class<?> vaClass; 033 034 /** 035 * Discovers a {@link MethodExecutor}. 036 * <p> 037 * If the object is an array, an attempt will be made to find the 038 * method in a List (see {@link ArrayListWrapper}) 039 * </p> 040 * <p> 041 * If the object is a class, an attempt will be made to find the 042 * method as a static method of that class. 043 * </p> 044 * @param is the introspector used to discover the method 045 * @param obj the object to introspect 046 * @param method the name of the method to find 047 * @param args the method arguments 048 * @return a filled up parameter (may contain a null method) 049 */ 050 public static MethodExecutor discover(final Introspector is, final Object obj, final String method, final Object[] args) { 051 final Class<?> clazz = obj.getClass(); 052 final MethodKey key = new MethodKey(method, args); 053 java.lang.reflect.Method m = is.getMethod(clazz, key); 054 if (m == null && clazz.isArray()) { 055 // check for support via our array->list wrapper 056 m = is.getMethod(ArrayListWrapper.class, key); 057 } 058 if (m == null && obj instanceof Class<?>) { 059 m = is.getMethod((Class<?>) obj, key); 060 } 061 return m == null? null : new MethodExecutor(clazz, m, key); 062 } 063 064 /** 065 * Creates a new instance. 066 * @param c the class this executor applies to 067 * @param m the method 068 * @param k the MethodKey 069 */ 070 private MethodExecutor(final Class<?> c, final java.lang.reflect.Method m, final MethodKey k) { 071 super(c, m, k); 072 int vastart = -1; 073 Class<?> vaclass = null; 074 if (MethodKey.isVarArgs(method)) { 075 // if the last parameter is an array, the method is considered as vararg 076 final Class<?>[] formal = method.getParameterTypes(); 077 vastart = formal.length - 1; 078 vaclass = formal[vastart].getComponentType(); 079 } 080 vaStart = vastart; 081 vaClass = vaclass; 082 } 083 084 @Override 085 public Object invoke(final Object o, Object... args) throws IllegalAccessException, InvocationTargetException { 086 if (vaClass != null && args != null) { 087 args = handleVarArg(args); 088 } 089 if (method.getDeclaringClass() == ArrayListWrapper.class && o.getClass().isArray()) { 090 return method.invoke(new ArrayListWrapper(o), args); 091 } 092 return method.invoke(o, args); 093 } 094 095 @Override 096 public Object tryInvoke(final String name, final Object obj, final Object... args) { 097 final MethodKey tkey = new MethodKey(name, args); 098 // let's assume that invocation will fly if the declaring class is the 099 // same and arguments have the same type 100 if (objectClass.equals(obj.getClass()) && tkey.equals(key)) { 101 try { 102 return invoke(obj, args); 103 } catch (IllegalAccessException | IllegalArgumentException xill) { 104 return TRY_FAILED;// fail 105 } catch (final InvocationTargetException xinvoke) { 106 throw JexlException.tryFailed(xinvoke); // throw 107 } 108 } 109 return JexlEngine.TRY_FAILED; 110 } 111 112 113 /** 114 * Reassembles arguments if the method is a vararg method. 115 * @param actual The actual arguments being passed to this method 116 * @return The actual parameters adjusted for the varargs in order 117 * to fit the method declaration. 118 */ 119 @SuppressWarnings("SuspiciousSystemArraycopy") 120 private Object[] handleVarArg(Object[] actual) { 121 final Class<?> vaclass = vaClass; 122 final int vastart = vaStart; 123 // variable arguments count 124 final int varargc = actual.length - vastart; 125 // if no values are being passed into the vararg, size == 0 126 if (varargc == 1) { 127 // if one non-null value is being passed into the vararg, 128 // and that arg is not the sole argument and not an array of the expected type, 129 // make the last arg an array of the expected type 130 if (actual[vastart] != null) { 131 final Class<?> aclazz = actual[vastart].getClass(); 132 if (!aclazz.isArray() || !vaclass.isAssignableFrom(aclazz.getComponentType())) { 133 // create a 1-length array to hold and replace the last argument 134 final Object lastActual = Array.newInstance(vaclass, 1); 135 Array.set(lastActual, 0, actual[vastart]); 136 actual[vastart] = lastActual; 137 } 138 } 139 // else, the vararg is null and used as is, considered as T[] 140 } else { 141 // if no or multiple values are being passed into the vararg, 142 // put them in an array of the expected type 143 final Object varargs = Array.newInstance(vaclass, varargc); 144 System.arraycopy(actual, vastart, varargs, 0, varargc); 145 // put all arguments into a new actual array of the appropriate size 146 final Object[] newActual = new Object[vastart + 1]; 147 System.arraycopy(actual, 0, newActual, 0, vastart); 148 newActual[vastart] = varargs; 149 // replace the old actual array 150 actual = newActual; 151 } 152 return actual; 153 } 154} 155 156