Created
March 18, 2018 13:04
-
-
Save eatgrass/329a6087208cfc03c7e5c2045e60a3df to your computer and use it in GitHub Desktop.
return type resolver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Static methods for working with types. | |
* | |
* @author Bob Lee | |
* @author Jesse Wilson | |
*/ | |
final class Types { | |
private static final Type[] EMPTY_TYPE_ARRAY = new Type[0]; | |
private Types() { | |
// No instances. | |
} | |
static Class<?> getRawType(Type type) { | |
if (type instanceof Class<?>) { | |
// Type is a normal class. | |
return (Class<?>) type; | |
} else if (type instanceof ParameterizedType) { | |
ParameterizedType parameterizedType = (ParameterizedType) type; | |
// I'm not exactly sure why getRawType() returns Type instead of Class. Neal isn't either but | |
// suspects some pathological case related to nested classes exists. | |
Type rawType = parameterizedType.getRawType(); | |
if (!(rawType instanceof Class)) { | |
throw new IllegalArgumentException(); | |
} | |
return (Class<?>) rawType; | |
} else if (type instanceof GenericArrayType) { | |
Type componentType = ((GenericArrayType) type).getGenericComponentType(); | |
return Array.newInstance(getRawType(componentType), 0).getClass(); | |
} else if (type instanceof TypeVariable) { | |
// We could use the variable's bounds, but that won't work if there are multiple. Having a raw | |
// type that's more general than necessary is okay. | |
return Object.class; | |
} else if (type instanceof WildcardType) { | |
return getRawType(((WildcardType) type).getUpperBounds()[0]); | |
} else { | |
String className = type == null ? "null" : type.getClass().getName(); | |
throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " | |
+ "GenericArrayType, but <" + type + "> is of type " | |
+ className); | |
} | |
} | |
/** | |
* Returns true if {@code a} and {@code b} are equal. | |
*/ | |
static boolean equals(Type a, Type b) { | |
if (a == b) { | |
return true; // Also handles (a == null && b == null). | |
} else if (a instanceof Class) { | |
return a.equals(b); // Class already specifies equals(). | |
} else if (a instanceof ParameterizedType) { | |
if (!(b instanceof ParameterizedType)) { | |
return false; | |
} | |
ParameterizedType pa = (ParameterizedType) a; | |
ParameterizedType pb = (ParameterizedType) b; | |
return equal(pa.getOwnerType(), pb.getOwnerType()) | |
&& pa.getRawType().equals(pb.getRawType()) | |
&& Arrays.equals(pa.getActualTypeArguments(), pb.getActualTypeArguments()); | |
} else if (a instanceof GenericArrayType) { | |
if (!(b instanceof GenericArrayType)) { | |
return false; | |
} | |
GenericArrayType ga = (GenericArrayType) a; | |
GenericArrayType gb = (GenericArrayType) b; | |
return equals(ga.getGenericComponentType(), gb.getGenericComponentType()); | |
} else if (a instanceof WildcardType) { | |
if (!(b instanceof WildcardType)) { | |
return false; | |
} | |
WildcardType wa = (WildcardType) a; | |
WildcardType wb = (WildcardType) b; | |
return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds()) | |
&& Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds()); | |
} else if (a instanceof TypeVariable) { | |
if (!(b instanceof TypeVariable)) { | |
return false; | |
} | |
TypeVariable<?> va = (TypeVariable<?>) a; | |
TypeVariable<?> vb = (TypeVariable<?>) b; | |
return va.getGenericDeclaration() == vb.getGenericDeclaration() | |
&& va.getName().equals(vb.getName()); | |
} else { | |
return false; // This isn't a type we support! | |
} | |
} | |
/** | |
* Returns the generic supertype for {@code supertype}. For example, given a class {@code | |
* IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set<Integer>} and the | |
* result when the supertype is {@code Collection.class} is {@code Collection<Integer>}. | |
*/ | |
static Type getGenericSupertype(Type context, Class<?> rawType, Class<?> toResolve) { | |
if (toResolve == rawType) { | |
return context; | |
} | |
// We skip searching through interfaces if unknown is an interface. | |
if (toResolve.isInterface()) { | |
Class<?>[] interfaces = rawType.getInterfaces(); | |
for (int i = 0, length = interfaces.length; i < length; i++) { | |
if (interfaces[i] == toResolve) { | |
return rawType.getGenericInterfaces()[i]; | |
} else if (toResolve.isAssignableFrom(interfaces[i])) { | |
return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve); | |
} | |
} | |
} | |
// Check our supertypes. | |
if (!rawType.isInterface()) { | |
while (rawType != Object.class) { | |
Class<?> rawSupertype = rawType.getSuperclass(); | |
if (rawSupertype == toResolve) { | |
return rawType.getGenericSuperclass(); | |
} else if (toResolve.isAssignableFrom(rawSupertype)) { | |
return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, toResolve); | |
} | |
rawType = rawSupertype; | |
} | |
} | |
// We can't resolve this further. | |
return toResolve; | |
} | |
private static int indexOf(Object[] array, Object toFind) { | |
for (int i = 0; i < array.length; i++) { | |
if (toFind.equals(array[i])) { | |
return i; | |
} | |
} | |
throw new NoSuchElementException(); | |
} | |
private static boolean equal(Object a, Object b) { | |
return a == b || (a != null && a.equals(b)); | |
} | |
private static int hashCodeOrZero(Object o) { | |
return o != null ? o.hashCode() : 0; | |
} | |
static String typeToString(Type type) { | |
return type instanceof Class ? ((Class<?>) type).getName() : type.toString(); | |
} | |
/** | |
* Returns the generic form of {@code supertype}. For example, if this is {@code | |
* ArrayList<String>}, this returns {@code Iterable<String>} given the input {@code | |
* Iterable.class}. | |
* | |
* @param supertype a superclass of, or interface implemented by, this. | |
*/ | |
static Type getSupertype(Type context, Class<?> contextRawType, Class<?> supertype) { | |
if (!supertype.isAssignableFrom(contextRawType)) { | |
throw new IllegalArgumentException(); | |
} | |
return resolve(context, contextRawType, | |
getGenericSupertype(context, contextRawType, supertype)); | |
} | |
static Type resolve(Type context, Class<?> contextRawType, Type toResolve) { | |
// This implementation is made a little more complicated in an attempt to avoid object-creation. | |
while (true) { | |
if (toResolve instanceof TypeVariable) { | |
TypeVariable<?> typeVariable = (TypeVariable<?>) toResolve; | |
toResolve = resolveTypeVariable(context, contextRawType, typeVariable); | |
if (toResolve == typeVariable) { | |
return toResolve; | |
} | |
} else if (toResolve instanceof Class && ((Class<?>) toResolve).isArray()) { | |
Class<?> original = (Class<?>) toResolve; | |
Type componentType = original.getComponentType(); | |
Type newComponentType = resolve(context, contextRawType, componentType); | |
return componentType == newComponentType ? original : new GenericArrayTypeImpl( | |
newComponentType); | |
} else if (toResolve instanceof GenericArrayType) { | |
GenericArrayType original = (GenericArrayType) toResolve; | |
Type componentType = original.getGenericComponentType(); | |
Type newComponentType = resolve(context, contextRawType, componentType); | |
return componentType == newComponentType ? original : new GenericArrayTypeImpl( | |
newComponentType); | |
} else if (toResolve instanceof ParameterizedType) { | |
ParameterizedType original = (ParameterizedType) toResolve; | |
Type ownerType = original.getOwnerType(); | |
Type newOwnerType = resolve(context, contextRawType, ownerType); | |
boolean changed = newOwnerType != ownerType; | |
Type[] args = original.getActualTypeArguments(); | |
for (int t = 0, length = args.length; t < length; t++) { | |
Type resolvedTypeArgument = resolve(context, contextRawType, args[t]); | |
if (resolvedTypeArgument != args[t]) { | |
if (!changed) { | |
args = args.clone(); | |
changed = true; | |
} | |
args[t] = resolvedTypeArgument; | |
} | |
} | |
return changed | |
? new ParameterizedTypeImpl(newOwnerType, original.getRawType(), args) | |
: original; | |
} else if (toResolve instanceof WildcardType) { | |
WildcardType original = (WildcardType) toResolve; | |
Type[] originalLowerBound = original.getLowerBounds(); | |
Type[] originalUpperBound = original.getUpperBounds(); | |
if (originalLowerBound.length == 1) { | |
Type lowerBound = resolve(context, contextRawType, originalLowerBound[0]); | |
if (lowerBound != originalLowerBound[0]) { | |
return new WildcardTypeImpl(new Type[]{Object.class}, new Type[]{lowerBound}); | |
} | |
} else if (originalUpperBound.length == 1) { | |
Type upperBound = resolve(context, contextRawType, originalUpperBound[0]); | |
if (upperBound != originalUpperBound[0]) { | |
return new WildcardTypeImpl(new Type[]{upperBound}, EMPTY_TYPE_ARRAY); | |
} | |
} | |
return original; | |
} else { | |
return toResolve; | |
} | |
} | |
} | |
private static Type resolveTypeVariable( | |
Type context, Class<?> contextRawType, TypeVariable<?> unknown) { | |
Class<?> declaredByRaw = declaringClassOf(unknown); | |
// We can't reduce this further. | |
if (declaredByRaw == null) { | |
return unknown; | |
} | |
Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw); | |
if (declaredBy instanceof ParameterizedType) { | |
int index = indexOf(declaredByRaw.getTypeParameters(), unknown); | |
return ((ParameterizedType) declaredBy).getActualTypeArguments()[index]; | |
} | |
return unknown; | |
} | |
/** | |
* Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by | |
* a class. | |
*/ | |
private static Class<?> declaringClassOf(TypeVariable<?> typeVariable) { | |
GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration(); | |
return genericDeclaration instanceof Class ? (Class<?>) genericDeclaration : null; | |
} | |
private static void checkNotPrimitive(Type type) { | |
if (type instanceof Class<?> && ((Class<?>) type).isPrimitive()) { | |
throw new IllegalArgumentException(); | |
} | |
} | |
static final class ParameterizedTypeImpl implements ParameterizedType { | |
private final Type ownerType; | |
private final Type rawType; | |
private final Type[] typeArguments; | |
ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) { | |
// Require an owner type if the raw type needs it. | |
if (rawType instanceof Class<?> | |
&& (ownerType == null) != (((Class<?>) rawType).getEnclosingClass() == null)) { | |
throw new IllegalArgumentException(); | |
} | |
this.ownerType = ownerType; | |
this.rawType = rawType; | |
this.typeArguments = typeArguments.clone(); | |
for (Type typeArgument : this.typeArguments) { | |
if (typeArgument == null) { | |
throw new NullPointerException(); | |
} | |
checkNotPrimitive(typeArgument); | |
} | |
} | |
public Type[] getActualTypeArguments() { | |
return typeArguments.clone(); | |
} | |
public Type getRawType() { | |
return rawType; | |
} | |
public Type getOwnerType() { | |
return ownerType; | |
} | |
@Override | |
public boolean equals(Object other) { | |
return other instanceof ParameterizedType && Types.equals(this, (ParameterizedType) other); | |
} | |
@Override | |
public int hashCode() { | |
return Arrays.hashCode(typeArguments) ^ rawType.hashCode() ^ hashCodeOrZero(ownerType); | |
} | |
@Override | |
public String toString() { | |
StringBuilder result = new StringBuilder(30 * (typeArguments.length + 1)); | |
result.append(typeToString(rawType)); | |
if (typeArguments.length == 0) { | |
return result.toString(); | |
} | |
result.append("<").append(typeToString(typeArguments[0])); | |
for (int i = 1; i < typeArguments.length; i++) { | |
result.append(", ").append(typeToString(typeArguments[i])); | |
} | |
return result.append(">").toString(); | |
} | |
} | |
private static final class GenericArrayTypeImpl implements GenericArrayType { | |
private final Type componentType; | |
GenericArrayTypeImpl(Type componentType) { | |
this.componentType = componentType; | |
} | |
public Type getGenericComponentType() { | |
return componentType; | |
} | |
@Override | |
public boolean equals(Object o) { | |
return o instanceof GenericArrayType | |
&& Types.equals(this, (GenericArrayType) o); | |
} | |
@Override | |
public int hashCode() { | |
return componentType.hashCode(); | |
} | |
@Override | |
public String toString() { | |
return typeToString(componentType) + "[]"; | |
} | |
} | |
/** | |
* The WildcardType interface supports multiple upper bounds and multiple lower bounds. We only | |
* support what the Java 6 language needs - at most one bound. If a lower bound is set, the upper | |
* bound must be Object.class. | |
*/ | |
static final class WildcardTypeImpl implements WildcardType { | |
private final Type upperBound; | |
private final Type lowerBound; | |
WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) { | |
if (lowerBounds.length > 1) { | |
throw new IllegalArgumentException(); | |
} | |
if (upperBounds.length != 1) { | |
throw new IllegalArgumentException(); | |
} | |
if (lowerBounds.length == 1) { | |
if (lowerBounds[0] == null) { | |
throw new NullPointerException(); | |
} | |
checkNotPrimitive(lowerBounds[0]); | |
if (upperBounds[0] != Object.class) { | |
throw new IllegalArgumentException(); | |
} | |
this.lowerBound = lowerBounds[0]; | |
this.upperBound = Object.class; | |
} else { | |
if (upperBounds[0] == null) { | |
throw new NullPointerException(); | |
} | |
checkNotPrimitive(upperBounds[0]); | |
this.lowerBound = null; | |
this.upperBound = upperBounds[0]; | |
} | |
} | |
public Type[] getUpperBounds() { | |
return new Type[]{upperBound}; | |
} | |
public Type[] getLowerBounds() { | |
return lowerBound != null ? new Type[]{lowerBound} : EMPTY_TYPE_ARRAY; | |
} | |
@Override | |
public boolean equals(Object other) { | |
return other instanceof WildcardType && Types.equals(this, (WildcardType) other); | |
} | |
@Override | |
public int hashCode() { | |
// This equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()). | |
return (lowerBound != null ? 31 + lowerBound.hashCode() : 1) ^ (31 + upperBound.hashCode()); | |
} | |
@Override | |
public String toString() { | |
if (lowerBound != null) { | |
return "? super " + typeToString(lowerBound); | |
} | |
if (upperBound == Object.class) { | |
return "?"; | |
} | |
return "? extends " + typeToString(upperBound); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment