package tc.oc.commons.core.reflect; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.GenericDeclaration; 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.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; import javax.annotation.Nullable; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.reflect.TypeParameter; import com.google.common.reflect.TypeToken; import com.google.inject.TypeLiteral; import tc.oc.commons.core.util.ArrayUtils; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; public final class Types { private Types() {} /** * Convert a Guava {@link TypeToken} to a Guice {@link TypeLiteral} */ public static <T> TypeLiteral<T> toLiteral(TypeToken<T> typeToken) { return (TypeLiteral<T>) TypeLiteral.get(typeToken.getType()); } /** * Convert a Guice {@link TypeLiteral} to a Guava {@link TypeToken} */ public static <T> TypeToken<T> toToken(TypeLiteral<T> typeLiteral) { return (TypeToken<T>) TypeToken.of(typeLiteral.getType()); } public static <T, P> TypeLiteral<T> resolve(TypeLiteral<T> type, TypeParameter<P> parameter, Class<P> argument) { return toLiteral(toToken(type).where(parameter, argument)); } public static <T, P> TypeLiteral<T> resolve(TypeLiteral<T> type, TypeParameter<P> parameter, TypeLiteral<P> argument) { return toLiteral(toToken(type).where(parameter, toToken(argument))); } public static <T> TypeLiteral<T> resolve(TypeLiteral<T> type, Class<?> declaringClass) { return (TypeLiteral<T>) toLiteral(TypeToken.of(declaringClass).resolveType(type.getType())); } public static boolean isAssignable(Class<?> to, TypeToken<?> from) { return to.isAssignableFrom(from.getRawType()); } public static boolean isAssignable(Class<?> to, TypeLiteral<?> from) { return to.isAssignableFrom(from.getRawType()); } public static boolean isAssignable(TypeLiteral<?> to, TypeLiteral<?> from) { return toToken(to).isAssignableFrom(from.getType()); } public static boolean isAssignable(Type to, Type from) { return TypeToken.of(to).isAssignableFrom(from); } private static final ImmutableMap<Class<?>, Class<?>> PRIMITIVE_PROMOTIONS = ImmutableMap .<Class<?>, Class<?>>builder() .put(byte.class, short.class) .put(short.class, int.class) .put(char.class, int.class) .put(int.class, long.class) .put(long.class, float.class) .put(float.class, double.class) .build(); public static boolean isPromotable(Class<?> to, @Nullable Class<?> from) { return from != null && (to.equals(from) || isPromotable(to, PRIMITIVE_PROMOTIONS.get(from))); } private static final ImmutableBiMap<Class<?>, Class<?>> BOXINGS; static { BOXINGS = ImmutableBiMap.<Class<?>, Class<?>>builder() .put(boolean.class, Boolean.class) .put(char.class, Character.class) .put(byte.class, Byte.class) .put(short.class, Short.class) .put(int.class, Integer.class) .put(long.class, Long.class) .put(float.class, Float.class) .put(double.class, Double.class) .build(); } public static <T> Class<T> box(Class<T> type) { return type.isPrimitive() ? (Class<T>) BOXINGS.get(type) : type; } public static <T> TypeToken<T> box(TypeToken<T> type) { return type.isPrimitive() ? TypeToken.of(box((Class<T>) type.getRawType())) : type; } public static <T> Class<T> unbox(Class<T> type) { if(type.isPrimitive()) return type; type = (Class<T>) BOXINGS.inverse().get(type); if(type == null) throw new IllegalArgumentException(type.getName() + " is not a primitive type"); return type; } /** * Test if an invocation conversion can be applied to the given types. * * https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.3 */ public static boolean isConvertibleForInvocation(TypeToken<?> to, TypeToken<?> from) { if(to.isPrimitive()) { // Assigning to a primitive allows for both unboxing and primitive widening Class<?> fromRaw = from.getRawType(); if(!fromRaw.isPrimitive()) { fromRaw = BOXINGS.inverse().get(fromRaw); if(fromRaw == null) return false; } return isPromotable(to.getRawType(), fromRaw); } else if(from.isPrimitive()) { // Assigning to an object from a primitive allows boxing and reference widening return to.isAssignableFrom(box(from.getRawType())); } else { return to.isAssignableFrom(from); } } public static boolean isConvertibleForInvocation(Type to, Type from) { return isConvertibleForInvocation(TypeToken.of(to), TypeToken.of(from)); } public static boolean isFullySpecified(Type type) { checkNotNull(type); if(type instanceof Class) { return true; } else if(type instanceof TypeVariable) { return false; } else if(type instanceof GenericArrayType) { return isFullySpecified(((GenericArrayType) type).getGenericComponentType()); } else if(type instanceof WildcardType) { final WildcardType wildcard = (WildcardType) type; return Stream.of(wildcard.getLowerBounds()).allMatch(Types::isFullySpecified) && Stream.of(wildcard.getUpperBounds()).allMatch(Types::isFullySpecified); } else if(type instanceof ParameterizedType) { final ParameterizedType parameterized = (ParameterizedType) type; return isFullySpecified(parameterized.getRawType()) && (parameterized.getOwnerType() == null || isFullySpecified(parameterized.getOwnerType())) && Stream.of(parameterized.getActualTypeArguments()).allMatch(Types::isFullySpecified); } else { throw new IllegalArgumentException("Unhandled metatype " + type.getClass()); } } public static boolean isFullySpecified(TypeToken<?> type) { return isFullySpecified(type.getType()); } public static Type assertFullySpecified(Type type) { if(!isFullySpecified(type)) { throw new IllegalArgumentException("Type " + type + " is not fully specified"); } return type; } public static <T> TypeLiteral<T> assertFullySpecified(TypeLiteral<T> type) { assertFullySpecified(type.getType()); return type; } public static <T> TypeToken<T> assertFullySpecified(TypeToken<T> type) { assertFullySpecified(type.getType()); return type; } public static <D extends GenericDeclaration> TypeVariable<D> typeVariable(D decl, String name) { for(TypeVariable<?> var : decl.getTypeParameters()) { if(name.equals(var.getName())) { return (TypeVariable<D>) var; } } throw new IllegalArgumentException(decl + " has no type parameter named '" + name + "'"); } public static TypeToken<?> actualTypeArgument(TypeToken<?> parameterizedType, int position) { return actualTypeArgument(parameterizedType.getType(), position); } public static TypeToken<?> actualTypeArgument(Type parameterizedType, int position) { checkArgument(parameterizedType instanceof ParameterizedType); return TypeToken.of(((ParameterizedType) parameterizedType).getActualTypeArguments()[position]); } public static @Nullable Type superclass(Type type) { return type instanceof Class ? ((Class) type).getGenericSuperclass() : null; } /** * Get all superclasses of the given type, including the type itself. * If the given type is an interface, it will be the only element * in the result. */ public static <T> List<Class<? super T>> superclasses(Class<T> type) { ImmutableList.Builder<Class<? super T>> list = ImmutableList.builder(); for(Class<?> sup = type; sup != null; sup = sup.getSuperclass()) { list.add((Class<? super T>) sup); } return list.build(); } public static List<Type> interfaces(Type type) { return type instanceof Class ? Arrays.asList(((Class) type).getGenericInterfaces()) : Collections.emptyList(); } public static <T> List<Class<? super T>> interfaces(Class<T> type) { return Arrays.asList((Class<? super T>[]) type.getInterfaces()); } public static <T> Set<Class<? super T>> minimalInheritedInterfaces(Class<T> type) { if(type.isInterface()) { return ImmutableSet.of(type); } final MinimalSupertypeSet<T> interfaces = new MinimalSupertypeSet<>(); for(Class<? super T> supertype = type; supertype != null; supertype = supertype.getSuperclass()) { for(Class<?> iface : supertype.getInterfaces()) { interfaces.add(iface); } } return interfaces.toClassSet(); } /** * Get the immediate parent types of the given class i.e. the * direct superclass (if any) and any directly implemented interfaces. */ public static <T> Iterable<Class<? super T>> parents(Class<T> type) { Class<? super T> superclass = type.getSuperclass(); Class<? super T>[] interfaces = (Class<? super T>[]) type.getInterfaces(); if(superclass == null) { return Arrays.asList(interfaces); } else { return Iterables.concat(Arrays.asList(interfaces), Collections.singleton(superclass)); } } /** * Get all ancestor types of the given type, both classes and interfaces, * in breadth-first order. The superclass of each class is traversed before * its interfaces. * * @param allowDuplicates If true, duplicate interfaces will appear in the result wherever * they occur in the ancestry graph. If false, all but the * first occurance of each interface will be omitted from the result. * This makes the operation somewhat more expensive, so duplicates * should be allowed if possible. */ public static <T> Collection<Class<? super T>> ancestors(Class<T> type, boolean allowDuplicates) { List<Class<? super T>> list = new ArrayList<>(); list.add(type); for(int i = 0; i < list.size(); i++) { final Class<?> t = list.get(i); if(t.getSuperclass() != null) list.add((Class<? super T>) t.getSuperclass()); final Class<? super T>[] interfaces = (Class<? super T>[]) t.getInterfaces(); if(allowDuplicates) { Collections.addAll(list, interfaces); } else { for(Class<? super T> iface : interfaces) { if(!list.contains(iface)) list.add(iface); } } } return list; } /** * Equivalent to {@link #ancestors(Class, boolean)} with duplicate results allowed. */ public static <T> Collection<Class<? super T>> ancestors(Class<T> type) { return ancestors(type, true); } /** * Traverse ancestors of the given type, in the same order as {@link #ancestors(Class, boolean)}, * and return the first ancestor for which the given predicate returns true. */ public static @Nullable <U> Class<? extends U> findAncestor(Class<? extends U> type, Class<U> upperBound, java.util.function.Predicate<Class<?>> pred) { Deque<Class<? extends U>> queue = new ArrayDeque<>(); queue.add(type); while(!queue.isEmpty()) { final Class<? extends U> t = queue.remove(); if(pred.test(t)) return t; if(t.getSuperclass() != null && upperBound.isAssignableFrom(t.getSuperclass())) { queue.add((Class<? extends U>) t.getSuperclass()); } for(Class<?> iface : t.getInterfaces()) { if(upperBound.isAssignableFrom(iface)) { queue.add((Class<? extends U>) iface); } } } return null; } public static @Nullable Class<?> findAncestor(Class<?> type, java.util.function.Predicate<Class<?>> pred) { Deque<Class<?>> queue = new ArrayDeque<>(); queue.add(type); while(!queue.isEmpty()) { final Class<?> t = queue.remove(); if(pred.test(t)) return t; if(t.getSuperclass() != null) { queue.add(t.getSuperclass()); } Collections.addAll(queue, t.getInterfaces()); } return null; } /** * Traverse ancestors of the given type, in the same order as {@link #ancestors(Class, boolean)}, * applying the given function to each ancestor, and return the first non-null result, or null * if the function returns null for all ancestors. */ public static @Nullable <T, R> R findForAncestor(Class<T> type, Function<Class<? super T>, R> func) { Deque<Class<? super T>> queue = new ArrayDeque<>(); queue.add(type); while(!queue.isEmpty()) { final Class<? super T> t = queue.remove(); final R result = func.apply(t); if(result != null) return result; if(t.getSuperclass() != null) queue.add(t.getSuperclass()); Collections.addAll(queue, (Class<? super T>[]) t.getInterfaces()); } return null; } public static String externalToInternal(String name) { return name.replace('.', '/'); } public static String internalToExternal(String name) { return name.replace('/', '.'); } public static String descriptor(Class<?> type) { if(type.isPrimitive()) { if(type == byte.class) return "B"; if(type == char.class) return "C"; if(type == double.class) return "D"; if(type == float.class) return "F"; if(type == int.class) return "I"; if(type == long.class) return "J"; if(type == short.class) return "S"; if(type == boolean.class) return "Z"; if(type == void.class) return "V"; throw new RuntimeException("Unrecognized primitive " + type); } String desc = type.isArray() ? type.getName() // Array type names already have "L...;" : 'L' + type.getName() + ';'; return externalToInternal(desc); } /** * Get the inner class of the given parent with the given name. * The name can be fully qualified, or "simple" i.e. just the member name. * The numeric names of anonymous classes will also work. */ public static @Nullable Class<?> getDeclaredClass(Class<?> parent, String name) { if(name.indexOf('$') == -1) { name = parent.getName() + '$' + name; } for(Class<?> type : parent.getDeclaredClasses()) { if(name.equals(type.getName())) return type; } return null; } public static Class<?> needDeclaredClass(Class<?> parent, String name) throws ClassNotFoundException { final Class<?> type = getDeclaredClass(parent, name); if(type == null) { throw new ClassNotFoundException("No inner class with name '" + name + "' in type '" + parent.getName() + "'"); } return type; } public static @Nullable <T extends Annotation> T inheritableAnnotation(Class<?> cls, Class<T> annotation) { for(Class<?> anc : ancestors(cls)) { T annot = anc.getAnnotation(annotation); if(annot != null) return annot; } return null; } public static boolean instanceOfAny(Object o, Class... types) { for(Class type : types) { if(type.isInstance(o)) return true; } return false; } public static boolean instanceOfAll(Object o, Class... types) { for(Class type : types) { if(!type.isInstance(o)) return false; } return true; } public static Predicate<? super Type> assignableFrom(final Type from) { return to -> to != null && TypeToken.of(to).isAssignableFrom(from); } public static Predicate<? super Type> assignableTo(final Type to) { final TypeToken<?> toToken = TypeToken.of(to); return from -> from != null && toToken.isAssignableFrom(from); } public static <V extends ReflectionVisitor> V walkAncestors(Class<?> cls, V visitor) { return walkAncestors(cls, null, visitor); } public static <V extends ReflectionVisitor> V walkAncestors(Class<?> cls, @Nullable Predicate<? super Class<?>> filter, V visitor) { if(filter != null && !filter.test(cls)) return visitor; if(!visitor.visit(cls)) return visitor; for(Method method : cls.getDeclaredMethods()) { visitor.visit(cls, method); } for(Field field : cls.getDeclaredFields()) { visitor.visit(cls, field); } Class<?> superclass = cls.getSuperclass(); if(superclass != null) { walkAncestors(superclass, filter, visitor); } for(Class<?> iface : cls.getInterfaces()) { walkAncestors(iface, filter, visitor); } return visitor; } public static @Nullable <T> Optional<Class<? extends T>> commonAncestor(Class<T> base, Stream<Class<?>> types) { return types .reduce((a, b) -> a.isAssignableFrom(b) ? a : b) .map(t -> t.asSubclass(base)); } public static TypeToken parameterizedTypeToken(TypeToken<?> owner, TypeToken<?>... args) { return parameterizedTypeToken(owner.getType(), args); } public static TypeToken parameterizedTypeToken(Type owner, TypeToken<?>... args) { return parameterizedTypeToken(owner, ArrayUtils.transform(args, new Type[args.length], TypeToken::getType)); } public static TypeToken parameterizedTypeToken(Type owner, Type... args) { return TypeToken.of(com.google.inject.util.Types.newParameterizedType(owner, (Type[]) args)); } public static TypeLiteral parameterizedTypeLiteral(TypeLiteral<?> owner, TypeLiteral<?>... args) { return parameterizedTypeLiteral(owner.getType(), args); } public static TypeLiteral parameterizedTypeLiteral(Type owner, TypeLiteral<?>... args) { return parameterizedTypeLiteral(owner, ArrayUtils.transform(args, new Type[args.length], TypeLiteral::getType)); } public static TypeLiteral parameterizedTypeLiteral(Type owner, Type... args) { return TypeLiteral.get(com.google.inject.util.Types.newParameterizedType(owner, (Type[]) args)); } public static <T> TypeToken<Set<T>> setOf(TypeToken<T> elementType) { return new TypeToken<Set<T>>(){}.where(new TypeParameter<T>(){}, elementType); } public static <T> TypeLiteral<Set<T>> setOf(TypeLiteral<T> elementType) { return toLiteral(setOf(toToken(elementType))); } public static <T> TypeToken<List<T>> listOf(TypeToken<T> elementType) { return new TypeToken<List<T>>(){}.where(new TypeParameter<T>(){}, elementType); } public static <T> TypeLiteral<List<T>> listOf(TypeLiteral<T> elementType) { return toLiteral(listOf(toToken(elementType))); } }