1. Project Clover database Mon Aug 5 2019 23:32:56 UTC
  2. Package org.xwiki.velocity.introspection

File MethodArgumentsUberspector.java

 

Coverage histogram

../../../../img/srcFileCovDistChart9.png
29% of files have more coverage

Code metrics

20
47
10
2
235
127
26
0.55
4.7
5
2.6

Classes

Class Line # Actions
MethodArgumentsUberspector 53 42 0% 21 4
0.940298594%
MethodArgumentsUberspector.ConvertingVelMethod 196 5 0% 5 6
0.440%
 

Contributing tests

This file is covered by 13 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.velocity.introspection;
21   
22    import java.lang.reflect.Field;
23    import java.lang.reflect.Method;
24    import java.lang.reflect.Type;
25    import java.util.Arrays;
26   
27    import org.apache.commons.lang3.reflect.TypeUtils;
28    import org.apache.velocity.runtime.RuntimeServices;
29    import org.apache.velocity.util.RuntimeServicesAware;
30    import org.apache.velocity.util.introspection.AbstractChainableUberspector;
31    import org.apache.velocity.util.introspection.Info;
32    import org.apache.velocity.util.introspection.VelMethod;
33    import org.xwiki.component.manager.ComponentLookupException;
34    import org.xwiki.component.manager.ComponentManager;
35    import org.xwiki.properties.ConverterManager;
36   
37    /**
38    * Chainable Velocity Uberspector that tries to convert method arguments to formal parameter types when the passed
39    * arguments don't match the method signature. In other words, it looks for a method matching the passed arguments and
40    * if none is found then it tries the convert the arguments to match the available method signatures (the available
41    * methods with the same name and the same number of parameters but with different parameter types). E.g.:
42    *
43    * <pre>
44    * {@code $obj.someMethod('VALUE')
45    * // will forward to
46    * obj.someMethod(SomeEnum.VALUE)
47    * // if obj has someMethod(SomeEnum) and not someMethod(String)}
48    * </pre>
49    *
50    * @since 4.1M2
51    * @version $Id: ae4723b2db8250158eb786dc276607310c6f539c $
52    */
 
53    public class MethodArgumentsUberspector extends AbstractChainableUberspector implements RuntimeServicesAware
54    {
55    /**
56    * The component used to convert method arguments to formal parameter types.
57    */
58    private ConverterManager converterManager;
59   
 
60  13 toggle @Override
61    public void setRuntimeServices(RuntimeServices runtimeServices)
62    {
63  13 ComponentManager componentManager =
64    (ComponentManager) runtimeServices.getApplicationAttribute(ComponentManager.class.getName());
65  13 try {
66  13 this.converterManager = componentManager.getInstance(ConverterManager.class);
67    } catch (ComponentLookupException e) {
68  0 this.log.warn("Failed to initialize " + this.getClass().getSimpleName(), e);
69    }
70    }
71   
 
72  13 toggle @Override
73    public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i) throws Exception
74    {
75    // Let Velocity find a matching method. However, Velocity finds the closest matching method.
76    // According to the JavaDoc of MethodMap:
77    // "Attempts to find the most specific applicable method using the algorithm described in the JLS section
78    // 15.12.2 (with the exception that it can't distinguish a primitive type argument from an object type
79    // argument, since in reflection primitive type arguments are represented by their object counterparts, so for
80    // an argument of type (say) java.lang.Integer, it will not be able to decide between a method that takes int
81    // and a method that takes java.lang.Integer as a parameter."
82    // Thus we need to apply the following logic:
83    // - if the returned VelMethod has a different number of parameters than the signature asked for, then go into
84    // our conversion code
85    // - if our conversion code doesn't find any match, then return the VelMethod found by Velocity.
86   
87  13 VelMethod initialVelMethod = super.getMethod(obj, methodName, args, i);
88  13 VelMethod velMethod = initialVelMethod;
89   
90  13 boolean shouldConvert = false;
91  13 if (this.converterManager != null) {
92  13 if (velMethod == null) {
93  6 shouldConvert = true;
94    } else {
95  7 Method method = getPrivateMethod(velMethod);
96  7 boolean sameParameterNumbers = method.getParameterTypes().length == args.length;
97  7 if (!sameParameterNumbers) {
98  4 shouldConvert = true;
99    }
100    }
101    }
102   
103  13 if (shouldConvert) {
104    // Try to convert method arguments to formal parameter types.
105  10 Object[] convertedArguments = this.convertArguments(obj, methodName, args);
106  10 if (convertedArguments != null) {
107  8 velMethod = super.getMethod(obj, methodName, convertedArguments, i);
108  8 if (velMethod != null) {
109  8 velMethod = new ConvertingVelMethod(velMethod);
110    } else {
111  0 velMethod = initialVelMethod;
112    }
113    }
114    }
115   
116  13 return velMethod;
117    }
118   
119    /**
120    * This is hackish but there's no way in Velocity to get access to the underlying Method from a VelMethod instance.
121    */
 
122  7 toggle private Method getPrivateMethod(VelMethod velMethod) throws Exception
123    {
124  7 Field methodField = velMethod.getClass().getDeclaredField("method");
125  7 boolean isAccessible = methodField.isAccessible();
126  7 try {
127  7 methodField.setAccessible(true);
128  7 return (Method) methodField.get(velMethod);
129    } finally {
130  7 methodField.setAccessible(isAccessible);
131    }
132    }
133   
134    /**
135    * Converts the given arguments to match a method with the specified name and the same number of formal parameters
136    * as the number of arguments.
137    *
138    * @param obj the object the method is invoked on, used to retrieve the list of available methods
139    * @param methodName the method we're looking for
140    * @param args the method arguments
141    * @return a new array of arguments where some values have been converted to match the formal method parameter
142    * types, {@code null} if no such method is found
143    */
 
144  18 toggle private Object[] convertArguments(Object obj, String methodName, Object[] args)
145    {
146  18 for (Method method : obj.getClass().getMethods()) {
147  68 if (method.getName().equalsIgnoreCase(methodName)
148    && (method.getGenericParameterTypes().length == args.length || method.isVarArgs())) {
149  16 try {
150  16 return convertArguments(args, method.getGenericParameterTypes(), method.isVarArgs());
151    } catch (Exception e) {
152    // Ignore and try the next method.
153    }
154    }
155    }
156   
157  2 return null;
158    }
159   
160    /**
161    * Tries to convert the given arguments to match the specified formal parameters types.
162    * <p>
163    * Throws a runtime exception if the conversion fails.
164    *
165    * @param arguments the method actual arguments
166    * @param parameterTypes the method formal parameter types
167    * @param isVarArgs true if the method contains a varargs (ie the last parameter is a varargs)
168    * @return a new array of arguments where some values have been converted to match the formal method parameter types
169    */
 
170  16 toggle private Object[] convertArguments(Object[] arguments, Type[] parameterTypes, boolean isVarArgs)
171    {
172  16 Object[] convertedArguments = Arrays.copyOf(arguments, arguments.length);
173  46 for (int i = 0; i < arguments.length; i++) {
174    // Try to convert the argument if it's not null and if it doesn't match the parameter type.
175    // If the method is a varargs then extract the type from the vararg array
176  30 Type expectedType;
177  30 if (isVarArgs && i >= parameterTypes.length - 1) {
178  14 expectedType = ((Class<?>) parameterTypes[parameterTypes.length - 1]).getComponentType();
179    } else {
180  16 expectedType = parameterTypes[i];
181    }
182   
183  30 if (arguments[i] != null && !TypeUtils.isInstance(arguments[i], expectedType)) {
184  14 convertedArguments[i] = this.converterManager.convert(expectedType, arguments[i]);
185    }
186    }
187   
188  16 return convertedArguments;
189    }
190   
191    /**
192    * Wrapper for a real VelMethod that converts the passed arguments to the real arguments expected by the method.
193    *
194    * @version $Id: ae4723b2db8250158eb786dc276607310c6f539c $
195    */
 
196    private class ConvertingVelMethod implements VelMethod
197    {
198    /** The real method that performs the actual call. */
199    private VelMethod innerMethod;
200   
201    /**
202    * Constructor.
203    *
204    * @param realMethod the real method to wrap
205    */
 
206  8 toggle ConvertingVelMethod(VelMethod realMethod)
207    {
208  8 this.innerMethod = realMethod;
209    }
210   
 
211  8 toggle @Override
212    public Object invoke(Object o, Object[] params) throws Exception
213    {
214  8 return this.innerMethod.invoke(o, convertArguments(o, this.innerMethod.getMethodName(), params));
215    }
216   
 
217  0 toggle @Override
218    public boolean isCacheable()
219    {
220  0 return this.innerMethod.isCacheable();
221    }
222   
 
223  0 toggle @Override
224    public String getMethodName()
225    {
226  0 return this.innerMethod.getMethodName();
227    }
228   
 
229  0 toggle @Override
230    public Class<?> getReturnType()
231    {
232  0 return this.innerMethod.getReturnType();
233    }
234    }
235    }