InterfaceInfo.java
package nz.co.gregs.dbvolution.internal.properties;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Used internally to extract information about generics in reflected classes.
*/
public class InterfaceInfo {
private boolean interfaceImplementedByImplementation = false;
private ParameterBounds[] typeArgumentBounds;
/**
* Resolves the most concrete known type arguments against the given interface
* class, as specified an the implementation class, or one of its ancestors
* (supertype or interfaces). If the implementation class does not extend or
* implement the supertype orinterface (after a recursive search), this method
* returns {@code null}.
*
* @param interfaceClass the supertype class or interface you're looking for
* @param implementationObject the actual implementation object you're testing
* @throws UnsupportedOperationException if encounter generics that aren't
* handled yet
*/
public InterfaceInfo(Class<?> interfaceClass, Object implementationObject) {
this(interfaceClass, implementationObject.getClass());
}
/**
* Resolves the most concrete known type arguments against the given interface
* class, as specified an the implementation class, or one of its ancestors
* (supertype or interfaces). If the implementation class does not extend or
* implement the supertype orinterface (after a recursive search), this method
* returns {@code null}.
*
* @param interfaceClass the supertype class or interface you're looking for
* @param implementationClass the actual implementation class you're testing
* @throws UnsupportedOperationException if encounter generics that aren't
* handled yet
*/
public InterfaceInfo(Class<?> interfaceClass, Class<?> implementationClass) {
this.typeArgumentBounds = getParameterBounds(interfaceClass, implementationClass);
this.interfaceImplementedByImplementation = (typeArgumentBounds != null);
}
/**
* Indicates whether the implementation type actually makes any attempt to
* implement the interface type.
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
*
* @return TRUE or FALSE
*/
public boolean isInterfaceImplementedByImplementation() {
return interfaceImplementedByImplementation;
}
/**
* Gets the concrete parameter values as bounds. The value bounds are resolved
* to the most concrete known values against the given interface class, as
* specified an the implementation class, or one of its ancestors (supertype
* or interfaces).
*
* <p>
* Then method returns {@code null} if the implementation class does not
* extend or implement the supertype or interface (recursively).
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
*
* @return the parameter values, ordered according to the parameters on the
* configured interface class; empty array if the implementation class
* implements or extends the interface or supertype, but that the
* interface/supertype has no generic parameters; null if the implementation
* class does not implement or extend the interface or supertype.
*/
public ParameterBounds[] getInterfaceParameterValueBounds() {
if (typeArgumentBounds == null) {
return null;
} else {
return Arrays.copyOf(typeArgumentBounds, typeArgumentBounds.length);
}
}
/**
* Convenience method for getting default parameter bounds for each parameter
* of the given class.
*
* @param clazz the class to inspect
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
* @return the list of known and understood bounds for each parameter; empty
* bounds if the interface has no parameters; null if doesn't extend or
* implement the interface class (directly or indirectly)
* @throws UnsupportedOperationException if any generic type references are
* encountered that are not understood
*/
protected static ParameterBounds[] getParameterBounds(Class<?> clazz) {
return getParameterBounds(clazz, clazz);
}
/**
* Resolves the most concrete known type arguments against the given interface
* class, as specified an the implementation class, or one of its ancestors
* (supertype or interfaces). If the implementation class does not implement
* the interface (after a recursive search), {@code null} is returned.
*
* @param interfaceClass the supertype class or interface you're looking for
* @param implementationClass the actual implementation class you're testing
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
* @return the list of known and understood bounds for each parameter; empty
* bounds if the interface has no parameters; null if doesn't extend or
* implement the interface class (directly or indirectly)
* @throws UnsupportedOperationException if any generic type references are
* encountered that are not understood
*/
protected static ParameterBounds[] getParameterBounds(Class<?> interfaceClass, Class<?> implementationClass) {
return getParameterBounds(interfaceClass, implementationClass, null);
}
/**
* Resolves the most concrete known type arguments against the given interface
* class, as specified an the implementation class, or one of its ancestors
* (supertype or interfaces). If the implementation class does not implement
* the interface (after a recursive search), {@code null} is returned.
*
* @param interfaceClass interfaceClass
* @param implementationClass implementationClass
* @param argumentValues bound type argument values for each of the type
* arguments on the implementation class (empty array if none, null if not yet
* defined)
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
* @return null if doesn't implement the interface, empty bounds if the
* interface has no parameters, or the list of known and understood bounds for
* each parameter
* @throws UnsupportedOperationException if any generic type references are
* encountered that are not understood
*/
protected static ParameterBounds[] getParameterBounds(Class<?> interfaceClass, Class<?> implementationClass,
ParameterBounds[] argumentValues) {
// parse argument values against type variables of implementation class
Map<String, ParameterBounds> argumentValueByTypeVariableName = null;
if (argumentValues != null) {
TypeVariable<? extends Class<?>>[] typeVariables = implementationClass.getTypeParameters();
// sanity check
if (typeVariables.length != argumentValues.length) {
// unexpected exception, this shouldn't be possible
throw new UnsupportedOperationException("Encountered mismatched number of type parameters ("
+ typeVariables.length + ") and values ("
+ argumentValues.length + ") on " + implementationClass.getSimpleName()
+ ": values=" + Arrays.toString(argumentValues));
}
argumentValueByTypeVariableName = new HashMap<String, ParameterBounds>();
for (int i = 0; i < argumentValues.length && i < typeVariables.length; i++) {
argumentValueByTypeVariableName.put(typeVariables[i].getName(), argumentValues[i]);
}
}
// check if sitting on target
if (implementationClass.equals(interfaceClass)) {
return ParameterBounds.boundsForParametersOf(implementationClass, argumentValueByTypeVariableName);
}
// get bounds from "implements InterfaceClass"
// (assume either ParameterizedType or Class)
Type implementedInterfaceType = ancestorTypeByClass(interfaceClass, implementationClass);
if (implementedInterfaceType != null) {
return ParameterBounds.boundsForParametersOf(implementedInterfaceType, argumentValueByTypeVariableName);
}
// retrieve bounds from supertype and interface references
for (Type ancestorType : ancestorTypesOf(implementationClass)) {
ParameterBounds[] ancestorArgumentValues = ParameterBounds.boundsForParametersOf(ancestorType, argumentValueByTypeVariableName);
// recurse into type
try {
Class<?> ancestorClass = resolveClassOf(ancestorType);
ParameterBounds[] result = getParameterBounds(interfaceClass, ancestorClass, ancestorArgumentValues);
if (result != null) {
return result;
}
} catch (UnsupportedType dropped) {
// try next ancestor
}
}
// doesn't implement the interface
return null;
}
/**
* Gets all direct ancestors of the given class, including its supertype and
* all directly implemented interfaces. Excludes ancestors of type
* {@code Object}.
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
*
* @return non-null list, empty if only ancestor is {@code Object}
*/
private static List<Type> ancestorTypesOf(Class<?> child) {
List<Type> ancestors = new ArrayList<Type>();
// get the class that the child class directly extends
Type supertype = child.getGenericSuperclass();
if (supertype != null && !Object.class.equals(supertype)) {
ancestors.add(supertype);
}
// get all the interfaces that the class implements
Type[] interfaces = child.getGenericInterfaces();
if (interfaces != null) {
ancestors.addAll(Arrays.asList(interfaces));
}
return ancestors;
}
/**
* Non-recursive: checks on this class only.
*
* @param ancestorClass a supertype or interface
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
* @return the actual Type reference to the interface class or null if not
* implemented/extended
*/
private static Type ancestorTypeByClass(Class<?> ancestorClass, Class<?> child) {
// look for "extends InterfaceClass"
Type supertype = child.getGenericSuperclass();
if (supertype != null && !supertype.equals(Object.class)) {
try {
Class<?> supertypeClass = resolveClassOf(supertype);
if (supertypeClass.equals(ancestorClass)) {
return supertype;
}
} catch (UnsupportedType dropped) {
// try next
}
}
// look for "implements InterfaceClass"
Type[] implementedInterfaces = child.getGenericInterfaces();
for (Type implementedInterface : implementedInterfaces) {
try {
Class<?> implementedInterfaceClass = resolveClassOf(implementedInterface);
if (implementedInterfaceClass.equals(ancestorClass)) {
return implementedInterface;
}
} catch (UnsupportedType dropped) {
// try next
}
}
return null;
}
// supports only classes and parameterized types
// doesn't support Array[] types or wildcard types
private static Class<?> resolveClassOf(Type type) throws UnsupportedType {
if (type instanceof Class<?>) {
return (Class<?>) type;
} else if (type instanceof GenericArrayType) {
return resolveClassOf(((GenericArrayType) type).getGenericComponentType());
} else if (type instanceof ParameterizedType) {
return resolveClassOf(((ParameterizedType) type).getRawType());
} else {
throw new UnsupportedType("Can't yet handle " + type.getClass().getSimpleName() + " types");
}
}
/**
* Converts the given type into a concise representation suitable for
* inclusion in error messages and logging.
*
* @param type type
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
* @return a String describing the type succinctly
*/
protected static String descriptionOf(Type type) {
try {
return descriptionOf(type, new HashSet<TypeVariable<?>>());
} catch (RuntimeException dropped) {
// drop exception and quietly handle because this method is called in the context of other error handling
// and musn't cause its own errors
return type.toString();
}
}
/**
* Converts the given type array into a concise representation suitable for
* inclusion in error messages and logging.
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
*
* @return a String describing the types succinctly.
*/
static String descriptionOf(Type[] types) {
try {
return descriptionOf(types, new HashSet<TypeVariable<?>>());
} catch (RuntimeException dropped) {
// drop exception and quietly handle because this method is called in the context of other error handling
// and musn't cause its own errors
return Arrays.toString(types);
}
}
// returns the description or empty string of types array is empty
private static String descriptionOf(Type[] types, Set<TypeVariable<?>> observedTypeVariables) {
return conditionalDescriptionOf(null, types, null, observedTypeVariables);
}
// returns the description or empty string of types array is empty
private static String conditionalDescriptionOf(String conditionalPrefix, Type[] types, String conditionalPostfix,
Set<TypeVariable<?>> observedTypeVariables) {
StringBuilder buf = new StringBuilder();
if (types != null && types.length > 0) {
boolean first = true;
if (conditionalPrefix != null) {
buf.append(conditionalPrefix);
}
for (Type type : types) {
if (!first) {
buf.append(",");
}
buf.append(descriptionOf(type, observedTypeVariables));
first = false;
}
if (conditionalPostfix != null) {
buf.append(conditionalPostfix);
}
}
return buf.toString();
}
private static String descriptionOf(Type type, Set<TypeVariable<?>> observedTypeVariables) {
StringBuilder buf = new StringBuilder();
// handle nulls
if (type == null) {
buf.append("null");
} // handle simple class references
else if (type instanceof Class) {
buf.append(((Class<?>) type).getSimpleName());
} // handle parameterized type references, eg: "MyInterface<T,Q extends QueryableDatatype>"
else if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
buf.append(descriptionOf(pt.getRawType()));
buf.append(conditionalDescriptionOf("<", pt.getActualTypeArguments(), ">", observedTypeVariables));
} // handle type variables, eg: "T extends Number"
else if (type instanceof TypeVariable) {
// format name and generic declaration together as: "MyInterface.T"
TypeVariable<? extends GenericDeclaration> typeVariable = (TypeVariable<? extends GenericDeclaration>) type;
buf.append(typeVariable.getName());
// format bounds: " extends BoundType"
if (!observedTypeVariables.contains(typeVariable)) {
if (typeVariable.getBounds() != null && typeVariable.getBounds().length > 0) {
buf.append(" extends ");
Set<TypeVariable<?>> nestedObservedTypeVariables = new HashSet<TypeVariable<?>>(observedTypeVariables);
nestedObservedTypeVariables.add(typeVariable);
boolean first = true;
for (Type boundingType : typeVariable.getBounds()) {
if (!first) {
buf.append(",");
}
String boundingTypeDescr = descriptionOf(boundingType, nestedObservedTypeVariables);
if (boundingTypeDescr.contains(" ")) {
buf.append("(").append(boundingTypeDescr).append(")");
} else {
buf.append(boundingTypeDescr);
}
first = false;
}
}
}
} // handle wildcards, eg "? extends MyInterface" and "? super Number"
else if (type instanceof WildcardType) {
WildcardType wildcard = (WildcardType) type;
buf.append("?");
if (wildcard.getUpperBounds() != null && wildcard.getUpperBounds().length > 0) {
buf.append(" extends ");
boolean first = true;
for (Type boundingType : wildcard.getUpperBounds()) {
if (!first) {
buf.append(",");
}
String boundingTypeDescr = descriptionOf(boundingType, observedTypeVariables);
if (boundingTypeDescr.contains(" ")) {
buf.append("(").append(boundingTypeDescr).append(")");
} else {
buf.append(boundingTypeDescr);
}
first = false;
}
}
if (wildcard.getLowerBounds() != null && wildcard.getLowerBounds().length > 0) {
buf.append(" super ");
boolean first = true;
for (Type boundingType : wildcard.getLowerBounds()) {
if (!first) {
buf.append(",");
}
String boundingTypeDescr = descriptionOf(boundingType, observedTypeVariables);
if (boundingTypeDescr.contains(" ")) {
buf.append("(").append(boundingTypeDescr).append(")");
} else {
buf.append(boundingTypeDescr);
}
first = false;
}
}
} // handle generic arrays, eg: "T[]" and "List<String>[]"
else if (type instanceof GenericArrayType) {
GenericArrayType array = (GenericArrayType) type;
String typeDescr = descriptionOf(array.getGenericComponentType(), observedTypeVariables);
if (typeDescr.contains(" ")) {
buf.append("(").append(typeDescr).append(")");
} else {
buf.append(typeDescr);
}
buf.append("[]");
} // handle unexpected cases
else {
buf.append(type.toString());
}
return buf.toString();
}
/**
* Represents the known and understood bounds of a type variable. If not
* understood, references of this type should be null. The widest open bound
* looks like an upper bound of {@code Object} and a null lower bound.
*
* <p>
* The bounds types themselves can be one of the following supported types:
* <ul>
* <li> Class
* <li> GenericArrayType
* </ul>
* All other {@code Type}s are either expanded out into the types above, or
* they are not supported. ParameterizedType is the only type that is not
* supported. Attempts to use it will result in an
* UnsupportedOperationException.
*/
public static class ParameterBounds {
private final Type[] upperTypes; // always null or non-empty
private final Type[] lowerTypes; // always null or non-empty
/**
* Gets a default single bound given no further information. This has an
* upper bound of {@code Object}, and no lower bound.
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
*
* @return a ParameterBounds
*/
public static ParameterBounds defaultBounds() {
return new ParameterBounds(new Type[]{Object.class}, null);
}
/**
* Gets parameter bounds for each of the generic type arguments of the given
* class or parameterized type reference. If the given class has bounded
* type parameters, the returned bounds will reflect that.
*
* <p>
* The {@code specifiedValuesByTypeVariableName} map is used for populating
* referenced values where type variable references are used. This is
* limited to the use of parameterized type references, and to only those
* type arguments which refer by type variable (ie: excludes direct class
* name references and other type references).
*
* <p>
* If the class has no generic parameters, an empty array is returned.
*
* @param parameterizedTypeRef a Class or ParameterizedType
* @param paramValuesByTypeVariableName a map from TypeVariable name to
* actual specified bounds; must contain values for all type variable
* references; null if none defined
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
* @return an array of ParameterBounds object.
* @throws UnsupportedOperationException if not a class or parameterized
* type
*/
public static ParameterBounds[] boundsForParametersOf(Type parameterizedTypeRef,
Map<String, ParameterBounds> paramValuesByTypeVariableName) {
// Class reference without parameters: use default bound for each parameter
// in reference interface type (this is correct handling for jdk1.4 style code)
if (parameterizedTypeRef instanceof Class) {
return boundsForParametersOf((Class<?>) parameterizedTypeRef);
} // Parameterized class reference: use bounds as they are provided
else if (parameterizedTypeRef instanceof ParameterizedType) {
return boundsForParametersOf((ParameterizedType) parameterizedTypeRef,
paramValuesByTypeVariableName);
} // refuse to process other types
// (not actually expecting any other types anyway)
else {
throw new UnsupportedOperationException(
"Expecting only Class and ParameterizedType references, encountered "
+ parameterizedTypeRef.getClass().getSimpleName() + ": " + descriptionOf(parameterizedTypeRef));
}
}
/**
* Gets default parameter bounds for each of the generic parameters of the
* given class. If the given class has bounded type parameters, the returned
* bounds will reflect that. If the class has no generic parameters, an
* empty array is returned.
*
* @param parameterizedClass parameterizedClass
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
* @return an array of ParameterBounds objects
*/
public static ParameterBounds[] boundsForParametersOf(Class<?> parameterizedClass) {
List<ParameterBounds> bounds = new ArrayList<ParameterBounds>();
for (TypeVariable<? extends Class<?>> typeVariable : parameterizedClass.getTypeParameters()) {
bounds.add(getBoundsOf(typeVariable));
}
return bounds.toArray(new ParameterBounds[]{});
}
/**
* Gets parameter bounds for each of the actual type arguments in the given
* parameterized class reference. If the type arguments are bounded, the
* returned bounds instances will reflect that.
*
* <p>
* The {@code specifiedValuesByTypeVariableName} map is used for populating
* referenced values where type variable references are used. This is
* limited to the use of parameterized type references, and to only those
* type arguments which refer by type variable (ie: excludes direct class
* name references and other type references).
*
* @param parameterizedType parameterizedType
* @param paramValuesByTypeVariableName a map from TypeVariable name to
* actual specified bounds; must contain values for all type variable
* references
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
* @return an array of bounds, one item for each type argument
*/
public static ParameterBounds[] boundsForParametersOf(ParameterizedType parameterizedType,
Map<String, ParameterBounds> paramValuesByTypeVariableName) {
List<ParameterBounds> allBounds = new ArrayList<ParameterBounds>();
for (Type typeArgument : parameterizedType.getActualTypeArguments()) {
// use pre-defined values
if (paramValuesByTypeVariableName != null && typeArgument instanceof TypeVariable) {
TypeVariable<?> typeVariable = (TypeVariable<?>) typeArgument;
ParameterBounds value = paramValuesByTypeVariableName.get(typeVariable.getName());
if (value == null) {
throw new UnsupportedOperationException("No known value for TypeVariable " + typeVariable.getName() + " "
+ "in " + paramValuesByTypeVariableName + " "
+ "when extracting parameters of " + descriptionOf(parameterizedType));
}
allBounds.add(value);
} // derive from definitions
else {
allBounds.add(getBoundsOf(typeArgument));
}
}
return allBounds.toArray(new ParameterBounds[]{});
}
/**
* Creates a single bounds instance from the supplied type reference.
*
* @param type type
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
* @return the single bounds instance created
*/
public static ParameterBounds getBoundsOf(Type type) {
if (type instanceof Class<?>) {
return new ParameterBounds(new Type[]{type}, null);
} else if (type instanceof GenericArrayType) {
return new ParameterBounds(new Type[]{type}, null);
} else if (type instanceof TypeVariable) {
TypeVariable<?> typeVariable = (TypeVariable<?>) type;
return new ParameterBounds(typeVariable.getBounds(), null);
} else if (type instanceof WildcardType) {
WildcardType wildcard = (WildcardType) type;
return new ParameterBounds(wildcard.getUpperBounds(), wildcard.getLowerBounds());
} else if (type instanceof ParameterizedType) {
// experimental support for parameterized type references
// (makes no attempt to understand what's inside it,
// included so caller can return detailed error)
return new ParameterBounds(new Type[]{type}, null);
} else {
throw new UnsupportedOperationException("Unsupported type " + type.getClass().getSimpleName() + ": " + descriptionOf(type));
}
}
/**
* Creates a new instance with the given bounds. Only supported types may be
* supplied.
*
* @param upperTypes nulls and empty arrays are converted to
* {@code [Object]}.
* @param lowerTypes empty arrays are converted to null
*/
public ParameterBounds(Type[] upperTypes, Type[] lowerTypes) {
this.upperTypes = (upperTypes == null || upperTypes.length == 0) ? new Type[]{Object.class} : upperTypes;
this.lowerTypes = (lowerTypes == null || lowerTypes.length == 0) ? null : lowerTypes;
}
/**
* Gets a string representation suitable for debugging.
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
*
* @return a string of this object.
*/
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
if (isUpperMulti()) {
buf.append("{").append(descriptionOf(upperTypes)).append("}");
} else {
buf.append(descriptionOf(upperTypes));
}
if (lowerTypes != null) {
buf.append(" super ");
if (isUpperMulti()) {
buf.append("{").append(descriptionOf(lowerTypes)).append("}");
} else {
buf.append(descriptionOf(lowerTypes));
}
}
return buf.toString();
}
boolean hasUpperBound() {
return (upperTypes != null);
}
boolean hasLowerBound() {
return (lowerTypes != null);
}
boolean isUpperMulti() {
return (upperTypes != null) && (upperTypes.length > 1);
}
boolean isLowerMulti() {
return (lowerTypes != null) && (lowerTypes.length > 1);
}
/**
* Assumes there's only one upper class and returns it.
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
*
* @return the non-null upper class (defaults to {@code Object})
* @throws UnsupportedType if type reference cannot be converted to a class
* @throws IllegalStateException if there's actually more than one class
*/
Class<?> upperClass() throws UnsupportedType {
Type type = upperType();
if (type != null) {
return resolveClassOf(type);
}
return Object.class;
}
/**
* Assumes there's only one lower class and returns it.
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
*
* @return the lower class or null if none
* @throws UnsupportedType if type reference cannot be converted to a class
* @throws IllegalStateException if there's actually more than one class
*/
public Class<?> lowerClass() throws UnsupportedType {
Type type = lowerType();
if (type != null) {
return resolveClassOf(type);
}
return null;
}
/**
* Gets the upper bounding classes. This method returns the equivalent of
* {@link #upperTypes()} after resolving types to classes.
*
* <p>
* In most cases only one type will be supplied. Multiple are used where a
* generic type reference is of form {@code T extends TypeOne,TypeTwo}. For
* example, it can be used to require that a type <i>both</i> is an enum,
* and implements an particular interface.
*
* <p>
* If the upper bound has not been specialised, it will be
* {@code Object.class}.
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
*
* @return non-empty upper bounding types (usually only one)
* @throws UnsupportedType if type reference cannot be converted to a class
*/
public Class<?>[] upperClasses() throws UnsupportedType {
if (upperTypes == null) {
return null;
}
Class<?>[] classes = new Class<?>[upperTypes.length];
for (int i = 0; i < classes.length; i++) {
classes[i] = resolveClassOf(upperTypes[i]);
}
return classes;
}
/**
* Gets the lower bounding classes. This method returns the equivalent of
* {@link #upperTypes()} after resolving types to classes.
*
* <p>
* In most cases only one type will be supplied. Multiple are used where a
* generic type reference is of form {@code T super TypeOne,TypeTwo}.
*
* <p>
* If the lower bound has not been specialised, it will be null.
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
*
* @return null if no lower bound, or non-empty bounding types (usually only
* one)
* @throws UnsupportedType if type reference cannot be converted to a class
*/
public Class<?>[] lowerClasses() throws UnsupportedType {
if (lowerTypes == null) {
return null;
}
Class<?>[] classes = new Class<?>[lowerTypes.length];
for (int i = 0; i < classes.length; i++) {
classes[i] = resolveClassOf(lowerTypes[i]);
}
return classes;
}
/**
* Assumes there's only one upper type and returns it.
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
*
* @return the non-null upper type (defaults to {@code Object})
* @throws IllegalStateException if there's actually more than one type
*/
public Type upperType() {
if (upperTypes != null && upperTypes.length > 1) {
throw new IllegalStateException("Cannot get single type where multiple types are used");
}
return (upperTypes == null) ? null : upperTypes[0];
}
/**
* Assumes there's only one lower type and returns it.
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
*
* @return the lower type or null if none
* @throws IllegalStateException if there's actually more than one type
*/
public Type lowerType() {
if (lowerTypes != null && lowerTypes.length > 1) {
throw new IllegalStateException("Cannot get single type where multiple types are used");
}
return (lowerTypes == null) ? null : lowerTypes[0];
}
/**
* Gets the upper bounding types.
*
* <p>
* In most cases only one type will be supplied. Multiple are used where a
* generic type reference is of form {@code T extends TypeOne,TypeTwo}. For
* example, it can be used to require that a type <i>both</i> is an enum,
* and implements an particular interface.
*
* <p>
* If the upper bound has not been specialised, it will be
* {@code Object.class}.
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
*
* @return non-empty upper bounding types (usually only one)
*/
public Type[] upperTypes() {
if (upperTypes == null) {
return null;
} else {
return Arrays.copyOf(upperTypes, upperTypes.length);
}
}
/**
* Gets the lower bounding types.
*
* <p>
* In most cases only one type will be supplied. Multiple are used where a
* generic type reference is of form {@code T super TypeOne,TypeTwo}.
*
* <p>
* If the lower bound has not been specialised, it will be null.
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
*
* @return null if no lower bound, or non-empty bounding types (usually only
* one)
*/
public Type[] lowerTypes() {
if (lowerTypes == null) {
return null;
} else {
return Arrays.copyOf(lowerTypes, lowerTypes.length);
}
}
}
/**
* Thrown internally when a Type is not supported by a method
*/
static class UnsupportedType extends Exception {
private static final long serialVersionUID = 1L;
UnsupportedType(String message) {
super(message);
}
}
}