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