View Javadoc
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