View Javadoc

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