io.smallrye.reactive.messaging.providers.helpers.TypeUtils Maven / Gradle / Ivy
package io.smallrye.reactive.messaging.providers.helpers;
import static io.smallrye.reactive.messaging.providers.i18n.ProviderExceptions.ex;
import java.lang.reflect.*;
import java.util.*;
/**
* Utility methods focusing on type inspection, particularly with regard to
* generics.
*
* Code extracted from Apache Commons Lang 3 - TypeUtils.java and ArrayUtils.java
*/
public class TypeUtils {
private TypeUtils() {
// Avoid direct instantiation.
}
/**
*
* Checks if the subject type may be implicitly cast to the target type
* following the Java generics rules.
*
* @param type the subject type to be assigned to the target type
* @param toType the target type
* @return {@code true} if {@code type} is assignable to {@code toType}.
*/
public static boolean isAssignable(final Type type, final Type toType) {
return isAssignable(type, toType, null);
}
/**
*
* Checks if the subject type may be implicitly cast to the target type
* following the Java generics rules.
*
*
* @param type the subject type to be assigned to the target type
* @param toType the target type
* @param typeVarAssigns optional map of type variable assignments
* @return {@code true} if {@code type} is assignable to {@code toType}.
*/
private static boolean isAssignable(final Type type, final Type toType,
final Map, Type> typeVarAssigns) {
if (toType == null || toType instanceof Class) {
return isAssignable(type, (Class) toType);
}
if (toType instanceof ParameterizedType) {
return isAssignable(type, (ParameterizedType) toType, typeVarAssigns);
}
if (toType instanceof GenericArrayType) {
return isAssignable(type, (GenericArrayType) toType, typeVarAssigns);
}
if (toType instanceof WildcardType) {
return isAssignable(type, (WildcardType) toType, typeVarAssigns);
}
if (toType instanceof TypeVariable) {
return isAssignable(type, (TypeVariable) toType);
}
throw ex.illegalStateUnhandledType(toType);
}
/**
*
* Checks if the subject type may be implicitly cast to the target class
* following the Java generics rules.
*
*
* @param type the subject type to be assigned to the target type
* @param toClass the target class
* @return {@code true} if {@code type} is assignable to {@code toClass}.
*/
private static boolean isAssignable(final Type type, final Class toClass) {
if (type == null) {
return toClass == null || !toClass.isPrimitive();
}
// only a null type can be assigned to null type which
// would have cause the previous to return true
if (toClass == null) {
return false;
}
// all types are assignable to themselves
if (toClass.equals(type)) {
return true;
}
if (type instanceof Class) {
return ClassUtils.isAssignable((Class) type, toClass);
}
if (type instanceof ParameterizedType) {
// only have to compare the raw type to the class
return isAssignable(getRawType((ParameterizedType) type), toClass);
}
if (type instanceof TypeVariable) {
// if any of the bounds are assignable to the class, then the
// type is assignable to the class.
for (final Type bound : ((TypeVariable) type).getBounds()) {
if (isAssignable(bound, toClass)) {
return true;
}
}
return false;
}
// the only classes to which a generic array type can be assigned
// are class Object and array classes
if (type instanceof GenericArrayType) {
return toClass.equals(Object.class)
|| toClass.isArray()
&& isAssignable(((GenericArrayType) type).getGenericComponentType(), toClass
.getComponentType());
}
// wildcard types are not assignable to a class (though one would think
// "? super Object" would be assignable to Object)
if (type instanceof WildcardType) {
return false;
}
throw ex.illegalStateUnhandledType(type);
}
/**
*
* Checks if the subject type may be implicitly cast to the target
* parameterized type following the Java generics rules.
*
*
* @param type the subject type to be assigned to the target type
* @param toParameterizedType the target parameterized type
* @param typeVarAssigns a map with type variables
* @return {@code true} if {@code type} is assignable to {@code toType}.
*/
static boolean isAssignable(final Type type, final ParameterizedType toParameterizedType,
final Map, Type> typeVarAssigns) {
if (type == null) {
return true;
}
// only a null type can be assigned to null type which
// would have cause the previous to return true
if (toParameterizedType == null) {
return false;
}
// all types are assignable to themselves
if (toParameterizedType.equals(type)) {
return true;
}
// get the target type's raw type
final Class toClass = getRawType(toParameterizedType);
// get the subject type's type arguments including owner type arguments
// and supertype arguments up to and including the target class.
final Map, Type> fromTypeVarAssigns = getTypeArguments(type, toClass, null);
// null means the two types are not compatible
if (fromTypeVarAssigns == null) {
return false;
}
// compatible types, but there's no type arguments. this is equivalent
// to comparing Map< ?, ? > to Map, and raw types are always assignable
// to parameterized types.
if (fromTypeVarAssigns.isEmpty()) {
return true;
}
// get the target type's type arguments including owner type arguments
final Map, Type> toTypeVarAssigns = getTypeArguments(toParameterizedType,
toClass, typeVarAssigns);
if (toTypeVarAssigns == null) {
return false;
}
// now to check each type argument
for (final TypeVariable var : toTypeVarAssigns.keySet()) {
final Type toTypeArg = unrollVariableAssignments(var, toTypeVarAssigns);
final Type fromTypeArg = unrollVariableAssignments(var, fromTypeVarAssigns);
if (toTypeArg == null && fromTypeArg instanceof Class) {
continue;
}
// parameters must either be absent from the subject type, within
// the bounds of the wildcard type, or be an exact match to the
// parameters of the target type.
if (fromTypeArg != null
&& !fromTypeArg.equals(toTypeArg)
&& !(toTypeArg instanceof WildcardType && isAssignable(fromTypeArg, toTypeArg,
typeVarAssigns))) {
return false;
}
}
return true;
}
private static Type unrollVariableAssignments(TypeVariable var, final Map, Type> typeVarAssigns) {
Type result;
do {
result = typeVarAssigns.get(var);
if (result instanceof TypeVariable && !result.equals(var)) {
var = (TypeVariable) result;
continue;
}
break;
} while (true);
return result;
}
/**
*
* Checks if the subject type may be implicitly cast to the target
* generic array type following the Java generics rules.
*
*
* @param type the subject type to be assigned to the target type
* @param toGenericArrayType the target generic array type
* @param typeVarAssigns a map with type variables
* @return {@code true} if {@code type} is assignable to
* {@code toGenericArrayType}.
*/
static boolean isAssignable(final Type type, final GenericArrayType toGenericArrayType,
final Map, Type> typeVarAssigns) {
if (type == null) {
return true;
}
// only a null type can be assigned to null type which
// would have cause the previous to return true
if (toGenericArrayType == null) {
return false;
}
// all types are assignable to themselves
if (toGenericArrayType.equals(type)) {
return true;
}
final Type toComponentType = toGenericArrayType.getGenericComponentType();
if (type instanceof Class) {
final Class cls = (Class) type;
// compare the component types
return cls.isArray()
&& isAssignable(cls.getComponentType(), toComponentType, typeVarAssigns);
}
if (type instanceof GenericArrayType) {
// compare the component types
return isAssignable(((GenericArrayType) type).getGenericComponentType(),
toComponentType, typeVarAssigns);
}
if (type instanceof WildcardType) {
// so long as one of the upper bounds is assignable, it's good
for (final Type bound : getImplicitUpperBounds((WildcardType) type)) {
if (isAssignable(bound, toGenericArrayType)) {
return true;
}
}
return false;
}
if (type instanceof TypeVariable) {
// probably should remove the following logic and just return false.
// type variables cannot specify arrays as bounds.
for (final Type bound : getImplicitBounds((TypeVariable) type)) {
if (isAssignable(bound, toGenericArrayType)) {
return true;
}
}
return false;
}
if (type instanceof ParameterizedType) {
return false;
}
throw ex.illegalStateUnhandledType(type);
}
/**
*
* Checks if the subject type may be implicitly cast to the target
* wildcard type following the Java generics rules.
*
*
* @param type the subject type to be assigned to the target type
* @param toWildcardType the target wildcard type
* @param typeVarAssigns a map with type variables
* @return {@code true} if {@code type} is assignable to
* {@code toWildcardType}.
*/
private static boolean isAssignable(final Type type, final WildcardType toWildcardType,
final Map, Type> typeVarAssigns) {
// all types are assignable to themselves
if (toWildcardType.equals(type)) {
return true;
}
final Type[] toUpperBounds = getImplicitUpperBounds(toWildcardType);
final Type[] toLowerBounds = getImplicitLowerBounds(toWildcardType);
if (type instanceof WildcardType) {
final WildcardType wildcardType = (WildcardType) type;
final Type[] upperBounds = getImplicitUpperBounds(wildcardType);
final Type[] lowerBounds = getImplicitLowerBounds(wildcardType);
for (Type toBound : toUpperBounds) {
// if there are assignments for unresolved type variables,
// now's the time to substitute them.
toBound = substituteTypeVariables(toBound, typeVarAssigns);
// each upper bound of the subject type has to be assignable to
// each
// upper bound of the target type
for (final Type bound : upperBounds) {
if (!isAssignable(bound, toBound, typeVarAssigns)) {
return false;
}
}
}
for (Type toBound : toLowerBounds) {
// if there are assignments for unresolved type variables,
// now's the time to substitute them.
toBound = substituteTypeVariables(toBound, typeVarAssigns);
// each lower bound of the target type has to be assignable to
// each
// lower bound of the subject type
for (final Type bound : lowerBounds) {
if (!isAssignable(toBound, bound, typeVarAssigns)) {
return false;
}
}
}
return true;
}
for (final Type toBound : toUpperBounds) {
// if there are assignments for unresolved type variables,
// now's the time to substitute them.
if (!isAssignable(type, substituteTypeVariables(toBound, typeVarAssigns),
typeVarAssigns)) {
return false;
}
}
for (final Type toBound : toLowerBounds) {
// if there are assignments for unresolved type variables,
// now's the time to substitute them.
if (!isAssignable(substituteTypeVariables(toBound, typeVarAssigns), type,
typeVarAssigns)) {
return false;
}
}
return true;
}
/**
*
* Checks if the subject type may be implicitly cast to the target type
* variable following the Java generics rules.
*
*
* @param type the subject type to be assigned to the target type
* @param toTypeVariable the target type variable
* @return {@code true} if {@code type} is assignable to
* {@code toTypeVariable}.
*/
private static boolean isAssignable(final Type type, final TypeVariable toTypeVariable) {
// TODO HERE
if (type == null) {
return true;
}
// only a null type can be assigned to null type which
// would have cause the previous to return true
if (toTypeVariable == null) {
return false;
}
// all types are assignable to themselves
if (toTypeVariable.equals(type)) {
return true;
}
if (type instanceof TypeVariable) {
// a type variable is assignable to another type variable, if
// and only if the former is the latter, extends the latter, or
// is otherwise a descendant of the latter.
final Type[] bounds = getImplicitBounds((TypeVariable) type);
for (final Type bound : bounds) {
if (isAssignable(bound, toTypeVariable)) {
return true;
}
}
}
if (type instanceof Class || type instanceof ParameterizedType
|| type instanceof GenericArrayType || type instanceof WildcardType) {
return false;
}
throw ex.illegalStateUnhandledType(type);
}
/**
*
* Find the mapping for {@code type} in {@code typeVarAssigns}.
*
*
* @param type the type to be replaced
* @param typeVarAssigns the map with type variables
* @return the replaced type
* @throws IllegalArgumentException if the type cannot be substituted
*/
private static Type substituteTypeVariables(final Type type, final Map, Type> typeVarAssigns) {
if (type instanceof TypeVariable && typeVarAssigns != null) {
final Type replacementType = typeVarAssigns.get(type);
if (replacementType == null) {
throw ex.illegalArgumentMissingAssignment(type);
}
return replacementType;
}
return type;
}
/**
*
* Gets the type arguments of a class/interface based on a subtype. For
* instance, this method will determine that both of the parameters for the
* interface {@link Map} are {@link Object} for the subtype
* {@link java.util.Properties Properties} even though the subtype does not
* directly implement the {@code Map} interface.
*
*
* This method returns {@code null} if {@code type} is not assignable to
* {@code toClass}. It returns an empty map if none of the classes or
* interfaces in its inheritance hierarchy specify any type arguments.
*
*
* A side effect of this method is that it also retrieves the type
* arguments for the classes and interfaces that are part of the hierarchy
* between {@code type} and {@code toClass}. So with the above
* example, this method will also determine that the type arguments for
* {@link java.util.Hashtable Hashtable} are also both {@code Object}.
* In cases where the interface specified by {@code toClass} is
* (indirectly) implemented more than once (e.g. where {@code toClass}
* specifies the interface {@link Iterable Iterable} and
* {@code type} specifies a parameterized type that implements both
* {@link Set Set} and {@link java.util.Collection Collection}),
* this method will look at the inheritance hierarchy of only one of the
* implementations/subclasses; the first interface encountered that isn't a
* subinterface to one of the others in the {@code type} to
* {@code toClass} hierarchy.
*
*
* @param type the type from which to determine the type parameters of
* {@code toClass}
* @param toClass the class whose type parameters are to be determined based
* on the subtype {@code type}
* @return a {@code Map} of the type assignments for the type variables in
* each type in the inheritance hierarchy from {@code type} to
* {@code toClass} inclusive.
*/
static Map, Type> getTypeArguments(final Type type, final Class toClass) {
return getTypeArguments(type, toClass, null);
}
/**
*
* Return a map of the type arguments of {@code type} in the context of {@code toClass}.
*
*
* @param type the type in question
* @param toClass the class
* @param subtypeVarAssigns a map with type variables
* @return the {@code Map} with type arguments
*/
private static Map, Type> getTypeArguments(final Type type, final Class toClass,
final Map, Type> subtypeVarAssigns) {
if (type instanceof Class) {
return getTypeArguments((Class) type, toClass, subtypeVarAssigns);
}
if (type instanceof ParameterizedType) {
return getTypeArguments((ParameterizedType) type, toClass, subtypeVarAssigns);
}
// TODO HERE
if (type instanceof GenericArrayType) {
return getTypeArguments(((GenericArrayType) type).getGenericComponentType(), toClass
.isArray() ? toClass.getComponentType() : toClass, subtypeVarAssigns);
}
// since wildcard types are not assignable to classes, should this just
// return null?
if (type instanceof WildcardType) {
for (final Type bound : getImplicitUpperBounds((WildcardType) type)) {
// find the first bound that is assignable to the target class
if (isAssignable(bound, toClass)) {
return getTypeArguments(bound, toClass, subtypeVarAssigns);
}
}
return null;
}
if (type instanceof TypeVariable) {
for (final Type bound : getImplicitBounds((TypeVariable) type)) {
// find the first bound that is assignable to the target class
if (isAssignable(bound, toClass)) {
return getTypeArguments(bound, toClass, subtypeVarAssigns);
}
}
return null;
}
throw ex.illegalStateUnhandledType(type);
}
/**
*
* Return a map of the type arguments of a parameterized type in the context of {@code toClass}.
*
*
* @param parameterizedType the parameterized type
* @param toClass the class
* @param subtypeVarAssigns a map with type variables
* @return the {@code Map} with type arguments
*/
private static Map, Type> getTypeArguments(
final ParameterizedType parameterizedType, final Class toClass,
final Map, Type> subtypeVarAssigns) {
final Class cls = getRawType(parameterizedType);
// make sure they're assignable
if (!isAssignable(cls, toClass)) {
return null;
}
if (cls == null) {
throw new IllegalArgumentException("Invalid parameterized type: " + parameterizedType);
}
final Type ownerType = parameterizedType.getOwnerType();
Map, Type> typeVarAssigns;
if (ownerType instanceof ParameterizedType) {
// get the owner type arguments first
final ParameterizedType parameterizedOwnerType = (ParameterizedType) ownerType;
typeVarAssigns = getTypeArguments(parameterizedOwnerType,
getRawType(parameterizedOwnerType), subtypeVarAssigns);
} else {
// no owner, prep the type variable assignments map
typeVarAssigns = subtypeVarAssigns == null ? new HashMap<>()
: new HashMap<>(subtypeVarAssigns);
}
// get the subject parameterized type's arguments
final Type[] typeArgs = parameterizedType.getActualTypeArguments();
// and get the corresponding type variables from the raw class
final TypeVariable[] typeParams = cls.getTypeParameters();
// map the arguments to their respective type variables
for (int i = 0; i < typeParams.length; i++) {
final Type typeArg = typeArgs[i];
typeVarAssigns.put(typeParams[i], typeVarAssigns.getOrDefault(typeArg, typeArg));
}
if (toClass.equals(cls)) {
// target class has been reached. Done.
return typeVarAssigns;
}
// walk the inheritance hierarchy until the target class is reached
return getTypeArguments(getClosestParentType(cls, toClass), toClass, typeVarAssigns);
}
/**
*
* Return a map of the type arguments of a class in the context of {@code toClass}.
*
*
* @param cls the class in question
* @param toClass the context class
* @param subtypeVarAssigns a map with type variables
* @return the {@code Map} with type arguments
*/
private static Map, Type> getTypeArguments(Class cls, final Class toClass,
final Map, Type> subtypeVarAssigns) {
if (toClass == null) {
throw new IllegalArgumentException("`toClass` must not be `null`");
}
// make sure they're assignable
if (!isAssignable(cls, toClass)) {
return null;
}
// can't work with primitives
if (cls != null && cls.isPrimitive()) {
// both classes are primitives?
if (toClass.isPrimitive()) {
// dealing with widening here. No type arguments to be
// harvested with these two types.
return new HashMap<>();
}
// work with wrapper the wrapper class instead of the primitive
cls = ClassUtils.primitiveToWrapper(cls);
}
// create a copy of the incoming map, or an empty one if it's null
final HashMap, Type> typeVarAssigns = subtypeVarAssigns == null ? new HashMap<>()
: new HashMap<>(subtypeVarAssigns);
// has target class been reached?
if (toClass.equals(cls)) {
return typeVarAssigns;
}
// walk the inheritance hierarchy until the target class is reached
return getTypeArguments(getClosestParentType(cls, toClass), toClass, typeVarAssigns);
}
/**
*
* Get the closest parent type to the
* super class specified by {@code superClass}.
*
*
* @param cls the class in question
* @param superClass the super class
* @return the closes parent type
*/
private static Type getClosestParentType(final Class cls, final Class superClass) {
// only look at the interfaces if the super class is also an interface
if (superClass.isInterface()) {
// get the generic interfaces of the subject class
final Type[] interfaceTypes = cls.getGenericInterfaces();
// will hold the best generic interface match found
Type genericInterface = null;
// find the interface closest to the super class
for (final Type midType : interfaceTypes) {
Class midClass;
if (midType instanceof ParameterizedType) {
midClass = getRawType((ParameterizedType) midType);
} else if (midType instanceof Class) {
midClass = (Class) midType;
} else {
throw ex.illegalStateUnexpectedGenericInterface(midType);
}
// check if this interface is further up the inheritance chain
// than the previously found match
if (isAssignable(midClass, superClass)
&& isAssignable(genericInterface, (Type) midClass)) {
genericInterface = midType;
}
}
// found a match?
if (genericInterface != null) {
return genericInterface;
}
}
// none of the interfaces were descendants of the target class, so the
// super class has to be one, instead
return cls.getGenericSuperclass();
}
/**
*
* This method strips out the redundant upper bound types in type
* variable types and wildcard types (or it would with wildcard types if
* multiple upper bounds were allowed).
*
*
* Example, with the variable
* type declaration:
*
*
* <K extends java.util.Collection<String> &
* java.util.List<String>>
*
*
*
* since {@code List} is a subinterface of {@code Collection},
* this method will return the bounds as if the declaration had been:
*
*
*
* <K extends java.util.List<String>>
*
*
* @param bounds an array of types representing the upper bounds of either
* {@link WildcardType} or {@link TypeVariable}, not {@code null}.
* @return an array containing the values from {@code bounds} minus the
* redundant types.
*/
public static Type[] normalizeUpperBounds(final Type[] bounds) {
// don't bother if there's only one (or none) type
if (bounds.length < 2) {
return bounds;
}
final Set types = new HashSet<>(bounds.length);
for (final Type type1 : bounds) {
boolean subtypeFound = false;
for (final Type type2 : bounds) {
if (type1 != type2 && isAssignable(type2, type1, null)) {
subtypeFound = true;
break;
}
}
if (!subtypeFound) {
types.add(type1);
}
}
return types.toArray(new Type[0]);
}
/**
*
* Returns an array containing the sole type of {@link Object} if
* {@link TypeVariable#getBounds()} returns an empty array. Otherwise, it
* returns the result of {@link TypeVariable#getBounds()} passed into
* {@link #normalizeUpperBounds}.
*
*
* @param typeVariable the subject type variable, not {@code null}
* @return a non-empty array containing the bounds of the type variable.
*/
private static Type[] getImplicitBounds(final TypeVariable typeVariable) {
final Type[] bounds = typeVariable.getBounds();
return bounds.length == 0 ? new Type[] { Object.class } : normalizeUpperBounds(bounds);
}
/**
*
* Returns an array containing the sole value of {@link Object} if
* {@link WildcardType#getUpperBounds()} returns an empty array. Otherwise,
* it returns the result of {@link WildcardType#getUpperBounds()}
* passed into {@link #normalizeUpperBounds}.
*
*
* @param wildcardType the subject wildcard type, not {@code null}
* @return a non-empty array containing the upper bounds of the wildcard
* type.
*/
static Type[] getImplicitUpperBounds(final WildcardType wildcardType) {
final Type[] bounds = wildcardType.getUpperBounds();
return bounds.length == 0 ? new Type[] { Object.class } : normalizeUpperBounds(bounds);
}
/**
*
* Returns an array containing a single value of {@code null} if
* {@link WildcardType#getLowerBounds()} returns an empty array. Otherwise,
* it returns the result of {@link WildcardType#getLowerBounds()}.
*
*
* @param wildcardType the subject wildcard type, not {@code null}
* @return a non-empty array containing the lower bounds of the wildcard
* type.
*/
static Type[] getImplicitLowerBounds(final WildcardType wildcardType) {
final Type[] bounds = wildcardType.getLowerBounds();
return bounds.length == 0 ? new Type[] { null } : bounds;
}
/**
*
* Transforms the passed in type to a {@link Class} object. Type-checking method of convenience.
*
*
* @param parameterizedType the type to be converted
* @return the corresponding {@code Class} object
* @throws IllegalStateException if the conversion fails
*/
private static Class getRawType(final ParameterizedType parameterizedType) {
final Type rawType = parameterizedType.getRawType();
return (Class) rawType;
}
/**
* Returns the raw type if the given type is parameterized, if not returns the type.
*
* @param type the type to convert
* @return the raw type if parameterized
*/
public static Type getRawTypeIfParameterized(Type type) {
if (type instanceof ParameterizedType) {
return ((ParameterizedType) type).getRawType();
}
return type;
}
/**
* Check equality of types.
*
* @param t1 the first type
* @param t2 the second type
* @return boolean
*/
public static boolean equals(final Type t1, final Type t2) {
if (Objects.equals(t1, t2)) {
return true;
}
if (t1 instanceof ParameterizedType) {
return equals((ParameterizedType) t1, t2);
}
if (t1 instanceof GenericArrayType) {
return equals((GenericArrayType) t1, t2);
}
if (t1 instanceof WildcardType) {
return equals((WildcardType) t1, t2);
}
return false;
}
private static boolean equals(final ParameterizedType p, final Type t) {
if (t instanceof ParameterizedType) {
final ParameterizedType other = (ParameterizedType) t;
if (equals(p.getRawType(), other.getRawType()) && equals(p.getOwnerType(), other.getOwnerType())) {
return equals(p.getActualTypeArguments(), other.getActualTypeArguments());
}
}
return false;
}
private static boolean equals(final GenericArrayType a, final Type t) {
return t instanceof GenericArrayType
&& equals(a.getGenericComponentType(), ((GenericArrayType) t).getGenericComponentType());
}
private static boolean equals(final WildcardType w, final Type t) {
if (t instanceof WildcardType) {
final WildcardType other = (WildcardType) t;
return equals(getImplicitLowerBounds(w), getImplicitLowerBounds(other))
&& equals(getImplicitUpperBounds(w), getImplicitUpperBounds(other));
}
return false;
}
private static boolean equals(final Type[] t1, final Type[] t2) {
if (t1.length == t2.length) {
for (int i = 0; i < t1.length; i++) {
if (!equals(t1[i], t2[i])) {
return false;
}
}
return true;
}
return false;
}
}