001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.core; 018 019import java.lang.annotation.Annotation; 020import java.lang.reflect.AnnotatedElement; 021import java.lang.reflect.Constructor; 022import java.lang.reflect.Member; 023import java.lang.reflect.Method; 024import java.lang.reflect.Modifier; 025import java.lang.reflect.ParameterizedType; 026import java.lang.reflect.Type; 027import java.util.HashMap; 028import java.util.Map; 029 030import org.springframework.util.Assert; 031import org.springframework.util.ClassUtils; 032import org.springframework.util.ObjectUtils; 033 034/** 035 * Helper class that encapsulates the specification of a method parameter, i.e. a {@link Method} 036 * or {@link Constructor} plus a parameter index and a nested type index for a declared generic 037 * type. Useful as a specification object to pass along. 038 * 039 * <p>As of 4.2, there is a {@link org.springframework.core.annotation.SynthesizingMethodParameter} 040 * subclass available which synthesizes annotations with attribute aliases. That subclass is used 041 * for web and message endpoint processing, in particular. 042 * 043 * @author Juergen Hoeller 044 * @author Rob Harrop 045 * @author Andy Clement 046 * @author Sam Brannen 047 * @since 2.0 048 * @see org.springframework.core.annotation.SynthesizingMethodParameter 049 */ 050public class MethodParameter { 051 052 private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; 053 054 private static final Class<?> javaUtilOptionalClass; 055 056 static { 057 Class<?> clazz; 058 try { 059 clazz = ClassUtils.forName("java.util.Optional", MethodParameter.class.getClassLoader()); 060 } 061 catch (ClassNotFoundException ex) { 062 // Java 8 not available - Optional references simply not supported then. 063 clazz = null; 064 } 065 javaUtilOptionalClass = clazz; 066 } 067 068 069 private final Method method; 070 071 private final Constructor<?> constructor; 072 073 private final int parameterIndex; 074 075 private int nestingLevel; 076 077 /** Map from Integer level to Integer type index */ 078 Map<Integer, Integer> typeIndexesPerLevel; 079 080 /** The containing class. Could also be supplied by overriding {@link #getContainingClass()} */ 081 private volatile Class<?> containingClass; 082 083 private volatile Class<?> parameterType; 084 085 private volatile Type genericParameterType; 086 087 private volatile Annotation[] parameterAnnotations; 088 089 private volatile ParameterNameDiscoverer parameterNameDiscoverer; 090 091 private volatile String parameterName; 092 093 private volatile MethodParameter nestedMethodParameter; 094 095 096 /** 097 * Create a new {@code MethodParameter} for the given method, with nesting level 1. 098 * @param method the Method to specify a parameter for 099 * @param parameterIndex the index of the parameter: -1 for the method 100 * return type; 0 for the first method parameter; 1 for the second method 101 * parameter, etc. 102 */ 103 public MethodParameter(Method method, int parameterIndex) { 104 this(method, parameterIndex, 1); 105 } 106 107 /** 108 * Create a new {@code MethodParameter} for the given method. 109 * @param method the Method to specify a parameter for 110 * @param parameterIndex the index of the parameter: -1 for the method 111 * return type; 0 for the first method parameter; 1 for the second method 112 * parameter, etc. 113 * @param nestingLevel the nesting level of the target type 114 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the 115 * nested List, whereas 2 would indicate the element of the nested List) 116 */ 117 public MethodParameter(Method method, int parameterIndex, int nestingLevel) { 118 Assert.notNull(method, "Method must not be null"); 119 this.method = method; 120 this.parameterIndex = parameterIndex; 121 this.nestingLevel = nestingLevel; 122 this.constructor = null; 123 } 124 125 /** 126 * Create a new MethodParameter for the given constructor, with nesting level 1. 127 * @param constructor the Constructor to specify a parameter for 128 * @param parameterIndex the index of the parameter 129 */ 130 public MethodParameter(Constructor<?> constructor, int parameterIndex) { 131 this(constructor, parameterIndex, 1); 132 } 133 134 /** 135 * Create a new MethodParameter for the given constructor. 136 * @param constructor the Constructor to specify a parameter for 137 * @param parameterIndex the index of the parameter 138 * @param nestingLevel the nesting level of the target type 139 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the 140 * nested List, whereas 2 would indicate the element of the nested List) 141 */ 142 public MethodParameter(Constructor<?> constructor, int parameterIndex, int nestingLevel) { 143 Assert.notNull(constructor, "Constructor must not be null"); 144 this.constructor = constructor; 145 this.parameterIndex = parameterIndex; 146 this.nestingLevel = nestingLevel; 147 this.method = null; 148 } 149 150 /** 151 * Copy constructor, resulting in an independent MethodParameter object 152 * based on the same metadata and cache state that the original object was in. 153 * @param original the original MethodParameter object to copy from 154 */ 155 public MethodParameter(MethodParameter original) { 156 Assert.notNull(original, "Original must not be null"); 157 this.method = original.method; 158 this.constructor = original.constructor; 159 this.parameterIndex = original.parameterIndex; 160 this.nestingLevel = original.nestingLevel; 161 this.typeIndexesPerLevel = original.typeIndexesPerLevel; 162 this.containingClass = original.containingClass; 163 this.parameterType = original.parameterType; 164 this.genericParameterType = original.genericParameterType; 165 this.parameterAnnotations = original.parameterAnnotations; 166 this.parameterNameDiscoverer = original.parameterNameDiscoverer; 167 this.parameterName = original.parameterName; 168 } 169 170 171 /** 172 * Return the wrapped Method, if any. 173 * <p>Note: Either Method or Constructor is available. 174 * @return the Method, or {@code null} if none 175 */ 176 public Method getMethod() { 177 return this.method; 178 } 179 180 /** 181 * Return the wrapped Constructor, if any. 182 * <p>Note: Either Method or Constructor is available. 183 * @return the Constructor, or {@code null} if none 184 */ 185 public Constructor<?> getConstructor() { 186 return this.constructor; 187 } 188 189 /** 190 * Return the class that declares the underlying Method or Constructor. 191 */ 192 public Class<?> getDeclaringClass() { 193 return getMember().getDeclaringClass(); 194 } 195 196 /** 197 * Return the wrapped member. 198 * @return the Method or Constructor as Member 199 */ 200 public Member getMember() { 201 // NOTE: no ternary expression to retain JDK <8 compatibility even when using 202 // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable 203 // as common type, with that new base class not available on older JDKs) 204 if (this.method != null) { 205 return this.method; 206 } 207 else { 208 return this.constructor; 209 } 210 } 211 212 /** 213 * Return the wrapped annotated element. 214 * <p>Note: This method exposes the annotations declared on the method/constructor 215 * itself (i.e. at the method/constructor level, not at the parameter level). 216 * @return the Method or Constructor as AnnotatedElement 217 */ 218 public AnnotatedElement getAnnotatedElement() { 219 // NOTE: no ternary expression to retain JDK <8 compatibility even when using 220 // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable 221 // as common type, with that new base class not available on older JDKs) 222 if (this.method != null) { 223 return this.method; 224 } 225 else { 226 return this.constructor; 227 } 228 } 229 230 /** 231 * Return the index of the method/constructor parameter. 232 * @return the parameter index (-1 in case of the return type) 233 */ 234 public int getParameterIndex() { 235 return this.parameterIndex; 236 } 237 238 /** 239 * Increase this parameter's nesting level. 240 * @see #getNestingLevel() 241 */ 242 public void increaseNestingLevel() { 243 this.nestingLevel++; 244 } 245 246 /** 247 * Decrease this parameter's nesting level. 248 * @see #getNestingLevel() 249 */ 250 public void decreaseNestingLevel() { 251 getTypeIndexesPerLevel().remove(this.nestingLevel); 252 this.nestingLevel--; 253 } 254 255 /** 256 * Return the nesting level of the target type 257 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the 258 * nested List, whereas 2 would indicate the element of the nested List). 259 */ 260 public int getNestingLevel() { 261 return this.nestingLevel; 262 } 263 264 /** 265 * Set the type index for the current nesting level. 266 * @param typeIndex the corresponding type index 267 * (or {@code null} for the default type index) 268 * @see #getNestingLevel() 269 */ 270 public void setTypeIndexForCurrentLevel(int typeIndex) { 271 getTypeIndexesPerLevel().put(this.nestingLevel, typeIndex); 272 } 273 274 /** 275 * Return the type index for the current nesting level. 276 * @return the corresponding type index, or {@code null} 277 * if none specified (indicating the default type index) 278 * @see #getNestingLevel() 279 */ 280 public Integer getTypeIndexForCurrentLevel() { 281 return getTypeIndexForLevel(this.nestingLevel); 282 } 283 284 /** 285 * Return the type index for the specified nesting level. 286 * @param nestingLevel the nesting level to check 287 * @return the corresponding type index, or {@code null} 288 * if none specified (indicating the default type index) 289 */ 290 public Integer getTypeIndexForLevel(int nestingLevel) { 291 return getTypeIndexesPerLevel().get(nestingLevel); 292 } 293 294 /** 295 * Obtain the (lazily constructed) type-indexes-per-level Map. 296 */ 297 private Map<Integer, Integer> getTypeIndexesPerLevel() { 298 if (this.typeIndexesPerLevel == null) { 299 this.typeIndexesPerLevel = new HashMap<Integer, Integer>(4); 300 } 301 return this.typeIndexesPerLevel; 302 } 303 304 /** 305 * Return a variant of this {@code MethodParameter} which points to the 306 * same parameter but one nesting level deeper. This is effectively the 307 * same as {@link #increaseNestingLevel()}, just with an independent 308 * {@code MethodParameter} object (e.g. in case of the original being cached). 309 * @since 4.3 310 */ 311 public MethodParameter nested() { 312 if (this.nestedMethodParameter != null) { 313 return this.nestedMethodParameter; 314 } 315 MethodParameter nestedParam = clone(); 316 nestedParam.nestingLevel = this.nestingLevel + 1; 317 this.nestedMethodParameter = nestedParam; 318 return nestedParam; 319 } 320 321 /** 322 * Return whether this method parameter is declared as optional 323 * in the form of Java 8's {@link java.util.Optional}. 324 * @since 4.3 325 */ 326 public boolean isOptional() { 327 return (getParameterType() == javaUtilOptionalClass); 328 } 329 330 /** 331 * Return a variant of this {@code MethodParameter} which points to 332 * the same parameter but one nesting level deeper in case of a 333 * {@link java.util.Optional} declaration. 334 * @since 4.3 335 * @see #isOptional() 336 * @see #nested() 337 */ 338 public MethodParameter nestedIfOptional() { 339 return (isOptional() ? nested() : this); 340 } 341 342 /** 343 * Set a containing class to resolve the parameter type against. 344 */ 345 void setContainingClass(Class<?> containingClass) { 346 this.containingClass = containingClass; 347 } 348 349 /** 350 * Return the containing class for this method parameter. 351 * @return a specific containing class (potentially a subclass of the 352 * declaring class), or otherwise simply the declaring class itself 353 * @see #getDeclaringClass() 354 */ 355 public Class<?> getContainingClass() { 356 return (this.containingClass != null ? this.containingClass : getDeclaringClass()); 357 } 358 359 /** 360 * Set a resolved (generic) parameter type. 361 */ 362 void setParameterType(Class<?> parameterType) { 363 this.parameterType = parameterType; 364 } 365 366 /** 367 * Return the type of the method/constructor parameter. 368 * @return the parameter type (never {@code null}) 369 */ 370 public Class<?> getParameterType() { 371 Class<?> paramType = this.parameterType; 372 if (paramType == null) { 373 if (this.parameterIndex < 0) { 374 Method method = getMethod(); 375 paramType = (method != null ? method.getReturnType() : void.class); 376 } 377 else { 378 paramType = (this.method != null ? 379 this.method.getParameterTypes()[this.parameterIndex] : 380 this.constructor.getParameterTypes()[this.parameterIndex]); 381 } 382 this.parameterType = paramType; 383 } 384 return paramType; 385 } 386 387 /** 388 * Return the generic type of the method/constructor parameter. 389 * @return the parameter type (never {@code null}) 390 * @since 3.0 391 */ 392 public Type getGenericParameterType() { 393 Type paramType = this.genericParameterType; 394 if (paramType == null) { 395 if (this.parameterIndex < 0) { 396 Method method = getMethod(); 397 paramType = (method != null ? method.getGenericReturnType() : void.class); 398 } 399 else { 400 Type[] genericParameterTypes = (this.method != null ? 401 this.method.getGenericParameterTypes() : this.constructor.getGenericParameterTypes()); 402 int index = this.parameterIndex; 403 if (this.constructor != null && this.constructor.getDeclaringClass().isMemberClass() && 404 !Modifier.isStatic(this.constructor.getDeclaringClass().getModifiers()) && 405 genericParameterTypes.length == this.constructor.getParameterTypes().length - 1) { 406 // Bug in javac: type array excludes enclosing instance parameter 407 // for inner classes with at least one generic constructor parameter, 408 // so access it with the actual parameter index lowered by 1 409 index = this.parameterIndex - 1; 410 } 411 paramType = (index >= 0 && index < genericParameterTypes.length ? 412 genericParameterTypes[index] : getParameterType()); 413 } 414 this.genericParameterType = paramType; 415 } 416 return paramType; 417 } 418 419 /** 420 * Return the nested type of the method/constructor parameter. 421 * @return the parameter type (never {@code null}) 422 * @since 3.1 423 * @see #getNestingLevel() 424 */ 425 public Class<?> getNestedParameterType() { 426 if (this.nestingLevel > 1) { 427 Type type = getGenericParameterType(); 428 for (int i = 2; i <= this.nestingLevel; i++) { 429 if (type instanceof ParameterizedType) { 430 Type[] args = ((ParameterizedType) type).getActualTypeArguments(); 431 Integer index = getTypeIndexForLevel(i); 432 type = args[index != null ? index : args.length - 1]; 433 } 434 // TODO: Object.class if unresolvable 435 } 436 if (type instanceof Class) { 437 return (Class<?>) type; 438 } 439 else if (type instanceof ParameterizedType) { 440 Type arg = ((ParameterizedType) type).getRawType(); 441 if (arg instanceof Class) { 442 return (Class<?>) arg; 443 } 444 } 445 return Object.class; 446 } 447 else { 448 return getParameterType(); 449 } 450 } 451 452 /** 453 * Return the nested generic type of the method/constructor parameter. 454 * @return the parameter type (never {@code null}) 455 * @since 4.2 456 * @see #getNestingLevel() 457 */ 458 public Type getNestedGenericParameterType() { 459 if (this.nestingLevel > 1) { 460 Type type = getGenericParameterType(); 461 for (int i = 2; i <= this.nestingLevel; i++) { 462 if (type instanceof ParameterizedType) { 463 Type[] args = ((ParameterizedType) type).getActualTypeArguments(); 464 Integer index = getTypeIndexForLevel(i); 465 type = args[index != null ? index : args.length - 1]; 466 } 467 } 468 return type; 469 } 470 else { 471 return getGenericParameterType(); 472 } 473 } 474 475 /** 476 * Return the annotations associated with the target method/constructor itself. 477 */ 478 public Annotation[] getMethodAnnotations() { 479 return adaptAnnotationArray(getAnnotatedElement().getAnnotations()); 480 } 481 482 /** 483 * Return the method/constructor annotation of the given type, if available. 484 * @param annotationType the annotation type to look for 485 * @return the annotation object, or {@code null} if not found 486 */ 487 public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) { 488 return adaptAnnotation(getAnnotatedElement().getAnnotation(annotationType)); 489 } 490 491 /** 492 * Return whether the method/constructor is annotated with the given type. 493 * @param annotationType the annotation type to look for 494 * @since 4.3 495 * @see #getMethodAnnotation(Class) 496 */ 497 public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) { 498 return getAnnotatedElement().isAnnotationPresent(annotationType); 499 } 500 501 /** 502 * Return the annotations associated with the specific method/constructor parameter. 503 */ 504 public Annotation[] getParameterAnnotations() { 505 Annotation[] paramAnns = this.parameterAnnotations; 506 if (paramAnns == null) { 507 Annotation[][] annotationArray = (this.method != null ? 508 this.method.getParameterAnnotations() : this.constructor.getParameterAnnotations()); 509 int index = this.parameterIndex; 510 if (this.constructor != null && this.constructor.getDeclaringClass().isMemberClass() && 511 !Modifier.isStatic(this.constructor.getDeclaringClass().getModifiers()) && 512 annotationArray.length == this.constructor.getParameterTypes().length - 1) { 513 // Bug in javac in JDK <9: annotation array excludes enclosing instance parameter 514 // for inner classes, so access it with the actual parameter index lowered by 1 515 index = this.parameterIndex - 1; 516 } 517 paramAnns = (index >= 0 && index < annotationArray.length ? 518 adaptAnnotationArray(annotationArray[index]) : EMPTY_ANNOTATION_ARRAY); 519 this.parameterAnnotations = paramAnns; 520 } 521 return paramAnns; 522 } 523 524 /** 525 * Return {@code true} if the parameter has at least one annotation, 526 * {@code false} if it has none. 527 * @see #getParameterAnnotations() 528 */ 529 public boolean hasParameterAnnotations() { 530 return (getParameterAnnotations().length != 0); 531 } 532 533 /** 534 * Return the parameter annotation of the given type, if available. 535 * @param annotationType the annotation type to look for 536 * @return the annotation object, or {@code null} if not found 537 */ 538 @SuppressWarnings("unchecked") 539 public <A extends Annotation> A getParameterAnnotation(Class<A> annotationType) { 540 Annotation[] anns = getParameterAnnotations(); 541 for (Annotation ann : anns) { 542 if (annotationType.isInstance(ann)) { 543 return (A) ann; 544 } 545 } 546 return null; 547 } 548 549 /** 550 * Return whether the parameter is declared with the given annotation type. 551 * @param annotationType the annotation type to look for 552 * @see #getParameterAnnotation(Class) 553 */ 554 public <A extends Annotation> boolean hasParameterAnnotation(Class<A> annotationType) { 555 return (getParameterAnnotation(annotationType) != null); 556 } 557 558 /** 559 * Initialize parameter name discovery for this method parameter. 560 * <p>This method does not actually try to retrieve the parameter name at 561 * this point; it just allows discovery to happen when the application calls 562 * {@link #getParameterName()} (if ever). 563 */ 564 public void initParameterNameDiscovery(ParameterNameDiscoverer parameterNameDiscoverer) { 565 this.parameterNameDiscoverer = parameterNameDiscoverer; 566 } 567 568 /** 569 * Return the name of the method/constructor parameter. 570 * @return the parameter name (may be {@code null} if no 571 * parameter name metadata is contained in the class file or no 572 * {@link #initParameterNameDiscovery ParameterNameDiscoverer} 573 * has been set to begin with) 574 */ 575 public String getParameterName() { 576 ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer; 577 if (discoverer != null) { 578 String[] parameterNames = (this.method != null ? 579 discoverer.getParameterNames(this.method) : discoverer.getParameterNames(this.constructor)); 580 if (parameterNames != null) { 581 this.parameterName = parameterNames[this.parameterIndex]; 582 } 583 this.parameterNameDiscoverer = null; 584 } 585 return this.parameterName; 586 } 587 588 589 /** 590 * A template method to post-process a given annotation instance before 591 * returning it to the caller. 592 * <p>The default implementation simply returns the given annotation as-is. 593 * @param annotation the annotation about to be returned 594 * @return the post-processed annotation (or simply the original one) 595 * @since 4.2 596 */ 597 protected <A extends Annotation> A adaptAnnotation(A annotation) { 598 return annotation; 599 } 600 601 /** 602 * A template method to post-process a given annotation array before 603 * returning it to the caller. 604 * <p>The default implementation simply returns the given annotation array as-is. 605 * @param annotations the annotation array about to be returned 606 * @return the post-processed annotation array (or simply the original one) 607 * @since 4.2 608 */ 609 protected Annotation[] adaptAnnotationArray(Annotation[] annotations) { 610 return annotations; 611 } 612 613 614 @Override 615 public boolean equals(Object other) { 616 if (this == other) { 617 return true; 618 } 619 if (!(other instanceof MethodParameter)) { 620 return false; 621 } 622 MethodParameter otherParam = (MethodParameter) other; 623 return (getContainingClass() == otherParam.getContainingClass() && 624 ObjectUtils.nullSafeEquals(this.typeIndexesPerLevel, otherParam.typeIndexesPerLevel) && 625 this.nestingLevel == otherParam.nestingLevel && 626 this.parameterIndex == otherParam.parameterIndex && 627 getMember().equals(otherParam.getMember())); 628 } 629 630 @Override 631 public int hashCode() { 632 return (getMember().hashCode() * 31 + this.parameterIndex); 633 } 634 635 @Override 636 public String toString() { 637 return (this.method != null ? "method '" + this.method.getName() + "'" : "constructor") + 638 " parameter " + this.parameterIndex; 639 } 640 641 @Override 642 public MethodParameter clone() { 643 return new MethodParameter(this); 644 } 645 646 647 /** 648 * Create a new MethodParameter for the given method or constructor. 649 * <p>This is a convenience constructor for scenarios where a 650 * Method or Constructor reference is treated in a generic fashion. 651 * @param methodOrConstructor the Method or Constructor to specify a parameter for 652 * @param parameterIndex the index of the parameter 653 * @return the corresponding MethodParameter instance 654 */ 655 public static MethodParameter forMethodOrConstructor(Object methodOrConstructor, int parameterIndex) { 656 if (methodOrConstructor instanceof Method) { 657 return new MethodParameter((Method) methodOrConstructor, parameterIndex); 658 } 659 else if (methodOrConstructor instanceof Constructor) { 660 return new MethodParameter((Constructor<?>) methodOrConstructor, parameterIndex); 661 } 662 else { 663 throw new IllegalArgumentException( 664 "Given object [" + methodOrConstructor + "] is neither a Method nor a Constructor"); 665 } 666 } 667 668}