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 * https://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 *
28 * @since 2.0
29 */
30 public final class MethodExecutor extends AbstractExecutor.Method {
31
32 /**
33 * Discovers a {@link MethodExecutor}.
34 * <p>
35 * If the object is an array, an attempt will be made to find the
36 * method in a List (see {@link ArrayListWrapper})
37 * </p>
38 * <p>
39 * If the object is a class, an attempt will be made to find the
40 * method as a static method of that class.
41 * </p>
42 *
43 * @param is the introspector used to discover the method
44 * @param obj the object to introspect
45 * @param method the name of the method to find
46 * @param args the method arguments
47 * @return a filled up parameter (may contain a null method)
48 */
49 public static MethodExecutor discover(final Introspector is, final Object obj, final String method, final Object[] args) {
50 final Class<?> clazz = obj.getClass();
51 final MethodKey key = new MethodKey(method, args);
52 java.lang.reflect.Method m = is.getMethod(clazz, key);
53 if (m == null && clazz.isArray()) {
54 // check for support via our array->list wrapper
55 m = is.getMethod(ArrayListWrapper.class, key);
56 }
57 if (m == null && obj instanceof Class<?>) {
58 m = is.getMethod((Class<?>) obj, key);
59 }
60 return m == null ? null : new MethodExecutor(clazz, m, key);
61 }
62
63 /** If this method is a vararg method, vaStart is the last argument index. */
64 private final int vaStart;
65
66 /** If this method is a vararg method, vaClass is the component type of the vararg array. */
67 private final Class<?> vaClass;
68
69 /**
70 * Creates a new instance.
71 *
72 * @param c the class this executor applies to
73 * @param m the method
74 * @param k the MethodKey
75 */
76 private MethodExecutor(final Class<?> c, final java.lang.reflect.Method m, final MethodKey k) {
77 super(c, m, k);
78 int vastart = -1;
79 Class<?> vaclass = null;
80 if (MethodKey.isVarArgs(method)) {
81 // if the last parameter is an array, the method is considered as vararg
82 final Class<?>[] formal = method.getParameterTypes();
83 vastart = formal.length - 1;
84 vaclass = formal[vastart].getComponentType();
85 }
86 vaStart = vastart;
87 vaClass = vaclass;
88 }
89
90 /**
91 * Reassembles arguments if the method is a vararg method.
92 *
93 * @param args The actual arguments being passed to this method
94 * @return The actual parameters adjusted for the varargs in order
95 * to fit the method declaration.
96 */
97 @SuppressWarnings("SuspiciousSystemArraycopy")
98 private Object[] handleVarArg(final Object[] args) {
99 final Class<?> vaclass = vaClass;
100 final int vastart = vaStart;
101 // variable arguments count
102 Object[] actual = args;
103 final int varargc = actual.length - vastart;
104 // if no values are being passed into the vararg, size == 0
105 if (varargc == 1) {
106 // if one non-null value is being passed into the vararg,
107 // and that arg is not the sole argument and not an array of the expected type,
108 // make the last arg an array of the expected type
109 if (actual[vastart] != null) {
110 final Class<?> aclazz = actual[vastart].getClass();
111 if (!aclazz.isArray() || !vaclass.isAssignableFrom(aclazz.getComponentType())) {
112 // create a 1-length array to hold and replace the last argument
113 final Object lastActual = Array.newInstance(vaclass, 1);
114 Array.set(lastActual, 0, actual[vastart]);
115 actual[vastart] = lastActual;
116 }
117 }
118 // else, the vararg is null and used as is, considered as T[]
119 } else {
120 // if no or multiple values are being passed into the vararg,
121 // put them in an array of the expected type
122 final Object varargs = Array.newInstance(vaclass, varargc);
123 System.arraycopy(actual, vastart, varargs, 0, varargc);
124 // put all arguments into a new actual array of the appropriate size
125 final Object[] newActual = new Object[vastart + 1];
126 System.arraycopy(actual, 0, newActual, 0, vastart);
127 newActual[vastart] = varargs;
128 // replace the old actual array
129 actual = newActual;
130 }
131 return actual;
132 }
133
134 @Override
135 public Object invoke(final Object o, final Object... oArgs) throws IllegalAccessException, InvocationTargetException {
136 Object[] args = oArgs;
137 if (vaClass != null && args != null) {
138 args = handleVarArg(args);
139 }
140 if (method.getDeclaringClass() == ArrayListWrapper.class && o.getClass().isArray()) {
141 return method.invoke(new ArrayListWrapper(o), args);
142 }
143 return method.invoke(o, args);
144 }
145
146 @Override
147 public Object tryInvoke(final String name, final Object obj, final Object... args) {
148 final MethodKey tkey = new MethodKey(name, args);
149 // let's assume that invocation will fly if the declaring class is the
150 // same and arguments have the same type
151 if (objectClass.equals(obj.getClass()) && tkey.equals(key)) {
152 try {
153 return invoke(obj, args);
154 } catch (IllegalAccessException | IllegalArgumentException xill) {
155 return TRY_FAILED; // fail
156 } catch (final InvocationTargetException xinvoke) {
157 throw JexlException.tryFailed(xinvoke); // throw
158 }
159 }
160 return JexlEngine.TRY_FAILED;
161 }
162 }
163