diff --git a/src/main/java/io/leangen/geantyref/GenericTypeReflector.java b/src/main/java/io/leangen/geantyref/GenericTypeReflector.java index d7f965c..b2bb8fa 100644 --- a/src/main/java/io/leangen/geantyref/GenericTypeReflector.java +++ b/src/main/java/io/leangen/geantyref/GenericTypeReflector.java @@ -24,6 +24,8 @@ import java.util.Set; import java.util.stream.Stream; +import static java.util.Arrays.stream; + /** * Utility class for doing reflection on types. * @@ -31,492 +33,569 @@ * @author Bojan Tomic {@literal (veggen@gmail.com)} */ public class GenericTypeReflector { - private static final WildcardType UNBOUND_WILDCARD = new WildcardTypeImpl(new Type[]{Object.class}, new Type[]{}); - - /** - * Returns the erasure of the given type. - */ - public static Class> erase(Type type) { - if (type instanceof Class) { - return (Class>)type; - } else if (type instanceof ParameterizedType) { - return (Class>)((ParameterizedType) type).getRawType(); - } else if (type instanceof TypeVariable) { - TypeVariable> tv = (TypeVariable>)type; - if (tv.getBounds().length == 0) - return Object.class; - else - return erase(tv.getBounds()[0]); - } else if (type instanceof GenericArrayType) { - GenericArrayType aType = (GenericArrayType) type; - return GenericArrayTypeImpl.createArrayType(erase(aType.getGenericComponentType())); - } else if (type instanceof WildcardType) { - WildcardType wildcardType = (WildcardType) type; - Type[] lowerBounds = wildcardType.getLowerBounds(); - return erase(lowerBounds.length > 0 ? lowerBounds[0] : wildcardType.getUpperBounds()[0]); - } else { - throw new RuntimeException("not supported: " + type.getClass()); - } - } - - /** - * Maps type parameters in a type to their values. - * @param toMapType Type possibly containing type arguments - * @param typeAndParams must be either ParameterizedType, or (in case there are no type arguments, or it's a raw type) Class - * @return toMapType, but with type parameters from typeAndParams replaced. - */ - public static AnnotatedType mapTypeParameters(AnnotatedType toMapType, AnnotatedType typeAndParams) { - if (isMissingTypeParameters(typeAndParams.getType())) { - return new AnnotatedTypeImpl(erase(toMapType.getType()), toMapType.getAnnotations()); - } else { - VarMap varMap = new VarMap(); - AnnotatedType handlingTypeAndParams = typeAndParams; - while(handlingTypeAndParams instanceof AnnotatedParameterizedType) { - AnnotatedParameterizedType pType = (AnnotatedParameterizedType)handlingTypeAndParams; - Class> clazz = (Class>)((ParameterizedType) pType.getType()).getRawType(); // getRawType should always be Class - TypeVariable[] vars = clazz.getTypeParameters(); - varMap.addAll(vars, pType.getAnnotatedActualTypeArguments()); - Type owner = ((ParameterizedType) pType.getType()).getOwnerType(); - handlingTypeAndParams = owner == null ? null : annotate(owner); - } - return varMap.map(toMapType); - } - } - - /** - * Checks if the given type is a class that is supposed to have type parameters, but doesn't. - * In other words, if it's a really raw type. - */ - public static boolean isMissingTypeParameters(Type type) { - if (type instanceof Class) { - for (Class> clazz = (Class>) type; clazz != null; clazz = clazz.getEnclosingClass()) { - if (clazz.getTypeParameters().length != 0) - return true; - } - return false; - } else if (type instanceof ParameterizedType) { - return false; - } else { - throw new AssertionError("Unexpected type " + type.getClass()); - } - } - - /** - * Returns a type representing the class, with all type parameters the unbound wildcard ("?"). - * For example, {@code addWildcardParameters(Map.class)} returns a type representing {@code Map,?>}. - * @return
For example, with {@code class StringList implements List
For example, with {@code class StringList implements List
For example, with {@code getExactSubType(new TypeToken>(){}.getAnnotatedType(), ArrayList.class)}
+ * returns a {@link AnnotatedParameterizedType} representing {@code ArrayList
- * This is mostly useful if you get a type from one of the other methods in {@code GenericTypeReflector}, - * but you don't want to deal with all the different sorts of types, - * and you are only really interested in concrete classes and interfaces. - *
- * - * @return A List of classes, each of them a supertype of the given type. - * If the given type is a class or interface itself, returns a List with just the given type. - * The list contains no duplicates, and is ordered in the order the upper bounds are defined on the type. - */ - public static List+ * This is mostly useful if you get a type from one of the other methods in {@code GenericTypeReflector}, + * but you don't want to deal with all the different sorts of types, + * and you are only really interested in concrete classes and interfaces. + *
+ * + * @return A List of classes, each of them a supertype of the given type. + * If the given type is a class or interface itself, returns a List with just the given type. + * The list contains no duplicates, and is ordered in the order the upper bounds are defined on the type. + */ + public static ListSee {@link #annotate(Type)}
*/ - public static AnnotatedType annotate(Type type, Annotation[] annotations) { + public static AnnotatedType annotate(Type type, Annotation[] annotations) { return updateAnnotations(annotate(type), annotations); } @@ -561,56 +640,62 @@ public static AnnotatedType annotate(Type type, Annotation[] annotations) { *See {@link CaptureCacheKey}
*See {@link CaptureType}
*/ - private static AnnotatedType annotate(Type type, MapSee {@link #annotate(Type, Map)}
*/ - private static class CaptureCacheKey { - CaptureType capture; - - CaptureCacheKey(CaptureType capture) { - this.capture = capture; - } - - @Override - public int hashCode() { - return capture.getWildcardType().hashCode() + capture.getTypeVariable().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof CaptureCacheKey)) { - return false; - } - CaptureType other = ((CaptureCacheKey) obj).capture; - if (!capture.getWildcardType().equals(other.getWildcardType()) - || !capture.getTypeVariable().equals(other.getTypeVariable())) { - return false; - } - return Arrays.equals(capture.getUpperBounds(), other.getUpperBounds()); - } - } + private static class CaptureCacheKey { + CaptureType capture; + + CaptureCacheKey(CaptureType capture) { + this.capture = capture; + } + + @Override + public int hashCode() { + return capture.getWildcardType().hashCode() + capture.getTypeVariable().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof CaptureCacheKey)) { + return false; + } + CaptureType other = ((CaptureCacheKey) obj).capture; + if (!capture.getWildcardType().equals(other.getWildcardType()) + || !capture.getTypeVariable().equals(other.getTypeVariable())) { + return false; + } + return Arrays.equals(capture.getUpperBounds(), other.getUpperBounds()); + } + } } diff --git a/src/main/java/io/leangen/geantyref/TypeFactory.java b/src/main/java/io/leangen/geantyref/TypeFactory.java index d152642..6a810c9 100644 --- a/src/main/java/io/leangen/geantyref/TypeFactory.java +++ b/src/main/java/io/leangen/geantyref/TypeFactory.java @@ -43,7 +43,7 @@ public static AnnotatedType parameterizedAnnotatedClass(Class> clazz, Annotati Type[] typeArguments = Arrays.stream(arguments).map(AnnotatedType::getType).toArray(Type[]::new); return new AnnotatedParameterizedTypeImpl((ParameterizedType) parameterizedClass(clazz, typeArguments), annotations, arguments); } - + /** * Creates a type of {@code clazz} nested in {@code owner}. * diff --git a/src/test/java/io/leangen/geantyref/GenericTypeReflectorTest.java b/src/test/java/io/leangen/geantyref/GenericTypeReflectorTest.java index d55b4b5..d2facca 100644 --- a/src/test/java/io/leangen/geantyref/GenericTypeReflectorTest.java +++ b/src/test/java/io/leangen/geantyref/GenericTypeReflectorTest.java @@ -1,6 +1,9 @@ package io.leangen.geantyref; import java.awt.*; +import java.lang.reflect.AnnotatedArrayType; +import java.lang.reflect.AnnotatedParameterizedType; +import java.lang.reflect.AnnotatedType; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Type; @@ -9,6 +12,9 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Set; + +import static io.leangen.geantyref.GenericTypeReflector.getExactSubType; /** * Test for reflection done in GenericTypeReflector. @@ -62,7 +68,7 @@ public void testGetExactFieldTypeIllegalArgument() throws SecurityException, NoS } } - public void testgetExactParameterTypes() throws SecurityException, NoSuchMethodException { + public void testGetExactParameterTypes() throws SecurityException, NoSuchMethodException { // method: boolean add(int index, E o), erasure is boolean add(int index, Object o) Method getMethod = List.class.getMethod("add", int.class, Object.class); Type[] result = GenericTypeReflector.getExactParameterTypes(getMethod, new TypeToken>(){}.getAnnotatedType(); + AnnotatedParameterizedType subType = (AnnotatedParameterizedType) getExactSubType(parent, C.class); + assertNotNull(subType); + assertEquals(Integer.class, subType.getAnnotatedActualTypeArguments()[0].getType()); + assertEquals(String.class, subType.getAnnotatedActualTypeArguments()[1].getType()); + } + + public void testGetExactSubTypeUnresolvable() { + AnnotatedParameterizedType parent = (AnnotatedParameterizedType) new TypeToken
>(){}.getAnnotatedType();
+ AnnotatedType resolved = GenericTypeReflector.getExactSubType(parent, C1.class);
+ assertNotNull(resolved);
+ assertEquals(C1.class, resolved.getType());
+ }
+
+ public void testGetExactSubTypeNotOverlapping() {
+ AnnotatedParameterizedType parent = (AnnotatedParameterizedType) new TypeToken>(){}.getAnnotatedType();
+ AnnotatedType subType = getExactSubType(parent, Set.class);
+ assertNull(subType);
+ }
+
+ public void testGetExactSubTypeNotParameterized() {
+ AnnotatedParameterizedType parent = (AnnotatedParameterizedType) new TypeToken
>(){}.getAnnotatedType();
+ AnnotatedType subType = getExactSubType(parent, String.class);
+ assertNotNull(subType);
+ assertEquals(String.class, subType.getType());
+ }
+
+ public void testGetExactSubTypeArray() {
+ AnnotatedType parent = new TypeToken
[]>(){}.getAnnotatedType();
+ AnnotatedType subType = getExactSubType(parent, ArrayList[].class);
+ assertNotNull(subType);
+ assertTrue(subType instanceof AnnotatedArrayType);
+ AnnotatedType componentType = ((AnnotatedArrayType) subType).getAnnotatedGenericComponentType();
+ assertTrue(componentType instanceof AnnotatedParameterizedType);
+ assertEquals(String.class, ((AnnotatedParameterizedType) componentType).getAnnotatedActualTypeArguments()[0].getType());
+ }
+
+ private class P
{}
+ private class M extends P{}
+ private class C