View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.util.generics;
8   
9   import java.lang.annotation.Annotation;
10  import java.lang.reflect.Constructor;
11  import java.lang.reflect.Method;
12  import java.lang.reflect.Type;
13  import java.lang.reflect.TypeVariable;
14  import java.util.HashMap;
15  import java.util.Map;
16  
17  /**
18   * Helper class that encapsulates the specification of a method parameter, i.e.
19   * a Method or Constructor plus a parameter index and a nested type index for
20   * a declared generic type. Useful as a specification object to pass along.
21   * <p/>
22   * author: Spring
23   */
24  public class MethodParameter
25  {
26  
27      private Method method;
28  
29      private Constructor constructor;
30  
31      private final int parameterIndex;
32  
33      private Class<?> parameterType;
34  
35      private Type genericParameterType;
36  
37      private Annotation[] parameterAnnotations;
38  
39      private ParameterNameDiscoverer parameterNameDiscoverer;
40  
41      private String parameterName;
42  
43      private int nestingLevel = 1;
44  
45      /**
46       * Map from Integer level to Integer type index
47       */
48      private Map<Integer, Integer> typeIndexesPerLevel;
49  
50      Map<TypeVariable, Type> typeVariableMap;
51  
52  
53      /**
54       * Create a new MethodParameter for the given method, with nesting level 1.
55       *
56       * @param method         the Method to specify a parameter for
57       * @param parameterIndex the index of the parameter
58       */
59      public MethodParameter(Method method, int parameterIndex)
60      {
61          this(method, parameterIndex, 1);
62      }
63  
64      /**
65       * Create a new MethodParameter for the given method.
66       *
67       * @param method         the Method to specify a parameter for
68       * @param parameterIndex the index of the parameter
69       *                       (-1 for the method return type; 0 for the first method parameter,
70       *                       1 for the second method parameter, etc)
71       * @param nestingLevel   the nesting level of the target type
72       *                       (typically 1; e.g. in case of a List of Lists, 1 would indicate the
73       *                       nested List, whereas 2 would indicate the element of the nested List)
74       */
75      public MethodParameter(Method method, int parameterIndex, int nestingLevel)
76      {
77          this.method = method;
78          this.parameterIndex = parameterIndex;
79          this.nestingLevel = nestingLevel;
80      }
81  
82      /**
83       * Create a new MethodParameter for the given constructor, with nesting level 1.
84       *
85       * @param constructor    the Constructor to specify a parameter for
86       * @param parameterIndex the index of the parameter
87       */
88      public MethodParameter(Constructor constructor, int parameterIndex)
89      {
90          this(constructor, parameterIndex, 1);
91      }
92  
93      /**
94       * Create a new MethodParameter for the given constructor.
95       *
96       * @param constructor    the Constructor to specify a parameter for
97       * @param parameterIndex the index of the parameter
98       * @param nestingLevel   the nesting level of the target type
99       *                       (typically 1; e.g. in case of a List of Lists, 1 would indicate the
100      *                       nested List, whereas 2 would indicate the element of the nested List)
101      */
102     public MethodParameter(Constructor constructor, int parameterIndex, int nestingLevel)
103     {
104         this.constructor = constructor;
105         this.parameterIndex = parameterIndex;
106         this.nestingLevel = nestingLevel;
107     }
108 
109     /**
110      * Copy constructor, resulting in an independent MethodParameter object
111      * based on the same metadata and cache state that the original object was in.
112      *
113      * @param original the original MethodParameter object to copy from
114      */
115     public MethodParameter(MethodParameter original)
116     {
117         this.method = original.method;
118         this.constructor = original.constructor;
119         this.parameterIndex = original.parameterIndex;
120         this.parameterType = original.parameterType;
121         this.parameterAnnotations = original.parameterAnnotations;
122         this.typeVariableMap = original.typeVariableMap;
123     }
124 
125 
126     /**
127      * Return the wrapped Method, if any.
128      * <p>Note: Either Method or Constructor is available.
129      *
130      * @return the Method, or <code>null</code> if none
131      */
132     public Method getMethod()
133     {
134         return this.method;
135     }
136 
137     /**
138      * Return the wrapped Constructor, if any.
139      * <p>Note: Either Method or Constructor is available.
140      *
141      * @return the Constructor, or <code>null</code> if none
142      */
143     public Constructor getConstructor()
144     {
145         return this.constructor;
146     }
147 
148     /**
149      * Return the class that declares the underlying Method or Constructor.
150      */
151     public Class getDeclaringClass()
152     {
153         return (this.method != null ? this.method.getDeclaringClass() : this.constructor.getDeclaringClass());
154     }
155 
156     /**
157      * Return the index of the method/constructor parameter.
158      *
159      * @return the parameter index (never negative)
160      */
161     public int getParameterIndex()
162     {
163         return this.parameterIndex;
164     }
165 
166     /**
167      * Set a resolved (generic) parameter type.
168      */
169     void setParameterType(Class<?> parameterType)
170     {
171         this.parameterType = parameterType;
172     }
173 
174     /**
175      * Return the type of the method/constructor parameter.
176      *
177      * @return the parameter type (never <code>null</code>)
178      */
179     public Class<?> getParameterType()
180     {
181         if (this.parameterType == null)
182         {
183             if (this.parameterIndex < 0)
184             {
185                 this.parameterType = (this.method != null ? this.method.getReturnType() : null);
186             }
187             else
188             {
189                 this.parameterType = (this.method != null ?
190                         this.method.getParameterTypes()[this.parameterIndex] :
191                         this.constructor.getParameterTypes()[this.parameterIndex]);
192             }
193         }
194         return this.parameterType;
195     }
196 
197     /**
198      * Return the generic type of the method/constructor parameter.
199      *
200      * @return the parameter type (never <code>null</code>)
201      */
202     public Type getGenericParameterType()
203     {
204         if (this.genericParameterType == null)
205         {
206             if (this.parameterIndex < 0)
207             {
208                 this.genericParameterType = (this.method != null ? this.method.getGenericReturnType() : null);
209             }
210             else
211             {
212                 this.genericParameterType = (this.method != null ?
213                         this.method.getGenericParameterTypes()[this.parameterIndex] :
214                         this.constructor.getGenericParameterTypes()[this.parameterIndex]);
215             }
216         }
217         return this.genericParameterType;
218     }
219 
220     /**
221      * Return the annotations associated with the target method/constructor itself.
222      */
223     public Annotation[] getMethodAnnotations()
224     {
225         return (this.method != null ? this.method.getAnnotations() : this.constructor.getAnnotations());
226     }
227 
228     /**
229      * Return the method/constructor annotation of the given type, if available.
230      *
231      * @param annotationType the annotation type to look for
232      * @return the annotation object, or <code>null</code> if not found
233      */
234     @SuppressWarnings("unchecked")
235     public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType)
236     {
237         return (this.method != null ? this.method.getAnnotation(annotationType) :
238                 (T) this.constructor.getAnnotation(annotationType));
239     }
240 
241     /**
242      * Return the annotations associated with the specific method/constructor parameter.
243      */
244     public Annotation[] getParameterAnnotations()
245     {
246         if (this.parameterAnnotations == null)
247         {
248             Annotation[][] annotationArray = (this.method != null ?
249                     this.method.getParameterAnnotations() : this.constructor.getParameterAnnotations());
250             this.parameterAnnotations = annotationArray[this.parameterIndex];
251         }
252         return this.parameterAnnotations;
253     }
254 
255     /**
256      * Return the parameter annotation of the given type, if available.
257      *
258      * @param annotationType the annotation type to look for
259      * @return the annotation object, or <code>null</code> if not found
260      */
261     @SuppressWarnings("unchecked")
262     public <T extends Annotation> T getParameterAnnotation(Class<T> annotationType)
263     {
264         Annotation[] anns = getParameterAnnotations();
265         for (Annotation ann : anns)
266         {
267             if (annotationType.isInstance(ann))
268             {
269                 return (T) ann;
270             }
271         }
272         return null;
273     }
274 
275     /**
276      * Initialize parameter name discovery for this method parameter.
277      * <p>This method does not actually try to retrieve the parameter name at
278      * this point; it just allows discovery to happen when the application calls
279      * {@link #getParameterName()} (if ever).
280      */
281     public void initParameterNameDiscovery(ParameterNameDiscoverer parameterNameDiscoverer)
282     {
283         this.parameterNameDiscoverer = parameterNameDiscoverer;
284     }
285 
286     /**
287      * Return the name of the method/constructor parameter.
288      *
289      * @return the parameter name (may be <code>null</code> if no
290      *         parameter name metadata is contained in the class file or no
291      *         {@link #initParameterNameDiscovery ParameterNameDiscoverer}
292      *         has been set to begin with)
293      */
294     public String getParameterName()
295     {
296         if (this.parameterNameDiscoverer != null)
297         {
298             String[] parameterNames = (this.method != null ?
299                     this.parameterNameDiscoverer.getParameterNames(this.method) :
300                     this.parameterNameDiscoverer.getParameterNames(this.constructor));
301             if (parameterNames != null)
302             {
303                 this.parameterName = parameterNames[this.parameterIndex];
304             }
305             this.parameterNameDiscoverer = null;
306         }
307         return this.parameterName;
308     }
309 
310     /**
311      * Increase this parameter's nesting level.
312      *
313      * @see #getNestingLevel()
314      */
315     public void increaseNestingLevel()
316     {
317         this.nestingLevel++;
318     }
319 
320     /**
321      * Decrease this parameter's nesting level.
322      *
323      * @see #getNestingLevel()
324      */
325     public void decreaseNestingLevel()
326     {
327         getTypeIndexesPerLevel().remove(this.nestingLevel);
328         this.nestingLevel--;
329     }
330 
331     /**
332      * Return the nesting level of the target type
333      * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
334      * nested List, whereas 2 would indicate the element of the nested List).
335      */
336     public int getNestingLevel()
337     {
338         return this.nestingLevel;
339     }
340 
341     /**
342      * Set the type index for the current nesting level.
343      *
344      * @param typeIndex the corresponding type index
345      *                  (or <code>null</code> for the default type index)
346      * @see #getNestingLevel()
347      */
348     public void setTypeIndexForCurrentLevel(int typeIndex)
349     {
350         getTypeIndexesPerLevel().put(this.nestingLevel, typeIndex);
351     }
352 
353     /**
354      * Return the type index for the current nesting level.
355      *
356      * @return the corresponding type index, or <code>null</code>
357      *         if none specified (indicating the default type index)
358      * @see #getNestingLevel()
359      */
360     public Integer getTypeIndexForCurrentLevel()
361     {
362         return getTypeIndexForLevel(this.nestingLevel);
363     }
364 
365     /**
366      * Return the type index for the specified nesting level.
367      *
368      * @param nestingLevel the nesting level to check
369      * @return the corresponding type index, or <code>null</code>
370      *         if none specified (indicating the default type index)
371      */
372     public Integer getTypeIndexForLevel(int nestingLevel)
373     {
374         return getTypeIndexesPerLevel().get(nestingLevel);
375     }
376 
377     /**
378      * Obtain the (lazily constructed) type-indexes-per-level Map.
379      */
380     private Map<Integer, Integer> getTypeIndexesPerLevel()
381     {
382         if (this.typeIndexesPerLevel == null)
383         {
384             this.typeIndexesPerLevel = new HashMap<Integer, Integer>(4);
385         }
386         return this.typeIndexesPerLevel;
387     }
388 
389 
390     /**
391      * Create a new MethodParameter for the given method or constructor.
392      * <p>This is a convenience constructor for scenarios where a
393      * Method or Constructor reference is treated in a generic fashion.
394      *
395      * @param methodOrConstructor the Method or Constructor to specify a parameter for
396      * @param parameterIndex      the index of the parameter
397      * @return the corresponding MethodParameter instance
398      */
399     public static MethodParameter forMethodOrConstructor(Object methodOrConstructor, int parameterIndex)
400     {
401         if (methodOrConstructor instanceof Method)
402         {
403             return new MethodParameter((Method) methodOrConstructor, parameterIndex);
404         }
405         else if (methodOrConstructor instanceof Constructor) {
406             return new MethodParameter((Constructor) methodOrConstructor, parameterIndex);
407         }
408         else {
409             throw new IllegalArgumentException(
410                     "Given object [" + methodOrConstructor + "] is neither a Method nor a Constructor");
411         }
412     }
413 
414 }