diff --git a/common/api/src/main/java/com/dfsek/terra/api/util/ReflectionUtil.java b/common/api/src/main/java/com/dfsek/terra/api/util/ReflectionUtil.java index 1614bac0d..03e871ddc 100644 --- a/common/api/src/main/java/com/dfsek/terra/api/util/ReflectionUtil.java +++ b/common/api/src/main/java/com/dfsek/terra/api/util/ReflectionUtil.java @@ -4,8 +4,14 @@ import org.jetbrains.annotations.NotNull; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Array; import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; import java.util.Arrays; import java.util.function.Consumer; import java.util.stream.Stream; @@ -33,4 +39,29 @@ public final class ReflectionUtil { T a = element.getAnnotation(annotation); if(a != null) operation.accept(a); } + + public static Class getRawType(Type type) { + if (type instanceof Class) { + return (Class) type; + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Type rawType = parameterizedType.getRawType(); + 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) { + 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); + } + } + + public static String typeToString(Type type) { + return type instanceof Class ? ((Class) type).getName() : type.toString(); + } } diff --git a/common/api/src/main/java/com/dfsek/terra/api/util/TypeToken.java b/common/api/src/main/java/com/dfsek/terra/api/util/TypeToken.java new file mode 100644 index 000000000..3fa3114c6 --- /dev/null +++ b/common/api/src/main/java/com/dfsek/terra/api/util/TypeToken.java @@ -0,0 +1,104 @@ +package com.dfsek.terra.api.util; + +import java.lang.reflect.*; +import java.util.Arrays; +import java.util.Objects; + +public class TypeToken { + final Class rawType; + final Type type; + final int hashCode; + + @SuppressWarnings("unchecked") + protected TypeToken() { + this.type = getSuperclassTypeParameter(getClass()); + this.rawType = (Class) ReflectionUtil.getRawType(type); + this.hashCode = type.hashCode(); + } + + + static Type getSuperclassTypeParameter(Class subclass) { + Type superclass = subclass.getGenericSuperclass(); + if(superclass instanceof Class) { + throw new RuntimeException("Missing type parameter."); + } + ParameterizedType parameterized = (ParameterizedType) superclass; + return parameterized.getActualTypeArguments()[0]; + } + + /** + * Returns the raw (non-generic) type for this type. + */ + public final Class getRawType() { + return rawType; + } + + /** + * Gets underlying {@code Type} instance. + */ + public final Type getType() { + return type; + } + + @Override + public final int hashCode() { + return this.hashCode; + } + + @Override + public final boolean equals(Object o) { + return o instanceof TypeToken + && equals(type, ((TypeToken) o).type); + } + + @Override + public final String toString() { + return ReflectionUtil.typeToString(type); + } + + public static boolean equals(Type a, Type b) { + if(a == b) { + return true; + } else if(a instanceof Class) { + return a.equals(b); + } else if(a instanceof ParameterizedType) { + if(!(b instanceof ParameterizedType)) { + return false; + } + + ParameterizedType pa = (ParameterizedType) a; + ParameterizedType pb = (ParameterizedType) b; + return Objects.equals(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; + } + } +} +