View Javadoc
1   /*
2    * Copyright 2002-2015 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.core;
18  
19  import java.lang.annotation.Annotation;
20  import java.lang.reflect.AnnotatedElement;
21  import java.lang.reflect.Constructor;
22  import java.lang.reflect.Member;
23  import java.lang.reflect.Method;
24  import java.lang.reflect.ParameterizedType;
25  import java.lang.reflect.Type;
26  import java.util.HashMap;
27  import java.util.Map;
28  
29  import org.springframework.util.Assert;
30  
31  /**
32   * Helper class that encapsulates the specification of a method parameter, i.e.
33   * a Method or Constructor plus a parameter index and a nested type index for
34   * a declared generic type. Useful as a specification object to pass along.
35   *
36   * @author Juergen Hoeller
37   * @author Rob Harrop
38   * @author Andy Clement
39   * @since 2.0
40   * @see GenericCollectionTypeResolver
41   */
42  public class MethodParameter {
43  
44  	private final Method method;
45  
46  	private final Constructor<?> constructor;
47  
48  	private final int parameterIndex;
49  
50  	private int nestingLevel = 1;
51  
52  	/** Map from Integer level to Integer type index */
53  	Map<Integer, Integer> typeIndexesPerLevel;
54  
55  	private volatile Class<?> containingClass;
56  
57  	private volatile Class<?> parameterType;
58  
59  	private volatile Type genericParameterType;
60  
61  	private volatile Annotation[] parameterAnnotations;
62  
63  	private volatile ParameterNameDiscoverer parameterNameDiscoverer;
64  
65  	private volatile String parameterName;
66  
67  
68  	/**
69  	 * Create a new MethodParameter for the given method, with nesting level 1.
70  	 * @param method the Method to specify a parameter for
71  	 * @param parameterIndex the index of the parameter
72  	 */
73  	public MethodParameter(Method method, int parameterIndex) {
74  		this(method, parameterIndex, 1);
75  	}
76  
77  	/**
78  	 * Create a new MethodParameter for the given method.
79  	 * @param method the Method to specify a parameter for
80  	 * @param parameterIndex the index of the parameter
81  	 * (-1 for the method return type; 0 for the first method parameter,
82  	 * 1 for the second method parameter, etc)
83  	 * @param nestingLevel the nesting level of the target type
84  	 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
85  	 * nested List, whereas 2 would indicate the element of the nested List)
86  	 */
87  	public MethodParameter(Method method, int parameterIndex, int nestingLevel) {
88  		Assert.notNull(method, "Method must not be null");
89  		this.method = method;
90  		this.parameterIndex = parameterIndex;
91  		this.nestingLevel = nestingLevel;
92  		this.constructor = null;
93  	}
94  
95  	/**
96  	 * Create a new MethodParameter for the given constructor, with nesting level 1.
97  	 * @param constructor the Constructor to specify a parameter for
98  	 * @param parameterIndex the index of the parameter
99  	 */
100 	public MethodParameter(Constructor<?> constructor, int parameterIndex) {
101 		this(constructor, parameterIndex, 1);
102 	}
103 
104 	/**
105 	 * Create a new MethodParameter for the given constructor.
106 	 * @param constructor the Constructor to specify a parameter for
107 	 * @param parameterIndex the index of the parameter
108 	 * @param nestingLevel the nesting level of the target type
109 	 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
110 	 * nested List, whereas 2 would indicate the element of the nested List)
111 	 */
112 	public MethodParameter(Constructor<?> constructor, int parameterIndex, int nestingLevel) {
113 		Assert.notNull(constructor, "Constructor must not be null");
114 		this.constructor = constructor;
115 		this.parameterIndex = parameterIndex;
116 		this.nestingLevel = nestingLevel;
117 		this.method = null;
118 	}
119 
120 	/**
121 	 * Copy constructor, resulting in an independent MethodParameter object
122 	 * based on the same metadata and cache state that the original object was in.
123 	 * @param original the original MethodParameter object to copy from
124 	 */
125 	public MethodParameter(MethodParameter original) {
126 		Assert.notNull(original, "Original must not be null");
127 		this.method = original.method;
128 		this.constructor = original.constructor;
129 		this.parameterIndex = original.parameterIndex;
130 		this.nestingLevel = original.nestingLevel;
131 		this.typeIndexesPerLevel = original.typeIndexesPerLevel;
132 		this.containingClass = original.containingClass;
133 		this.parameterType = original.parameterType;
134 		this.genericParameterType = original.genericParameterType;
135 		this.parameterAnnotations = original.parameterAnnotations;
136 		this.parameterNameDiscoverer = original.parameterNameDiscoverer;
137 		this.parameterName = original.parameterName;
138 	}
139 
140 
141 	/**
142 	 * Return the wrapped Method, if any.
143 	 * <p>Note: Either Method or Constructor is available.
144 	 * @return the Method, or {@code null} if none
145 	 */
146 	public Method getMethod() {
147 		return this.method;
148 	}
149 
150 	/**
151 	 * Return the wrapped Constructor, if any.
152 	 * <p>Note: Either Method or Constructor is available.
153 	 * @return the Constructor, or {@code null} if none
154 	 */
155 	public Constructor<?> getConstructor() {
156 		return this.constructor;
157 	}
158 
159 	/**
160 	 * Returns the wrapped member.
161 	 * @return the Method or Constructor as Member
162 	 */
163 	public Member getMember() {
164 		// NOTE: no ternary expression to retain JDK <8 compatibility even when using
165 		// the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
166 		// as common type, with that new base class not available on older JDKs)
167 		if (this.method != null) {
168 			return this.method;
169 		}
170 		else {
171 			return this.constructor;
172 		}
173 	}
174 
175 	/**
176 	 * Returns the wrapped annotated element.
177 	 * @return the Method or Constructor as AnnotatedElement
178 	 */
179 	public AnnotatedElement getAnnotatedElement() {
180 		// NOTE: no ternary expression to retain JDK <8 compatibility even when using
181 		// the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
182 		// as common type, with that new base class not available on older JDKs)
183 		if (this.method != null) {
184 			return this.method;
185 		}
186 		else {
187 			return this.constructor;
188 		}
189 	}
190 
191 	/**
192 	 * Return the class that declares the underlying Method or Constructor.
193 	 */
194 	public Class<?> getDeclaringClass() {
195 		return getMember().getDeclaringClass();
196 	}
197 
198 	/**
199 	 * Return the index of the method/constructor parameter.
200 	 * @return the parameter index (never negative)
201 	 */
202 	public int getParameterIndex() {
203 		return this.parameterIndex;
204 	}
205 
206 	/**
207 	 * Increase this parameter's nesting level.
208 	 * @see #getNestingLevel()
209 	 */
210 	public void increaseNestingLevel() {
211 		this.nestingLevel++;
212 	}
213 
214 	/**
215 	 * Decrease this parameter's nesting level.
216 	 * @see #getNestingLevel()
217 	 */
218 	public void decreaseNestingLevel() {
219 		getTypeIndexesPerLevel().remove(this.nestingLevel);
220 		this.nestingLevel--;
221 	}
222 
223 	/**
224 	 * Return the nesting level of the target type
225 	 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
226 	 * nested List, whereas 2 would indicate the element of the nested List).
227 	 */
228 	public int getNestingLevel() {
229 		return this.nestingLevel;
230 	}
231 
232 	/**
233 	 * Set the type index for the current nesting level.
234 	 * @param typeIndex the corresponding type index
235 	 * (or {@code null} for the default type index)
236 	 * @see #getNestingLevel()
237 	 */
238 	public void setTypeIndexForCurrentLevel(int typeIndex) {
239 		getTypeIndexesPerLevel().put(this.nestingLevel, typeIndex);
240 	}
241 
242 	/**
243 	 * Return the type index for the current nesting level.
244 	 * @return the corresponding type index, or {@code null}
245 	 * if none specified (indicating the default type index)
246 	 * @see #getNestingLevel()
247 	 */
248 	public Integer getTypeIndexForCurrentLevel() {
249 		return getTypeIndexForLevel(this.nestingLevel);
250 	}
251 
252 	/**
253 	 * Return the type index for the specified nesting level.
254 	 * @param nestingLevel the nesting level to check
255 	 * @return the corresponding type index, or {@code null}
256 	 * if none specified (indicating the default type index)
257 	 */
258 	public Integer getTypeIndexForLevel(int nestingLevel) {
259 		return getTypeIndexesPerLevel().get(nestingLevel);
260 	}
261 
262 	/**
263 	 * Obtain the (lazily constructed) type-indexes-per-level Map.
264 	 */
265 	private Map<Integer, Integer> getTypeIndexesPerLevel() {
266 		if (this.typeIndexesPerLevel == null) {
267 			this.typeIndexesPerLevel = new HashMap<Integer, Integer>(4);
268 		}
269 		return this.typeIndexesPerLevel;
270 	}
271 
272 
273 	/**
274 	 * Set a containing class to resolve the parameter type against.
275 	 */
276 	void setContainingClass(Class<?> containingClass) {
277 		this.containingClass = containingClass;
278 	}
279 
280 	public Class<?> getContainingClass() {
281 		return (this.containingClass != null ? this.containingClass : getDeclaringClass());
282 	}
283 
284 	/**
285 	 * Set a resolved (generic) parameter type.
286 	 */
287 	void setParameterType(Class<?> parameterType) {
288 		this.parameterType = parameterType;
289 	}
290 
291 	/**
292 	 * Return the type of the method/constructor parameter.
293 	 * @return the parameter type (never {@code null})
294 	 */
295 	public Class<?> getParameterType() {
296 		if (this.parameterType == null) {
297 			if (this.parameterIndex < 0) {
298 				this.parameterType = (this.method != null ? this.method.getReturnType() : null);
299 			}
300 			else {
301 				this.parameterType = (this.method != null ?
302 					this.method.getParameterTypes()[this.parameterIndex] :
303 					this.constructor.getParameterTypes()[this.parameterIndex]);
304 			}
305 		}
306 		return this.parameterType;
307 	}
308 
309 	/**
310 	 * Return the generic type of the method/constructor parameter.
311 	 * @return the parameter type (never {@code null})
312 	 * @since 3.0
313 	 */
314 	public Type getGenericParameterType() {
315 		if (this.genericParameterType == null) {
316 			if (this.parameterIndex < 0) {
317 				this.genericParameterType = (this.method != null ? this.method.getGenericReturnType() : null);
318 			}
319 			else {
320 				this.genericParameterType = (this.method != null ?
321 					this.method.getGenericParameterTypes()[this.parameterIndex] :
322 					this.constructor.getGenericParameterTypes()[this.parameterIndex]);
323 			}
324 		}
325 		return this.genericParameterType;
326 	}
327 
328 	/**
329 	 * Return the nested type of the method/constructor parameter.
330 	 * @return the parameter type (never {@code null})
331 	 * @see #getNestingLevel()
332 	 * @since 3.1
333 	 */
334 	public Class<?> getNestedParameterType() {
335 		if (this.nestingLevel > 1) {
336 			Type type = getGenericParameterType();
337 			for (int i = 2; i <= this.nestingLevel; i++) {
338 				if (type instanceof ParameterizedType) {
339 					Type[] args = ((ParameterizedType) type).getActualTypeArguments();
340 					Integer index = getTypeIndexForLevel(i);
341 					type = args[index != null ? index : args.length - 1];
342 				}
343 			}
344 			if (type instanceof Class) {
345 				return (Class<?>) type;
346 			}
347 			else if (type instanceof ParameterizedType) {
348 				Type arg = ((ParameterizedType) type).getRawType();
349 				if (arg instanceof Class) {
350 					return (Class<?>) arg;
351 				}
352 			}
353 			return Object.class;
354 		}
355 		else {
356 			return getParameterType();
357 		}
358 	}
359 
360 	/**
361 	 * Return the annotations associated with the target method/constructor itself.
362 	 */
363 	public Annotation[] getMethodAnnotations() {
364 		return getAnnotatedElement().getAnnotations();
365 	}
366 
367 	/**
368 	 * Return the method/constructor annotation of the given type, if available.
369 	 * @param annotationType the annotation type to look for
370 	 * @return the annotation object, or {@code null} if not found
371 	 */
372 	public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) {
373 		return getAnnotatedElement().getAnnotation(annotationType);
374 	}
375 
376 	/**
377 	 * Return the annotations associated with the specific method/constructor parameter.
378 	 */
379 	public Annotation[] getParameterAnnotations() {
380 		if (this.parameterAnnotations == null) {
381 			Annotation[][] annotationArray = (this.method != null ?
382 					this.method.getParameterAnnotations() : this.constructor.getParameterAnnotations());
383 			if (this.parameterIndex >= 0 && this.parameterIndex < annotationArray.length) {
384 				this.parameterAnnotations = annotationArray[this.parameterIndex];
385 			}
386 			else {
387 				this.parameterAnnotations = new Annotation[0];
388 			}
389 		}
390 		return this.parameterAnnotations;
391 	}
392 
393 	/**
394 	 * Return the parameter annotation of the given type, if available.
395 	 * @param annotationType the annotation type to look for
396 	 * @return the annotation object, or {@code null} if not found
397 	 */
398 	@SuppressWarnings("unchecked")
399 	public <T extends Annotation> T getParameterAnnotation(Class<T> annotationType) {
400 		Annotation[] anns = getParameterAnnotations();
401 		for (Annotation ann : anns) {
402 			if (annotationType.isInstance(ann)) {
403 				return (T) ann;
404 			}
405 		}
406 		return null;
407 	}
408 
409 	/**
410 	 * Return true if the parameter has at least one annotation, false if it has none.
411 	 */
412 	public boolean hasParameterAnnotations() {
413 		return (getParameterAnnotations().length != 0);
414 	}
415 
416 	/**
417 	 * Return true if the parameter has the given annotation type, and false if it doesn't.
418 	 */
419 	public <T extends Annotation> boolean hasParameterAnnotation(Class<T> annotationType) {
420 		return (getParameterAnnotation(annotationType) != null);
421 	}
422 
423 	/**
424 	 * Initialize parameter name discovery for this method parameter.
425 	 * <p>This method does not actually try to retrieve the parameter name at
426 	 * this point; it just allows discovery to happen when the application calls
427 	 * {@link #getParameterName()} (if ever).
428 	 */
429 	public void initParameterNameDiscovery(ParameterNameDiscoverer parameterNameDiscoverer) {
430 		this.parameterNameDiscoverer = parameterNameDiscoverer;
431 	}
432 
433 	/**
434 	 * Return the name of the method/constructor parameter.
435 	 * @return the parameter name (may be {@code null} if no
436 	 * parameter name metadata is contained in the class file or no
437 	 * {@link #initParameterNameDiscovery ParameterNameDiscoverer}
438 	 * has been set to begin with)
439 	 */
440 	public String getParameterName() {
441 		ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;
442 		if (discoverer != null) {
443 			String[] parameterNames = (this.method != null ?
444 					discoverer.getParameterNames(this.method) : discoverer.getParameterNames(this.constructor));
445 			if (parameterNames != null) {
446 				this.parameterName = parameterNames[this.parameterIndex];
447 			}
448 			this.parameterNameDiscoverer = null;
449 		}
450 		return this.parameterName;
451 	}
452 
453 
454 	@Override
455 	public boolean equals(Object other) {
456 		if (this == other) {
457 			return true;
458 		}
459 		if (!(other instanceof MethodParameter)) {
460 			return false;
461 		}
462 		MethodParameter otherParam = (MethodParameter) other;
463 		return (this.parameterIndex == otherParam.parameterIndex && getMember().equals(otherParam.getMember()));
464 	}
465 
466 	@Override
467 	public int hashCode() {
468 		return (getMember().hashCode() * 31 + this.parameterIndex);
469 	}
470 
471 
472 	/**
473 	 * Create a new MethodParameter for the given method or constructor.
474 	 * <p>This is a convenience constructor for scenarios where a
475 	 * Method or Constructor reference is treated in a generic fashion.
476 	 * @param methodOrConstructor the Method or Constructor to specify a parameter for
477 	 * @param parameterIndex the index of the parameter
478 	 * @return the corresponding MethodParameter instance
479 	 */
480 	public static MethodParameter forMethodOrConstructor(Object methodOrConstructor, int parameterIndex) {
481 		if (methodOrConstructor instanceof Method) {
482 			return new MethodParameter((Method) methodOrConstructor, parameterIndex);
483 		}
484 		else if (methodOrConstructor instanceof Constructor) {
485 			return new MethodParameter((Constructor<?>) methodOrConstructor, parameterIndex);
486 		}
487 		else {
488 			throw new IllegalArgumentException(
489 					"Given object [" + methodOrConstructor + "] is neither a Method nor a Constructor");
490 		}
491 	}
492 
493 }