1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.velocity.introspection

File MethodArgumentsUberspector.java

 

Coverage histogram

../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

20
47
10
2
236
127
26
0.55
4.7
5
2.6

Classes

Class Line # Actions
MethodArgumentsUberspector 54 42 0% 21 2
0.970149397%
MethodArgumentsUberspector.ConvertingVelMethod 197 5 0% 5 4
0.660%
 

Contributing tests

This file is covered by 48 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: a09267204cbcd8713670092e866a4ca236812a6f $
52    * @see ChainableUberspector
53    */
 
54    public class MethodArgumentsUberspector extends AbstractChainableUberspector implements RuntimeServicesAware
55    {
56    /**
57    * The component used to convert method arguments to formal parameter types.
58    */
59    private ConverterManager converterManager;
60   
 
61  85 toggle @Override
62    public void setRuntimeServices(RuntimeServices runtimeServices)
63    {
64  85 ComponentManager componentManager =
65    (ComponentManager) runtimeServices.getApplicationAttribute(ComponentManager.class.getName());
66  85 try {
67  85 this.converterManager = componentManager.getInstance(ConverterManager.class);
68    } catch (ComponentLookupException e) {
69  0 this.log.warn("Failed to initialize " + this.getClass().getSimpleName(), e);
70    }
71    }
72   
 
73  442896 toggle @Override
74    public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i) throws Exception
75    {
76    // Let Velocity find a matching method. However, Velocity finds the closest matching method.
77    // According to the JavaDoc of MethodMap:
78    // "Attempts to find the most specific applicable method using the algorithm described in the JLS section
79    // 15.12.2 (with the exception that it can't distinguish a primitive type argument from an object type
80    // argument, since in reflection primitive type arguments are represented by their object counterparts, so for
81    // an argument of type (say) java.lang.Integer, it will not be able to decide between a method that takes int
82    // and a method that takes java.lang.Integer as a parameter."
83    // Thus we need to apply the following logic:
84    // - if the returned VelMethod has a different number of parameters than the signature asked for, then go into
85    // our conversion code
86    // - if our conversion code doesn't find any match, then return the VelMethod found by Velocity.
87   
88  442893 VelMethod initialVelMethod = super.getMethod(obj, methodName, args, i);
89  442892 VelMethod velMethod = initialVelMethod;
90   
91  442892 boolean shouldConvert = false;
92  442895 if (this.converterManager != null) {
93  442889 if (velMethod == null) {
94  54082 shouldConvert = true;
95    } else {
96  388807 Method method = getPrivateMethod(velMethod);
97  388796 boolean sameParameterNumbers = method.getParameterTypes().length == args.length;
98  388801 if (!sameParameterNumbers) {
99  21563 shouldConvert = true;
100    }
101    }
102    }
103   
104  442874 if (shouldConvert) {
105    // Try to convert method arguments to formal parameter types.
106  75644 Object[] convertedArguments = this.convertArguments(obj, methodName, args);
107  75648 if (convertedArguments != null) {
108  67775 velMethod = super.getMethod(obj, methodName, convertedArguments, i);
109  67776 if (velMethod != null) {
110  67766 velMethod = new ConvertingVelMethod(velMethod);
111    } else {
112  11 velMethod = initialVelMethod;
113    }
114    }
115    }
116   
117  442876 return velMethod;
118    }
119   
120    /**
121    * This is hackish but there's no way in Velocity to get access to the underlying Method from a VelMethod instance.
122    */
 
123  388799 toggle private Method getPrivateMethod(VelMethod velMethod) throws Exception
124    {
125  388801 Field methodField = velMethod.getClass().getDeclaredField("method");
126  388791 boolean isAccessible = methodField.isAccessible();
127  388792 try {
128  388792 methodField.setAccessible(true);
129  388792 return (Method) methodField.get(velMethod);
130    } finally {
131  388797 methodField.setAccessible(isAccessible);
132    }
133    }
134   
135    /**
136    * Converts the given arguments to match a method with the specified name and the same number of formal parameters
137    * as the number of arguments.
138    *
139    * @param obj the object the method is invoked on, used to retrieve the list of available methods
140    * @param methodName the method we're looking for
141    * @param args the method arguments
142    * @return a new array of arguments where some values have been converted to match the formal method parameter
143    * types, {@code null} if no such method is found
144    */
 
145  202174 toggle private Object[] convertArguments(Object obj, String methodName, Object[] args)
146    {
147  202174 for (Method method : obj.getClass().getMethods()) {
148  1898784 if (method.getName().equalsIgnoreCase(methodName)
149    && (method.getGenericParameterTypes().length == args.length || method.isVarArgs())) {
150  194306 try {
151  194309 return convertArguments(args, method.getGenericParameterTypes(), method.isVarArgs());
152    } catch (Exception e) {
153    // Ignore and try the next method.
154    }
155    }
156    }
157   
158  7872 return null;
159    }
160   
161    /**
162    * Tries to convert the given arguments to match the specified formal parameters types.
163    * <p>
164    * Throws a runtime exception if the conversion fails.
165    *
166    * @param arguments the method actual arguments
167    * @param parameterTypes the method formal parameter types
168    * @param isVarArgs true if the method contains a varargs (ie the last parameter is a varargs)
169    * @return a new array of arguments where some values have been converted to match the formal method parameter types
170    */
 
171  194301 toggle private Object[] convertArguments(Object[] arguments, Type[] parameterTypes, boolean isVarArgs)
172    {
173  194300 Object[] convertedArguments = Arrays.copyOf(arguments, arguments.length);
174  483847 for (int i = 0; i < arguments.length; i++) {
175    // Try to convert the argument if it's not null and if it doesn't match the parameter type.
176    // If the method is a varargs then extract the type from the vararg array
177  289539 Type expectedType;
178  289537 if (isVarArgs && i >= parameterTypes.length - 1) {
179  19884 expectedType = ((Class<?>) parameterTypes[parameterTypes.length - 1]).getComponentType();
180    } else {
181  269653 expectedType = parameterTypes[i];
182    }
183   
184  289534 if (arguments[i] != null && !TypeUtils.isInstance(arguments[i], expectedType)) {
185  138515 convertedArguments[i] = this.converterManager.convert(expectedType, arguments[i]);
186    }
187    }
188   
189  194307 return convertedArguments;
190    }
191   
192    /**
193    * Wrapper for a real VelMethod that converts the passed arguments to the real arguments expected by the method.
194    *
195    * @version $Id: a09267204cbcd8713670092e866a4ca236812a6f $
196    */
 
197    private class ConvertingVelMethod implements VelMethod
198    {
199    /** The real method that performs the actual call. */
200    private VelMethod innerMethod;
201   
202    /**
203    * Constructor.
204    *
205    * @param realMethod the real method to wrap
206    */
 
207  67763 toggle ConvertingVelMethod(VelMethod realMethod)
208    {
209  67765 this.innerMethod = realMethod;
210    }
211   
 
212  126530 toggle @Override
213    public Object invoke(Object o, Object[] params) throws Exception
214    {
215  126533 return this.innerMethod.invoke(o, convertArguments(o, this.innerMethod.getMethodName(), params));
216    }
217   
 
218  0 toggle @Override
219    public boolean isCacheable()
220    {
221  0 return this.innerMethod.isCacheable();
222    }
223   
 
224  0 toggle @Override
225    public String getMethodName()
226    {
227  0 return this.innerMethod.getMethodName();
228    }
229   
 
230  18 toggle @Override
231    public Class<?> getReturnType()
232    {
233  18 return this.innerMethod.getReturnType();
234    }
235    }
236    }