
cn.taketoday.core.MethodParameter Maven / Gradle / Ivy
/*
* Original Author -> Harry Yang ([email protected]) https://taketoday.cn
* Copyright © TODAY & 2017 - 2021 All Rights Reserved.
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see [http://www.gnu.org/licenses/]
*/
package cn.taketoday.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.Map;
import java.util.Optional;
import cn.taketoday.lang.Assert;
import cn.taketoday.lang.Constant;
import cn.taketoday.lang.Nullable;
import cn.taketoday.util.ClassUtils;
import cn.taketoday.util.ObjectUtils;
import cn.taketoday.util.ReflectionUtils;
/**
* 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.
*
* there is a {@link cn.taketoday.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
* @author Phillip Webb
* @see cn.taketoday.core.annotation.SynthesizingMethodParameter
* @since 4.0
*/
public class MethodParameter {
private final Executable executable;
private final int parameterIndex;
@Nullable
private volatile Parameter parameter;
private int nestingLevel;
/** Map from Integer level to Integer type index. */
@Nullable
Map typeIndexesPerLevel;
/** The containing class. Could also be supplied by overriding {@link #getContainingClass()} */
@Nullable
private 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;
}
/**
* Internal constructor used to create a {@link MethodParameter} with a
* containing class already set.
*
* @param executable the Executable to specify a parameter for
* @param parameterIndex the index of the parameter
* @param containingClass the containing class
*/
MethodParameter(Executable executable, int parameterIndex, @Nullable Class> containingClass) {
Assert.notNull(executable, "Executable must not be null");
this.nestingLevel = 1;
this.executable = executable;
this.containingClass = containingClass;
this.parameterIndex = validateIndex(executable, parameterIndex);
}
/**
* 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
*/
public Executable getExecutable() {
return this.executable;
}
/**
* Return the {@link Parameter} descriptor for method/constructor parameter.
*/
public Parameter getParameter() {
if (this.parameterIndex < 0) {
throw new IllegalStateException("Cannot retrieve Parameter descriptor for method return type");
}
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;
}
/**
* Return a variant of this {@code MethodParameter} with the type
* for the current level set to the specified value.
*
* @param typeIndex the new type index
*/
public MethodParameter withTypeIndex(int typeIndex) {
return nested(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 a variant of this {@code MethodParameter} which points to the
* same parameter but one nesting level deeper.
*/
public MethodParameter nested() {
return nested(null);
}
/**
* Return a variant of this {@code MethodParameter} which points to the
* same parameter but one nesting level deeper.
*
* @param typeIndex the type index for the new nesting level
*/
public MethodParameter nested(@Nullable Integer typeIndex) {
MethodParameter nestedParam = this.nestedMethodParameter;
if (nestedParam != null && typeIndex == null) {
return nestedParam;
}
nestedParam = nested(this.nestingLevel + 1, typeIndex);
if (typeIndex == null) {
this.nestedMethodParameter = nestedParam;
}
return nestedParam;
}
private MethodParameter nested(int nestingLevel, @Nullable Integer typeIndex) {
MethodParameter copy = clone();
copy.nestingLevel = nestingLevel;
if (this.typeIndexesPerLevel != null) {
copy.typeIndexesPerLevel = new HashMap<>(this.typeIndexesPerLevel);
}
if (typeIndex != null) {
copy.getTypeIndexesPerLevel().put(copy.nestingLevel, typeIndex);
}
copy.parameterType = null;
copy.genericParameterType = null;
return copy;
}
/**
* 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 or {@code Continuation} parameter in Kotlin.
*/
public boolean isOptional() {
return getParameterType() == Optional.class || isNullable();
}
/**
* Return whether this method indicates a parameter which can be {@code null}:
* either in the form of 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
*/
public boolean isNullable() {
for (Annotation ann : getParameterAnnotations()) {
if ("Nullable".equals(ann.annotationType().getSimpleName())) {
return true;
}
}
return false;
}
/**
* Return a variant of this {@code MethodParameter} which points to
* the same parameter but one nesting level deeper in case of a
* {@link java.util.Optional} declaration.
*
* @see #isOptional()
* @see #nested()
*/
public MethodParameter nestedIfOptional() {
return getParameterType() == Optional.class ? nested() : this;
}
/**
* Return a variant of this {@code MethodParameter} which refers to the
* given containing class.
*
* @param containingClass a specific containing class (potentially a
* subclass of the declaring class, e.g. substituting a type variable)
* @see #getParameterType()
*/
public MethodParameter withContainingClass(@Nullable Class> containingClass) {
MethodParameter result = clone();
result.parameterType = null;
result.containingClass = containingClass;
return result;
}
/**
* Return the containing class for this method parameter.
*
* @return a specific containing class (potentially a subclass of the
* declaring class), or otherwise simply the declaring class itself
* @see #getDeclaringClass()
*/
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) {
return paramType;
}
if (getContainingClass() != getDeclaringClass()) {
paramType = ResolvableType.forMethodParameter(this, null, 1).resolve();
}
if (paramType == null) {
paramType = computeParameterType();
}
this.parameterType = paramType;
return paramType;
}
/**
* Return the generic type of the method/constructor parameter.
*
* @return the parameter type (never {@code null})
*/
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 {
Type[] genericParameterTypes = this.executable.getGenericParameterTypes();
int index = this.parameterIndex;
if (this.executable instanceof Constructor &&
ClassUtils.isInnerClass(this.executable.getDeclaringClass()) &&
genericParameterTypes.length == this.executable.getParameterCount() - 1) {
// Bug in javac: type array excludes enclosing instance parameter
// for inner classes with at least one generic constructor parameter,
// so access it with the actual parameter index lowered by 1
index = this.parameterIndex - 1;
}
paramType = (index >= 0 && index < genericParameterTypes.length ?
genericParameterTypes[index] : computeParameterType());
}
this.genericParameterType = paramType;
}
return paramType;
}
private Class> computeParameterType() {
if (this.parameterIndex < 0) {
Method method = getMethod();
if (method == null) {
return void.class;
}
return method.getReturnType();
}
return this.executable.getParameterTypes()[this.parameterIndex];
}
/**
* Return the nested type of the method/constructor parameter.
*
* @return the parameter type (never {@code null})
* @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})
* @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 method/constructor annotation of the given type, if available.
*
* @param annotationType the annotation type to look for
* @return the annotation object, or {@code null} if not found
*/
@Nullable
public A getMethodAnnotation(Class annotationType) {
A annotation = getAnnotatedElement().getAnnotation(annotationType);
return annotation != null ? adaptAnnotation(annotation) : null;
}
/**
* Return whether the method/constructor is annotated with the given type.
*
* @param annotationType the annotation type to look for
* @see #getMethodAnnotation(Class)
*/
public boolean hasMethodAnnotation(Class annotationType) {
return getAnnotatedElement().isAnnotationPresent(annotationType);
}
/**
* 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();
int index = this.parameterIndex;
if (this.executable instanceof Constructor
&& ClassUtils.isInnerClass(this.executable.getDeclaringClass())
&& annotationArray.length == this.executable.getParameterCount() - 1) {
// Bug in javac in JDK <9: annotation array excludes enclosing instance parameter
// for inner classes, so access it with the actual parameter index lowered by 1
index = this.parameterIndex - 1;
}
paramAnns = index >= 0 && index < annotationArray.length
? adaptAnnotationArray(annotationArray[index])
: Constant.EMPTY_ANNOTATIONS;
this.parameterAnnotations = paramAnns;
}
return paramAnns;
}
/**
* Return {@code true} if the parameter has at least one annotation,
* {@code false} if it has none.
*
* @see #getParameterAnnotations()
*/
public boolean hasParameterAnnotations() {
return getParameterAnnotations().length != 0;
}
/**
* Return the parameter annotation of the given type, if available.
*
* @param annotationType the annotation type to look for
* @return the annotation object, or {@code null} if not found
*/
@SuppressWarnings("unchecked")
@Nullable
public A getParameterAnnotation(Class annotationType) {
Annotation[] anns = getParameterAnnotations();
for (Annotation ann : anns) {
if (annotationType.isInstance(ann)) {
return (A) ann;
}
}
return null;
}
/**
* Return whether the parameter is declared with the given annotation type.
*
* @param annotationType the annotation type to look for
* @see #getParameterAnnotation(Class)
*/
public boolean hasParameterAnnotation(Class annotationType) {
return getParameterAnnotation(annotationType) != null;
}
/**
* Initialize parameter name discovery for this method parameter.
* This method does not actually try to retrieve the parameter name at
* this point; it just allows discovery to happen when the application calls
* {@link #getParameterName()} (if ever).
*/
public void initParameterNameDiscovery(@Nullable ParameterNameDiscoverer parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer;
}
/**
* 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() {
if (this.parameterIndex < 0) {
return null;
}
String parameterName = this.parameterName;
if (parameterName == null) {
ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;
if (discoverer != null) {
String[] parameterNames = discoverer.getParameterNames(this.executable);
if (parameterNames != null) {
parameterName = parameterNames[this.parameterIndex];
this.parameterName = parameterName;
}
this.parameterNameDiscoverer = null;
}
}
return parameterName;
}
/**
* A template method to post-process a given annotation instance before
* returning it to the caller.
*