1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.jexl3.internal.introspection;
18
19 import java.lang.reflect.Array;
20 import java.lang.reflect.InvocationTargetException;
21
22 import org.apache.commons.jexl3.JexlEngine;
23 import org.apache.commons.jexl3.JexlException;
24
25 /**
26 * Specialized executor to invoke a method on an object.
27 * @since 2.0
28 */
29 public final class MethodExecutor extends AbstractExecutor.Method {
30 /**
31 * Discovers a {@link MethodExecutor}.
32 * <p>
33 * If the object is an array, an attempt will be made to find the
34 * method in a List (see {@link ArrayListWrapper})
35 * </p>
36 * <p>
37 * If the object is a class, an attempt will be made to find the
38 * method as a static method of that class.
39 * </p>
40 * @param is the introspector used to discover the method
41 * @param obj the object to introspect
42 * @param method the name of the method to find
43 * @param args the method arguments
44 * @return a filled up parameter (may contain a null method)
45 */
46 public static MethodExecutor discover(final Introspector is, final Object obj, final String method, final Object[] args) {
47 final Class<?> clazz = obj.getClass();
48 final MethodKey key = new MethodKey(method, args);
49 java.lang.reflect.Method m = is.getMethod(clazz, key);
50 if (m == null && clazz.isArray()) {
51 // check for support via our array->list wrapper
52 m = is.getMethod(ArrayListWrapper.class, key);
53 }
54 if (m == null && obj instanceof Class<?>) {
55 m = is.getMethod((Class<?>) obj, key);
56 }
57 return m == null ? null : new MethodExecutor(clazz, m, key);
58 }
59 /** If this method is a vararg method, vaStart is the last argument index. */
60 private final int vaStart;
61
62 /** If this method is a vararg method, vaClass is the component type of the vararg array. */
63 private final Class<?> vaClass;
64
65 /**
66 * Creates a new instance.
67 * @param c the class this executor applies to
68 * @param m the method
69 * @param k the MethodKey
70 */
71 private MethodExecutor(final Class<?> c, final java.lang.reflect.Method m, final MethodKey k) {
72 super(c, m, k);
73 int vastart = -1;
74 Class<?> vaclass = null;
75 if (MethodKey.isVarArgs(method)) {
76 // if the last parameter is an array, the method is considered as vararg
77 final Class<?>[] formal = method.getParameterTypes();
78 vastart = formal.length - 1;
79 vaclass = formal[vastart].getComponentType();
80 }
81 vaStart = vastart;
82 vaClass = vaclass;
83 }
84
85 /**
86 * Reassembles arguments if the method is a vararg method.
87 * @param args The actual arguments being passed to this method
88 * @return The actual parameters adjusted for the varargs in order
89 * to fit the method declaration.
90 */
91 @SuppressWarnings("SuspiciousSystemArraycopy")
92 private Object[] handleVarArg(final Object[] args) {
93 final Class<?> vaclass = vaClass;
94 final int vastart = vaStart;
95 // variable arguments count
96 Object[] actual = args;
97 final int varargc = actual.length - vastart;
98 // if no values are being passed into the vararg, size == 0
99 if (varargc == 1) {
100 // if one non-null value is being passed into the vararg,
101 // and that arg is not the sole argument and not an array of the expected type,
102 // make the last arg an array of the expected type
103 if (actual[vastart] != null) {
104 final Class<?> aclazz = actual[vastart].getClass();
105 if (!aclazz.isArray() || !vaclass.isAssignableFrom(aclazz.getComponentType())) {
106 // create a 1-length array to hold and replace the last argument
107 final Object lastActual = Array.newInstance(vaclass, 1);
108 Array.set(lastActual, 0, actual[vastart]);
109 actual[vastart] = lastActual;
110 }
111 }
112 // else, the vararg is null and used as is, considered as T[]
113 } else {
114 // if no or multiple values are being passed into the vararg,
115 // put them in an array of the expected type
116 final Object varargs = Array.newInstance(vaclass, varargc);
117 System.arraycopy(actual, vastart, varargs, 0, varargc);
118 // put all arguments into a new actual array of the appropriate size
119 final Object[] newActual = new Object[vastart + 1];
120 System.arraycopy(actual, 0, newActual, 0, vastart);
121 newActual[vastart] = varargs;
122 // replace the old actual array
123 actual = newActual;
124 }
125 return actual;
126 }
127
128 @Override
129 public Object invoke(final Object o, final Object... oArgs) throws IllegalAccessException, InvocationTargetException {
130 Object[] args = oArgs;
131 if (vaClass != null && args != null) {
132 args = handleVarArg(args);
133 }
134 if (method.getDeclaringClass() == ArrayListWrapper.class && o.getClass().isArray()) {
135 return method.invoke(new ArrayListWrapper(o), args);
136 }
137 return method.invoke(o, args);
138 }
139
140 @Override
141 public Object tryInvoke(final String name, final Object obj, final Object... args) {
142 final MethodKey tkey = new MethodKey(name, args);
143 // let's assume that invocation will fly if the declaring class is the
144 // same and arguments have the same type
145 if (objectClass.equals(obj.getClass()) && tkey.equals(key)) {
146 try {
147 return invoke(obj, args);
148 } catch (IllegalAccessException | IllegalArgumentException xill) {
149 return TRY_FAILED; // fail
150 } catch (final InvocationTargetException xinvoke) {
151 throw JexlException.tryFailed(xinvoke); // throw
152 }
153 }
154 return JexlEngine.TRY_FAILED;
155 }
156 }
157