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