新增ResolvableType相关基础类及测试代码

This commit is contained in:
aoshiguchen
2022-09-25 00:25:11 +08:00
parent b40e947b17
commit f3322fdf38
10 changed files with 4049 additions and 0 deletions
@@ -0,0 +1,654 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.base.type;
import fun.asgc.neutrino.core.util.Assert;
import fun.asgc.neutrino.core.util.ObjectUtil;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
/**
* @author: aoshiguchen
* @date: 2022/9/25
*/
public class MethodParameter {
private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
private static final Class<?> javaUtilOptionalClass;
static {
Class<?> clazz;
try {
clazz = Class.forName("java.util.Optional");
}
catch (ClassNotFoundException ex) {
// Java 8 not available - Optional references simply not supported then.
clazz = null;
}
javaUtilOptionalClass = clazz;
}
private final Method method;
private final Constructor<?> constructor;
private final int parameterIndex;
private int nestingLevel;
/** Map from Integer level to Integer type index */
Map<Integer, Integer> typeIndexesPerLevel;
/** The containing class. Could also be supplied by overriding {@link #getContainingClass()} */
private volatile Class<?> containingClass;
private volatile Class<?> parameterType;
private volatile Type genericParameterType;
private volatile Annotation[] parameterAnnotations;
private volatile ParameterNameDiscoverer parameterNameDiscoverer;
private volatile String parameterName;
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.method = method;
this.parameterIndex = parameterIndex;
this.nestingLevel = nestingLevel;
this.constructor = null;
}
/**
* 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.constructor = constructor;
this.parameterIndex = parameterIndex;
this.nestingLevel = nestingLevel;
this.method = null;
}
/**
* 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.method = original.method;
this.constructor = original.constructor;
this.parameterIndex = original.parameterIndex;
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.
* <p>Note: Either Method or Constructor is available.
* @return the Method, or {@code null} if none
*/
public Method getMethod() {
return this.method;
}
/**
* Return the wrapped Constructor, if any.
* <p>Note: Either Method or Constructor is available.
* @return the Constructor, or {@code null} if none
*/
public Constructor<?> getConstructor() {
return this.constructor;
}
/**
* Return the class that declares the underlying Method or Constructor.
*/
public Class<?> getDeclaringClass() {
return getMember().getDeclaringClass();
}
/**
* Return the wrapped member.
* @return the Method or Constructor as Member
*/
public Member getMember() {
// NOTE: no ternary expression to retain JDK <8 compatibility even when using
// the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
// as common type, with that new base class not available on older JDKs)
if (this.method != null) {
return this.method;
}
else {
return this.constructor;
}
}
/**
* Return the wrapped annotated element.
* <p>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() {
// NOTE: no ternary expression to retain JDK <8 compatibility even when using
// the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
// as common type, with that new base class not available on older JDKs)
if (this.method != null) {
return this.method;
}
else {
return this.constructor;
}
}
/**
* 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;
}
/**
* Increase this parameter's nesting level.
* @see #getNestingLevel()
*/
public void increaseNestingLevel() {
this.nestingLevel++;
}
/**
* Decrease this parameter's nesting level.
* @see #getNestingLevel()
*/
public void decreaseNestingLevel() {
getTypeIndexesPerLevel().remove(this.nestingLevel);
this.nestingLevel--;
}
/**
* 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()
*/
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)
*/
public Integer getTypeIndexForLevel(int nestingLevel) {
return getTypeIndexesPerLevel().get(nestingLevel);
}
/**
* Obtain the (lazily constructed) type-indexes-per-level Map.
*/
private Map<Integer, Integer> getTypeIndexesPerLevel() {
if (this.typeIndexesPerLevel == null) {
this.typeIndexesPerLevel = new HashMap<Integer, Integer>(4);
}
return this.typeIndexesPerLevel;
}
/**
* Return a variant of this {@code MethodParameter} which points to the
* same parameter but one nesting level deeper. This is effectively the
* same as {@link #increaseNestingLevel()}, just with an independent
* {@code MethodParameter} object (e.g. in case of the original being cached).
* @since 4.3
*/
public MethodParameter nested() {
if (this.nestedMethodParameter != null) {
return this.nestedMethodParameter;
}
MethodParameter nestedParam = clone();
nestedParam.nestingLevel = this.nestingLevel + 1;
this.nestedMethodParameter = nestedParam;
return nestedParam;
}
/**
* Return whether this method parameter is declared as optional
* in the form of Java 8's {@link java.util.Optional}.
* @since 4.3
*/
public boolean isOptional() {
return (getParameterType() == javaUtilOptionalClass);
}
/**
* 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.
* @since 4.3
* @see #isOptional()
* @see #nested()
*/
public MethodParameter nestedIfOptional() {
return (isOptional() ? nested() : this);
}
/**
* Set a containing class to resolve the parameter type against.
*/
void setContainingClass(Class<?> containingClass) {
this.containingClass = containingClass;
}
/**
* 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() {
return (this.containingClass != null ? this.containingClass : getDeclaringClass());
}
/**
* Set a resolved (generic) parameter type.
*/
void setParameterType(Class<?> parameterType) {
this.parameterType = parameterType;
}
/**
* 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.method != null ?
this.method.getParameterTypes()[this.parameterIndex] :
this.constructor.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 {
Type[] genericParameterTypes = (this.method != null ?
this.method.getGenericParameterTypes() : this.constructor.getGenericParameterTypes());
int index = this.parameterIndex;
if (this.constructor != null && this.constructor.getDeclaringClass().isMemberClass() &&
!Modifier.isStatic(this.constructor.getDeclaringClass().getModifiers()) &&
genericParameterTypes.length == this.constructor.getParameterTypes().length - 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] : getParameterType());
}
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 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
*/
public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
return adaptAnnotation(getAnnotatedElement().getAnnotation(annotationType));
}
/**
* Return whether the method/constructor is annotated with the given type.
* @param annotationType the annotation type to look for
* @since 4.3
* @see #getMethodAnnotation(Class)
*/
public <A extends Annotation> boolean hasMethodAnnotation(Class<A> 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.method != null ?
this.method.getParameterAnnotations() : this.constructor.getParameterAnnotations());
int index = this.parameterIndex;
if (this.constructor != null && this.constructor.getDeclaringClass().isMemberClass() &&
!Modifier.isStatic(this.constructor.getDeclaringClass().getModifiers()) &&
annotationArray.length == this.constructor.getParameterTypes().length - 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]) : EMPTY_ANNOTATION_ARRAY);
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")
public <A extends Annotation> A getParameterAnnotation(Class<A> 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 <A extends Annotation> boolean hasParameterAnnotation(Class<A> annotationType) {
return (getParameterAnnotation(annotationType) != null);
}
/**
* Initialize parameter name discovery for this method parameter.
* <p>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(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)
*/
public String getParameterName() {
ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;
if (discoverer != null) {
String[] parameterNames = (this.method != null ?
discoverer.getParameterNames(this.method) : discoverer.getParameterNames(this.constructor));
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.
* <p>The default implementation simply returns the given annotation as-is.
* @param annotation the annotation about to be returned
* @return the post-processed annotation (or simply the original one)
* @since 4.2
*/
protected <A extends Annotation> A adaptAnnotation(A annotation) {
return annotation;
}
/**
* A template method to post-process a given annotation array before
* returning it to the caller.
* <p>The default implementation simply returns the given annotation array as-is.
* @param annotations the annotation array about to be returned
* @return the post-processed annotation array (or simply the original one)
* @since 4.2
*/
protected Annotation[] adaptAnnotationArray(Annotation[] annotations) {
return annotations;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof MethodParameter)) {
return false;
}
MethodParameter otherParam = (MethodParameter) other;
return (getContainingClass() == otherParam.getContainingClass() &&
ObjectUtil.nullSafeEquals(this.typeIndexesPerLevel, otherParam.typeIndexesPerLevel) &&
this.nestingLevel == otherParam.nestingLevel &&
this.parameterIndex == otherParam.parameterIndex &&
getMember().equals(otherParam.getMember()));
}
@Override
public int hashCode() {
return (getMember().hashCode() * 31 + this.parameterIndex);
}
@Override
public String toString() {
return (this.method != null ? "method '" + this.method.getName() + "'" : "constructor") +
" parameter " + this.parameterIndex;
}
@Override
public MethodParameter clone() {
return new MethodParameter(this);
}
/**
* Create a new MethodParameter for the given method or constructor.
* <p>This is a convenience constructor for scenarios where a
* Method or Constructor reference is treated in a generic fashion.
* @param methodOrConstructor the Method or Constructor to specify a parameter for
* @param parameterIndex the index of the parameter
* @return the corresponding MethodParameter instance
*/
public static MethodParameter forMethodOrConstructor(Object methodOrConstructor, int parameterIndex) {
if (methodOrConstructor instanceof Method) {
return new MethodParameter((Method) methodOrConstructor, parameterIndex);
}
else if (methodOrConstructor instanceof Constructor) {
return new MethodParameter((Constructor<?>) methodOrConstructor, parameterIndex);
}
else {
throw new IllegalArgumentException(
"Given object [" + methodOrConstructor + "] is neither a Method nor a Constructor");
}
}
}
@@ -0,0 +1,51 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.base.type;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* @author: aoshiguchen
* @date: 2022/9/25
*/
public interface ParameterNameDiscoverer {
/**
* Return parameter names for this method,
* or {@code null} if they cannot be determined.
* @param method method to find parameter names for
* @return an array of parameter names if the names can be resolved,
* or {@code null} if they cannot
*/
String[] getParameterNames(Method method);
/**
* Return parameter names for this constructor,
* or {@code null} if they cannot be determined.
* @param ctor constructor to find parameter names for
* @return an array of parameter names if the names can be resolved,
* or {@code null} if they cannot
*/
String[] getParameterNames(Constructor<?> ctor);
}
@@ -0,0 +1,100 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.base.type;
import fun.asgc.neutrino.core.util.Assert;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* @author: aoshiguchen
* @date: 2022/9/25
*/
public abstract class ParameterizedTypeReference<T> {
private final Type type;
protected ParameterizedTypeReference() {
Class<?> parameterizedTypeReferenceSubclass = findParameterizedTypeReferenceSubclass(getClass());
Type type = parameterizedTypeReferenceSubclass.getGenericSuperclass();
Assert.isInstanceOf(ParameterizedType.class, type, "Type must be a parameterized type");
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
Assert.isTrue(actualTypeArguments.length == 1, "Number of type arguments must be 1");
this.type = actualTypeArguments[0];
}
private ParameterizedTypeReference(Type type) {
this.type = type;
}
public Type getType() {
return this.type;
}
@Override
public boolean equals(Object obj) {
return (this == obj || (obj instanceof ParameterizedTypeReference &&
this.type.equals(((ParameterizedTypeReference<?>) obj).type)));
}
@Override
public int hashCode() {
return this.type.hashCode();
}
@Override
public String toString() {
return "ParameterizedTypeReference<" + this.type + ">";
}
/**
* Build a {@code ParameterizedTypeReference} wrapping the given type.
* @param type a generic type (possibly obtained via reflection,
* e.g. from {@link java.lang.reflect.Method#getGenericReturnType()})
* @return a corresponding reference which may be passed into
* {@code ParameterizedTypeReference}-accepting methods
* @since 4.3.12
*/
public static <T> ParameterizedTypeReference<T> forType(Type type) {
return new ParameterizedTypeReference<T>(type) {
};
}
private static Class<?> findParameterizedTypeReferenceSubclass(Class<?> child) {
Class<?> parent = child.getSuperclass();
if (Object.class == parent) {
throw new IllegalStateException("Expected ParameterizedTypeReference superclass");
}
else if (ParameterizedTypeReference.class == parent) {
return child;
}
else {
return findParameterizedTypeReferenceSubclass(parent);
}
}
}
@@ -0,0 +1,1556 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.base.type;
import fun.asgc.neutrino.core.base.type.SerializableTypeWrapper.FieldTypeProvider;
import fun.asgc.neutrino.core.base.type.SerializableTypeWrapper.MethodParameterTypeProvider;
import fun.asgc.neutrino.core.base.type.SerializableTypeWrapper.TypeProvider;
import fun.asgc.neutrino.core.util.Assert;
import fun.asgc.neutrino.core.util.ConcurrentReferenceHashMap;
import fun.asgc.neutrino.core.util.ObjectUtil;
import fun.asgc.neutrino.core.util.StringUtil;
import org.apache.commons.lang3.ClassUtils;
import java.io.Serializable;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Map;
/**
* @author: aoshiguchen
* @date: 2022/9/24
*/
@SuppressWarnings("serial")
public class ResolvableType implements Serializable {
/**
* {@code ResolvableType} returned when no value is available. {@code NONE} is used
* in preference to {@code null} so that multiple method calls can be safely chained.
*/
public static final ResolvableType NONE = new ResolvableType(null, null, null, 0);
private static final ResolvableType[] EMPTY_TYPES_ARRAY = new ResolvableType[0];
private static final ConcurrentReferenceHashMap<ResolvableType, ResolvableType> cache =
new ConcurrentReferenceHashMap<ResolvableType, ResolvableType>(256);
/**
* The underlying Java type being managed (only ever {@code null} for {@link #NONE}).
*/
private final Type type;
/**
* Optional provider for the type.
*/
private final TypeProvider typeProvider;
/**
* The {@code VariableResolver} to use or {@code null} if no resolver is available.
*/
private final VariableResolver variableResolver;
/**
* The component type for an array or {@code null} if the type should be deduced.
*/
private final ResolvableType componentType;
/**
* Copy of the resolved value.
*/
private final Class<?> resolved;
private final Integer hash;
private ResolvableType superType;
private ResolvableType[] interfaces;
private ResolvableType[] generics;
/**
* Private constructor used to create a new {@link ResolvableType} for cache key purposes,
* with no upfront resolution.
*/
private ResolvableType(Type type, TypeProvider typeProvider, VariableResolver variableResolver) {
this.type = type;
this.typeProvider = typeProvider;
this.variableResolver = variableResolver;
this.componentType = null;
this.resolved = null;
this.hash = calculateHashCode();
}
/**
* Private constructor used to create a new {@link ResolvableType} for cache value purposes,
* with upfront resolution and a pre-calculated hash.
* @since 4.2
*/
private ResolvableType(Type type, TypeProvider typeProvider, VariableResolver variableResolver, Integer hash) {
this.type = type;
this.typeProvider = typeProvider;
this.variableResolver = variableResolver;
this.componentType = null;
this.resolved = resolveClass();
this.hash = hash;
}
/**
* Private constructor used to create a new {@link ResolvableType} for uncached purposes,
* with upfront resolution but lazily calculated hash.
*/
private ResolvableType(
Type type, TypeProvider typeProvider, VariableResolver variableResolver, ResolvableType componentType) {
this.type = type;
this.typeProvider = typeProvider;
this.variableResolver = variableResolver;
this.componentType = componentType;
this.resolved = resolveClass();
this.hash = null;
}
/**
* Private constructor used to create a new {@link ResolvableType} on a {@link Class} basis.
* Avoids all {@code instanceof} checks in order to create a straight {@link Class} wrapper.
* @since 4.2
*/
private ResolvableType(Class<?> clazz) {
this.resolved = (clazz != null ? clazz : Object.class);
this.type = this.resolved;
this.typeProvider = null;
this.variableResolver = null;
this.componentType = null;
this.hash = null;
}
/**
* Return the underling Java {@link Type} being managed. With the exception of
* the {@link #NONE} constant, this method will never return {@code null}.
*/
public Type getType() {
return SerializableTypeWrapper.unwrap(this.type);
}
/**
* Return the underlying Java {@link Class} being managed, if available;
* otherwise {@code null}.
*/
public Class<?> getRawClass() {
if (this.type == this.resolved) {
return this.resolved;
}
Type rawType = this.type;
if (rawType instanceof ParameterizedType) {
rawType = ((ParameterizedType) rawType).getRawType();
}
return (rawType instanceof Class ? (Class<?>) rawType : null);
}
/**
* Return the underlying source of the resolvable type. Will return a {@link Field},
* {@link MethodParameter} or {@link Type} depending on how the {@link ResolvableType}
* was constructed. With the exception of the {@link #NONE} constant, this method will
* never return {@code null}. This method is primarily to provide access to additional
* type information or meta-data that alternative JVM languages may provide.
*/
public Object getSource() {
Object source = (this.typeProvider != null ? this.typeProvider.getSource() : null);
return (source != null ? source : this.type);
}
/**
* Determine whether the given object is an instance of this {@code ResolvableType}.
* @param obj the object to check
* @since 4.2
* @see #isAssignableFrom(Class)
*/
public boolean isInstance(Object obj) {
return (obj != null && isAssignableFrom(obj.getClass()));
}
/**
* Determine whether this {@code ResolvableType} is assignable from the
* specified other type.
* @param other the type to be checked against (as a {@code Class})
* @since 4.2
* @see #isAssignableFrom(ResolvableType)
*/
public boolean isAssignableFrom(Class<?> other) {
return isAssignableFrom(forClass(other), null);
}
/**
* Determine whether this {@code ResolvableType} is assignable from the
* specified other type.
* <p>Attempts to follow the same rules as the Java compiler, considering
* whether both the {@link #resolve() resolved} {@code Class} is
* {@link Class#isAssignableFrom(Class) assignable from} the given type
* as well as whether all {@link #getGenerics() generics} are assignable.
* @param other the type to be checked against (as a {@code ResolvableType})
* @return {@code true} if the specified other type can be assigned to this
* {@code ResolvableType}; {@code false} otherwise
*/
public boolean isAssignableFrom(ResolvableType other) {
return isAssignableFrom(other, null);
}
private boolean isAssignableFrom(ResolvableType other, Map<Type, Type> matchedBefore) {
Assert.notNull(other, "ResolvableType must not be null");
// If we cannot resolve types, we are not assignable
if (this == NONE || other == NONE) {
return false;
}
// Deal with array by delegating to the component type
if (isArray()) {
return (other.isArray() && getComponentType().isAssignableFrom(other.getComponentType()));
}
if (matchedBefore != null && matchedBefore.get(this.type) == other.type) {
return true;
}
// Deal with wildcard bounds
WildcardBounds ourBounds = WildcardBounds.get(this);
WildcardBounds typeBounds = WildcardBounds.get(other);
// In the form X is assignable to <? extends Number>
if (typeBounds != null) {
return (ourBounds != null && ourBounds.isSameKind(typeBounds) &&
ourBounds.isAssignableFrom(typeBounds.getBounds()));
}
// In the form <? extends Number> is assignable to X...
if (ourBounds != null) {
return ourBounds.isAssignableFrom(other);
}
// Main assignability check about to follow
boolean exactMatch = (matchedBefore != null); // We're checking nested generic variables now...
boolean checkGenerics = true;
Class<?> ourResolved = null;
if (this.type instanceof TypeVariable) {
TypeVariable<?> variable = (TypeVariable<?>) this.type;
// Try default variable resolution
if (this.variableResolver != null) {
ResolvableType resolved = this.variableResolver.resolveVariable(variable);
if (resolved != null) {
ourResolved = resolved.resolve();
}
}
if (ourResolved == null) {
// Try variable resolution against target type
if (other.variableResolver != null) {
ResolvableType resolved = other.variableResolver.resolveVariable(variable);
if (resolved != null) {
ourResolved = resolved.resolve();
checkGenerics = false;
}
}
}
if (ourResolved == null) {
// Unresolved type variable, potentially nested -> never insist on exact match
exactMatch = false;
}
}
if (ourResolved == null) {
ourResolved = resolve(Object.class);
}
Class<?> otherResolved = other.resolve(Object.class);
// We need an exact type match for generics
// List<CharSequence> is not assignable from List<String>
if (exactMatch ? !ourResolved.equals(otherResolved) : !ClassUtils.isAssignable(ourResolved, otherResolved)) {
return false;
}
if (checkGenerics) {
// Recursively check each generic
ResolvableType[] ourGenerics = getGenerics();
ResolvableType[] typeGenerics = other.as(ourResolved).getGenerics();
if (ourGenerics.length != typeGenerics.length) {
return false;
}
if (matchedBefore == null) {
matchedBefore = new IdentityHashMap<Type, Type>(1);
}
matchedBefore.put(this.type, other.type);
for (int i = 0; i < ourGenerics.length; i++) {
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], matchedBefore)) {
return false;
}
}
}
return true;
}
/**
* Return {@code true} if this type resolves to a Class that represents an array.
* @see #getComponentType()
*/
public boolean isArray() {
if (this == NONE) {
return false;
}
return ((this.type instanceof Class && ((Class<?>) this.type).isArray()) ||
this.type instanceof GenericArrayType || resolveType().isArray());
}
/**
* Return the ResolvableType representing the component type of the array or
* {@link #NONE} if this type does not represent an array.
* @see #isArray()
*/
public ResolvableType getComponentType() {
if (this == NONE) {
return NONE;
}
if (this.componentType != null) {
return this.componentType;
}
if (this.type instanceof Class) {
Class<?> componentType = ((Class<?>) this.type).getComponentType();
return forType(componentType, this.variableResolver);
}
if (this.type instanceof GenericArrayType) {
return forType(((GenericArrayType) this.type).getGenericComponentType(), this.variableResolver);
}
return resolveType().getComponentType();
}
/**
* Convenience method to return this type as a resolvable {@link Collection} type.
* Returns {@link #NONE} if this type does not implement or extend
* {@link Collection}.
* @see #as(Class)
* @see #asMap()
*/
public ResolvableType asCollection() {
return as(Collection.class);
}
/**
* Convenience method to return this type as a resolvable {@link Map} type.
* Returns {@link #NONE} if this type does not implement or extend
* {@link Map}.
* @see #as(Class)
* @see #asCollection()
*/
public ResolvableType asMap() {
return as(Map.class);
}
/**
* Return this type as a {@link ResolvableType} of the specified class. Searches
* {@link #getSuperType() supertype} and {@link #getInterfaces() interface}
* hierarchies to find a match, returning {@link #NONE} if this type does not
* implement or extend the specified class.
* @param type the required type (typically narrowed)
* @return a {@link ResolvableType} representing this object as the specified
* type, or {@link #NONE} if not resolvable as that type
* @see #asCollection()
* @see #asMap()
* @see #getSuperType()
* @see #getInterfaces()
*/
public ResolvableType as(Class<?> type) {
if (this == NONE) {
return NONE;
}
if (ObjectUtil.nullSafeEquals(resolve(), type)) {
return this;
}
for (ResolvableType interfaceType : getInterfaces()) {
ResolvableType interfaceAsType = interfaceType.as(type);
if (interfaceAsType != NONE) {
return interfaceAsType;
}
}
return getSuperType().as(type);
}
/**
* Return a {@link ResolvableType} representing the direct supertype of this type.
* If no supertype is available this method returns {@link #NONE}.
* @see #getInterfaces()
*/
public ResolvableType getSuperType() {
Class<?> resolved = resolve();
if (resolved == null || resolved.getGenericSuperclass() == null) {
return NONE;
}
if (this.superType == null) {
this.superType = forType(SerializableTypeWrapper.forGenericSuperclass(resolved), asVariableResolver());
}
return this.superType;
}
/**
* Return a {@link ResolvableType} array representing the direct interfaces
* implemented by this type. If this type does not implement any interfaces an
* empty array is returned.
* @see #getSuperType()
*/
public ResolvableType[] getInterfaces() {
Class<?> resolved = resolve();
if (resolved == null || ObjectUtil.isEmpty(resolved.getGenericInterfaces())) {
return EMPTY_TYPES_ARRAY;
}
if (this.interfaces == null) {
this.interfaces = forTypes(SerializableTypeWrapper.forGenericInterfaces(resolved), asVariableResolver());
}
return this.interfaces;
}
/**
* Return {@code true} if this type contains generic parameters.
* @see #getGeneric(int...)
* @see #getGenerics()
*/
public boolean hasGenerics() {
return (getGenerics().length > 0);
}
/**
* Return {@code true} if this type contains unresolvable generics only,
* that is, no substitute for any of its declared type variables.
*/
boolean isEntirelyUnresolvable() {
if (this == NONE) {
return false;
}
ResolvableType[] generics = getGenerics();
for (ResolvableType generic : generics) {
if (!generic.isUnresolvableTypeVariable() && !generic.isWildcardWithoutBounds()) {
return false;
}
}
return true;
}
/**
* Determine whether the underlying type has any unresolvable generics:
* either through an unresolvable type variable on the type itself
* or through implementing a generic interface in a raw fashion,
* i.e. without substituting that interface's type variables.
* The result will be {@code true} only in those two scenarios.
*/
public boolean hasUnresolvableGenerics() {
if (this == NONE) {
return false;
}
ResolvableType[] generics = getGenerics();
for (ResolvableType generic : generics) {
if (generic.isUnresolvableTypeVariable() || generic.isWildcardWithoutBounds()) {
return true;
}
}
Class<?> resolved = resolve();
if (resolved != null) {
for (Type genericInterface : resolved.getGenericInterfaces()) {
if (genericInterface instanceof Class) {
if (forClass((Class<?>) genericInterface).hasGenerics()) {
return true;
}
}
}
return getSuperType().hasUnresolvableGenerics();
}
return false;
}
/**
* Determine whether the underlying type is a type variable that
* cannot be resolved through the associated variable resolver.
*/
private boolean isUnresolvableTypeVariable() {
if (this.type instanceof TypeVariable) {
if (this.variableResolver == null) {
return true;
}
TypeVariable<?> variable = (TypeVariable<?>) this.type;
ResolvableType resolved = this.variableResolver.resolveVariable(variable);
if (resolved == null || resolved.isUnresolvableTypeVariable()) {
return true;
}
}
return false;
}
/**
* Determine whether the underlying type represents a wildcard
* without specific bounds (i.e., equal to {@code ? extends Object}).
*/
private boolean isWildcardWithoutBounds() {
if (this.type instanceof WildcardType) {
WildcardType wt = (WildcardType) this.type;
if (wt.getLowerBounds().length == 0) {
Type[] upperBounds = wt.getUpperBounds();
if (upperBounds.length == 0 || (upperBounds.length == 1 && Object.class == upperBounds[0])) {
return true;
}
}
}
return false;
}
/**
* Return a {@link ResolvableType} for the specified nesting level.
* See {@link #getNested(int, Map)} for details.
* @param nestingLevel the nesting level
* @return the {@link ResolvableType} type, or {@code #NONE}
*/
public ResolvableType getNested(int nestingLevel) {
return getNested(nestingLevel, null);
}
/**
* Return a {@link ResolvableType} for the specified nesting level.
* <p>The nesting level refers to the specific generic parameter that should be returned.
* A nesting level of 1 indicates this type; 2 indicates the first nested generic;
* 3 the second; and so on. For example, given {@code List<Set<Integer>>} level 1 refers
* to the {@code List}, level 2 the {@code Set}, and level 3 the {@code Integer}.
* <p>The {@code typeIndexesPerLevel} map can be used to reference a specific generic
* for the given level. For example, an index of 0 would refer to a {@code Map} key;
* whereas, 1 would refer to the value. If the map does not contain a value for a
* specific level the last generic will be used (e.g. a {@code Map} value).
* <p>Nesting levels may also apply to array types; for example given
* {@code String[]}, a nesting level of 2 refers to {@code String}.
* <p>If a type does not {@link #hasGenerics() contain} generics the
* {@link #getSuperType() supertype} hierarchy will be considered.
* @param nestingLevel the required nesting level, indexed from 1 for the
* current type, 2 for the first nested generic, 3 for the second and so on
* @param typeIndexesPerLevel a map containing the generic index for a given
* nesting level (may be {@code null})
* @return a {@link ResolvableType} for the nested level, or {@link #NONE}
*/
public ResolvableType getNested(int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) {
ResolvableType result = this;
for (int i = 2; i <= nestingLevel; i++) {
if (result.isArray()) {
result = result.getComponentType();
}
else {
// Handle derived types
while (result != ResolvableType.NONE && !result.hasGenerics()) {
result = result.getSuperType();
}
Integer index = (typeIndexesPerLevel != null ? typeIndexesPerLevel.get(i) : null);
index = (index == null ? result.getGenerics().length - 1 : index);
result = result.getGeneric(index);
}
}
return result;
}
/**
* Return a {@link ResolvableType} representing the generic parameter for the
* given indexes. Indexes are zero based; for example given the type
* {@code Map<Integer, List<String>>}, {@code getGeneric(0)} will access the
* {@code Integer}. Nested generics can be accessed by specifying multiple indexes;
* for example {@code getGeneric(1, 0)} will access the {@code String} from the
* nested {@code List}. For convenience, if no indexes are specified the first
* generic is returned.
* <p>If no generic is available at the specified indexes {@link #NONE} is returned.
* @param indexes the indexes that refer to the generic parameter
* (may be omitted to return the first generic)
* @return a {@link ResolvableType} for the specified generic, or {@link #NONE}
* @see #hasGenerics()
* @see #getGenerics()
* @see #resolveGeneric(int...)
* @see #resolveGenerics()
*/
public ResolvableType getGeneric(int... indexes) {
ResolvableType[] generics = getGenerics();
if (indexes == null || indexes.length == 0) {
return (generics.length == 0 ? NONE : generics[0]);
}
ResolvableType generic = this;
for (int index : indexes) {
generics = generic.getGenerics();
if (index < 0 || index >= generics.length) {
return NONE;
}
generic = generics[index];
}
return generic;
}
/**
* Return an array of {@link ResolvableType}s representing the generic parameters of
* this type. If no generics are available an empty array is returned. If you need to
* access a specific generic consider using the {@link #getGeneric(int...)} method as
* it allows access to nested generics and protects against
* {@code IndexOutOfBoundsExceptions}.
* @return an array of {@link ResolvableType}s representing the generic parameters
* (never {@code null})
* @see #hasGenerics()
* @see #getGeneric(int...)
* @see #resolveGeneric(int...)
* @see #resolveGenerics()
*/
public ResolvableType[] getGenerics() {
if (this == NONE) {
return EMPTY_TYPES_ARRAY;
}
if (this.generics == null) {
if (this.type instanceof Class) {
Class<?> typeClass = (Class<?>) this.type;
this.generics = forTypes(SerializableTypeWrapper.forTypeParameters(typeClass), this.variableResolver);
}
else if (this.type instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) this.type).getActualTypeArguments();
ResolvableType[] generics = new ResolvableType[actualTypeArguments.length];
for (int i = 0; i < actualTypeArguments.length; i++) {
generics[i] = forType(actualTypeArguments[i], this.variableResolver);
}
this.generics = generics;
}
else {
this.generics = resolveType().getGenerics();
}
}
return this.generics;
}
/**
* Convenience method that will {@link #getGenerics() get} and
* {@link #resolve() resolve} generic parameters.
* @return an array of resolved generic parameters (the resulting array
* will never be {@code null}, but it may contain {@code null} elements})
* @see #getGenerics()
* @see #resolve()
*/
public Class<?>[] resolveGenerics() {
return resolveGenerics(null);
}
/**
* Convenience method that will {@link #getGenerics() get} and {@link #resolve()
* resolve} generic parameters, using the specified {@code fallback} if any type
* cannot be resolved.
* @param fallback the fallback class to use if resolution fails
* @return an array of resolved generic parameters
* @see #getGenerics()
* @see #resolve()
*/
public Class<?>[] resolveGenerics(Class<?> fallback) {
ResolvableType[] generics = getGenerics();
Class<?>[] resolvedGenerics = new Class<?>[generics.length];
for (int i = 0; i < generics.length; i++) {
resolvedGenerics[i] = generics[i].resolve(fallback);
}
return resolvedGenerics;
}
/**
* Convenience method that will {@link #getGeneric(int...) get} and
* {@link #resolve() resolve} a specific generic parameters.
* @param indexes the indexes that refer to the generic parameter
* (may be omitted to return the first generic)
* @return a resolved {@link Class} or {@code null}
* @see #getGeneric(int...)
* @see #resolve()
*/
public Class<?> resolveGeneric(int... indexes) {
return getGeneric(indexes).resolve();
}
/**
* Resolve this type to a {@link java.lang.Class}, returning {@code null}
* if the type cannot be resolved. This method will consider bounds of
* {@link TypeVariable}s and {@link WildcardType}s if direct resolution fails;
* however, bounds of {@code Object.class} will be ignored.
* @return the resolved {@link Class}, or {@code null} if not resolvable
* @see #resolve(Class)
* @see #resolveGeneric(int...)
* @see #resolveGenerics()
*/
public Class<?> resolve() {
return resolve(null);
}
/**
* Resolve this type to a {@link java.lang.Class}, returning the specified
* {@code fallback} if the type cannot be resolved. This method will consider bounds
* of {@link TypeVariable}s and {@link WildcardType}s if direct resolution fails;
* however, bounds of {@code Object.class} will be ignored.
* @param fallback the fallback class to use if resolution fails
* @return the resolved {@link Class} or the {@code fallback}
* @see #resolve()
* @see #resolveGeneric(int...)
* @see #resolveGenerics()
*/
public Class<?> resolve(Class<?> fallback) {
return (this.resolved != null ? this.resolved : fallback);
}
private Class<?> resolveClass() {
if (this.type instanceof Class || this.type == null) {
return (Class<?>) this.type;
}
if (this.type instanceof GenericArrayType) {
Class<?> resolvedComponent = getComponentType().resolve();
return (resolvedComponent != null ? Array.newInstance(resolvedComponent, 0).getClass() : null);
}
return resolveType().resolve();
}
/**
* Resolve this type by a single level, returning the resolved value or {@link #NONE}.
* <p>Note: The returned {@link ResolvableType} should only be used as an intermediary
* as it cannot be serialized.
*/
ResolvableType resolveType() {
if (this.type instanceof ParameterizedType) {
return forType(((ParameterizedType) this.type).getRawType(), this.variableResolver);
}
if (this.type instanceof WildcardType) {
Type resolved = resolveBounds(((WildcardType) this.type).getUpperBounds());
if (resolved == null) {
resolved = resolveBounds(((WildcardType) this.type).getLowerBounds());
}
return forType(resolved, this.variableResolver);
}
if (this.type instanceof TypeVariable) {
TypeVariable<?> variable = (TypeVariable<?>) this.type;
// Try default variable resolution
if (this.variableResolver != null) {
ResolvableType resolved = this.variableResolver.resolveVariable(variable);
if (resolved != null) {
return resolved;
}
}
// Fallback to bounds
return forType(resolveBounds(variable.getBounds()), this.variableResolver);
}
return NONE;
}
private Type resolveBounds(Type[] bounds) {
if (ObjectUtil.isEmpty(bounds) || Object.class == bounds[0]) {
return null;
}
return bounds[0];
}
private ResolvableType resolveVariable(TypeVariable<?> variable) {
if (this.type instanceof TypeVariable) {
return resolveType().resolveVariable(variable);
}
if (this.type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) this.type;
TypeVariable<?>[] variables = resolve().getTypeParameters();
for (int i = 0; i < variables.length; i++) {
if (ObjectUtil.nullSafeEquals(variables[i].getName(), variable.getName())) {
Type actualType = parameterizedType.getActualTypeArguments()[i];
return forType(actualType, this.variableResolver);
}
}
if (parameterizedType.getOwnerType() != null) {
return forType(parameterizedType.getOwnerType(), this.variableResolver).resolveVariable(variable);
}
}
if (this.variableResolver != null) {
return this.variableResolver.resolveVariable(variable);
}
return null;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof ResolvableType)) {
return false;
}
ResolvableType otherType = (ResolvableType) other;
if (!ObjectUtil.nullSafeEquals(this.type, otherType.type)) {
return false;
}
if (this.typeProvider != otherType.typeProvider &&
(this.typeProvider == null || otherType.typeProvider == null ||
!ObjectUtil.nullSafeEquals(this.typeProvider.getType(), otherType.typeProvider.getType()))) {
return false;
}
if (this.variableResolver != otherType.variableResolver &&
(this.variableResolver == null || otherType.variableResolver == null ||
!ObjectUtil.nullSafeEquals(this.variableResolver.getSource(), otherType.variableResolver.getSource()))) {
return false;
}
if (!ObjectUtil.nullSafeEquals(this.componentType, otherType.componentType)) {
return false;
}
return true;
}
@Override
public int hashCode() {
return (this.hash != null ? this.hash : calculateHashCode());
}
private int calculateHashCode() {
int hashCode = ObjectUtil.nullSafeHashCode(this.type);
if (this.typeProvider != null) {
hashCode = 31 * hashCode + ObjectUtil.nullSafeHashCode(this.typeProvider.getType());
}
if (this.variableResolver != null) {
hashCode = 31 * hashCode + ObjectUtil.nullSafeHashCode(this.variableResolver.getSource());
}
if (this.componentType != null) {
hashCode = 31 * hashCode + ObjectUtil.nullSafeHashCode(this.componentType);
}
return hashCode;
}
/**
* Adapts this {@link ResolvableType} to a {@link VariableResolver}.
*/
VariableResolver asVariableResolver() {
if (this == NONE) {
return null;
}
return new DefaultVariableResolver();
}
/**
* Custom serialization support for {@link #NONE}.
*/
private Object readResolve() {
return (this.type == null ? NONE : this);
}
/**
* Return a String representation of this type in its fully resolved form
* (including any generic parameters).
*/
@Override
public String toString() {
if (isArray()) {
return getComponentType() + "[]";
}
if (this.resolved == null) {
return "?";
}
if (this.type instanceof TypeVariable) {
TypeVariable<?> variable = (TypeVariable<?>) this.type;
if (this.variableResolver == null || this.variableResolver.resolveVariable(variable) == null) {
// Don't bother with variable boundaries for toString()...
// Can cause infinite recursions in case of self-references
return "?";
}
}
StringBuilder result = new StringBuilder(this.resolved.getName());
if (hasGenerics()) {
result.append('<');
result.append(StringUtil.arrayToDelimitedString(getGenerics(), ", "));
result.append('>');
}
return result.toString();
}
// Factory methods
/**
* Return a {@link ResolvableType} for the specified {@link Class},
* using the full generic type information for assignability checks.
* For example: {@code ResolvableType.forClass(MyArrayList.class)}.
* @param clazz the class to introspect ({@code null} is semantically
* equivalent to {@code Object.class} for typical use cases here)
* @return a {@link ResolvableType} for the specified class
* @see #forClass(Class, Class)
* @see #forClassWithGenerics(Class, Class...)
*/
public static ResolvableType forClass(Class<?> clazz) {
return new ResolvableType(clazz);
}
/**
* Return a {@link ResolvableType} for the specified {@link Class},
* doing assignability checks against the raw class only (analogous to
* {@link Class#isAssignableFrom}, which this serves as a wrapper for.
* For example: {@code ResolvableType.forRawClass(List.class)}.
* @param clazz the class to introspect ({@code null} is semantically
* equivalent to {@code Object.class} for typical use cases here)
* @return a {@link ResolvableType} for the specified class
* @since 4.2
* @see #forClass(Class)
* @see #getRawClass()
*/
public static ResolvableType forRawClass(Class<?> clazz) {
return new ResolvableType(clazz) {
@Override
public ResolvableType[] getGenerics() {
return EMPTY_TYPES_ARRAY;
}
@Override
public boolean isAssignableFrom(Class<?> other) {
return ClassUtils.isAssignable(getRawClass(), other);
}
@Override
public boolean isAssignableFrom(ResolvableType other) {
Class<?> otherClass = other.getRawClass();
return (otherClass != null && ClassUtils.isAssignable(getRawClass(), otherClass));
}
};
}
/**
* Return a {@link ResolvableType} for the specified base type
* (interface or base class) with a given implementation class.
* For example: {@code ResolvableType.forClass(List.class, MyArrayList.class)}.
* @param baseType the base type (must not be {@code null})
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified base type backed by the
* given implementation class
* @see #forClass(Class)
* @see #forClassWithGenerics(Class, Class...)
*/
public static ResolvableType forClass(Class<?> baseType, Class<?> implementationClass) {
Assert.notNull(baseType, "Base type must not be null");
ResolvableType asType = forType(implementationClass).as(baseType);
return (asType == NONE ? forType(baseType) : asType);
}
/**
* Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics.
* @param clazz the class (or interface) to introspect
* @param generics the generics of the class
* @return a {@link ResolvableType} for the specific class and generics
* @see #forClassWithGenerics(Class, ResolvableType...)
*/
public static ResolvableType forClassWithGenerics(Class<?> clazz, Class<?>... generics) {
Assert.notNull(clazz, "Class must not be null");
Assert.notNull(generics, "Generics array must not be null");
ResolvableType[] resolvableGenerics = new ResolvableType[generics.length];
for (int i = 0; i < generics.length; i++) {
resolvableGenerics[i] = forClass(generics[i]);
}
return forClassWithGenerics(clazz, resolvableGenerics);
}
/**
* Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics.
* @param clazz the class (or interface) to introspect
* @param generics the generics of the class
* @return a {@link ResolvableType} for the specific class and generics
* @see #forClassWithGenerics(Class, Class...)
*/
public static ResolvableType forClassWithGenerics(Class<?> clazz, ResolvableType... generics) {
Assert.notNull(clazz, "Class must not be null");
Assert.notNull(generics, "Generics array must not be null");
TypeVariable<?>[] variables = clazz.getTypeParameters();
Assert.isTrue(variables.length == generics.length, "Mismatched number of generics specified");
Type[] arguments = new Type[generics.length];
for (int i = 0; i < generics.length; i++) {
ResolvableType generic = generics[i];
Type argument = (generic != null ? generic.getType() : null);
arguments[i] = (argument != null ? argument : variables[i]);
}
ParameterizedType syntheticType = new SyntheticParameterizedType(clazz, arguments);
return forType(syntheticType, new TypeVariablesVariableResolver(variables, generics));
}
/**
* Return a {@link ResolvableType} for the specified instance. The instance does not
* convey generic information but if it implements {@link ResolvableTypeProvider} a
* more precise {@link ResolvableType} can be used than the simple one based on
* the {@link #forClass(Class) Class instance}.
* @param instance the instance
* @return a {@link ResolvableType} for the specified instance
* @since 4.2
* @see ResolvableTypeProvider
*/
public static ResolvableType forInstance(Object instance) {
Assert.notNull(instance, "Instance must not be null");
if (instance instanceof ResolvableTypeProvider) {
ResolvableType type = ((ResolvableTypeProvider) instance).getResolvableType();
if (type != null) {
return type;
}
}
return ResolvableType.forClass(instance.getClass());
}
/**
* Return a {@link ResolvableType} for the specified {@link Field}.
* @param field the source field
* @return a {@link ResolvableType} for the specified field
* @see #forField(Field, Class)
*/
public static ResolvableType forField(Field field) {
Assert.notNull(field, "Field must not be null");
return forType(null, new FieldTypeProvider(field), null);
}
/**
* Return a {@link ResolvableType} for the specified {@link Field} with a given
* implementation.
* <p>Use this variant when the class that declares the field includes generic
* parameter variables that are satisfied by the implementation class.
* @param field the source field
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified field
* @see #forField(Field)
*/
public static ResolvableType forField(Field field, Class<?> implementationClass) {
Assert.notNull(field, "Field must not be null");
ResolvableType owner = forType(implementationClass).as(field.getDeclaringClass());
return forType(null, new FieldTypeProvider(field), owner.asVariableResolver());
}
/**
* Return a {@link ResolvableType} for the specified {@link Field} with a given
* implementation.
* <p>Use this variant when the class that declares the field includes generic
* parameter variables that are satisfied by the implementation type.
* @param field the source field
* @param implementationType the implementation type
* @return a {@link ResolvableType} for the specified field
* @see #forField(Field)
*/
public static ResolvableType forField(Field field, ResolvableType implementationType) {
Assert.notNull(field, "Field must not be null");
ResolvableType owner = (implementationType != null ? implementationType : NONE);
owner = owner.as(field.getDeclaringClass());
return forType(null, new FieldTypeProvider(field), owner.asVariableResolver());
}
/**
* Return a {@link ResolvableType} for the specified {@link Field} with the
* given nesting level.
* @param field the source field
* @param nestingLevel the nesting level (1 for the outer level; 2 for a nested
* generic type; etc)
* @see #forField(Field)
*/
public static ResolvableType forField(Field field, int nestingLevel) {
Assert.notNull(field, "Field must not be null");
return forType(null, new FieldTypeProvider(field), null).getNested(nestingLevel);
}
/**
* Return a {@link ResolvableType} for the specified {@link Field} with a given
* implementation and the given nesting level.
* <p>Use this variant when the class that declares the field includes generic
* parameter variables that are satisfied by the implementation class.
* @param field the source field
* @param nestingLevel the nesting level (1 for the outer level; 2 for a nested
* generic type; etc)
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified field
* @see #forField(Field)
*/
public static ResolvableType forField(Field field, int nestingLevel, Class<?> implementationClass) {
Assert.notNull(field, "Field must not be null");
ResolvableType owner = forType(implementationClass).as(field.getDeclaringClass());
return forType(null, new FieldTypeProvider(field), owner.asVariableResolver()).getNested(nestingLevel);
}
/**
* Return a {@link ResolvableType} for the specified {@link Constructor} parameter.
* @param constructor the source constructor (must not be {@code null})
* @param parameterIndex the parameter index
* @return a {@link ResolvableType} for the specified constructor parameter
* @see #forConstructorParameter(Constructor, int, Class)
*/
public static ResolvableType forConstructorParameter(Constructor<?> constructor, int parameterIndex) {
Assert.notNull(constructor, "Constructor must not be null");
return forMethodParameter(new MethodParameter(constructor, parameterIndex));
}
/**
* Return a {@link ResolvableType} for the specified {@link Constructor} parameter
* with a given implementation. Use this variant when the class that declares the
* constructor includes generic parameter variables that are satisfied by the
* implementation class.
* @param constructor the source constructor (must not be {@code null})
* @param parameterIndex the parameter index
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified constructor parameter
* @see #forConstructorParameter(Constructor, int)
*/
public static ResolvableType forConstructorParameter(Constructor<?> constructor, int parameterIndex,
Class<?> implementationClass) {
Assert.notNull(constructor, "Constructor must not be null");
MethodParameter methodParameter = new MethodParameter(constructor, parameterIndex);
methodParameter.setContainingClass(implementationClass);
return forMethodParameter(methodParameter);
}
/**
* Return a {@link ResolvableType} for the specified {@link Method} return type.
* @param method the source for the method return type
* @return a {@link ResolvableType} for the specified method return
* @see #forMethodReturnType(Method, Class)
*/
public static ResolvableType forMethodReturnType(Method method) {
Assert.notNull(method, "Method must not be null");
return forMethodParameter(new MethodParameter(method, -1));
}
/**
* Return a {@link ResolvableType} for the specified {@link Method} return type.
* Use this variant when the class that declares the method includes generic
* parameter variables that are satisfied by the implementation class.
* @param method the source for the method return type
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified method return
* @see #forMethodReturnType(Method)
*/
public static ResolvableType forMethodReturnType(Method method, Class<?> implementationClass) {
Assert.notNull(method, "Method must not be null");
MethodParameter methodParameter = new MethodParameter(method, -1);
methodParameter.setContainingClass(implementationClass);
return forMethodParameter(methodParameter);
}
/**
* Return a {@link ResolvableType} for the specified {@link Method} parameter.
* @param method the source method (must not be {@code null})
* @param parameterIndex the parameter index
* @return a {@link ResolvableType} for the specified method parameter
* @see #forMethodParameter(Method, int, Class)
* @see #forMethodParameter(MethodParameter)
*/
public static ResolvableType forMethodParameter(Method method, int parameterIndex) {
Assert.notNull(method, "Method must not be null");
return forMethodParameter(new MethodParameter(method, parameterIndex));
}
/**
* Return a {@link ResolvableType} for the specified {@link Method} parameter with a
* given implementation. Use this variant when the class that declares the method
* includes generic parameter variables that are satisfied by the implementation class.
* @param method the source method (must not be {@code null})
* @param parameterIndex the parameter index
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified method parameter
* @see #forMethodParameter(Method, int, Class)
* @see #forMethodParameter(MethodParameter)
*/
public static ResolvableType forMethodParameter(Method method, int parameterIndex, Class<?> implementationClass) {
Assert.notNull(method, "Method must not be null");
MethodParameter methodParameter = new MethodParameter(method, parameterIndex);
methodParameter.setContainingClass(implementationClass);
return forMethodParameter(methodParameter);
}
/**
* Return a {@link ResolvableType} for the specified {@link MethodParameter}.
* @param methodParameter the source method parameter (must not be {@code null})
* @return a {@link ResolvableType} for the specified method parameter
* @see #forMethodParameter(Method, int)
*/
public static ResolvableType forMethodParameter(MethodParameter methodParameter) {
return forMethodParameter(methodParameter, (Type) null);
}
/**
* Return a {@link ResolvableType} for the specified {@link MethodParameter} with a
* given implementation type. Use this variant when the class that declares the method
* includes generic parameter variables that are satisfied by the implementation type.
* @param methodParameter the source method parameter (must not be {@code null})
* @param implementationType the implementation type
* @return a {@link ResolvableType} for the specified method parameter
* @see #forMethodParameter(MethodParameter)
*/
public static ResolvableType forMethodParameter(MethodParameter methodParameter, ResolvableType implementationType) {
Assert.notNull(methodParameter, "MethodParameter must not be null");
implementationType = (implementationType != null ? implementationType :
forType(methodParameter.getContainingClass()));
ResolvableType owner = implementationType.as(methodParameter.getDeclaringClass());
return forType(null, new MethodParameterTypeProvider(methodParameter), owner.asVariableResolver()).
getNested(methodParameter.getNestingLevel(), methodParameter.typeIndexesPerLevel);
}
/**
* Return a {@link ResolvableType} for the specified {@link MethodParameter},
* overriding the target type to resolve with a specific given type.
* @param methodParameter the source method parameter (must not be {@code null})
* @param targetType the type to resolve (a part of the method parameter's type)
* @return a {@link ResolvableType} for the specified method parameter
* @see #forMethodParameter(Method, int)
*/
public static ResolvableType forMethodParameter(MethodParameter methodParameter, Type targetType) {
Assert.notNull(methodParameter, "MethodParameter must not be null");
ResolvableType owner = forType(methodParameter.getContainingClass()).as(methodParameter.getDeclaringClass());
return forType(targetType, new MethodParameterTypeProvider(methodParameter), owner.asVariableResolver()).
getNested(methodParameter.getNestingLevel(), methodParameter.typeIndexesPerLevel);
}
/**
* Resolve the top-level parameter type of the given {@code MethodParameter}.
* @param methodParameter the method parameter to resolve
* @since 4.1.9
* @see MethodParameter#setParameterType
*/
static void resolveMethodParameter(MethodParameter methodParameter) {
Assert.notNull(methodParameter, "MethodParameter must not be null");
ResolvableType owner = forType(methodParameter.getContainingClass()).as(methodParameter.getDeclaringClass());
methodParameter.setParameterType(
forType(null, new MethodParameterTypeProvider(methodParameter), owner.asVariableResolver()).resolve());
}
/**
* Return a {@link ResolvableType} as a array of the specified {@code componentType}.
* @param componentType the component type
* @return a {@link ResolvableType} as an array of the specified component type
*/
public static ResolvableType forArrayComponent(ResolvableType componentType) {
Assert.notNull(componentType, "Component type must not be null");
Class<?> arrayClass = Array.newInstance(componentType.resolve(), 0).getClass();
return new ResolvableType(arrayClass, null, null, componentType);
}
private static ResolvableType[] forTypes(Type[] types, VariableResolver owner) {
ResolvableType[] result = new ResolvableType[types.length];
for (int i = 0; i < types.length; i++) {
result[i] = forType(types[i], owner);
}
return result;
}
/**
* Return a {@link ResolvableType} for the specified {@link Type}.
* <p>Note: The resulting {@link ResolvableType} instance may not be {@link Serializable}.
* @param type the source type (potentially {@code null})
* @return a {@link ResolvableType} for the specified {@link Type}
* @see #forType(Type, ResolvableType)
*/
public static ResolvableType forType(Type type) {
return forType(type, null, null);
}
/**
* Return a {@link ResolvableType} for the specified {@link Type} backed by the given
* owner type.
* <p>Note: The resulting {@link ResolvableType} instance may not be {@link Serializable}.
* @param type the source type or {@code null}
* @param owner the owner type used to resolve variables
* @return a {@link ResolvableType} for the specified {@link Type} and owner
* @see #forType(Type)
*/
public static ResolvableType forType(Type type, ResolvableType owner) {
VariableResolver variableResolver = null;
if (owner != null) {
variableResolver = owner.asVariableResolver();
}
return forType(type, variableResolver);
}
/**
* Return a {@link ResolvableType} for the specified {@link ParameterizedTypeReference}.
* <p>Note: The resulting {@link ResolvableType} instance may not be {@link Serializable}.
* @param typeReference the reference to obtain the source type from
* @return a {@link ResolvableType} for the specified {@link ParameterizedTypeReference}
* @since 4.3.12
* @see #forType(Type)
*/
public static ResolvableType forType(ParameterizedTypeReference<?> typeReference) {
return forType(typeReference.getType(), null, null);
}
/**
* Return a {@link ResolvableType} for the specified {@link Type} backed by a given
* {@link VariableResolver}.
* @param type the source type or {@code null}
* @param variableResolver the variable resolver or {@code null}
* @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver}
*/
static ResolvableType forType(Type type, VariableResolver variableResolver) {
return forType(type, null, variableResolver);
}
/**
* Return a {@link ResolvableType} for the specified {@link Type} backed by a given
* {@link VariableResolver}.
* @param type the source type or {@code null}
* @param typeProvider the type provider or {@code null}
* @param variableResolver the variable resolver or {@code null}
* @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver}
*/
static ResolvableType forType(Type type, TypeProvider typeProvider, VariableResolver variableResolver) {
if (type == null && typeProvider != null) {
type = SerializableTypeWrapper.forTypeProvider(typeProvider);
}
if (type == null) {
return NONE;
}
// For simple Class references, build the wrapper right away -
// no expensive resolution necessary, so not worth caching...
if (type instanceof Class) {
return new ResolvableType(type, typeProvider, variableResolver, (ResolvableType) null);
}
// Purge empty entries on access since we don't have a clean-up thread or the like.
cache.purgeUnreferencedEntries();
// Check the cache - we may have a ResolvableType which has been resolved before...
ResolvableType key = new ResolvableType(type, typeProvider, variableResolver);
ResolvableType resolvableType = cache.get(key);
if (resolvableType == null) {
resolvableType = new ResolvableType(type, typeProvider, variableResolver, key.hash);
cache.put(resolvableType, resolvableType);
}
return resolvableType;
}
/**
* Clear the internal {@code ResolvableType}/{@code SerializableTypeWrapper} cache.
* @since 4.2
*/
public static void clearCache() {
cache.clear();
SerializableTypeWrapper.cache.clear();
}
/**
* Strategy interface used to resolve {@link TypeVariable}s.
*/
interface VariableResolver extends Serializable {
/**
* Return the source of the resolver (used for hashCode and equals).
*/
Object getSource();
/**
* Resolve the specified variable.
* @param variable the variable to resolve
* @return the resolved variable, or {@code null} if not found
*/
ResolvableType resolveVariable(TypeVariable<?> variable);
}
@SuppressWarnings("serial")
private class DefaultVariableResolver implements VariableResolver {
@Override
public ResolvableType resolveVariable(TypeVariable<?> variable) {
return ResolvableType.this.resolveVariable(variable);
}
@Override
public Object getSource() {
return ResolvableType.this;
}
}
@SuppressWarnings("serial")
private static class TypeVariablesVariableResolver implements VariableResolver {
private final TypeVariable<?>[] variables;
private final ResolvableType[] generics;
public TypeVariablesVariableResolver(TypeVariable<?>[] variables, ResolvableType[] generics) {
this.variables = variables;
this.generics = generics;
}
@Override
public ResolvableType resolveVariable(TypeVariable<?> variable) {
for (int i = 0; i < this.variables.length; i++) {
TypeVariable<?> v1 = SerializableTypeWrapper.unwrap(this.variables[i]);
TypeVariable<?> v2 = SerializableTypeWrapper.unwrap(variable);
if (ObjectUtil.nullSafeEquals(v1, v2)) {
return this.generics[i];
}
}
return null;
}
@Override
public Object getSource() {
return this.generics;
}
}
private static final class SyntheticParameterizedType implements ParameterizedType, Serializable {
private final Type rawType;
private final Type[] typeArguments;
public SyntheticParameterizedType(Type rawType, Type[] typeArguments) {
this.rawType = rawType;
this.typeArguments = typeArguments;
}
@Override // on Java 8
public String getTypeName() {
StringBuilder result = new StringBuilder(this.rawType.getTypeName());
if (this.typeArguments.length > 0) {
result.append('<');
for (int i = 0; i < this.typeArguments.length; i++) {
if (i > 0) {
result.append(", ");
}
result.append(this.typeArguments[i].getTypeName());
}
result.append('>');
}
return result.toString();
}
@Override
public Type getOwnerType() {
return null;
}
@Override
public Type getRawType() {
return this.rawType;
}
@Override
public Type[] getActualTypeArguments() {
return this.typeArguments;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof ParameterizedType)) {
return false;
}
ParameterizedType otherType = (ParameterizedType) other;
return (otherType.getOwnerType() == null && this.rawType.equals(otherType.getRawType()) &&
Arrays.equals(this.typeArguments, otherType.getActualTypeArguments()));
}
@Override
public int hashCode() {
return (this.rawType.hashCode() * 31 + Arrays.hashCode(this.typeArguments));
}
}
/**
* Internal helper to handle bounds from {@link WildcardType}s.
*/
private static class WildcardBounds {
private final Kind kind;
private final ResolvableType[] bounds;
/**
* Internal constructor to create a new {@link WildcardBounds} instance.
* @param kind the kind of bounds
* @param bounds the bounds
* @see #get(ResolvableType)
*/
public WildcardBounds(Kind kind, ResolvableType[] bounds) {
this.kind = kind;
this.bounds = bounds;
}
/**
* Return {@code true} if this bounds is the same kind as the specified bounds.
*/
public boolean isSameKind(WildcardBounds bounds) {
return this.kind == bounds.kind;
}
/**
* Return {@code true} if this bounds is assignable to all the specified types.
* @param types the types to test against
* @return {@code true} if this bounds is assignable to all types
*/
public boolean isAssignableFrom(ResolvableType... types) {
for (ResolvableType bound : this.bounds) {
for (ResolvableType type : types) {
if (!isAssignable(bound, type)) {
return false;
}
}
}
return true;
}
private boolean isAssignable(ResolvableType source, ResolvableType from) {
return (this.kind == Kind.UPPER ? source.isAssignableFrom(from) : from.isAssignableFrom(source));
}
/**
* Return the underlying bounds.
*/
public ResolvableType[] getBounds() {
return this.bounds;
}
/**
* Get a {@link WildcardBounds} instance for the specified type, returning
* {@code null} if the specified type cannot be resolved to a {@link WildcardType}.
* @param type the source type
* @return a {@link WildcardBounds} instance or {@code null}
*/
public static WildcardBounds get(ResolvableType type) {
ResolvableType resolveToWildcard = type;
while (!(resolveToWildcard.getType() instanceof WildcardType)) {
if (resolveToWildcard == NONE) {
return null;
}
resolveToWildcard = resolveToWildcard.resolveType();
}
WildcardType wildcardType = (WildcardType) resolveToWildcard.type;
Kind boundsType = (wildcardType.getLowerBounds().length > 0 ? Kind.LOWER : Kind.UPPER);
Type[] bounds = (boundsType == Kind.UPPER ? wildcardType.getUpperBounds() : wildcardType.getLowerBounds());
ResolvableType[] resolvableBounds = new ResolvableType[bounds.length];
for (int i = 0; i < bounds.length; i++) {
resolvableBounds[i] = ResolvableType.forType(bounds[i], type.variableResolver);
}
return new WildcardBounds(boundsType, resolvableBounds);
}
/**
* The various kinds of bounds.
*/
enum Kind {UPPER, LOWER}
}
}
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.base.type;
/**
* @author: aoshiguchen
* @date: 2022/9/25
*/
public interface ResolvableTypeProvider {
/**
* Return the {@link ResolvableType} describing this instance
* (or {@code null} if some sort of default should be applied instead).
*/
ResolvableType getResolvableType();
}
@@ -0,0 +1,399 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.base.type;
import fun.asgc.neutrino.core.util.ConcurrentReferenceHashMap;
import fun.asgc.neutrino.core.util.ReflectUtil;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.reflect.*;
/**
* @author: aoshiguchen
* @date: 2022/9/25
*/
abstract class SerializableTypeWrapper {
private static final Class<?>[] SUPPORTED_SERIALIZABLE_TYPES = {
GenericArrayType.class, ParameterizedType.class, TypeVariable.class, WildcardType.class};
static final ConcurrentReferenceHashMap<Type, Type> cache = new ConcurrentReferenceHashMap<Type, Type>(256);
/**
* Return a {@link Serializable} variant of {@link Field#getGenericType()}.
*/
public static Type forField(Field field) {
return forTypeProvider(new FieldTypeProvider(field));
}
/**
* Return a {@link Serializable} variant of
* {@link MethodParameter#getGenericParameterType()}.
*/
public static Type forMethodParameter(MethodParameter methodParameter) {
return forTypeProvider(new MethodParameterTypeProvider(methodParameter));
}
/**
* Return a {@link Serializable} variant of {@link Class#getGenericSuperclass()}.
*/
@SuppressWarnings("serial")
public static Type forGenericSuperclass(final Class<?> type) {
return forTypeProvider(new SimpleTypeProvider() {
@Override
public Type getType() {
return type.getGenericSuperclass();
}
});
}
/**
* Return a {@link Serializable} variant of {@link Class#getGenericInterfaces()}.
*/
@SuppressWarnings("serial")
public static Type[] forGenericInterfaces(final Class<?> type) {
Type[] result = new Type[type.getGenericInterfaces().length];
for (int i = 0; i < result.length; i++) {
final int index = i;
result[i] = forTypeProvider(new SimpleTypeProvider() {
@Override
public Type getType() {
return type.getGenericInterfaces()[index];
}
});
}
return result;
}
/**
* Return a {@link Serializable} variant of {@link Class#getTypeParameters()}.
*/
@SuppressWarnings("serial")
public static Type[] forTypeParameters(final Class<?> type) {
Type[] result = new Type[type.getTypeParameters().length];
for (int i = 0; i < result.length; i++) {
final int index = i;
result[i] = forTypeProvider(new SimpleTypeProvider() {
@Override
public Type getType() {
return type.getTypeParameters()[index];
}
});
}
return result;
}
/**
* Unwrap the given type, effectively returning the original non-serializable type.
* @param type the type to unwrap
* @return the original non-serializable type
*/
@SuppressWarnings("unchecked")
public static <T extends Type> T unwrap(T type) {
Type unwrapped = type;
while (unwrapped instanceof SerializableTypeProxy) {
unwrapped = ((SerializableTypeProxy) type).getTypeProvider().getType();
}
return (T) unwrapped;
}
/**
* Return a {@link Serializable} {@link Type} backed by a {@link TypeProvider} .
*/
static Type forTypeProvider(TypeProvider provider) {
Type providedType = provider.getType();
if (providedType == null || providedType instanceof Serializable) {
// No serializable type wrapping necessary (e.g. for java.lang.Class)
return providedType;
}
// Obtain a serializable type proxy for the given provider...
Type cached = cache.get(providedType);
if (cached != null) {
return cached;
}
for (Class<?> type : SUPPORTED_SERIALIZABLE_TYPES) {
if (type.isInstance(providedType)) {
ClassLoader classLoader = provider.getClass().getClassLoader();
Class<?>[] interfaces = new Class<?>[] {type, SerializableTypeProxy.class, Serializable.class};
InvocationHandler handler = new TypeProxyInvocationHandler(provider);
cached = (Type) Proxy.newProxyInstance(classLoader, interfaces, handler);
cache.put(providedType, cached);
return cached;
}
}
throw new IllegalArgumentException("Unsupported Type class: " + providedType.getClass().getName());
}
/**
* Additional interface implemented by the type proxy.
*/
interface SerializableTypeProxy {
/**
* Return the underlying type provider.
*/
TypeProvider getTypeProvider();
}
/**
* A {@link Serializable} interface providing access to a {@link Type}.
*/
interface TypeProvider extends Serializable {
/**
* Return the (possibly non {@link Serializable}) {@link Type}.
*/
Type getType();
/**
* Return the source of the type or {@code null}.
*/
Object getSource();
}
/**
* Base implementation of {@link TypeProvider} with a {@code null} source.
*/
@SuppressWarnings("serial")
private static abstract class SimpleTypeProvider implements TypeProvider {
@Override
public Object getSource() {
return null;
}
}
/**
* {@link Serializable} {@link InvocationHandler} used by the proxied {@link Type}.
* Provides serialization support and enhances any methods that return {@code Type}
* or {@code Type[]}.
*/
@SuppressWarnings("serial")
private static class TypeProxyInvocationHandler implements InvocationHandler, Serializable {
private final TypeProvider provider;
public TypeProxyInvocationHandler(TypeProvider provider) {
this.provider = provider;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("equals")) {
Object other = args[0];
// Unwrap proxies for speed
if (other instanceof Type) {
other = unwrap((Type) other);
}
return this.provider.getType().equals(other);
}
else if (method.getName().equals("hashCode")) {
return this.provider.getType().hashCode();
}
else if (method.getName().equals("getTypeProvider")) {
return this.provider;
}
if (Type.class == method.getReturnType() && args == null) {
return forTypeProvider(new MethodInvokeTypeProvider(this.provider, method, -1));
}
else if (Type[].class == method.getReturnType() && args == null) {
Type[] result = new Type[((Type[]) method.invoke(this.provider.getType(), args)).length];
for (int i = 0; i < result.length; i++) {
result[i] = forTypeProvider(new MethodInvokeTypeProvider(this.provider, method, i));
}
return result;
}
try {
return method.invoke(this.provider.getType(), args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
/**
* {@link TypeProvider} for {@link Type}s obtained from a {@link Field}.
*/
@SuppressWarnings("serial")
static class FieldTypeProvider implements TypeProvider {
private final String fieldName;
private final Class<?> declaringClass;
private transient Field field;
public FieldTypeProvider(Field field) {
this.fieldName = field.getName();
this.declaringClass = field.getDeclaringClass();
this.field = field;
}
@Override
public Type getType() {
return this.field.getGenericType();
}
@Override
public Object getSource() {
return this.field;
}
private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
inputStream.defaultReadObject();
try {
this.field = this.declaringClass.getDeclaredField(this.fieldName);
}
catch (Throwable ex) {
throw new IllegalStateException("Could not find original class structure", ex);
}
}
}
/**
* {@link TypeProvider} for {@link Type}s obtained from a {@link MethodParameter}.
*/
@SuppressWarnings("serial")
static class MethodParameterTypeProvider implements TypeProvider {
private final String methodName;
private final Class<?>[] parameterTypes;
private final Class<?> declaringClass;
private final int parameterIndex;
private transient MethodParameter methodParameter;
public MethodParameterTypeProvider(MethodParameter methodParameter) {
if (methodParameter.getMethod() != null) {
this.methodName = methodParameter.getMethod().getName();
this.parameterTypes = methodParameter.getMethod().getParameterTypes();
}
else {
this.methodName = null;
this.parameterTypes = methodParameter.getConstructor().getParameterTypes();
}
this.declaringClass = methodParameter.getDeclaringClass();
this.parameterIndex = methodParameter.getParameterIndex();
this.methodParameter = methodParameter;
}
@Override
public Type getType() {
return this.methodParameter.getGenericParameterType();
}
@Override
public Object getSource() {
return this.methodParameter;
}
private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
inputStream.defaultReadObject();
try {
if (this.methodName != null) {
this.methodParameter = new MethodParameter(
this.declaringClass.getDeclaredMethod(this.methodName, this.parameterTypes), this.parameterIndex);
}
else {
this.methodParameter = new MethodParameter(
this.declaringClass.getDeclaredConstructor(this.parameterTypes), this.parameterIndex);
}
}
catch (Throwable ex) {
throw new IllegalStateException("Could not find original class structure", ex);
}
}
}
/**
* {@link TypeProvider} for {@link Type}s obtained by invoking a no-arg method.
*/
@SuppressWarnings("serial")
static class MethodInvokeTypeProvider implements TypeProvider {
private final TypeProvider provider;
private final String methodName;
private final Class<?> declaringClass;
private final int index;
private transient Method method;
private transient volatile Object result;
public MethodInvokeTypeProvider(TypeProvider provider, Method method, int index) {
this.provider = provider;
this.methodName = method.getName();
this.declaringClass = method.getDeclaringClass();
this.index = index;
this.method = method;
}
@Override
public Type getType() {
Object result = this.result;
if (result == null) {
// Lazy invocation of the target method on the provided type
result = ReflectUtil.invokeMethod(this.method, this.provider.getType());
// Cache the result for further calls to getType()
this.result = result;
}
return (result instanceof Type[] ? ((Type[]) result)[this.index] : (Type) result);
}
@Override
public Object getSource() {
return null;
}
private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
inputStream.defaultReadObject();
this.method = ReflectUtil.findMethod(this.declaringClass, this.methodName);
if (this.method.getReturnType() != Type.class && this.method.getReturnType() != Type[].class) {
throw new IllegalStateException(
"Invalid return type on deserialized method - needs to be Type or Type[]: " + this.method);
}
}
}
}
@@ -0,0 +1,1017 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.util;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author: aoshiguchen
* @date: 2022/9/24
*/
public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
private static final int DEFAULT_INITIAL_CAPACITY = 16;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
private static final ReferenceType DEFAULT_REFERENCE_TYPE = ReferenceType.SOFT;
private static final int MAXIMUM_CONCURRENCY_LEVEL = 1 << 16;
private static final int MAXIMUM_SEGMENT_SIZE = 1 << 30;
/**
* Array of segments indexed using the high order bits from the hash.
*/
private final Segment[] segments;
/**
* When the average number of references per table exceeds this value resize will be attempted.
*/
private final float loadFactor;
/**
* The reference type: SOFT or WEAK.
*/
private final ReferenceType referenceType;
/**
* The shift value used to calculate the size of the segments array and an index from the hash.
*/
private final int shift;
/**
* Late binding entry set.
*/
private volatile Set<Map.Entry<K, V>> entrySet;
/**
* Create a new {@code ConcurrentReferenceHashMap} instance.
*/
public ConcurrentReferenceHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE);
}
/**
* Create a new {@code ConcurrentReferenceHashMap} instance.
* @param initialCapacity the initial capacity of the map
*/
public ConcurrentReferenceHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE);
}
/**
* Create a new {@code ConcurrentReferenceHashMap} instance.
* @param initialCapacity the initial capacity of the map
* @param loadFactor the load factor. When the average number of references per table
* exceeds this value resize will be attempted
*/
public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE);
}
/**
* Create a new {@code ConcurrentReferenceHashMap} instance.
* @param initialCapacity the initial capacity of the map
* @param concurrencyLevel the expected number of threads that will concurrently
* write to the map
*/
public ConcurrentReferenceHashMap(int initialCapacity, int concurrencyLevel) {
this(initialCapacity, DEFAULT_LOAD_FACTOR, concurrencyLevel, DEFAULT_REFERENCE_TYPE);
}
/**
* Create a new {@code ConcurrentReferenceHashMap} instance.
* @param initialCapacity the initial capacity of the map
* @param referenceType the reference type used for entries (soft or weak)
*/
public ConcurrentReferenceHashMap(int initialCapacity, ReferenceType referenceType) {
this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, referenceType);
}
/**
* Create a new {@code ConcurrentReferenceHashMap} instance.
* @param initialCapacity the initial capacity of the map
* @param loadFactor the load factor. When the average number of references per
* table exceeds this value, resize will be attempted.
* @param concurrencyLevel the expected number of threads that will concurrently
* write to the map
*/
public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
this(initialCapacity, loadFactor, concurrencyLevel, DEFAULT_REFERENCE_TYPE);
}
/**
* Create a new {@code ConcurrentReferenceHashMap} instance.
* @param initialCapacity the initial capacity of the map
* @param loadFactor the load factor. When the average number of references per
* table exceeds this value, resize will be attempted.
* @param concurrencyLevel the expected number of threads that will concurrently
* write to the map
* @param referenceType the reference type used for entries (soft or weak)
*/
@SuppressWarnings("unchecked")
public ConcurrentReferenceHashMap(
int initialCapacity, float loadFactor, int concurrencyLevel, ReferenceType referenceType) {
Assert.isTrue(initialCapacity >= 0, "Initial capacity must not be negative");
Assert.isTrue(loadFactor > 0f, "Load factor must be positive");
Assert.isTrue(concurrencyLevel > 0, "Concurrency level must be positive");
Assert.notNull(referenceType, "Reference type must not be null");
this.loadFactor = loadFactor;
this.shift = calculateShift(concurrencyLevel, MAXIMUM_CONCURRENCY_LEVEL);
int size = 1 << this.shift;
this.referenceType = referenceType;
int roundedUpSegmentCapacity = (int) ((initialCapacity + size - 1L) / size);
this.segments = (Segment[]) Array.newInstance(Segment.class, size);
for (int i = 0; i < this.segments.length; i++) {
this.segments[i] = new Segment(roundedUpSegmentCapacity);
}
}
protected final float getLoadFactor() {
return this.loadFactor;
}
protected final int getSegmentsSize() {
return this.segments.length;
}
protected final Segment getSegment(int index) {
return this.segments[index];
}
/**
* Factory method that returns the {@link ReferenceManager}.
* This method will be called once for each {@link Segment}.
* @return a new reference manager
*/
protected ReferenceManager createReferenceManager() {
return new ReferenceManager();
}
/**
* Get the hash for a given object, apply an additional hash function to reduce
* collisions. This implementation uses the same Wang/Jenkins algorithm as
* {@link ConcurrentHashMap}. Subclasses can override to provide alternative hashing.
* @param o the object to hash (may be null)
* @return the resulting hash code
*/
protected int getHash(Object o) {
int hash = (o != null ? o.hashCode() : 0);
hash += (hash << 15) ^ 0xffffcd7d;
hash ^= (hash >>> 10);
hash += (hash << 3);
hash ^= (hash >>> 6);
hash += (hash << 2) + (hash << 14);
hash ^= (hash >>> 16);
return hash;
}
@Override
public V get(Object key) {
Entry<K, V> entry = getEntryIfAvailable(key);
return (entry != null ? entry.getValue() : null);
}
@Override
public V getOrDefault(Object key, V defaultValue) {
Entry<K, V> entry = getEntryIfAvailable(key);
return (entry != null ? entry.getValue() : defaultValue);
}
@Override
public boolean containsKey(Object key) {
Entry<K, V> entry = getEntryIfAvailable(key);
return (entry != null && ObjectUtil.nullSafeEquals(entry.getKey(), key));
}
private Entry<K, V> getEntryIfAvailable(Object key) {
Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY);
return (ref != null ? ref.get() : null);
}
/**
* Return a {@link Reference} to the {@link Entry} for the specified {@code key},
* or {@code null} if not found.
* @param key the key (can be {@code null})
* @param restructure types of restructure allowed during this call
* @return the reference, or {@code null} if not found
*/
protected final Reference<K, V> getReference(Object key, Restructure restructure) {
int hash = getHash(key);
return getSegmentForHash(hash).getReference(key, hash, restructure);
}
@Override
public V put(K key, V value) {
return put(key, value, true);
}
@Override
public V putIfAbsent(K key, V value) {
return put(key, value, false);
}
private V put(final K key, final V value, final boolean overwriteExisting) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
@Override
protected V execute(Reference<K, V> ref, Entry<K, V> entry, Entries entries) {
if (entry != null) {
V oldValue = entry.getValue();
if (overwriteExisting) {
entry.setValue(value);
}
return oldValue;
}
entries.add(value);
return null;
}
});
}
@Override
public V remove(Object key) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
@Override
protected V execute(Reference<K, V> ref, Entry<K, V> entry) {
if (entry != null) {
ref.release();
return entry.value;
}
return null;
}
});
}
@Override
public boolean remove(Object key, final Object value) {
return doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
@Override
protected Boolean execute(Reference<K, V> ref, Entry<K, V> entry) {
if (entry != null && ObjectUtil.nullSafeEquals(entry.getValue(), value)) {
ref.release();
return true;
}
return false;
}
});
}
@Override
public boolean replace(K key, final V oldValue, final V newValue) {
return doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
@Override
protected Boolean execute(Reference<K, V> ref, Entry<K, V> entry) {
if (entry != null && ObjectUtil.nullSafeEquals(entry.getValue(), oldValue)) {
entry.setValue(newValue);
return true;
}
return false;
}
});
}
@Override
public V replace(K key, final V value) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
@Override
protected V execute(Reference<K, V> ref, Entry<K, V> entry) {
if (entry != null) {
V oldValue = entry.getValue();
entry.setValue(value);
return oldValue;
}
return null;
}
});
}
@Override
public void clear() {
for (Segment segment : this.segments) {
segment.clear();
}
}
/**
* Remove any entries that have been garbage collected and are no longer referenced.
* Under normal circumstances garbage collected entries are automatically purged as
* items are added or removed from the Map. This method can be used to force a purge,
* and is useful when the Map is read frequently but updated less often.
*/
public void purgeUnreferencedEntries() {
for (Segment segment : this.segments) {
segment.restructureIfNecessary(false);
}
}
@Override
public int size() {
int size = 0;
for (Segment segment : this.segments) {
size += segment.getCount();
}
return size;
}
@Override
public boolean isEmpty() {
for (Segment segment : this.segments) {
if (segment.getCount() > 0) {
return false;
}
}
return true;
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
Set<Map.Entry<K, V>> entrySet = this.entrySet;
if (entrySet == null) {
entrySet = new EntrySet();
this.entrySet = entrySet;
}
return entrySet;
}
private <T> T doTask(Object key, Task<T> task) {
int hash = getHash(key);
return getSegmentForHash(hash).doTask(hash, key, task);
}
private Segment getSegmentForHash(int hash) {
return this.segments[(hash >>> (32 - this.shift)) & (this.segments.length - 1)];
}
/**
* Calculate a shift value that can be used to create a power-of-two value between
* the specified maximum and minimum values.
* @param minimumValue the minimum value
* @param maximumValue the maximum value
* @return the calculated shift (use {@code 1 << shift} to obtain a value)
*/
protected static int calculateShift(int minimumValue, int maximumValue) {
int shift = 0;
int value = 1;
while (value < minimumValue && value < maximumValue) {
value <<= 1;
shift++;
}
return shift;
}
/**
* Various reference types supported by this map.
*/
public enum ReferenceType {
/** Use {@link SoftReference}s */
SOFT,
/** Use {@link WeakReference}s */
WEAK
}
/**
* A single segment used to divide the map to allow better concurrent performance.
*/
@SuppressWarnings("serial")
protected final class Segment extends ReentrantLock {
private final ReferenceManager referenceManager;
private final int initialSize;
/**
* Array of references indexed using the low order bits from the hash.
* This property should only be set along with {@code resizeThreshold}.
*/
private volatile Reference<K, V>[] references;
/**
* The total number of references contained in this segment. This includes chained
* references and references that have been garbage collected but not purged.
*/
private volatile int count = 0;
/**
* The threshold when resizing of the references should occur. When {@code count}
* exceeds this value references will be resized.
*/
private int resizeThreshold;
public Segment(int initialCapacity) {
this.referenceManager = createReferenceManager();
this.initialSize = 1 << calculateShift(initialCapacity, MAXIMUM_SEGMENT_SIZE);
setReferences(createReferenceArray(this.initialSize));
}
public Reference<K, V> getReference(Object key, int hash, Restructure restructure) {
if (restructure == Restructure.WHEN_NECESSARY) {
restructureIfNecessary(false);
}
if (this.count == 0) {
return null;
}
// Use a local copy to protect against other threads writing
Reference<K, V>[] references = this.references;
int index = getIndex(hash, references);
Reference<K, V> head = references[index];
return findInChain(head, key, hash);
}
/**
* Apply an update operation to this segment.
* The segment will be locked during the update.
* @param hash the hash of the key
* @param key the key
* @param task the update operation
* @return the result of the operation
*/
public <T> T doTask(final int hash, final Object key, final Task<T> task) {
boolean resize = task.hasOption(TaskOption.RESIZE);
if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) {
restructureIfNecessary(resize);
}
if (task.hasOption(TaskOption.SKIP_IF_EMPTY) && this.count == 0) {
return task.execute(null, null, null);
}
lock();
try {
final int index = getIndex(hash, this.references);
final Reference<K, V> head = this.references[index];
Reference<K, V> ref = findInChain(head, key, hash);
Entry<K, V> entry = (ref != null ? ref.get() : null);
Entries entries = new Entries() {
@Override
public void add(V value) {
@SuppressWarnings("unchecked")
Entry<K, V> newEntry = new Entry<K, V>((K) key, value);
Reference<K, V> newReference = Segment.this.referenceManager.createReference(newEntry, hash, head);
Segment.this.references[index] = newReference;
Segment.this.count++;
}
};
return task.execute(ref, entry, entries);
}
finally {
unlock();
if (task.hasOption(TaskOption.RESTRUCTURE_AFTER)) {
restructureIfNecessary(resize);
}
}
}
/**
* Clear all items from this segment.
*/
public void clear() {
if (this.count == 0) {
return;
}
lock();
try {
setReferences(createReferenceArray(this.initialSize));
this.count = 0;
}
finally {
unlock();
}
}
/**
* Restructure the underlying data structure when it becomes necessary. This
* method can increase the size of the references table as well as purge any
* references that have been garbage collected.
* @param allowResize if resizing is permitted
*/
protected final void restructureIfNecessary(boolean allowResize) {
boolean needsResize = (this.count > 0 && this.count >= this.resizeThreshold);
Reference<K, V> ref = this.referenceManager.pollForPurge();
if (ref != null || (needsResize && allowResize)) {
lock();
try {
int countAfterRestructure = this.count;
Set<Reference<K, V>> toPurge = Collections.emptySet();
if (ref != null) {
toPurge = new HashSet<Reference<K, V>>();
while (ref != null) {
toPurge.add(ref);
ref = this.referenceManager.pollForPurge();
}
}
countAfterRestructure -= toPurge.size();
// Recalculate taking into account count inside lock and items that
// will be purged
needsResize = (countAfterRestructure > 0 && countAfterRestructure >= this.resizeThreshold);
boolean resizing = false;
int restructureSize = this.references.length;
if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) {
restructureSize <<= 1;
resizing = true;
}
// Either create a new table or reuse the existing one
Reference<K, V>[] restructured =
(resizing ? createReferenceArray(restructureSize) : this.references);
// Restructure
for (int i = 0; i < this.references.length; i++) {
ref = this.references[i];
if (!resizing) {
restructured[i] = null;
}
while (ref != null) {
if (!toPurge.contains(ref) && (ref.get() != null)) {
int index = getIndex(ref.getHash(), restructured);
restructured[index] = this.referenceManager.createReference(
ref.get(), ref.getHash(), restructured[index]);
}
ref = ref.getNext();
}
}
// Replace volatile members
if (resizing) {
setReferences(restructured);
}
this.count = Math.max(countAfterRestructure, 0);
}
finally {
unlock();
}
}
}
private Reference<K, V> findInChain(Reference<K, V> ref, Object key, int hash) {
Reference<K, V> currRef = ref;
while (currRef != null) {
if (currRef.getHash() == hash) {
Entry<K, V> entry = currRef.get();
if (entry != null) {
K entryKey = entry.getKey();
if (ObjectUtil.nullSafeEquals(entryKey, key)) {
return currRef;
}
}
}
currRef = currRef.getNext();
}
return null;
}
@SuppressWarnings({"rawtypes", "unchecked"})
private Reference<K, V>[] createReferenceArray(int size) {
return new Reference[size];
}
private int getIndex(int hash, Reference<K, V>[] references) {
return (hash & (references.length - 1));
}
/**
* Replace the references with a new value, recalculating the resizeThreshold.
* @param references the new references
*/
private void setReferences(Reference<K, V>[] references) {
this.references = references;
this.resizeThreshold = (int) (references.length * getLoadFactor());
}
/**
* Return the size of the current references array.
*/
public final int getSize() {
return this.references.length;
}
/**
* Return the total number of references in this segment.
*/
public final int getCount() {
return this.count;
}
}
/**
* A reference to an {@link Entry} contained in the map. Implementations are usually
* wrappers around specific Java reference implementations (e.g., {@link SoftReference}).
*/
protected interface Reference<K, V> {
/**
* Return the referenced entry, or {@code null} if the entry is no longer available.
*/
Entry<K, V> get();
/**
* Return the hash for the reference.
*/
int getHash();
/**
* Return the next reference in the chain, or {@code null} if none.
*/
Reference<K, V> getNext();
/**
* Release this entry and ensure that it will be returned from
* {@code ReferenceManager#pollForPurge()}.
*/
void release();
}
/**
* A single map entry.
*/
protected static final class Entry<K, V> implements Map.Entry<K, V> {
private final K key;
private volatile V value;
public Entry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return this.key;
}
@Override
public V getValue() {
return this.value;
}
@Override
public V setValue(V value) {
V previous = this.value;
this.value = value;
return previous;
}
@Override
public String toString() {
return (this.key + "=" + this.value);
}
@Override
@SuppressWarnings("rawtypes")
public final boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof Map.Entry)) {
return false;
}
Map.Entry otherEntry = (Map.Entry) other;
return (ObjectUtil.nullSafeEquals(getKey(), otherEntry.getKey()) &&
ObjectUtil.nullSafeEquals(getValue(), otherEntry.getValue()));
}
@Override
public final int hashCode() {
return (ObjectUtil.nullSafeHashCode(this.key) ^ ObjectUtil.nullSafeHashCode(this.value));
}
}
/**
* A task that can be {@link Segment#doTask run} against a {@link Segment}.
*/
private abstract class Task<T> {
private final EnumSet<TaskOption> options;
public Task(TaskOption... options) {
this.options = (options.length == 0 ? EnumSet.noneOf(TaskOption.class) : EnumSet.of(options[0], options));
}
public boolean hasOption(TaskOption option) {
return this.options.contains(option);
}
/**
* Execute the task.
* @param ref the found reference (or {@code null})
* @param entry the found entry (or {@code null})
* @param entries access to the underlying entries
* @return the result of the task
* @see #execute(Reference, Entry)
*/
protected T execute(Reference<K, V> ref, Entry<K, V> entry, Entries entries) {
return execute(ref, entry);
}
/**
* Convenience method that can be used for tasks that do not need access to {@link Entries}.
* @param ref the found reference (or {@code null})
* @param entry the found entry (or {@code null})
* @return the result of the task
* @see #execute(Reference, Entry, Entries)
*/
protected T execute(Reference<K, V> ref, Entry<K, V> entry) {
return null;
}
}
/**
* Various options supported by a {@code Task}.
*/
private enum TaskOption {
RESTRUCTURE_BEFORE, RESTRUCTURE_AFTER, SKIP_IF_EMPTY, RESIZE
}
/**
* Allows a task access to {@link Segment} entries.
*/
private abstract class Entries {
/**
* Add a new entry with the specified value.
* @param value the value to add
*/
public abstract void add(V value);
}
/**
* Internal entry-set implementation.
*/
private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
@Override
public Iterator<Map.Entry<K, V>> iterator() {
return new EntryIterator();
}
@Override
public boolean contains(Object o) {
if (o instanceof Map.Entry<?, ?>) {
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
Reference<K, V> ref = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER);
Entry<K, V> otherEntry = (ref != null ? ref.get() : null);
if (otherEntry != null) {
return ObjectUtil.nullSafeEquals(otherEntry.getValue(), otherEntry.getValue());
}
}
return false;
}
@Override
public boolean remove(Object o) {
if (o instanceof Map.Entry<?, ?>) {
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
return ConcurrentReferenceHashMap.this.remove(entry.getKey(), entry.getValue());
}
return false;
}
@Override
public int size() {
return ConcurrentReferenceHashMap.this.size();
}
@Override
public void clear() {
ConcurrentReferenceHashMap.this.clear();
}
}
/**
* Internal entry iterator implementation.
*/
private class EntryIterator implements Iterator<Map.Entry<K, V>> {
private int segmentIndex;
private int referenceIndex;
private Reference<K, V>[] references;
private Reference<K, V> reference;
private Entry<K, V> next;
private Entry<K, V> last;
public EntryIterator() {
moveToNextSegment();
}
@Override
public boolean hasNext() {
getNextIfNecessary();
return (this.next != null);
}
@Override
public Entry<K, V> next() {
getNextIfNecessary();
if (this.next == null) {
throw new NoSuchElementException();
}
this.last = this.next;
this.next = null;
return this.last;
}
private void getNextIfNecessary() {
while (this.next == null) {
moveToNextReference();
if (this.reference == null) {
return;
}
this.next = this.reference.get();
}
}
private void moveToNextReference() {
if (this.reference != null) {
this.reference = this.reference.getNext();
}
while (this.reference == null && this.references != null) {
if (this.referenceIndex >= this.references.length) {
moveToNextSegment();
this.referenceIndex = 0;
}
else {
this.reference = this.references[this.referenceIndex];
this.referenceIndex++;
}
}
}
private void moveToNextSegment() {
this.reference = null;
this.references = null;
if (this.segmentIndex < ConcurrentReferenceHashMap.this.segments.length) {
this.references = ConcurrentReferenceHashMap.this.segments[this.segmentIndex].references;
this.segmentIndex++;
}
}
@Override
public void remove() {
Assert.state(this.last != null, "No element to remove");
ConcurrentReferenceHashMap.this.remove(this.last.getKey());
}
}
/**
* The types of restructuring that can be performed.
*/
protected enum Restructure {
WHEN_NECESSARY, NEVER
}
/**
* Strategy class used to manage {@link Reference}s. This class can be overridden if
* alternative reference types need to be supported.
*/
protected class ReferenceManager {
private final ReferenceQueue<Entry<K, V>> queue = new ReferenceQueue<Entry<K, V>>();
/**
* Factory method used to create a new {@link Reference}.
* @param entry the entry contained in the reference
* @param hash the hash
* @param next the next reference in the chain, or {@code null} if none
* @return a new {@link Reference}
*/
public Reference<K, V> createReference(Entry<K, V> entry, int hash, Reference<K, V> next) {
if (ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK) {
return new WeakEntryReference<K, V>(entry, hash, next, this.queue);
}
return new SoftEntryReference<K, V>(entry, hash, next, this.queue);
}
/**
* Return any reference that has been garbage collected and can be purged from the
* underlying structure or {@code null} if no references need purging. This
* method must be thread safe and ideally should not block when returning
* {@code null}. References should be returned once and only once.
* @return a reference to purge or {@code null}
*/
@SuppressWarnings("unchecked")
public Reference<K, V> pollForPurge() {
return (Reference<K, V>) this.queue.poll();
}
}
/**
* Internal {@link Reference} implementation for {@link SoftReference}s.
*/
private static final class SoftEntryReference<K, V> extends SoftReference<Entry<K, V>> implements Reference<K, V> {
private final int hash;
private final Reference<K, V> nextReference;
public SoftEntryReference(Entry<K, V> entry, int hash, Reference<K, V> next, ReferenceQueue<Entry<K, V>> queue) {
super(entry, queue);
this.hash = hash;
this.nextReference = next;
}
@Override
public int getHash() {
return this.hash;
}
@Override
public Reference<K, V> getNext() {
return this.nextReference;
}
@Override
public void release() {
enqueue();
clear();
}
}
/**
* Internal {@link Reference} implementation for {@link WeakReference}s.
*/
private static final class WeakEntryReference<K, V> extends WeakReference<Entry<K, V>> implements Reference<K, V> {
private final int hash;
private final Reference<K, V> nextReference;
public WeakEntryReference(Entry<K, V> entry, int hash, Reference<K, V> next, ReferenceQueue<Entry<K, V>> queue) {
super(entry, queue);
this.hash = hash;
this.nextReference = next;
}
@Override
public int getHash() {
return this.hash;
}
@Override
public Reference<K, V> getNext() {
return this.nextReference;
}
@Override
public void release() {
enqueue();
clear();
}
}
}
@@ -29,7 +29,9 @@ import fun.asgc.neutrino.core.cache.MemoryCache;
import fun.asgc.neutrino.core.type.TypeMatchLevel;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -464,4 +466,133 @@ public class ReflectUtil {
}
}
}
/**
* Invoke the specified {@link Method} against the supplied target object with no arguments.
* The target object can be {@code null} when invoking a static {@link Method}.
* <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}.
* @param method the method to invoke
* @param target the target object to invoke the method on
* @return the invocation result, if any
* @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
*/
public static Object invokeMethod(Method method, Object target) {
return invokeMethod(method, target, new Object[0]);
}
/**
* Invoke the specified {@link Method} against the supplied target object with the
* supplied arguments. The target object can be {@code null} when invoking a
* static {@link Method}.
* <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}.
* @param method the method to invoke
* @param target the target object to invoke the method on
* @param args the invocation arguments (may be {@code null})
* @return the invocation result, if any
*/
public static Object invokeMethod(Method method, Object target, Object... args) {
try {
return method.invoke(target, args);
}
catch (Exception ex) {
handleReflectionException(ex);
}
throw new IllegalStateException("Should never get here");
}
/**
* Handle the given reflection exception. Should only be called if no
* checked exception is expected to be thrown by the target method.
* <p>Throws the underlying RuntimeException or Error in case of an
* InvocationTargetException with such a root cause. Throws an
* IllegalStateException with an appropriate message or
* UndeclaredThrowableException otherwise.
* @param ex the reflection exception to handle
*/
public static void handleReflectionException(Exception ex) {
if (ex instanceof NoSuchMethodException) {
throw new IllegalStateException("Method not found: " + ex.getMessage());
}
if (ex instanceof IllegalAccessException) {
throw new IllegalStateException("Could not access method: " + ex.getMessage());
}
if (ex instanceof InvocationTargetException) {
handleInvocationTargetException((InvocationTargetException) ex);
}
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
throw new UndeclaredThrowableException(ex);
}
/**
* Handle the given invocation target exception. Should only be called if no
* checked exception is expected to be thrown by the target method.
* <p>Throws the underlying RuntimeException or Error in case of such a root
* cause. Throws an UndeclaredThrowableException otherwise.
* @param ex the invocation target exception to handle
*/
public static void handleInvocationTargetException(InvocationTargetException ex) {
rethrowRuntimeException(ex.getTargetException());
}
/**
* Rethrow the given {@link Throwable exception}, which is presumably the
* <em>target exception</em> of an {@link InvocationTargetException}.
* Should only be called if no checked exception is expected to be thrown
* by the target method.
* <p>Rethrows the underlying exception cast to a {@link RuntimeException} or
* {@link Error} if appropriate; otherwise, throws an
* {@link UndeclaredThrowableException}.
* @param ex the exception to rethrow
* @throws RuntimeException the rethrown exception
*/
public static void rethrowRuntimeException(Throwable ex) {
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
if (ex instanceof Error) {
throw (Error) ex;
}
throw new UndeclaredThrowableException(ex);
}
/**
* Attempt to find a {@link Method} on the supplied class with the supplied name
* and no parameters. Searches all superclasses up to {@code Object}.
* <p>Returns {@code null} if no {@link Method} can be found.
* @param clazz the class to introspect
* @param name the name of the method
* @return the Method object, or {@code null} if none found
*/
public static Method findMethod(Class<?> clazz, String name) {
return findMethod(clazz, name, new Class<?>[0]);
}
/**
* Attempt to find a {@link Method} on the supplied class with the supplied name
* and parameter types. Searches all superclasses up to {@code Object}.
* <p>Returns {@code null} if no {@link Method} can be found.
* @param clazz the class to introspect
* @param name the name of the method
* @param paramTypes the parameter types of the method
* (may be {@code null} to indicate any signature)
* @return the Method object, or {@code null} if none found
*/
public static Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
Assert.notNull(clazz, "Class must not be null");
Assert.notNull(name, "Method name must not be null");
Class<?> searchType = clazz;
while (searchType != null) {
Set<Method> methods = (searchType.isInterface() ? Sets.newHashSet(searchType.getMethods()) : getDeclaredMethods(searchType));
for (Method method : methods) {
if (name.equals(method.getName()) &&
(paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) {
return method;
}
}
searchType = searchType.getSuperclass();
}
return null;
}
}
@@ -0,0 +1,55 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.type;
import org.junit.Test;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
/**
* @author: aoshiguchen
* @date: 2022/9/24
*/
public class Test1 {
@Test
public void test() throws NoSuchFieldException {
Field param = GenericClazz.class.getDeclaredField("param");
Type genericType = param.getGenericType();
ParameterizedType type = (ParameterizedType) genericType;
Type[] typeArguments = type.getActualTypeArguments();
System.out.println("从 HashMap<String, List<Integer>> 中获取 String:" + typeArguments[0]);
System.out.println("从 HashMap<String, List<Integer>> 中获取 List<Integer> :" + typeArguments[1]);
System.out.println(
"从 HashMap<String, List<Integer>> 中获取 List :" + ((ParameterizedType) typeArguments[1]).getRawType());
System.out.println("从 HashMap<String, List<Integer>> 中获取 Integer:" + ((ParameterizedType) typeArguments[1])
.getActualTypeArguments()[0]);
System.out.println("从 HashMap<String, List<Integer>> 中获取父类型:"+param.getType().getGenericSuperclass());
}
public static class GenericClazz {
private HashMap<String, List<Integer>> param;
}
}
@@ -0,0 +1,50 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.type;
import fun.asgc.neutrino.core.base.type.ResolvableType;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
/**
* @author: aoshiguchen
* @date: 2022/9/25
*/
public class Test2 {
@Test
public void test1() throws NoSuchFieldException {
ResolvableType param = ResolvableType.forField(GenericClazz.class.getDeclaredField("param"));
System.out.println("从 HashMap<String, List<Integer>> 中获取 String:" + param.getGeneric(0).resolve());
System.out.println("从 HashMap<String, List<Integer>> 中获取 List<Integer> :" + param.getGeneric(1));
System.out.println(
"从 HashMap<String, List<Integer>> 中获取 List :" + param.getGeneric(1).resolve());
System.out.println("从 HashMap<String, List<Integer>> 中获取 Integer:" + param.getGeneric(1,0));
System.out.println("从 HashMap<String, List<Integer>> 中获取父类型:" +param.getSuperType());
}
public static class GenericClazz {
private HashMap<String, List<Integer>> param;
}
}