diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/MethodParameter.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/MethodParameter.java new file mode 100644 index 00000000..8dc48afc --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/MethodParameter.java @@ -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 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. + *

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. + *

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. + *

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 getTypeIndexesPerLevel() { + if (this.typeIndexesPerLevel == null) { + this.typeIndexesPerLevel = new HashMap(4); + } + return this.typeIndexesPerLevel; + } + + /** + * Return a variant of this {@code MethodParameter} which points to the + * same parameter but one nesting level deeper. 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 getMethodAnnotation(Class 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 boolean hasMethodAnnotation(Class annotationType) { + return getAnnotatedElement().isAnnotationPresent(annotationType); + } + + /** + * Return the annotations associated with the specific method/constructor parameter. + */ + public Annotation[] getParameterAnnotations() { + Annotation[] paramAnns = this.parameterAnnotations; + if (paramAnns == null) { + Annotation[][] annotationArray = (this.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 getParameterAnnotation(Class annotationType) { + Annotation[] anns = getParameterAnnotations(); + for (Annotation ann : anns) { + if (annotationType.isInstance(ann)) { + return (A) ann; + } + } + return null; + } + + /** + * Return whether the parameter is declared with the given annotation type. + * @param annotationType the annotation type to look for + * @see #getParameterAnnotation(Class) + */ + public boolean hasParameterAnnotation(Class annotationType) { + return (getParameterAnnotation(annotationType) != null); + } + + /** + * Initialize parameter name discovery for this method parameter. + *

This method does not actually try to retrieve the parameter name at + * this point; it just allows discovery to happen when the application calls + * {@link #getParameterName()} (if ever). + */ + public void initParameterNameDiscovery(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. + *

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 adaptAnnotation(A annotation) { + return annotation; + } + + /** + * A template method to post-process a given annotation array before + * returning it to the caller. + *

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. + *

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"); + } + } + +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ParameterNameDiscoverer.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ParameterNameDiscoverer.java new file mode 100644 index 00000000..4d713d97 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ParameterNameDiscoverer.java @@ -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); + +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ParameterizedTypeReference.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ParameterizedTypeReference.java new file mode 100644 index 00000000..279d8322 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ParameterizedTypeReference.java @@ -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 { + + 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 ParameterizedTypeReference forType(Type type) { + return new ParameterizedTypeReference(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); + } + } + +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ResolvableType.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ResolvableType.java new file mode 100644 index 00000000..5792cb80 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ResolvableType.java @@ -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 cache = + new ConcurrentReferenceHashMap(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. + *

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 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 + if (typeBounds != null) { + return (ourBounds != null && ourBounds.isSameKind(typeBounds) && + ourBounds.isAssignableFrom(typeBounds.getBounds())); + } + + // In the form 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 is not assignable from List + 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(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. + *

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>} level 1 refers + * to the {@code List}, level 2 the {@code Set}, and level 3 the {@code Integer}. + *

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). + *

Nesting levels may also apply to array types; for example given + * {@code String[]}, a nesting level of 2 refers to {@code String}. + *

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 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>}, {@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. + *

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}. + *

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. + *

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. + *

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. + *

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}. + *

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. + *

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}. + *

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} + } + +} + diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ResolvableTypeProvider.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ResolvableTypeProvider.java new file mode 100644 index 00000000..db566cf2 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ResolvableTypeProvider.java @@ -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(); + +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/SerializableTypeWrapper.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/SerializableTypeWrapper.java new file mode 100644 index 00000000..dc67e08d --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/SerializableTypeWrapper.java @@ -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 cache = new ConcurrentReferenceHashMap(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 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); + } + } + } + +} + diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ConcurrentReferenceHashMap.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ConcurrentReferenceHashMap.java new file mode 100644 index 00000000..fc14aa9d --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ConcurrentReferenceHashMap.java @@ -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 extends AbstractMap implements ConcurrentMap { + + 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> 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 entry = getEntryIfAvailable(key); + return (entry != null ? entry.getValue() : null); + } + + @Override + public V getOrDefault(Object key, V defaultValue) { + Entry entry = getEntryIfAvailable(key); + return (entry != null ? entry.getValue() : defaultValue); + } + + @Override + public boolean containsKey(Object key) { + Entry entry = getEntryIfAvailable(key); + return (entry != null && ObjectUtil.nullSafeEquals(entry.getKey(), key)); + } + + private Entry getEntryIfAvailable(Object key) { + Reference 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 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(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) { + @Override + protected V execute(Reference ref, Entry 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(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) { + @Override + protected V execute(Reference ref, Entry 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(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) { + @Override + protected Boolean execute(Reference ref, Entry 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(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) { + @Override + protected Boolean execute(Reference ref, Entry 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(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) { + @Override + protected V execute(Reference ref, Entry 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> entrySet() { + Set> entrySet = this.entrySet; + if (entrySet == null) { + entrySet = new EntrySet(); + this.entrySet = entrySet; + } + return entrySet; + } + + private T doTask(Object key, Task 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[] 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 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[] references = this.references; + int index = getIndex(hash, references); + Reference 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 doTask(final int hash, final Object key, final Task 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 head = this.references[index]; + Reference ref = findInChain(head, key, hash); + Entry entry = (ref != null ? ref.get() : null); + Entries entries = new Entries() { + @Override + public void add(V value) { + @SuppressWarnings("unchecked") + Entry newEntry = new Entry((K) key, value); + Reference 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 ref = this.referenceManager.pollForPurge(); + if (ref != null || (needsResize && allowResize)) { + lock(); + try { + int countAfterRestructure = this.count; + Set> toPurge = Collections.emptySet(); + if (ref != null) { + toPurge = new HashSet>(); + 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[] 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 findInChain(Reference ref, Object key, int hash) { + Reference currRef = ref; + while (currRef != null) { + if (currRef.getHash() == hash) { + Entry 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[] createReferenceArray(int size) { + return new Reference[size]; + } + + private int getIndex(int hash, Reference[] 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[] 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 { + + /** + * Return the referenced entry, or {@code null} if the entry is no longer available. + */ + Entry get(); + + /** + * Return the hash for the reference. + */ + int getHash(); + + /** + * Return the next reference in the chain, or {@code null} if none. + */ + Reference 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 implements Map.Entry { + + 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 { + + private final EnumSet 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 ref, Entry 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 ref, Entry 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> { + + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) o; + Reference ref = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER); + Entry 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> { + + private int segmentIndex; + + private int referenceIndex; + + private Reference[] references; + + private Reference reference; + + private Entry next; + + private Entry last; + + public EntryIterator() { + moveToNextSegment(); + } + + @Override + public boolean hasNext() { + getNextIfNecessary(); + return (this.next != null); + } + + @Override + public Entry 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> queue = new ReferenceQueue>(); + + /** + * 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 createReference(Entry entry, int hash, Reference next) { + if (ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK) { + return new WeakEntryReference(entry, hash, next, this.queue); + } + return new SoftEntryReference(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 pollForPurge() { + return (Reference) this.queue.poll(); + } + } + + + /** + * Internal {@link Reference} implementation for {@link SoftReference}s. + */ + private static final class SoftEntryReference extends SoftReference> implements Reference { + + private final int hash; + + private final Reference nextReference; + + public SoftEntryReference(Entry entry, int hash, Reference next, ReferenceQueue> queue) { + super(entry, queue); + this.hash = hash; + this.nextReference = next; + } + + @Override + public int getHash() { + return this.hash; + } + + @Override + public Reference getNext() { + return this.nextReference; + } + + @Override + public void release() { + enqueue(); + clear(); + } + } + + + /** + * Internal {@link Reference} implementation for {@link WeakReference}s. + */ + private static final class WeakEntryReference extends WeakReference> implements Reference { + + private final int hash; + + private final Reference nextReference; + + public WeakEntryReference(Entry entry, int hash, Reference next, ReferenceQueue> queue) { + super(entry, queue); + this.hash = hash; + this.nextReference = next; + } + + @Override + public int getHash() { + return this.hash; + } + + @Override + public Reference getNext() { + return this.nextReference; + } + + @Override + public void release() { + enqueue(); + clear(); + } + } + +} + diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ReflectUtil.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ReflectUtil.java index d07aed51..726fdb93 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ReflectUtil.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ReflectUtil.java @@ -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}. + *

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}. + *

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. + *

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. + *

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 + * target exception of an {@link InvocationTargetException}. + * Should only be called if no checked exception is expected to be thrown + * by the target method. + *

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}. + *

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}. + *

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 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; + } } diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/type/Test1.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/type/Test1.java new file mode 100644 index 00000000..45762356 --- /dev/null +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/type/Test1.java @@ -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:" + typeArguments[0]); + System.out.println("从 HashMap> 中获取 List :" + typeArguments[1]); + System.out.println( + "从 HashMap> 中获取 List :" + ((ParameterizedType) typeArguments[1]).getRawType()); + System.out.println("从 HashMap> 中获取 Integer:" + ((ParameterizedType) typeArguments[1]) + .getActualTypeArguments()[0]); + System.out.println("从 HashMap> 中获取父类型:"+param.getType().getGenericSuperclass()); + } + + public static class GenericClazz { + private HashMap> param; + } +} diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/type/Test2.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/type/Test2.java new file mode 100644 index 00000000..a1f29b16 --- /dev/null +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/type/Test2.java @@ -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:" + param.getGeneric(0).resolve()); + System.out.println("从 HashMap> 中获取 List :" + param.getGeneric(1)); + System.out.println( + "从 HashMap> 中获取 List :" + param.getGeneric(1).resolve()); + System.out.println("从 HashMap> 中获取 Integer:" + param.getGeneric(1,0)); + System.out.println("从 HashMap> 中获取父类型:" +param.getSuperType()); + } + + public static class GenericClazz { + private HashMap> param; + } +}