org.springframework.core.MethodParameter Maven / Gradle / Ivy
Show all versions of spring-annotation Show documentation
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.core;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import kotlin.reflect.KFunction;
import kotlin.reflect.KParameter;
import kotlin.reflect.jvm.ReflectJvmMapping;
/**
* Helper class that encapsulates the specification of a method parameter, i.e. a {@link Method} or
* {@link Constructor} plus a parameter index and a nested type index for a declared generic type.
* Useful as a specification object to pass along.
*
*
* As of 4.2, there is a {@link org.springframework.core.annotation.SynthesizingMethodParameter}
* subclass available which synthesizes annotations with attribute aliases. That subclass is used
* for web and message endpoint processing, in particular.
*
* @author Juergen Hoeller
* @author Rob Harrop
* @author Andy Clement
* @author Sam Brannen
* @author Sebastien Deleuze
* @since 2.0
* @see org.springframework.core.annotation.SynthesizingMethodParameter
*/
public class MethodParameter {
private final Executable executable;
private final int parameterIndex;
@Nullable
private volatile Parameter parameter;
private int nestingLevel = 1;
/** Map from Integer level to Integer type index */
@Nullable
Map typeIndexesPerLevel;
@Nullable
private volatile Class containingClass;
@Nullable
private volatile Class parameterType;
@Nullable
private volatile Type genericParameterType;
@Nullable
private volatile Annotation[] parameterAnnotations;
@Nullable
private volatile ParameterNameDiscoverer parameterNameDiscoverer;
@Nullable
private volatile String parameterName;
@Nullable
private volatile MethodParameter nestedMethodParameter;
/**
* Create a new {@code MethodParameter} for the given method, with nesting level 1.
*
* @param method the Method to specify a parameter for
* @param parameterIndex the index of the parameter: -1 for the method return type; 0 for the
* first method parameter; 1 for the second method parameter, etc.
*/
public MethodParameter(Method method, int parameterIndex) {
this(method, parameterIndex, 1);
}
/**
* Create a new {@code MethodParameter} for the given method.
*
* @param method the Method to specify a parameter for
* @param parameterIndex the index of the parameter: -1 for the method return type; 0 for the
* first method parameter; 1 for the second method parameter, etc.
* @param nestingLevel the nesting level of the target type (typically 1; e.g. in case of a List
* of Lists, 1 would indicate the nested List, whereas 2 would indicate the element of
* the nested List)
*/
public MethodParameter(Method method, int parameterIndex, int nestingLevel) {
Assert.notNull(method, "Method must not be null");
this.executable = method;
this.parameterIndex = validateIndex(method, parameterIndex);
this.nestingLevel = nestingLevel;
}
/**
* Create a new MethodParameter for the given constructor, with nesting level 1.
*
* @param constructor the Constructor to specify a parameter for
* @param parameterIndex the index of the parameter
*/
public MethodParameter(Constructor constructor, int parameterIndex) {
this(constructor, parameterIndex, 1);
}
/**
* Create a new MethodParameter for the given constructor.
*
* @param constructor the Constructor to specify a parameter for
* @param parameterIndex the index of the parameter
* @param nestingLevel the nesting level of the target type (typically 1; e.g. in case of a List
* of Lists, 1 would indicate the nested List, whereas 2 would indicate the element of
* the nested List)
*/
public MethodParameter(Constructor constructor, int parameterIndex, int nestingLevel) {
Assert.notNull(constructor, "Constructor must not be null");
this.executable = constructor;
this.parameterIndex = validateIndex(constructor, parameterIndex);
this.nestingLevel = nestingLevel;
}
/**
* Copy constructor, resulting in an independent MethodParameter object based on the same metadata
* and cache state that the original object was in.
*
* @param original the original MethodParameter object to copy from
*/
public MethodParameter(MethodParameter original) {
Assert.notNull(original, "Original must not be null");
this.executable = original.executable;
this.parameterIndex = original.parameterIndex;
this.parameter = original.parameter;
this.nestingLevel = original.nestingLevel;
this.typeIndexesPerLevel = original.typeIndexesPerLevel;
this.containingClass = original.containingClass;
this.parameterType = original.parameterType;
this.genericParameterType = original.genericParameterType;
this.parameterAnnotations = original.parameterAnnotations;
this.parameterNameDiscoverer = original.parameterNameDiscoverer;
this.parameterName = original.parameterName;
}
/**
* Return the wrapped Method, if any.
*
* Note: Either Method or Constructor is available.
*
* @return the Method, or {@code null} if none
*/
@Nullable
public Method getMethod() {
return (this.executable instanceof Method ? (Method) this.executable : null);
}
/**
* Return the wrapped Constructor, if any.
*
* Note: Either Method or Constructor is available.
*
* @return the Constructor, or {@code null} if none
*/
@Nullable
public Constructor getConstructor() {
return (this.executable instanceof Constructor ? (Constructor) this.executable : null);
}
/**
* Return the class that declares the underlying Method or Constructor.
*/
public Class getDeclaringClass() {
return this.executable.getDeclaringClass();
}
/**
* Return the wrapped member.
*
* @return the Method or Constructor as Member
*/
public Member getMember() {
return this.executable;
}
/**
* Return the wrapped annotated element.
*
* Note: This method exposes the annotations declared on the method/constructor itself (i.e. at
* the method/constructor level, not at the parameter level).
*
* @return the Method or Constructor as AnnotatedElement
*/
public AnnotatedElement getAnnotatedElement() {
return this.executable;
}
/**
* Return the wrapped executable.
*
* @return the Method or Constructor as Executable
* @since 5.0
*/
public Executable getExecutable() {
return this.executable;
}
/**
* Return the {@link Parameter} descriptor for method/constructor parameter.
*
* @since 5.0
*/
public Parameter getParameter() {
Parameter parameter = this.parameter;
if (parameter == null) {
parameter = getExecutable().getParameters()[this.parameterIndex];
this.parameter = parameter;
}
return parameter;
}
/**
* Return the index of the method/constructor parameter.
*
* @return the parameter index (-1 in case of the return type)
*/
public int getParameterIndex() {
return this.parameterIndex;
}
/**
* Return the nesting level of the target type (typically 1; e.g. in case of a List of Lists, 1
* would indicate the nested List, whereas 2 would indicate the element of the nested List).
*/
public int getNestingLevel() {
return this.nestingLevel;
}
/**
* Set the type index for the current nesting level.
*
* @param typeIndex the corresponding type index (or {@code null} for the default type index)
* @see #getNestingLevel()
*/
public void setTypeIndexForCurrentLevel(int typeIndex) {
getTypeIndexesPerLevel().put(this.nestingLevel, typeIndex);
}
/**
* Return the type index for the current nesting level.
*
* @return the corresponding type index, or {@code null} if none specified (indicating the default
* type index)
* @see #getNestingLevel()
*/
@Nullable
public Integer getTypeIndexForCurrentLevel() {
return getTypeIndexForLevel(this.nestingLevel);
}
/**
* Return the type index for the specified nesting level.
*
* @param nestingLevel the nesting level to check
* @return the corresponding type index, or {@code null} if none specified (indicating the default
* type index)
*/
@Nullable
public Integer getTypeIndexForLevel(int nestingLevel) {
return getTypeIndexesPerLevel().get(nestingLevel);
}
/**
* Obtain the (lazily constructed) type-indexes-per-level Map.
*/
private Map getTypeIndexesPerLevel() {
if (this.typeIndexesPerLevel == null) {
this.typeIndexesPerLevel = new HashMap<>(4);
}
return this.typeIndexesPerLevel;
}
/**
* Return whether this method indicates a parameter which is not required: either in the form of
* Java 8's {@link java.util.Optional}, any variant of a parameter-level {@code Nullable}
* annotation (such as from JSR-305 or the FindBugs set of annotations), or a language-level
* nullable type declaration in Kotlin.
*
* @since 4.3
*/
public boolean isOptional() {
return (getParameterType() == Optional.class || hasNullableAnnotation() ||
(KotlinDetector.isKotlinType(getContainingClass()) && KotlinDelegate.isOptional(this)));
}
/**
* Check whether this method parameter is annotated with any variant of a {@code Nullable}
* annotation, e.g. {@code javax.annotation.Nullable} or
* {@code edu.umd.cs.findbugs.annotations.Nullable}.
*/
private boolean hasNullableAnnotation() {
for (Annotation ann : getParameterAnnotations()) {
if ("Nullable".equals(ann.annotationType().getSimpleName())) {
return true;
}
}
return false;
}
/**
* Set a containing class to resolve the parameter type against.
*/
void setContainingClass(Class containingClass) {
this.containingClass = containingClass;
}
public Class getContainingClass() {
Class containingClass = this.containingClass;
return (containingClass != null ? containingClass : getDeclaringClass());
}
/**
* Return the type of the method/constructor parameter.
*
* @return the parameter type (never {@code null})
*/
public Class getParameterType() {
Class paramType = this.parameterType;
if (paramType == null) {
if (this.parameterIndex < 0) {
Method method = getMethod();
paramType = (method != null ? method.getReturnType() : void.class);
} else {
paramType = this.executable.getParameterTypes()[this.parameterIndex];
}
this.parameterType = paramType;
}
return paramType;
}
/**
* Return the generic type of the method/constructor parameter.
*
* @return the parameter type (never {@code null})
* @since 3.0
*/
public Type getGenericParameterType() {
Type paramType = this.genericParameterType;
if (paramType == null) {
if (this.parameterIndex < 0) {
Method method = getMethod();
paramType = (method != null ? method.getGenericReturnType() : void.class);
} else {
paramType = this.executable.getGenericParameterTypes()[this.parameterIndex];
}
this.genericParameterType = paramType;
}
return paramType;
}
/**
* Return the nested type of the method/constructor parameter.
*
* @return the parameter type (never {@code null})
* @since 3.1
* @see #getNestingLevel()
*/
public Class getNestedParameterType() {
if (this.nestingLevel > 1) {
Type type = getGenericParameterType();
for (int i = 2; i <= this.nestingLevel; i++) {
if (type instanceof ParameterizedType) {
Type[] args = ((ParameterizedType) type).getActualTypeArguments();
Integer index = getTypeIndexForLevel(i);
type = args[index != null ? index : args.length - 1];
}
// TODO: Object.class if unresolvable
}
if (type instanceof Class) {
return (Class) type;
} else if (type instanceof ParameterizedType) {
Type arg = ((ParameterizedType) type).getRawType();
if (arg instanceof Class) {
return (Class) arg;
}
}
return Object.class;
} else {
return getParameterType();
}
}
/**
* Return the nested generic type of the method/constructor parameter.
*
* @return the parameter type (never {@code null})
* @since 4.2
* @see #getNestingLevel()
*/
public Type getNestedGenericParameterType() {
if (this.nestingLevel > 1) {
Type type = getGenericParameterType();
for (int i = 2; i <= this.nestingLevel; i++) {
if (type instanceof ParameterizedType) {
Type[] args = ((ParameterizedType) type).getActualTypeArguments();
Integer index = getTypeIndexForLevel(i);
type = args[index != null ? index : args.length - 1];
}
}
return type;
} else {
return getGenericParameterType();
}
}
/**
* Return the annotations associated with the target method/constructor itself.
*/
public Annotation[] getMethodAnnotations() {
return adaptAnnotationArray(getAnnotatedElement().getAnnotations());
}
/**
* Return the annotations associated with the specific method/constructor parameter.
*/
public Annotation[] getParameterAnnotations() {
Annotation[] paramAnns = this.parameterAnnotations;
if (paramAnns == null) {
Annotation[][] annotationArray = this.executable.getParameterAnnotations();
if (this.parameterIndex >= 0 && this.parameterIndex < annotationArray.length) {
paramAnns = adaptAnnotationArray(annotationArray[this.parameterIndex]);
} else {
paramAnns = new Annotation[0];
}
this.parameterAnnotations = paramAnns;
}
return paramAnns;
}
/**
* Return the name of the method/constructor parameter.
*
* @return the parameter name (may be {@code null} if no parameter name metadata is contained in
* the class file or no {@link #initParameterNameDiscovery ParameterNameDiscoverer} has
* been set to begin with)
*/
@Nullable
public String getParameterName() {
ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;
if (discoverer != null) {
String[] parameterNames = null;
if (this.executable instanceof Method) {
parameterNames = discoverer.getParameterNames((Method) this.executable);
} else if (this.executable instanceof Constructor) {
parameterNames = discoverer.getParameterNames((Constructor) this.executable);
}
if (parameterNames != null) {
this.parameterName = parameterNames[this.parameterIndex];
}
this.parameterNameDiscoverer = null;
}
return this.parameterName;
}
/**
* A template method to post-process a given annotation instance before returning it to the
* caller.
*