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    *      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