├── .gitignore ├── bnd.bnd ├── src ├── test │ └── java │ │ └── com │ │ └── coekie │ │ └── gentyref │ │ ├── factory │ │ ├── StringOuter.java │ │ ├── RawOuter.java │ │ ├── SimpleOuter.java │ │ ├── GenericOuter.java │ │ ├── Issue16Test.java │ │ └── TypeFactoryTest.java │ │ ├── ReflectionStrategy.java │ │ ├── GenTyRefReflectionStrategy.java │ │ ├── AbstractReflectionStrategy.java │ │ ├── Issue13Test.java │ │ ├── AddWildcardParametersTest.java │ │ ├── StackoverflowQ182872Test.java │ │ ├── CaptureSamplesTest.java │ │ ├── Issue3Test.java │ │ ├── SamplesTest.java │ │ ├── GenericTypeReflectorTest.java │ │ ├── TypeTest.java │ │ ├── GenericMethodTest.java │ │ └── AbstractGenericsReflectorTest.java └── main │ └── java │ └── com │ └── coekie │ └── gentyref │ ├── UnresolvedTypeVariableException.java │ ├── CaptureType.java │ ├── GenericArrayTypeImpl.java │ ├── TypeArgumentNotInBoundException.java │ ├── WildcardTypeImpl.java │ ├── TypeToken.java │ ├── CaptureTypeImpl.java │ ├── ParameterizedTypeImpl.java │ ├── VarMap.java │ ├── TypeFactory.java │ └── GenericTypeReflector.java ├── README.md ├── pom.xml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /bnd.bnd: -------------------------------------------------------------------------------- 1 | Bundle-SymbolicName: ${project.groupId}.${project.artifactId} 2 | Export-Package: com.coekie.gentyref 3 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/factory/StringOuter.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref.factory; 2 | 3 | public class StringOuter extends GenericOuter {} 4 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/factory/RawOuter.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref.factory; 2 | 3 | @SuppressWarnings("rawtypes") 4 | public class RawOuter extends GenericOuter {} 5 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/factory/SimpleOuter.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref.factory; 2 | 3 | public class SimpleOuter { 4 | class GenericInner {} 5 | 6 | class SimpleInner {} 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gentyref is a generic type reflection library for Java. 2 | 3 | # This repository is no longer maintained 4 | Instead you can use this fork with many bugfixes and new features: [GeantyRef](https://github.com/leangen/geantyref). 5 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/factory/GenericOuter.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref.factory; 2 | 3 | /** Generic class with inner classes for testing. */ 4 | public class GenericOuter { 5 | /** A non-generic inner class with a generic outer class. */ 6 | public class Inner {} 7 | 8 | /** Generic inner class with a generic outer class. */ 9 | public class DoubleGeneric {} 10 | 11 | /** Static generic inner class. */ 12 | public static class StaticGenericInner {} 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/ReflectionStrategy.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Method; 5 | import java.lang.reflect.Type; 6 | 7 | /** 8 | * Strategy to do reflection. This can be our own implementation, but also something else if we want 9 | * to reuse our tests to test another implementation. We do this for gson for example. 10 | */ 11 | public interface ReflectionStrategy { 12 | boolean isSupertype(Type supertype, Type subtype); 13 | 14 | void testInexactSupertype(Type supertype, Type subtype); 15 | 16 | void testExactSuperclass(Type expectedSuperclass, Type type); 17 | 18 | Type getReturnType(Type type, Method m); 19 | 20 | Type getFieldType(Type type, Field f); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/coekie/gentyref/UnresolvedTypeVariableException.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import java.lang.reflect.TypeVariable; 4 | 5 | class UnresolvedTypeVariableException extends RuntimeException { 6 | private final TypeVariable tv; 7 | 8 | UnresolvedTypeVariableException(TypeVariable tv) { 9 | super( 10 | "An exact type is requested, but the type contains a type variable that cannot be " 11 | + "resolved.\n" 12 | + " Variable: " 13 | + tv.getName() 14 | + " from " 15 | + tv.getGenericDeclaration() 16 | + "\n" 17 | + " Hint: This is usually caused by trying to get an exact type when a generic " 18 | + "method who's type parameters are not given is involved."); 19 | this.tv = tv; 20 | } 21 | 22 | TypeVariable getTypeVariable() { 23 | return tv; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/coekie/gentyref/CaptureType.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import java.lang.reflect.Type; 4 | import java.lang.reflect.WildcardType; 5 | 6 | /** 7 | * CaptureType represents a wildcard that has gone through capture conversion. It is a custom 8 | * subinterface of Type, not part of the java builtin Type hierarchy. 9 | * 10 | * @author Wouter Coekaerts 11 | */ 12 | public interface CaptureType extends Type { 13 | /** 14 | * Returns an array of Type objects representing the upper bound(s) of this capture. This 15 | * includes both the upper bound of a ? extends wildcard, and the bounds declared with 16 | * the type variable. References to other (or the same) type variables in bounds coming from the 17 | * type variable are replaced by their matching capture. 18 | */ 19 | Type[] getUpperBounds(); 20 | 21 | /** 22 | * Returns an array of Type objects representing the lower bound(s) of this type 23 | * variable. This is the bound of a ? super wildcard. This normally contains only one or 24 | * no types; it is an array for consistency with {@link WildcardType#getLowerBounds()}. 25 | */ 26 | Type[] getLowerBounds(); 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/GenTyRefReflectionStrategy.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Method; 7 | import java.lang.reflect.Type; 8 | 9 | class GenTyRefReflectionStrategy extends AbstractReflectionStrategy { 10 | public boolean isSupertype(Type superType, Type subType) { 11 | return GenericTypeReflector.isSuperType(superType, subType); 12 | } 13 | 14 | protected Type getExactSuperType(Type type, Class searchClass) { 15 | Type result = GenericTypeReflector.getExactSuperType(type, searchClass); 16 | 17 | // sanity check: erase(result) == searchClass and result is a supertype of type 18 | if (result != null) { 19 | assertEquals(searchClass, GenericTypeReflector.erase(result)); 20 | GenericTypeReflector.isSuperType(result, type); 21 | } 22 | 23 | return GenericTypeReflector.getExactSuperType(type, searchClass); 24 | } 25 | 26 | public Type getReturnType(Type type, Method m) { 27 | return GenericTypeReflector.getExactReturnType(m, type); 28 | } 29 | 30 | public Type getFieldType(Type type, Field f) { 31 | return GenericTypeReflector.getExactFieldType(f, type); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/AbstractReflectionStrategy.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | 6 | import java.lang.reflect.ParameterizedType; 7 | import java.lang.reflect.Type; 8 | 9 | /** 10 | * ReflectionStrategy for implementations that provide a getExactSuperType implementation 11 | * 12 | * @author Wouter Coekaerts 13 | */ 14 | public abstract class AbstractReflectionStrategy implements ReflectionStrategy { 15 | 16 | public final void testExactSuperclass(Type expectedSuperclass, Type type) { 17 | // test if the supertype of the given class is as expected 18 | assertEquals( 19 | expectedSuperclass, 20 | getExactSuperType(type, GenericTypeReflectorTest.getClassType(expectedSuperclass))); 21 | } 22 | 23 | public final void testInexactSupertype(Type superType, Type subType) { 24 | if (superType instanceof ParameterizedType || superType instanceof Class) { 25 | // test if it's not exact 26 | assertFalse( 27 | superType.equals( 28 | getExactSuperType(subType, GenericTypeReflectorTest.getClassType(superType)))); 29 | } 30 | } 31 | 32 | protected abstract Type getExactSuperType(Type type, Class searchClass); 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/Issue13Test.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import org.junit.Test; 9 | 10 | /** http://code.google.com/p/gentyref/issues/detail?id=13 */ 11 | public class Issue13Test { 12 | /** Test that Object is seen as superclass of any class, interface and array */ 13 | @Test 14 | public void testObjectSuperclassOfInterface() throws NoSuchMethodException { 15 | assertEquals( 16 | Object.class, GenericTypeReflector.getExactSuperType(ArrayList.class, Object.class)); 17 | assertEquals(Object.class, GenericTypeReflector.getExactSuperType(List.class, Object.class)); 18 | assertEquals( 19 | Object.class, GenericTypeReflector.getExactSuperType(String[].class, Object.class)); 20 | } 21 | 22 | /** Test that toString method can be resolved in any class, interface or array */ 23 | @Test 24 | public void testObjectMethodOnInterface() throws NoSuchMethodException { 25 | Method toString = Object.class.getMethod("toString"); 26 | assertEquals(String.class, GenericTypeReflector.getExactReturnType(toString, ArrayList.class)); 27 | assertEquals(String.class, GenericTypeReflector.getExactReturnType(toString, List.class)); 28 | assertEquals(String.class, GenericTypeReflector.getExactReturnType(toString, String[].class)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/coekie/gentyref/GenericArrayTypeImpl.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import java.lang.reflect.Array; 4 | import java.lang.reflect.GenericArrayType; 5 | import java.lang.reflect.Type; 6 | 7 | class GenericArrayTypeImpl implements GenericArrayType { 8 | private Type componentType; 9 | 10 | static Class createArrayType(Class componentType) { 11 | // there's no (clean) other way to create a array class, than creating an instance of it 12 | return Array.newInstance(componentType, 0).getClass(); 13 | } 14 | 15 | static Type createArrayType(Type componentType) { 16 | if (componentType instanceof Class) { 17 | return createArrayType((Class) componentType); 18 | } else { 19 | return new GenericArrayTypeImpl(componentType); 20 | } 21 | } 22 | 23 | private GenericArrayTypeImpl(Type componentType) { 24 | super(); 25 | this.componentType = componentType; 26 | } 27 | 28 | public Type getGenericComponentType() { 29 | return componentType; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object obj) { 34 | if (!(obj instanceof GenericArrayType)) return false; 35 | return componentType.equals(((GenericArrayType) obj).getGenericComponentType()); 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return componentType.hashCode() * 7; 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return componentType + "[]"; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/coekie/gentyref/TypeArgumentNotInBoundException.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import java.lang.reflect.Type; 4 | import java.lang.reflect.TypeVariable; 5 | 6 | /** 7 | * Thrown to indicate that a type argument for a parameterized type is not within the bound declared 8 | * on the type parameter. 9 | * 10 | * @author Wouter Coekaerts 11 | */ 12 | public class TypeArgumentNotInBoundException extends IllegalArgumentException { 13 | private final Type argument; 14 | private final TypeVariable parameter; 15 | private final Type bound; 16 | 17 | public TypeArgumentNotInBoundException(Type argument, TypeVariable parameter, Type bound) { 18 | super( 19 | "Given argument [" 20 | + argument 21 | + "]" 22 | + " for type parameter [" 23 | + parameter.getName() 24 | + "] is not within the bound [" 25 | + bound 26 | + "]"); 27 | this.argument = argument; 28 | this.parameter = parameter; 29 | this.bound = bound; 30 | } 31 | 32 | /** Returns the supplied argument that is not within the bound. */ 33 | public Type getArgument() { 34 | return argument; 35 | } 36 | 37 | /** Returns the type parameter. */ 38 | public TypeVariable getParameter() { 39 | return parameter; 40 | } 41 | 42 | /** 43 | * Returns the bound that was not satisfied. This is one of the members in 44 | * getParameter().getBounds(). 45 | */ 46 | public Type getBound() { 47 | return bound; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/coekie/gentyref/WildcardTypeImpl.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import java.lang.reflect.Type; 4 | import java.lang.reflect.WildcardType; 5 | import java.util.Arrays; 6 | 7 | class WildcardTypeImpl implements WildcardType { 8 | private final Type[] upperBounds; 9 | private final Type[] lowerBounds; 10 | 11 | public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) { 12 | if (upperBounds.length == 0) 13 | throw new IllegalArgumentException( 14 | "There must be at least one upper bound. For an unbound wildcard, the upper bound must" 15 | + " be Object"); 16 | this.upperBounds = upperBounds; 17 | this.lowerBounds = lowerBounds; 18 | } 19 | 20 | public Type[] getUpperBounds() { 21 | return upperBounds.clone(); 22 | } 23 | 24 | public Type[] getLowerBounds() { 25 | return lowerBounds.clone(); 26 | } 27 | 28 | @Override 29 | public boolean equals(Object obj) { 30 | if (!(obj instanceof WildcardType)) return false; 31 | WildcardType other = (WildcardType) obj; 32 | return Arrays.equals(lowerBounds, other.getLowerBounds()) 33 | && Arrays.equals(upperBounds, other.getUpperBounds()); 34 | } 35 | 36 | @Override 37 | public int hashCode() { 38 | return Arrays.hashCode(lowerBounds) ^ Arrays.hashCode(upperBounds); 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | if (lowerBounds.length > 0) { 44 | return "? super " + GenericTypeReflector.getTypeName(lowerBounds[0]); 45 | } else if (upperBounds[0] == Object.class) { 46 | return "?"; 47 | } else { 48 | return "? extends " + GenericTypeReflector.getTypeName(upperBounds[0]); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/coekie/gentyref/TypeToken.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import java.lang.reflect.ParameterizedType; 4 | import java.lang.reflect.Type; 5 | 6 | /** 7 | * Wrapper around {@link Type}. 8 | * 9 | *

You can use this to create instances of Type for a type known at compile time. 10 | * 11 | *

For example, to get the Type that represents List<String>: 12 | * Type listOfString = new TypeToken<List<String>>(){}.getType(); 13 | * 14 | * @author Wouter Coekaerts 15 | * @param The type represented by this TypeToken. 16 | */ 17 | public abstract class TypeToken { 18 | private final Type type; 19 | 20 | /** Constructs a type token. */ 21 | protected TypeToken() { 22 | this.type = extractType(); 23 | } 24 | 25 | private TypeToken(Type type) { 26 | this.type = type; 27 | } 28 | 29 | public Type getType() { 30 | return type; 31 | } 32 | 33 | private Type extractType() { 34 | Type t = getClass().getGenericSuperclass(); 35 | if (!(t instanceof ParameterizedType)) { 36 | throw new RuntimeException("Invalid TypeToken; must specify type parameters"); 37 | } 38 | ParameterizedType pt = (ParameterizedType) t; 39 | if (pt.getRawType() != TypeToken.class) { 40 | throw new RuntimeException("Invalid TypeToken; must directly extend TypeToken"); 41 | } 42 | return pt.getActualTypeArguments()[0]; 43 | } 44 | 45 | /** Gets type token for the given {@code Class} instance. */ 46 | public static TypeToken get(Class type) { 47 | return new TypeToken(type) {}; 48 | } 49 | 50 | /** Gets type token for the given {@code Type} instance. */ 51 | public static TypeToken get(Type type) { 52 | return new TypeToken(type) {}; 53 | } 54 | 55 | @Override 56 | public boolean equals(Object obj) { 57 | return (obj instanceof TypeToken) && type.equals(((TypeToken) obj).type); 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | return type.hashCode(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/AddWildcardParametersTest.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.util.Map; 6 | import org.junit.Test; 7 | 8 | public class AddWildcardParametersTest { 9 | @Test 10 | public void testNoParams() { 11 | assertEquals(String.class, GenericTypeReflector.addWildcardParameters(String.class)); 12 | } 13 | 14 | @Test 15 | public void testMap() { 16 | assertEquals( 17 | new TypeToken>() {}.getType(), 18 | GenericTypeReflector.addWildcardParameters(Map.class)); 19 | } 20 | 21 | @Test 22 | public void testInnerAndOuter() { 23 | class Outer { 24 | class Inner {} 25 | } 26 | assertEquals( 27 | new TypeToken.Inner>() {}.getType(), 28 | GenericTypeReflector.addWildcardParameters(Outer.Inner.class)); 29 | } 30 | 31 | @Test 32 | public void testInner() { 33 | class Outer { 34 | class Inner {} 35 | } 36 | assertEquals( 37 | new TypeToken>() {}.getType(), 38 | GenericTypeReflector.addWildcardParameters(Outer.Inner.class)); 39 | } 40 | 41 | @Test 42 | public void testOuter() { 43 | class Outer { 44 | class Inner {} 45 | } 46 | assertEquals( 47 | new TypeToken.Inner>() {}.getType(), 48 | GenericTypeReflector.addWildcardParameters(Outer.Inner.class)); 49 | } 50 | 51 | @Test 52 | public void testNoParamArray() { 53 | assertEquals(String[].class, GenericTypeReflector.addWildcardParameters(String[].class)); 54 | assertEquals(String[][].class, GenericTypeReflector.addWildcardParameters(String[][].class)); 55 | } 56 | 57 | @Test 58 | public void testGenericArray() { 59 | assertEquals( 60 | new TypeToken[]>() {}.getType(), 61 | GenericTypeReflector.addWildcardParameters(Map[].class)); 62 | assertEquals( 63 | new TypeToken[][]>() {}.getType(), 64 | GenericTypeReflector.addWildcardParameters(Map[][].class)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/factory/Issue16Test.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref.factory; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import com.coekie.gentyref.GenericTypeReflector; 6 | import com.coekie.gentyref.TypeFactory; 7 | import com.coekie.gentyref.TypeToken; 8 | import java.lang.reflect.Method; 9 | import java.lang.reflect.Type; 10 | import org.junit.Test; 11 | 12 | /** Test for http://code.google.com/p/gentyref/issues/detail?id=16 */ 13 | public class Issue16Test { 14 | public class GenericOuter { 15 | public class Inner { 16 | public T get() { 17 | return null; 18 | } 19 | } 20 | 21 | /** 22 | * This is the solution to the original problem in Issue 16, using the new {@link 23 | * TypeFactory#innerClass(Type, Class)} method. 24 | */ 25 | public Type getExactReturnType() throws NoSuchMethodException { 26 | final Type inner = TypeFactory.innerClass(this.getClass(), Inner.class); 27 | final Method get = Inner.class.getMethod("get"); 28 | return GenericTypeReflector.getExactReturnType(get, inner); 29 | } 30 | } 31 | 32 | public class StringOuter extends GenericOuter {} 33 | 34 | /** Simple test for our {@link GenericOuter#getExactReturnType()}. */ 35 | @Test 36 | public void test() throws NoSuchMethodException { 37 | assertEquals(String.class, new StringOuter().getExactReturnType()); 38 | } 39 | 40 | /** Just showing that you can do the same with a type token (if it is a constant) */ 41 | @Test 42 | public void testTypeToken() throws NoSuchMethodException { 43 | Type inner = new TypeToken() {}.getType(); 44 | final Method get = GenericOuter.Inner.class.getMethod("get"); 45 | assertEquals(String.class, GenericTypeReflector.getExactReturnType(get, inner)); 46 | } 47 | 48 | /** Testing our {@link GenericOuter#getExactReturnType()} with a raw type. */ 49 | @Test 50 | public void testRaw() throws NoSuchMethodException { 51 | assertEquals(Object.class, new GenericOuter().getExactReturnType()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/StackoverflowQ182872Test.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import static org.junit.Assert.assertTrue; 4 | 5 | import java.lang.reflect.Method; 6 | import java.lang.reflect.Type; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import org.junit.Test; 10 | 11 | /** 12 | * Sample in the form of a JUnit test, to show gentyref solves the problem on 13 | * http://stackoverflow.com/questions/182872/ 14 | * 15 | * @author Wouter Coekaerts 16 | */ 17 | @SuppressWarnings("unused") 18 | public class StackoverflowQ182872Test { 19 | private static Type LIST_OF_STRING = new TypeToken>() {}.getType(); 20 | 21 | public static class StringList extends ArrayList {} 22 | 23 | public List method1() { 24 | return null; 25 | } 26 | 27 | public ArrayList method2() { 28 | return null; 29 | } 30 | 31 | public StringList method3() { 32 | return null; 33 | } 34 | 35 | interface Parameterized { 36 | T method4(); 37 | } 38 | 39 | interface Parameterized2 { 40 | T method5(); 41 | } 42 | 43 | interface Parameterized2ListString extends Parameterized2> {} 44 | 45 | public boolean returnTypeExtendsListOfString(Class clazz, String methodName) 46 | throws NoSuchMethodException { 47 | Method method = clazz.getMethod(methodName); 48 | // needed for method5, else the return type is just "T" 49 | Type returnType = GenericTypeReflector.getExactReturnType(method, clazz); 50 | return GenericTypeReflector.isSuperType(LIST_OF_STRING, returnType); 51 | } 52 | 53 | @Test 54 | public void testIt() throws NoSuchMethodException { 55 | assertTrue(returnTypeExtendsListOfString(StackoverflowQ182872Test.class, "method1")); 56 | assertTrue(returnTypeExtendsListOfString(StackoverflowQ182872Test.class, "method2")); 57 | assertTrue(returnTypeExtendsListOfString(StackoverflowQ182872Test.class, "method3")); 58 | assertTrue(returnTypeExtendsListOfString(Parameterized.class, "method4")); 59 | assertTrue(returnTypeExtendsListOfString(Parameterized2ListString.class, "method5")); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/CaptureSamplesTest.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import java.lang.reflect.Type; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import org.junit.Test; 11 | 12 | /** 13 | * see http://code.google.com/p/gentyref/wiki/CaptureType 14 | * 15 | * @author Wouter Coekaerts 16 | */ 17 | public class CaptureSamplesTest { 18 | class Foo { 19 | List listWildcard; 20 | List listT; 21 | } 22 | 23 | @Test 24 | public void testFoo() throws NoSuchFieldException { 25 | Foo foo = new Foo(); 26 | foo.listWildcard = new ArrayList(); 27 | //foo.listT = new ArrayList(); // does not compile 28 | 29 | Type fooWildcard = new TypeToken>() {}.getType(); 30 | 31 | Type listWildcardFieldType = 32 | GenericTypeReflector.getExactFieldType( 33 | Foo.class.getDeclaredField("listWildcard"), fooWildcard); 34 | Type listTFieldType = 35 | GenericTypeReflector.getExactFieldType(Foo.class.getDeclaredField("listT"), fooWildcard); 36 | 37 | assertEquals(new TypeToken>() {}.getType(), listWildcardFieldType); 38 | assertTrue( 39 | GenericTypeReflector.isSuperType( 40 | listWildcardFieldType, new TypeToken>() {}.getType())); 41 | assertFalse( 42 | GenericTypeReflector.isSuperType( 43 | listTFieldType, new TypeToken>() {}.getType())); 44 | } 45 | 46 | class Bar { 47 | T t; 48 | } 49 | 50 | @Test 51 | @SuppressWarnings("unused") 52 | public void testBar() throws NoSuchFieldException { 53 | Bar bar = new Bar(); 54 | Number n = bar.t; 55 | 56 | Type barType = new TypeToken>() {}.getType(); 57 | Type captureType = 58 | GenericTypeReflector.getExactFieldType(Bar.class.getDeclaredField("t"), barType); 59 | assertTrue(GenericTypeReflector.isSuperType(Number.class, captureType)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/coekie/gentyref/CaptureTypeImpl.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import java.lang.reflect.Type; 4 | import java.lang.reflect.TypeVariable; 5 | import java.lang.reflect.WildcardType; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | class CaptureTypeImpl implements CaptureType { 11 | private final WildcardType wildcard; 12 | private final TypeVariable variable; 13 | private final Type[] lowerBounds; 14 | private Type[] upperBounds; 15 | 16 | /** 17 | * Creates an uninitialized CaptureTypeImpl. Before using this type, {@link #init(VarMap)} must be 18 | * called. 19 | * 20 | * @param wildcard The wildcard this is a capture of 21 | * @param variable The type variable where the wildcard is a parameter for. 22 | */ 23 | public CaptureTypeImpl(WildcardType wildcard, TypeVariable variable) { 24 | this.wildcard = wildcard; 25 | this.variable = variable; 26 | this.lowerBounds = wildcard.getLowerBounds(); 27 | } 28 | 29 | /** 30 | * Initialize this CaptureTypeImpl. This is needed for type variable bounds referring to each 31 | * other: we need the capture of the argument. 32 | */ 33 | void init(VarMap varMap) { 34 | ArrayList upperBoundsList = new ArrayList(); 35 | upperBoundsList.addAll(Arrays.asList(varMap.map(variable.getBounds()))); 36 | 37 | List wildcardUpperBounds = Arrays.asList(wildcard.getUpperBounds()); 38 | if (wildcardUpperBounds.size() > 0 && wildcardUpperBounds.get(0) == Object.class) { 39 | // skip the Object bound, we already have a first upper bound from 'variable' 40 | upperBoundsList.addAll(wildcardUpperBounds.subList(1, wildcardUpperBounds.size())); 41 | } else { 42 | upperBoundsList.addAll(wildcardUpperBounds); 43 | } 44 | upperBounds = new Type[upperBoundsList.size()]; 45 | upperBoundsList.toArray(upperBounds); 46 | } 47 | 48 | /** @see com.coekie.gentyref.CaptureType#getLowerBounds() */ 49 | public Type[] getLowerBounds() { 50 | return lowerBounds.clone(); 51 | } 52 | 53 | /** @see com.coekie.gentyref.CaptureType#getUpperBounds() */ 54 | public Type[] getUpperBounds() { 55 | assert upperBounds != null; 56 | return upperBounds.clone(); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "capture of " + wildcard; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/coekie/gentyref/ParameterizedTypeImpl.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import java.lang.reflect.ParameterizedType; 4 | import java.lang.reflect.Type; 5 | import java.util.Arrays; 6 | 7 | class ParameterizedTypeImpl implements ParameterizedType { 8 | private final Class rawType; 9 | private final Type[] actualTypeArguments; 10 | private final Type ownerType; 11 | 12 | public ParameterizedTypeImpl(Class rawType, Type[] actualTypeArguments, Type ownerType) { 13 | this.rawType = rawType; 14 | this.actualTypeArguments = actualTypeArguments; 15 | this.ownerType = ownerType; 16 | } 17 | 18 | public Type getRawType() { 19 | return rawType; 20 | } 21 | 22 | public Type[] getActualTypeArguments() { 23 | return actualTypeArguments; 24 | } 25 | 26 | public Type getOwnerType() { 27 | return ownerType; 28 | } 29 | 30 | @Override 31 | public boolean equals(Object obj) { 32 | if (!(obj instanceof ParameterizedType)) return false; 33 | 34 | ParameterizedType other = (ParameterizedType) obj; 35 | return rawType.equals(other.getRawType()) 36 | && Arrays.equals(actualTypeArguments, other.getActualTypeArguments()) 37 | && (ownerType == null 38 | ? other.getOwnerType() == null 39 | : ownerType.equals(other.getOwnerType())); 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | int result = rawType.hashCode() ^ Arrays.hashCode(actualTypeArguments); 45 | if (ownerType != null) result ^= ownerType.hashCode(); 46 | return result; 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | StringBuilder sb = new StringBuilder(); 52 | 53 | String clazz = rawType.getName(); 54 | 55 | if (ownerType != null) { 56 | sb.append(GenericTypeReflector.getTypeName(ownerType)).append('.'); 57 | 58 | String prefix = 59 | (ownerType instanceof ParameterizedType) 60 | ? ((Class) ((ParameterizedType) ownerType).getRawType()).getName() + '$' 61 | : ((Class) ownerType).getName() + '$'; 62 | if (clazz.startsWith(prefix)) clazz = clazz.substring(prefix.length()); 63 | } 64 | sb.append(clazz); 65 | 66 | if (actualTypeArguments.length != 0) { 67 | sb.append('<'); 68 | for (int i = 0; i < actualTypeArguments.length; i++) { 69 | Type arg = actualTypeArguments[i]; 70 | if (i != 0) sb.append(", "); 71 | sb.append(GenericTypeReflector.getTypeName(arg)); 72 | } 73 | sb.append('>'); 74 | } 75 | 76 | return sb.toString(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/Issue3Test.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.lang.reflect.Method; 7 | import java.lang.reflect.ParameterizedType; 8 | import java.lang.reflect.Type; 9 | import java.util.ArrayList; 10 | import java.util.Collection; 11 | import java.util.Collections; 12 | import java.util.List; 13 | import org.junit.Test; 14 | 15 | /** Test for http://code.google.com/p/gentyref/issues/detail?id=3 */ 16 | public class Issue3Test { 17 | public class NamedDataObject {} 18 | 19 | @SuppressWarnings("unused") 20 | public abstract class HierarchicalNamedDataObject< 21 | RealClass extends HierarchicalNamedDataObject> 22 | extends NamedDataObject { 23 | private RealClass parent; 24 | 25 | private List children = new ArrayList(); 26 | 27 | public List getChildren() { 28 | return children; 29 | } 30 | 31 | public RealClass getParent() { 32 | return parent; 33 | } 34 | } 35 | 36 | @Test 37 | public void testIt() throws NoSuchMethodException { 38 | // the return type for the raw type is a raw list 39 | Method getChildren = HierarchicalNamedDataObject.class.getMethod("getChildren"); 40 | assertEquals( 41 | List.class, 42 | GenericTypeReflector.getExactReturnType(getChildren, HierarchicalNamedDataObject.class)); 43 | 44 | // the return type for HierarchicalNamedDataObject (constructed using addWildcardParameters) 45 | // is a capture of a list whos element type is a HierarchicalNamedDataObject 46 | Type returnType = 47 | GenericTypeReflector.getExactReturnType( 48 | getChildren, 49 | GenericTypeReflector.addWildcardParameters(HierarchicalNamedDataObject.class)); 50 | 51 | Type listType = ((ParameterizedType) returnType).getActualTypeArguments()[0]; 52 | assertTrue(listType instanceof CaptureType); 53 | Type listElementType = ((CaptureType) listType).getUpperBounds()[0]; 54 | assertTrue(listElementType instanceof ParameterizedType); 55 | assertEquals( 56 | HierarchicalNamedDataObject.class, ((ParameterizedType) listElementType).getRawType()); 57 | 58 | // using getCollectionElementTypes, you can directly get the classes&interfaces contained in the 59 | // list 60 | assertEquals( 61 | Collections.>singletonList(HierarchicalNamedDataObject.class), 62 | getCollectionElementTypes(getChildren, HierarchicalNamedDataObject.class)); 63 | } 64 | 65 | private List> getCollectionElementTypes(Method getter, Class clazz) { 66 | return GenericTypeReflector.getUpperBoundClassAndInterfaces( 67 | GenericTypeReflector.getTypeParameter( 68 | GenericTypeReflector.getExactReturnType( 69 | getter, GenericTypeReflector.addWildcardParameters(clazz)), 70 | Collection.class.getTypeParameters()[0])); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/SamplesTest.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import java.lang.reflect.Method; 8 | import java.lang.reflect.ParameterizedType; 9 | import java.lang.reflect.Type; 10 | import java.lang.reflect.TypeVariable; 11 | import java.util.List; 12 | import org.junit.Test; 13 | 14 | /** 15 | * Simple samples of what gentyref does, in the form of tests. See 16 | * http://code.google.com/p/gentyref/wiki/ExampleUsage 17 | * 18 | * @author Wouter Coekaerts 19 | */ 20 | public class SamplesTest { 21 | interface Processor { 22 | void process(T t); 23 | } 24 | 25 | class StringProcessor implements Processor { 26 | public void process(String s) { 27 | System.out.println("processing " + s); 28 | } 29 | } 30 | 31 | class IntegerProcessor implements Processor { 32 | public void process(Integer i) { 33 | System.out.println("processing " + i); 34 | } 35 | } 36 | 37 | /* 38 | * Returns true if processorClass extends Processor 39 | */ 40 | public boolean isStringProcessor(Class> processorClass) { 41 | // Use TypeToken to get an instanceof a specific Type 42 | Type type = new TypeToken>() {}.getType(); 43 | // Use GenericTypeReflector.isSuperType to check if a type is a supertype of another 44 | return GenericTypeReflector.isSuperType(type, processorClass); 45 | } 46 | 47 | @Test 48 | public void testProsessor() { 49 | assertTrue(isStringProcessor(StringProcessor.class)); 50 | assertFalse(isStringProcessor(IntegerProcessor.class)); 51 | } 52 | 53 | abstract class Collector { 54 | public List list() { 55 | return null; 56 | } 57 | 58 | public void add(T item) {} 59 | } 60 | 61 | class StringCollector extends Collector {} 62 | 63 | @Test 64 | public void testCollectorList() throws NoSuchMethodException { 65 | Method listMethod = StringCollector.class.getMethod("list"); 66 | 67 | // java returns List 68 | Type returnType = listMethod.getGenericReturnType(); 69 | assertTrue(returnType instanceof ParameterizedType); 70 | assertTrue( 71 | ((ParameterizedType) returnType).getActualTypeArguments()[0] instanceof TypeVariable); 72 | 73 | // we get List 74 | Type exactReturnType = 75 | GenericTypeReflector.getExactReturnType(listMethod, StringCollector.class); 76 | assertEquals(new TypeToken>() {}.getType(), exactReturnType); 77 | } 78 | 79 | @Test 80 | public void testCollectorAdd() throws NoSuchMethodException { 81 | Method addMethod = StringCollector.class.getMethod("add", Object.class); 82 | 83 | // returns [T] 84 | Type[] parameterTypes = addMethod.getGenericParameterTypes(); 85 | assertEquals(1, parameterTypes.length); 86 | assertTrue(parameterTypes[0] instanceof TypeVariable); 87 | 88 | // returns [String] 89 | Type[] exactParameterTypes = 90 | GenericTypeReflector.getExactParameterTypes(addMethod, StringCollector.class); 91 | assertEquals(1, exactParameterTypes.length); 92 | assertEquals(String.class, exactParameterTypes[0]); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/GenericTypeReflectorTest.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.fail; 5 | 6 | import java.awt.Dimension; 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.Method; 9 | import java.lang.reflect.Type; 10 | import java.lang.reflect.TypeVariable; 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.Collection; 14 | import java.util.List; 15 | import org.junit.Test; 16 | 17 | /** 18 | * Test for reflection done in GenericTypeReflector. This class inherits most of its tests from the 19 | * superclass, and adds a few more. 20 | */ 21 | public class GenericTypeReflectorTest extends AbstractGenericsReflectorTest { 22 | public GenericTypeReflectorTest() { 23 | super(new GenTyRefReflectionStrategy()); 24 | } 25 | 26 | @Test 27 | public void testGetTypeParameter() { 28 | class StringList extends ArrayList {} 29 | assertEquals( 30 | String.class, 31 | GenericTypeReflector.getTypeParameter( 32 | StringList.class, Collection.class.getTypeParameters()[0])); 33 | } 34 | 35 | @Test 36 | public void testGetUpperBoundClassAndInterfaces() { 37 | class Foo, B extends A> {} 38 | TypeVariable a = Foo.class.getTypeParameters()[0]; 39 | TypeVariable b = Foo.class.getTypeParameters()[1]; 40 | assertEquals( 41 | Arrays.>asList(Number.class, Iterable.class), 42 | GenericTypeReflector.getUpperBoundClassAndInterfaces(a)); 43 | assertEquals( 44 | Arrays.>asList(Number.class, Iterable.class), 45 | GenericTypeReflector.getUpperBoundClassAndInterfaces(b)); 46 | } 47 | 48 | /** Call getExactReturnType with a method that is not a method of the given type. Issue #6 */ 49 | @Test 50 | public void testGetExactReturnTypeIllegalArgument() 51 | throws SecurityException, NoSuchMethodException { 52 | Method method = ArrayList.class.getMethod("set", int.class, Object.class); 53 | try { 54 | // ArrayList.set overrides List.set, but it's a different method so it's not a member of the List interface 55 | GenericTypeReflector.getExactReturnType(method, List.class); 56 | fail("expected exception"); 57 | } catch (IllegalArgumentException e) { // expected 58 | } 59 | } 60 | 61 | /** Same as {@link #testGetExactReturnTypeIllegalArgument()} for getExactFieldType */ 62 | @Test 63 | public void testGetExactFieldTypeIllegalArgument() 64 | throws SecurityException, NoSuchFieldException { 65 | Field field = Dimension.class.getField("width"); 66 | try { 67 | GenericTypeReflector.getExactFieldType(field, List.class); 68 | fail("expected exception"); 69 | } catch (IllegalArgumentException e) { // expected 70 | } 71 | } 72 | 73 | @Test 74 | public void testgetExactParameterTypes() throws SecurityException, NoSuchMethodException { 75 | // method: boolean add(int index, E o), erasure is boolean add(int index, Object o) 76 | Method getMethod = List.class.getMethod("add", int.class, Object.class); 77 | Type[] result = 78 | GenericTypeReflector.getExactParameterTypes( 79 | getMethod, new TypeToken>() {}.getType()); 80 | assertEquals(2, result.length); 81 | assertEquals(int.class, result[0]); 82 | assertEquals(String.class, result[1]); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/coekie/gentyref/VarMap.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import java.lang.reflect.GenericArrayType; 4 | import java.lang.reflect.ParameterizedType; 5 | import java.lang.reflect.Type; 6 | import java.lang.reflect.TypeVariable; 7 | import java.lang.reflect.WildcardType; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * Mapping between type variables and actual parameters. 13 | * 14 | * @author Wouter Coekaerts 15 | */ 16 | class VarMap { 17 | private final Map, Type> map = new HashMap, Type>(); 18 | 19 | /** Creates an empty VarMap */ 20 | VarMap() {} 21 | 22 | /** 23 | * Creates a VarMap mapping the type parameters of the class used in type to their actual 24 | * value. 25 | */ 26 | VarMap(ParameterizedType type) { 27 | // loop over the type and its generic owners 28 | do { 29 | Class clazz = (Class) type.getRawType(); 30 | Type[] arguments = type.getActualTypeArguments(); 31 | TypeVariable[] typeParameters = clazz.getTypeParameters(); 32 | 33 | // since we're looping over two arrays in parallel, just to be sure check they have the same 34 | // size 35 | if (arguments.length != typeParameters.length) { 36 | throw new IllegalStateException( 37 | "The given type [" 38 | + type 39 | + "] is inconsistent: it has " 40 | + arguments.length 41 | + " arguments instead of " 42 | + typeParameters.length); 43 | } 44 | 45 | for (int i = 0; i < arguments.length; i++) { 46 | add(typeParameters[i], arguments[i]); 47 | } 48 | 49 | Type owner = type.getOwnerType(); 50 | type = (owner instanceof ParameterizedType) ? (ParameterizedType) owner : null; 51 | } while (type != null); 52 | } 53 | 54 | void add(TypeVariable variable, Type value) { 55 | map.put(variable, value); 56 | } 57 | 58 | void addAll(TypeVariable[] variables, Type[] values) { 59 | assert variables.length == values.length; 60 | for (int i = 0; i < variables.length; i++) { 61 | map.put(variables[i], values[i]); 62 | } 63 | } 64 | 65 | VarMap(TypeVariable[] variables, Type[] values) { 66 | addAll(variables, values); 67 | } 68 | 69 | Type map(Type type) { 70 | if (type instanceof Class) { 71 | return type; 72 | } else if (type instanceof TypeVariable) { 73 | TypeVariable tv = (TypeVariable) type; 74 | if (!map.containsKey(type)) { 75 | throw new UnresolvedTypeVariableException(tv); 76 | } 77 | return map.get(type); 78 | } else if (type instanceof ParameterizedType) { 79 | ParameterizedType pType = (ParameterizedType) type; 80 | return new ParameterizedTypeImpl( 81 | (Class) pType.getRawType(), 82 | map(pType.getActualTypeArguments()), 83 | pType.getOwnerType() == null ? pType.getOwnerType() : map(pType.getOwnerType())); 84 | } else if (type instanceof WildcardType) { 85 | WildcardType wType = (WildcardType) type; 86 | return new WildcardTypeImpl(map(wType.getUpperBounds()), map(wType.getLowerBounds())); 87 | } else if (type instanceof GenericArrayType) { 88 | return GenericArrayTypeImpl.createArrayType( 89 | map(((GenericArrayType) type).getGenericComponentType())); 90 | } else { 91 | throw new RuntimeException("not implemented: mapping " + type.getClass() + " (" + type + ")"); 92 | } 93 | } 94 | 95 | Type[] map(Type[] types) { 96 | Type[] result = new Type[types.length]; 97 | for (int i = 0; i < types.length; i++) { 98 | result[i] = map(types[i]); 99 | } 100 | return result; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/TypeTest.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.lang.reflect.Type; 6 | import java.util.EnumSet; 7 | import java.util.List; 8 | import java.util.Map; 9 | import org.junit.Test; 10 | 11 | /** 12 | * Test the implementation of our types: 13 | * 14 | *
    15 | *
  • test that they're equal to the java implementation and vice-versa 16 | *
  • test toString 17 | *
18 | * 19 | * @author Wouter Coekaerts 20 | */ 21 | public class TypeTest { 22 | 23 | private void assertTypesEqual(TypeToken expectedToken, Type type, String toString) { 24 | Type expected = expectedToken.getType(); 25 | assertEquals(expected, type); 26 | assertEquals(type, expected); 27 | assertEquals(toString, type.toString()); 28 | // if (!toString.equals(expected.toString())) 29 | // System.err.println( 30 | // "WARN: jdk gives different toString for " + toString + ":\n " + expected.toString()); 31 | } 32 | 33 | @Test 34 | public void testParameterizedType() { 35 | assertTypesEqual( 36 | new TypeToken>() {}, 37 | new ParameterizedTypeImpl(List.class, new Type[] {String.class}, null), 38 | "java.util.List"); 39 | assertTypesEqual( 40 | new TypeToken, Integer>>() {}, 41 | new ParameterizedTypeImpl( 42 | Map.class, 43 | new Type[] { 44 | new ParameterizedTypeImpl(List.class, new Type[] {String.class}, null), Integer.class 45 | }, 46 | null), 47 | "java.util.Map, java.lang.Integer>"); 48 | } 49 | 50 | class InnerWithParam { 51 | class InnerInnerWithParam {} 52 | } 53 | 54 | @Test 55 | public void testNested() { 56 | assertTypesEqual( 57 | new TypeToken>() {}, 58 | new ParameterizedTypeImpl( 59 | TypeTest.InnerWithParam.class, new Type[] {String.class}, TypeTest.class), 60 | "com.coekie.gentyref.TypeTest.InnerWithParam"); 61 | assertTypesEqual( 62 | new TypeToken.InnerInnerWithParam>() {}, 63 | new ParameterizedTypeImpl( 64 | TypeTest.InnerWithParam.InnerInnerWithParam.class, 65 | new Type[] {Integer.class}, 66 | new ParameterizedTypeImpl( 67 | TypeTest.InnerWithParam.class, new Type[] {String.class}, TypeTest.class)), 68 | "com.coekie.gentyref.TypeTest.InnerWithParam.InnerInnerWithParam"); 69 | } 70 | 71 | @Test 72 | public void testUnboundWildcard() { 73 | assertTypesEqual( 74 | new TypeToken>() {}, 75 | new ParameterizedTypeImpl( 76 | List.class, 77 | new Type[] {new WildcardTypeImpl(new Type[] {Object.class}, new Type[] {})}, 78 | null), 79 | "java.util.List"); 80 | // upperbound on variable doesn't matter for the wildcard 81 | assertTypesEqual( 82 | new TypeToken>() {}, 83 | new ParameterizedTypeImpl( 84 | EnumSet.class, 85 | new Type[] {new WildcardTypeImpl(new Type[] {Object.class}, new Type[] {})}, 86 | null), 87 | "java.util.EnumSet"); 88 | } 89 | 90 | @Test 91 | public void testSuperWildcard() { 92 | assertTypesEqual( 93 | new TypeToken>() {}, 94 | new ParameterizedTypeImpl( 95 | List.class, 96 | new Type[] {new WildcardTypeImpl(new Type[] {Object.class}, new Type[] {Number.class})}, 97 | null), 98 | "java.util.List"); 99 | } 100 | 101 | @Test 102 | public void testExtendsWildcard() { 103 | assertTypesEqual( 104 | new TypeToken>() {}, 105 | new ParameterizedTypeImpl( 106 | List.class, 107 | new Type[] {new WildcardTypeImpl(new Type[] {Number.class}, new Type[] {})}, 108 | null), 109 | "java.util.List"); 110 | } 111 | 112 | @Test 113 | public void testGenericArray() { 114 | assertTypesEqual( 115 | new TypeToken[]>() {}, 116 | GenericArrayTypeImpl.createArrayType( 117 | new ParameterizedTypeImpl( 118 | List.class, 119 | new Type[] {new WildcardTypeImpl(new Type[] {Object.class}, new Type[] {})}, 120 | null)), 121 | "java.util.List[]"); 122 | } 123 | 124 | @Test 125 | public void testArray() { 126 | assertEquals("java.lang.String[]", GenericTypeReflector.getTypeName(String[].class)); 127 | } 128 | 129 | @Test 130 | public void testCapture() { 131 | // Note: there's no jdk counterpart for CaptureType 132 | assertEquals( 133 | "java.util.List", 134 | GenericTypeReflector.capture(new TypeToken>() {}.getType()).toString()); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.coekie.gentyref 4 | gentyref 5 | jar 6 | 1.3.1-SNAPSHOT 7 | 2008 8 | GenTyRef 9 | https://github.com/coekie/gentyref 10 | Generic type reflection library 11 | 12 | 13 | The Apache Software License, Version 2.0 14 | http://www.apache.org/licenses/LICENSE-2.0.txt 15 | repo 16 | 17 | 18 | 19 | scm:git:git://github.com/coekie/gentyref 20 | scm:git:git://github.com/coekie/gentyref 21 | https://github.com/coekie/gentyref 22 | HEAD 23 | 24 | 25 | 26 | sonatype-nexus-snapshots 27 | Sonatype Nexus Snapshots 28 | https://oss.sonatype.org/content/repositories/snapshots 29 | 30 | 31 | sonatype-nexus-staging 32 | Nexus Release Repository 33 | https://oss.sonatype.org/service/local/staging/deploy/maven2 34 | 35 | 36 | 37 | GitHub Issue Tracking 38 | https://github.com/coekie/gentyref/issues 39 | 40 | 41 | 42 | UTF-8 43 | 44 | 45 | 46 | 47 | junit 48 | junit 49 | 4.12 50 | test 51 | 52 | 53 | 54 | 3.0 55 | 56 | 57 | package 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-compiler-plugin 62 | 3.6.1 63 | 64 | 1.5 65 | 1.5 66 | 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-jar-plugin 71 | 3.0.2 72 | 73 | 74 | package 75 | 76 | test-jar 77 | 78 | 79 | 80 | 81 | 82 | ${project.build.outputDirectory}/META-INF/MANIFEST.MF 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-source-plugin 89 | 3.0.0 90 | 91 | 92 | attach-sources 93 | verify 94 | 95 | jar 96 | 97 | 98 | 99 | 100 | 101 | org.apache.maven.plugins 102 | maven-javadoc-plugin 103 | 2.10.4 104 | 105 | 106 | attach-javadocs 107 | 108 | jar 109 | 110 | 111 | 112 | 113 | 114 | http://download.oracle.com/javase/1.5.0/docs/api/ 115 | 116 | true 117 | public 118 | 119 | 120 | 121 | biz.aQute.bnd 122 | bnd-maven-plugin 123 | 3.3.0 124 | 125 | 126 | 127 | bnd-process 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | release 137 | 138 | 139 | 140 | org.apache.maven.plugins 141 | maven-release-plugin 142 | 2.5.3 143 | 144 | false 145 | v@{project.version} 146 | 147 | 148 | 149 | org.apache.maven.plugins 150 | maven-gpg-plugin 151 | 1.6 152 | 153 | 154 | sign-artifacts 155 | verify 156 | 157 | sign 158 | 159 | 160 | 161 | 162 | true 163 | 164 | 165 | 166 | org.sonatype.plugins 167 | nexus-staging-maven-plugin 168 | 1.6.8 169 | true 170 | 171 | sonatype-nexus-staging 172 | https://oss.sonatype.org/ 173 | false 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | Wouter Coekaerts 183 | wouter@coekaerts.be 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/GenericMethodTest.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import static org.junit.Assert.fail; 6 | 7 | import java.lang.reflect.Method; 8 | import java.lang.reflect.Type; 9 | import java.lang.reflect.TypeVariable; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.Collection; 13 | import java.util.List; 14 | import org.junit.Test; 15 | 16 | public class GenericMethodTest { 17 | 18 | /** 19 | * Test if UnresolvedTypeVariableException is thrown if there is an unresolved type parameter 20 | * coming from a simple generic method in a parameter. 21 | */ 22 | @Test 23 | public void testSimpleGenericMethodUnresolvedParameter() 24 | throws SecurityException, NoSuchMethodException { 25 | class C { 26 | @SuppressWarnings("unused") 27 | public String m(List l) { 28 | return null; 29 | } 30 | } 31 | Method m = C.class.getMethod("m", List.class); 32 | 33 | // getting the return type should succeed, because it doesn't contain the type variable 34 | assertEquals(String.class, GenericTypeReflector.getExactReturnType(m, C.class)); 35 | 36 | try { 37 | GenericTypeReflector.getExactParameterTypes(m, C.class); 38 | fail("expected UnresolvedTypeVariableException"); 39 | } catch (UnresolvedTypeVariableException e) { 40 | assertEquals(m.getTypeParameters()[0], e.getTypeVariable()); 41 | } 42 | } 43 | 44 | /** 45 | * Test if UnresolvedTypeVariableException is thrown if there is an unresolved type parameter 46 | * coming from a simple generic method in a return type 47 | */ 48 | @Test 49 | public void testSimpleGenericMethodUnresolvedReturnType() 50 | throws SecurityException, NoSuchMethodException { 51 | class C { 52 | @SuppressWarnings("unused") 53 | public List m(String p) { 54 | return null; 55 | } 56 | } 57 | 58 | Method m = C.class.getMethod("m", String.class); 59 | 60 | // getting the parameters should succeed, because it doesn't contain the type variable 61 | assertTrue( 62 | Arrays.equals( 63 | new Type[] {String.class}, GenericTypeReflector.getExactParameterTypes(m, C.class))); 64 | 65 | try { 66 | GenericTypeReflector.getExactReturnType(m, C.class); 67 | fail("expected UnresolvedTypeVariableException"); 68 | } catch (UnresolvedTypeVariableException e) { 69 | assertEquals(m.getTypeParameters()[0], e.getTypeVariable()); 70 | } 71 | } 72 | 73 | /** 74 | * Test if UnresolvedTypeVariableException is thrown if there is an unresolved type parameter 75 | * coming from a simple generic method, deep in the return type, nested in a generic array and 76 | * wildcards 77 | */ 78 | @Test 79 | public void testSimpleGenericMethodDeepUnresolvedReturnType() 80 | throws SecurityException, NoSuchMethodException { 81 | class C { 82 | @SuppressWarnings("unused") 83 | public List>[] m(String p) { 84 | return null; 85 | } 86 | } 87 | 88 | Method m = C.class.getMethod("m", String.class); 89 | 90 | try { 91 | GenericTypeReflector.getExactReturnType(m, C.class); 92 | fail("expected UnresolvedTypeVariableException"); 93 | } catch (UnresolvedTypeVariableException e) { 94 | assertEquals(m.getTypeParameters()[0], e.getTypeVariable()); 95 | } 96 | } 97 | 98 | /** 99 | * Test expected UnresolvedTypeVariableException for getExactReturnType and getExactParameterTypes 100 | * on Arrays.asList 101 | */ 102 | @Test 103 | public void testArraysAsListUnresolved() throws SecurityException, NoSuchMethodException { 104 | Method asList = Arrays.class.getMethod("asList", Object[].class); 105 | try { 106 | GenericTypeReflector.getExactReturnType(asList, Arrays.class); 107 | fail("expected UnresolvedTypeVariableException"); 108 | } catch (UnresolvedTypeVariableException e) { 109 | assertEquals(asList.getTypeParameters()[0], e.getTypeVariable()); 110 | } 111 | try { 112 | GenericTypeReflector.getExactParameterTypes(asList, Arrays.class); 113 | fail("expected UnresolvedTypeVariableException"); 114 | } catch (UnresolvedTypeVariableException e) { 115 | assertEquals(asList.getTypeParameters()[0], e.getTypeVariable()); 116 | } 117 | } 118 | 119 | /** 120 | * Returns a class nested in this method that contains a method "m" who's return type is this 121 | * method's type variable E. 122 | */ 123 | public Class returnsTypeVariableFromOuterMethod() 124 | throws SecurityException, NoSuchMethodException { 125 | class C { 126 | @SuppressWarnings("unused") 127 | public E m() { 128 | return null; 129 | } 130 | } 131 | return C.class; 132 | } 133 | 134 | @Test 135 | public void testReturnsTypeVariableFromOuterMethod() 136 | throws SecurityException, NoSuchMethodException { 137 | Class c = returnsTypeVariableFromOuterMethod(); 138 | Method m = c.getMethod("m"); 139 | TypeVariable e = 140 | GenericMethodTest.class.getMethod("returnsTypeVariableFromOuterMethod") 141 | .getTypeParameters()[0]; 142 | 143 | try { 144 | GenericTypeReflector.getExactReturnType(m, c); 145 | fail("expected UnresolvedTypeVariableException"); 146 | } catch (UnresolvedTypeVariableException ex) { 147 | assertEquals(e, ex.getTypeVariable()); 148 | } 149 | } 150 | 151 | public Class extendsWithTypeVariableFromOuterMethod() 152 | throws SecurityException, NoSuchMethodException { 153 | class C extends ArrayList {} 154 | return C.class; 155 | } 156 | 157 | @Test 158 | public void testExtendsWithTypeVariableFromOuterMethod() 159 | throws SecurityException, NoSuchMethodException { 160 | Class c = extendsWithTypeVariableFromOuterMethod(); 161 | TypeVariable e = 162 | GenericMethodTest.class.getMethod("extendsWithTypeVariableFromOuterMethod") 163 | .getTypeParameters()[0]; 164 | 165 | try { 166 | GenericTypeReflector.getExactSuperType(c, List.class); 167 | fail("expected UnresolvedTypeVariableException"); 168 | } catch (UnresolvedTypeVariableException ex) { 169 | assertEquals(e, ex.getTypeVariable()); 170 | } 171 | } 172 | 173 | public Type typeVariableFromMethod() { 174 | return new TypeToken>() {}.getType(); 175 | } 176 | 177 | /** 178 | * Test when a type containing a type variable from an unrelated method is given. This is 179 | * different from the other tests in that the type variable from the method is not introduced by 180 | * navigating types, but is directly given by the gentyref API user. 181 | */ 182 | @Test 183 | public void testTypeVariableFromMethodGiven() throws SecurityException, NoSuchMethodException { 184 | Type listE = typeVariableFromMethod(); 185 | TypeVariable e = 186 | GenericMethodTest.class.getMethod("typeVariableFromMethod").getTypeParameters()[0]; 187 | 188 | Type result = GenericTypeReflector.getExactSuperType(listE, Collection.class); 189 | 190 | Type collectionE = new ParameterizedTypeImpl(Collection.class, new Type[] {e}, null); 191 | assertEquals(collectionE, result); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/main/java/com/coekie/gentyref/TypeFactory.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import java.lang.reflect.GenericArrayType; 4 | import java.lang.reflect.Modifier; 5 | import java.lang.reflect.ParameterizedType; 6 | import java.lang.reflect.Type; 7 | import java.lang.reflect.TypeVariable; 8 | import java.lang.reflect.WildcardType; 9 | 10 | /** 11 | * Utility class for creating instances of {@link Type}. These types can be used with the {@link 12 | * GenericTypeReflector} or anything else handling Java types. 13 | * 14 | * @author Wouter Coekaerts 15 | */ 16 | public class TypeFactory { 17 | private static final WildcardType UNBOUND_WILDCARD = 18 | new WildcardTypeImpl(new Type[] {Object.class}, new Type[] {}); 19 | 20 | /** 21 | * Creates a type of class clazz with arguments as type arguments. 22 | * 23 | *

For example: parameterizedClass(Map.class, Integer.class, String.class) returns the 24 | * type Map<Integer, String>. 25 | * 26 | * @param clazz Type class of the type to create 27 | * @param arguments Type arguments for the variables of clazz, or null if these are not 28 | * known. 29 | * @return A {@link ParameterizedType}, or simply clazz if arguments is 30 | * null or empty. 31 | */ 32 | public static Type parameterizedClass(Class clazz, Type... arguments) { 33 | return parameterizedInnerClass(null, clazz, arguments); 34 | } 35 | 36 | /** 37 | * Creates a type of clazz nested in owner. 38 | * 39 | * @param owner The owner type. This should be a subtype of clazz.getDeclaringClass(), or 40 | * null if no owner is known. 41 | * @param clazz Type class of the type to create 42 | * @return A {@link ParameterizedType} if the class declaring clazz is generic and its 43 | * type parameters are known in owner and clazz itself has no type 44 | * parameters. Otherwise, just returns clazz. 45 | */ 46 | public static Type innerClass(Type owner, Class clazz) { 47 | return parameterizedInnerClass(owner, clazz, (Type[]) null); 48 | } 49 | 50 | /** 51 | * Creates a type of clazz with arguments as type arguments, nested in 52 | * owner. 53 | * 54 | *

In the ideal case, this returns a {@link ParameterizedType} with all generic information in 55 | * it. If some type arguments are missing or if the resulting type simply doesn't need any type 56 | * parameters, it returns the raw clazz. Note that types with some parameters specified 57 | * and others not, don't exist in Java. 58 | * 59 | *

If the caller does not know the exact owner type or arguments, 60 | * null should be given (or {@link #parameterizedClass(Class, Type...)} or {@link 61 | * #innerClass(Type, Class)} could be used). If they are not needed (non-generic owner and/or 62 | * clazz has no type parameters), they will be filled in automatically. If they are 63 | * needed but are not given, the raw clazz is returned. 64 | * 65 | *

The specified owner may be any subtype of clazz.getDeclaringClass(). It is 66 | * automatically converted into the right parameterized version of the declaring class. If 67 | * clazz is a static (nested) class, the owner is not used. 68 | * 69 | * @param owner The owner type. This should be a subtype of clazz.getDeclaringClass(), or 70 | * null if no owner is known. 71 | * @param clazz Type class of the type to create 72 | * @param arguments Type arguments for the variables of clazz, or null if these are not 73 | * known. 74 | * @return A {@link ParameterizedType} if clazz or the class declaring clazz is 75 | * generic, and all the needed type arguments are specified in owner and 76 | * arguments. Otherwise, just returns clazz. 77 | * @throws IllegalArgumentException if arguments (is non-null and) has an incorrect 78 | * length, or if one of the arguments is not within the bounds declared on the 79 | * matching type variable, or if owner is non-null but clazz has no declaring class 80 | * (e.g. is a top-level class), or if owner is not a a subtype of 81 | * clazz.getDeclaringClass(). 82 | * @throws NullPointerException if clazz or one of the elements in arguments is 83 | * null. 84 | */ 85 | public static Type parameterizedInnerClass(Type owner, Class clazz, Type... arguments) { 86 | // never allow an owner on a class that doesn't have one 87 | if (clazz.getDeclaringClass() == null && owner != null) { 88 | throw new IllegalArgumentException("Cannot specify an owner type for a top level class"); 89 | } 90 | 91 | Type realOwner = transformOwner(owner, clazz); 92 | 93 | if (arguments == null) { 94 | if (clazz.getTypeParameters().length == 0) { 95 | // no arguments known, but no needed so just use an empty argument list. 96 | // (we can still end up with a generic type if the owner is generic) 97 | arguments = new Type[0]; 98 | } else { 99 | // missing type arguments, return the raw type 100 | return clazz; 101 | } 102 | } else { 103 | if (arguments.length != clazz.getTypeParameters().length) { 104 | throw new IllegalArgumentException( 105 | "Incorrect number of type arguments for [" 106 | + clazz 107 | + "]: " 108 | + "expected " 109 | + clazz.getTypeParameters().length 110 | + ", but got " 111 | + arguments.length); 112 | } 113 | } 114 | 115 | // if the class and its owner simply have no parameters at all, this is not a parameterized type 116 | if (!GenericTypeReflector.isMissingTypeParameters(clazz)) { 117 | return clazz; 118 | } 119 | 120 | // if the owner type is missing type parameters and clazz is non-static, this is a raw type 121 | if (realOwner != null 122 | && !Modifier.isStatic(clazz.getModifiers()) 123 | && GenericTypeReflector.isMissingTypeParameters(realOwner)) { 124 | return clazz; 125 | } 126 | 127 | ParameterizedType result = new ParameterizedTypeImpl(clazz, arguments, realOwner); 128 | checkParametersWithinBound(result); 129 | return result; 130 | } 131 | 132 | /** 133 | * Check if the type arguments of the given type are within the bounds declared on the type 134 | * parameters. Only the type arguments of the type itself are checked, the possible owner type is 135 | * assumed to be valid. 136 | * 137 | *

For example wildcardExtends(String.class) returns the type ? extends 251 | * String. 252 | * 253 | * @param upperBound Upper bound of the wildcard 254 | * @return A wildcard type 255 | */ 256 | public static WildcardType wildcardExtends(Type upperBound) { 257 | if (upperBound == null) { 258 | throw new NullPointerException(); 259 | } 260 | return new WildcardTypeImpl(new Type[] {upperBound}, new Type[] {}); 261 | } 262 | 263 | /** 264 | * Creates a wildcard type with a lower bound. 265 | * 266 | *

For example wildcardSuper(String.class) returns the type ? super String. 267 | * 268 | * @param lowerBound Lower bound of the wildcard 269 | * @return A wildcard type 270 | */ 271 | public static WildcardType wildcardSuper(Type lowerBound) { 272 | if (lowerBound == null) { 273 | throw new NullPointerException(); 274 | } 275 | return new WildcardTypeImpl(new Type[] {Object.class}, new Type[] {lowerBound}); 276 | } 277 | 278 | /** 279 | * Creates a array type. 280 | * 281 | *

If componentType is not a generic type but a {@link Class} object, this returns the 282 | * {@link Class} representing the non-generic array type. Otherwise, returns a {@link 283 | * GenericArrayType}. 284 | * 285 | *

For example: 286 | * 287 | *

    288 | *
  • arrayOf(String.class) returns String[].class 289 | *
  • arrayOf(parameterizedClass(List.class, String.class)) returns the {@link 290 | * GenericArrayType} for List<String>[] 291 | *
292 | * 293 | * @param componentType The type of the components of the array. 294 | * @return An array type. 295 | */ 296 | public static Type arrayOf(Type componentType) { 297 | return GenericArrayTypeImpl.createArrayType(componentType); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/factory/TypeFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref.factory; 2 | 3 | import static com.coekie.gentyref.TypeFactory.innerClass; 4 | import static com.coekie.gentyref.TypeFactory.parameterizedClass; 5 | import static com.coekie.gentyref.TypeFactory.parameterizedInnerClass; 6 | import static com.coekie.gentyref.TypeFactory.unboundWildcard; 7 | import static com.coekie.gentyref.TypeFactory.wildcardExtends; 8 | import static com.coekie.gentyref.TypeFactory.wildcardSuper; 9 | import static org.junit.Assert.assertEquals; 10 | import static org.junit.Assert.assertSame; 11 | import static org.junit.Assert.assertTrue; 12 | import static org.junit.Assert.fail; 13 | 14 | import com.coekie.gentyref.TypeArgumentNotInBoundException; 15 | import com.coekie.gentyref.TypeFactory; 16 | import com.coekie.gentyref.TypeToken; 17 | import com.coekie.gentyref.factory.GenericOuter.DoubleGeneric; 18 | import com.coekie.gentyref.factory.GenericOuter.Inner; 19 | import java.lang.reflect.ParameterizedType; 20 | import java.lang.reflect.Type; 21 | import java.util.ArrayList; 22 | import java.util.Collection; 23 | import java.util.List; 24 | import org.junit.Ignore; 25 | import org.junit.Test; 26 | 27 | public class TypeFactoryTest { 28 | private static final Type GENERICOUTER_STRING = 29 | new TypeToken>() {}.getType(); 30 | 31 | /** If there are no type parameters, it's just a Class */ 32 | @Test 33 | public void testSimpleClass() { 34 | assertEquals(String.class, parameterizedClass(String.class, (Type[]) null)); 35 | } 36 | 37 | /** Also for an inner class: if there are no type parameters it's just a Class */ 38 | @Test 39 | public void testSimpleInner() { 40 | assertEquals( 41 | SimpleOuter.SimpleInner.class, 42 | innerClass(SimpleOuter.class, SimpleOuter.SimpleInner.class)); 43 | } 44 | 45 | @Test 46 | public void testSimpleGeneric() { 47 | assertEquals( 48 | new TypeToken>() {}.getType(), parameterizedClass(List.class, String.class)); 49 | } 50 | 51 | /** If the given parameters are null, it's a raw type */ 52 | @Test 53 | public void testSimpleRaw() { 54 | assertEquals(List.class, parameterizedClass(List.class, (Type[]) null)); 55 | } 56 | 57 | /** 58 | * An empty array as arguments is not the same as null: it means the caller explicitly expects the 59 | * class not to need type arguments. So we throw an exception if they were needed. 60 | */ 61 | @Test 62 | public void testEmptyArgumentsForGenericClass() { 63 | try { 64 | parameterizedClass(List.class, new Type[] {}); 65 | fail("expected exception"); 66 | } catch (IllegalArgumentException expected) { 67 | } 68 | } 69 | 70 | @Test 71 | public void testTooManyTypeArguments() { 72 | try { 73 | parameterizedClass(List.class, new Type[] {String.class, String.class}); 74 | fail("expected exception"); 75 | } catch (IllegalArgumentException expected) { 76 | } 77 | } 78 | 79 | @Test 80 | public void testGenericOwner() { 81 | assertEquals( 82 | new TypeToken.Inner>() {}.getType(), 83 | innerClass(GENERICOUTER_STRING, Inner.class)); 84 | } 85 | 86 | @Test 87 | public void testDoubleGeneric() { 88 | assertEquals( 89 | new TypeToken.DoubleGeneric>() {}.getType(), 90 | parameterizedInnerClass(GENERICOUTER_STRING, DoubleGeneric.class, Integer.class)); 91 | } 92 | 93 | /** If the owner is raw, the whole type is raw (test for a non-generic inner class) */ 94 | @Test 95 | public void testRawGenericOwner() { 96 | assertEquals(Inner.class, innerClass(GenericOuter.class, Inner.class)); 97 | } 98 | 99 | /** If the owner is raw, the whole type is raw (test also for a generic inner class) */ 100 | @Test 101 | public void testDoubleGenericRawOwner() { 102 | assertEquals( 103 | DoubleGeneric.class, 104 | parameterizedInnerClass(GenericOuter.class, DoubleGeneric.class, Integer.class)); 105 | } 106 | 107 | /** If the inner class is raw, the whole type is raw */ 108 | @Test 109 | public void testDoubleGenericRawInner() { 110 | assertEquals(DoubleGeneric.class, innerClass(GENERICOUTER_STRING, DoubleGeneric.class)); 111 | } 112 | 113 | /** If the outer class is not specified, take it as a raw one */ 114 | @Test 115 | public void testDoubleGenericMissingOwner() { 116 | assertEquals(DoubleGeneric.class, parameterizedClass(DoubleGeneric.class, Integer.class)); 117 | } 118 | 119 | @Test 120 | public void testOwnerForTopLevel() { 121 | try { 122 | innerClass(String.class, Integer.class); 123 | fail("expected exception"); 124 | } catch (IllegalArgumentException expected) { 125 | } 126 | } 127 | 128 | /** 129 | * An owner type that is a subtype of the enclosing class is converted into the right 130 | * parameterized version of that enclosing class. 131 | */ 132 | @Test 133 | public void testConcreteOuter() { 134 | assertEquals( 135 | new TypeToken.Inner>() {}.getType(), 136 | innerClass(StringOuter.class, Inner.class)); 137 | 138 | // sanity check: the compiler does the same 139 | assertEquals( 140 | new TypeToken.Inner>() {}.getType(), 141 | new TypeToken() {}.getType()); 142 | } 143 | 144 | @SuppressWarnings("rawtypes") 145 | @Test 146 | public void testConcreteRawOuter() { 147 | assertEquals(Inner.class, innerClass(RawOuter.class, Inner.class)); 148 | 149 | // sanity check: the compiler does the same 150 | assertEquals(Inner.class, new TypeToken() {}.getType()); 151 | } 152 | 153 | @Test 154 | public void testConcreteRawOuterGenericInner() { 155 | assertEquals( 156 | DoubleGeneric.class, 157 | parameterizedInnerClass(RawOuter.class, DoubleGeneric.class, String.class)); 158 | 159 | // TODO what does the GenericTypeReflector.getExactReturnType do for a method in RawOuter that 160 | // returns DoubleGeneric? 161 | } 162 | 163 | @Test 164 | public void testSimpleOuterGenericInner() { 165 | assertEquals( 166 | new TypeToken>() {}.getType(), 167 | parameterizedInnerClass(SimpleOuter.class, SimpleOuter.GenericInner.class, String.class)); 168 | } 169 | 170 | @Test 171 | public void testSimpleOuterRawInner() { 172 | assertEquals( 173 | SimpleOuter.GenericInner.class, 174 | innerClass(SimpleOuter.class, SimpleOuter.GenericInner.class)); 175 | } 176 | 177 | /** If the outer class is not specified, it doesn't matter if it's not generic anyways */ 178 | @Test 179 | public void testMissingSimpleOuterGenericInner() { 180 | assertEquals( 181 | new TypeToken>() {}.getType(), 182 | parameterizedClass(SimpleOuter.GenericInner.class, String.class)); 183 | } 184 | 185 | @Test 186 | public void testMissingSimpleOuterRawInner() { 187 | assertEquals( 188 | SimpleOuter.GenericInner.class, 189 | parameterizedClass(SimpleOuter.GenericInner.class, (Type[]) null)); 190 | } 191 | 192 | @Test 193 | public void testWrongOwnerSimple() { 194 | try { 195 | innerClass(String.class, SimpleOuter.SimpleInner.class); 196 | fail("expected exception"); 197 | } catch (IllegalArgumentException expected) { 198 | } 199 | } 200 | 201 | @Test 202 | public void testWrongOwnerGeneric() { 203 | try { 204 | parameterizedInnerClass(String.class, SimpleOuter.GenericInner.class, String.class); 205 | fail("expected exception"); 206 | } catch (IllegalArgumentException expected) { 207 | } 208 | } 209 | 210 | @Test 211 | public void testWrongOwnerRaw() { 212 | try { 213 | innerClass(String.class, SimpleOuter.GenericInner.class); 214 | fail("expected exception"); 215 | } catch (IllegalArgumentException expected) { 216 | } 217 | } 218 | 219 | @Test 220 | public void testStaticInnerWithoutOwner() { 221 | Type result = parameterizedClass(GenericOuter.StaticGenericInner.class, Integer.class); 222 | 223 | assertEquals(new TypeToken>() {}.getType(), result); 224 | 225 | // sanity check: even static inner classes' ParameterizedTypes refer to the owner 226 | assertEquals( 227 | GenericOuter.class, 228 | ((ParameterizedType) new TypeToken>() {}.getType()) 229 | .getOwnerType()); 230 | 231 | // so our ParameterizedType should do the same 232 | assertEquals(GenericOuter.class, ((ParameterizedType) result).getOwnerType()); 233 | } 234 | 235 | @Test 236 | public void testStaticInnerWithRawOwner() { 237 | assertEquals( 238 | new TypeToken>() {}.getType(), 239 | parameterizedInnerClass( 240 | GenericOuter.class, GenericOuter.StaticGenericInner.class, Integer.class)); 241 | } 242 | 243 | @Test 244 | public void testStaticInnerWithRawSubclassOwner() { 245 | assertEquals( 246 | new TypeToken>() {}.getType(), 247 | parameterizedInnerClass( 248 | RawOuter.class, GenericOuter.StaticGenericInner.class, Integer.class)); 249 | 250 | // sanity check: the compiler does the same 251 | assertEquals( 252 | new TypeToken>() {}.getType(), 253 | new TypeToken>() {}.getType()); 254 | } 255 | 256 | /** If the owner is given as a generic type, just ignore the type arguments */ 257 | @Test 258 | public void testStaticInnerWithGenericOwner() { 259 | Type result = 260 | parameterizedInnerClass( 261 | GENERICOUTER_STRING, GenericOuter.StaticGenericInner.class, Integer.class); 262 | 263 | assertEquals(new TypeToken>() {}.getType(), result); 264 | assertEquals(GenericOuter.class, ((ParameterizedType) result).getOwnerType()); 265 | } 266 | 267 | @Test 268 | public void testStaticInnerWithWrongOwner() { 269 | try { 270 | parameterizedInnerClass(String.class, GenericOuter.StaticGenericInner.class, Integer.class); 271 | fail("expected exception"); 272 | } catch (IllegalArgumentException expected) { 273 | } 274 | } 275 | 276 | // TODO what if the specified owner type is a wildcard, a capture, a (generic) array or a type 277 | // variable,... 278 | // The use of "getExactSuperType" should make it smart in handling those... 279 | 280 | @Test 281 | public void testNullTypeArgument() { 282 | try { 283 | parameterizedClass(List.class, new Type[] {null}); 284 | fail("expected exception"); 285 | } catch (NullPointerException expected) { 286 | } 287 | } 288 | 289 | static class Bound {} 290 | 291 | @Test 292 | public void testTypeArgumentInBound() { 293 | assertEquals( 294 | new TypeToken>() {}.getType(), 295 | parameterizedClass(Bound.class, Integer.class)); 296 | } 297 | 298 | @Test 299 | public void testTypeArgumentsNotInBound() { 300 | try { 301 | parameterizedClass(Bound.class, String.class); 302 | fail("expected exception"); 303 | } catch (IllegalArgumentException expected) { 304 | } 305 | } 306 | 307 | static class ReferingBound, B> {} 308 | 309 | @Test 310 | public void testTypeArgumentInReferingBound() { 311 | assertEquals( 312 | new TypeToken, Integer>>() {}.getType(), 313 | parameterizedClass( 314 | ReferingBound.class, parameterizedClass(List.class, Integer.class), Integer.class)); 315 | } 316 | 317 | @Test 318 | public void testTypeArgumentsNotInReferingBound() { 319 | try { 320 | parameterizedClass( 321 | ReferingBound.class, parameterizedClass(List.class, Integer.class), Number.class); 322 | fail("expected exception"); 323 | } catch (IllegalArgumentException expected) { 324 | } 325 | } 326 | 327 | static class RecursiveBound> {} 328 | 329 | static class InRecursiveBound extends RecursiveBound {} 330 | 331 | @Test 332 | public void testTypeArgumentInRecursiveBound() { 333 | assertEquals( 334 | new TypeToken>() {}.getType(), 335 | parameterizedClass(RecursiveBound.class, InRecursiveBound.class)); 336 | } 337 | 338 | static class NotInRecursiveBound extends RecursiveBound {} 339 | 340 | @Test 341 | public void testTypeArgumentNotInRecursiveBound() { 342 | // type RecursiveBound is not valid 343 | try { 344 | parameterizedClass(RecursiveBound.class, NotInRecursiveBound.class); 345 | fail("expected exception"); 346 | } catch (IllegalArgumentException expected) { 347 | } 348 | } 349 | 350 | @SuppressWarnings("rawtypes") 351 | static class RawBound {} 352 | 353 | @Test 354 | public void testTypeArgumentInRawBound() { 355 | assertEquals( 356 | new TypeToken>>() {}.getType(), 357 | parameterizedClass(RawBound.class, parameterizedClass(List.class, String.class))); 358 | } 359 | 360 | @Test 361 | public void testTypeArgumentNotInRawBound() { 362 | try { 363 | parameterizedClass(RawBound.class, parameterizedClass(Collection.class, String.class)); 364 | fail("expected exception"); 365 | } catch (IllegalArgumentException expected) { 366 | } 367 | } 368 | 369 | static class ParameterizedBound> {} 370 | 371 | /** A raw type argument to a parameter with a non-raw bound, is not valid */ 372 | // TODO testRawTypeArgumentInParameterizedBoundNotValid 373 | // this is not implemented yet, because isSuperType() can't signal that it's raw 374 | // (and a dumb check that the argument is missing variables isn't good enough, because it would 375 | // also block a type that is raw but has an (indirect non-raw supertype) 376 | @Test @Ignore 377 | public void testRawTypeArgumentInParameterizedBoundNotValid() { 378 | 379 | // ParameterizedBound is not valid 380 | try { 381 | parameterizedClass(ParameterizedBound.class, List.class); 382 | fail("expected exception"); 383 | } catch (IllegalArgumentException expected) { 384 | } 385 | } 386 | 387 | /** If the bound is raw, the a raw argument is fine */ 388 | @Test 389 | @SuppressWarnings("rawtypes") 390 | public void testRawTypeArgumentInRawBound() { 391 | assertEquals( 392 | new TypeToken>() {}.getType(), 393 | parameterizedClass(RawBound.class, ArrayList.class)); 394 | } 395 | 396 | static class BoundReferingToOwner { 397 | class In {} 398 | } 399 | 400 | @Test 401 | public void testTypeArgumentInBoundReferingToOwner() { 402 | Type result = 403 | parameterizedInnerClass( 404 | parameterizedClass(BoundReferingToOwner.class, Number.class), 405 | BoundReferingToOwner.In.class, 406 | Integer.class); 407 | 408 | // unfortunately this doesn't compile with JDK 5 (but it does with the Eclipse compiler) 409 | // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6557954 410 | // assertEquals(new TypeToken.In>(){}.getType(), 411 | // result 412 | // ); 413 | 414 | // so we only test that it didn't throw an exception, and returned a ParamerizedType 415 | assertTrue(result instanceof ParameterizedType); 416 | } 417 | 418 | @Test 419 | public void testTypeArgumentNotInBoundReferingToOwner() { 420 | try { 421 | parameterizedInnerClass( 422 | parameterizedClass(BoundReferingToOwner.class, Number.class), 423 | BoundReferingToOwner.In.class, 424 | String.class); 425 | fail("expected exception"); 426 | } catch (IllegalArgumentException expected) { 427 | } 428 | } 429 | 430 | @Test 431 | public void testUnboundWildcardTypeArgumentInBound() { 432 | assertEquals( 433 | new TypeToken>() {}.getType(), parameterizedClass(Bound.class, unboundWildcard())); 434 | } 435 | 436 | @Test 437 | public void testWildcardExtendsTypeArgumentInBound() { 438 | assertEquals( 439 | new TypeToken>() {}.getType(), 440 | parameterizedClass(Bound.class, wildcardExtends(Integer.class))); 441 | } 442 | 443 | @Test 444 | public void testWildcardExtendsTypeArgumentNotInBound() { 445 | try { 446 | parameterizedClass(Bound.class, wildcardExtends(Thread.class)); 447 | fail("expected exception"); 448 | } catch (IllegalArgumentException expected) { 449 | } 450 | } 451 | 452 | @Test 453 | public void testUnrelatedWildcardExtendsTypeArgumentInBound() { 454 | assertEquals( 455 | new TypeToken>() {}.getType(), 456 | parameterizedClass(Bound.class, wildcardExtends(Runnable.class))); 457 | } 458 | 459 | @Test 460 | public void testWildcardSuperTypeArgumentInBound() { 461 | assertEquals( 462 | new TypeToken>() {}.getType(), 463 | parameterizedClass(Bound.class, wildcardSuper(Integer.class))); 464 | } 465 | 466 | @Test 467 | public void testWildcardSuperTypeArgumentNotInBound() { 468 | try { 469 | parameterizedClass(Bound.class, wildcardSuper(String.class)); 470 | fail("expected exception"); 471 | } catch (IllegalArgumentException expected) { 472 | } 473 | } 474 | 475 | @Test 476 | public void testWildcardInReferingBound() { 477 | assertEquals( 478 | new TypeToken>() {}.getType(), 479 | parameterizedClass(ReferingBound.class, unboundWildcard(), String.class)); 480 | } 481 | 482 | @Test 483 | public void testInReferingToWildcardBound() { 484 | // JDK doesn't allow this, but the eclipse compiler does. 485 | // We prefer to be lenient, so we allow it. 486 | // But we can't assertEquals it to a TypeToken because that wouldn't compile. 487 | parameterizedClass( 488 | ReferingBound.class, 489 | parameterizedClass(List.class, wildcardExtends(Integer.class)), 490 | wildcardExtends(Number.class)); 491 | } 492 | 493 | @Test 494 | public void testNotInReferingToWildcardBound() { 495 | try { 496 | parameterizedClass( 497 | ReferingBound.class, 498 | parameterizedClass(List.class, wildcardExtends(Number.class)), 499 | wildcardExtends(Integer.class)); 500 | fail("expected exception"); 501 | } catch (TypeArgumentNotInBoundException expected) { 502 | } 503 | } 504 | 505 | @Test 506 | public void testLocalClass() { 507 | class Local {} 508 | System.out.println(Local.class.getDeclaringClass()); 509 | 510 | assertEquals( 511 | new TypeToken>() {}.getType(), parameterizedClass(Local.class, String.class)); 512 | } 513 | 514 | /** 515 | * Specifying an owner for a local class is not allowed, because such a ParameterizedType also 516 | * doesn't have an class as its direct owner (the method is the owner, but that can't be 517 | * represented). (Java reflection also doesn't see the enclosing class as owner). 518 | */ 519 | @Test 520 | public void testLocalClassWithOwner() { 521 | class Local {} 522 | try { 523 | parameterizedInnerClass(TypeFactoryTest.class, Local.class, String.class); 524 | fail("expected exception"); 525 | } catch (IllegalArgumentException expected) { 526 | } 527 | } 528 | 529 | private Type getFirstTypeArgument(TypeToken typeToken) { 530 | return ((ParameterizedType) typeToken.getType()).getActualTypeArguments()[0]; 531 | } 532 | 533 | @Test 534 | public void testUnboundWildcard() { 535 | assertEquals(getFirstTypeArgument(new TypeToken>() {}), TypeFactory.unboundWildcard()); 536 | } 537 | 538 | @Test 539 | public void testWildcardExtends() { 540 | assertEquals( 541 | getFirstTypeArgument(new TypeToken>() {}), 542 | TypeFactory.wildcardExtends(String.class)); 543 | } 544 | 545 | @Test 546 | public void testWildcardSuper() { 547 | assertEquals( 548 | getFirstTypeArgument(new TypeToken>() {}), 549 | TypeFactory.wildcardSuper(String.class)); 550 | } 551 | 552 | @Test 553 | public void testClassArray() { 554 | assertSame(String[].class, TypeFactory.arrayOf(String.class)); 555 | } 556 | 557 | @Test 558 | public void testGenericArray() { 559 | assertEquals( 560 | new TypeToken[]>() {}.getType(), 561 | TypeFactory.arrayOf(parameterizedClass(List.class, String.class))); 562 | } 563 | } 564 | -------------------------------------------------------------------------------- /src/main/java/com/coekie/gentyref/GenericTypeReflector.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import java.io.Serializable; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.GenericArrayType; 6 | import java.lang.reflect.Method; 7 | import java.lang.reflect.ParameterizedType; 8 | import java.lang.reflect.Type; 9 | import java.lang.reflect.TypeVariable; 10 | import java.lang.reflect.WildcardType; 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.LinkedHashSet; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | /** 18 | * Utility class for doing reflection on types. 19 | * 20 | * @author Wouter Coekaerts 21 | */ 22 | public class GenericTypeReflector { 23 | private static final Type UNBOUND_WILDCARD = 24 | new WildcardTypeImpl(new Type[] {Object.class}, new Type[] {}); 25 | 26 | /** Returns the erasure of the given type. */ 27 | public static Class erase(Type type) { 28 | if (type instanceof Class) { 29 | return (Class) type; 30 | } else if (type instanceof ParameterizedType) { 31 | return (Class) ((ParameterizedType) type).getRawType(); 32 | } else if (type instanceof TypeVariable) { 33 | TypeVariable tv = (TypeVariable) type; 34 | if (tv.getBounds().length == 0) return Object.class; 35 | else return erase(tv.getBounds()[0]); 36 | } else if (type instanceof GenericArrayType) { 37 | GenericArrayType aType = (GenericArrayType) type; 38 | return GenericArrayTypeImpl.createArrayType(erase(aType.getGenericComponentType())); 39 | } else { 40 | // TODO at least support CaptureType here 41 | throw new RuntimeException("not supported: " + type.getClass()); 42 | } 43 | } 44 | 45 | /** 46 | * Maps type parameters in a type to their values. 47 | * 48 | * @param toMapType Type possibly containing type arguments 49 | * @param typeAndParams must be either ParameterizedType, or (in case there are no type arguments, 50 | * or it's a raw type) Class 51 | * @return toMapType, but with type parameters from typeAndParams replaced. 52 | */ 53 | private static Type mapTypeParameters(Type toMapType, Type typeAndParams) { 54 | if (isMissingTypeParameters(typeAndParams)) { 55 | return erase(toMapType); 56 | } else { 57 | VarMap varMap = new VarMap(); 58 | Type handlingTypeAndParams = typeAndParams; 59 | while (handlingTypeAndParams instanceof ParameterizedType) { 60 | ParameterizedType pType = (ParameterizedType) handlingTypeAndParams; 61 | Class clazz = (Class) pType.getRawType(); // getRawType should always be Class 62 | varMap.addAll(clazz.getTypeParameters(), pType.getActualTypeArguments()); 63 | handlingTypeAndParams = pType.getOwnerType(); 64 | } 65 | return varMap.map(toMapType); 66 | } 67 | } 68 | 69 | /** 70 | * Checks if the given type is a class that is supposed to have type parameters, but doesn't. In 71 | * other words, if it's a really raw type. 72 | */ 73 | static boolean isMissingTypeParameters(Type type) { 74 | if (type instanceof Class) { 75 | for (Class clazz = (Class) type; clazz != null; clazz = clazz.getEnclosingClass()) { 76 | if (clazz.getTypeParameters().length != 0) return true; 77 | } 78 | return false; 79 | } else if (type instanceof ParameterizedType) { 80 | return false; 81 | } else { 82 | throw new AssertionError("Unexpected type " + type.getClass()); 83 | } 84 | } 85 | 86 | /** 87 | * Returns a type representing the class, with all type parameters the unbound wildcard ("?"). For 88 | * example, addWildcardParameters(Map.class) returns a type representing 89 | * Map<?,?>. 90 | * 91 | * @return 92 | *
    93 | *
  • If clazz is a class or interface without type parameters, clazz itself is returned. 94 | *
  • If clazz is a class or interface with type parameters, an instance of 95 | * ParameterizedType is returned. 96 | *
  • if clazz is an array type, an array type is returned with unbound wildcard parameters 97 | * added in the the component type. 98 | *
99 | */ 100 | public static Type addWildcardParameters(Class clazz) { 101 | if (clazz.isArray()) { 102 | return GenericArrayTypeImpl.createArrayType(addWildcardParameters(clazz.getComponentType())); 103 | } else if (isMissingTypeParameters(clazz)) { 104 | TypeVariable[] vars = clazz.getTypeParameters(); 105 | Type[] arguments = new Type[vars.length]; 106 | Arrays.fill(arguments, UNBOUND_WILDCARD); 107 | Type owner = 108 | clazz.getDeclaringClass() == null 109 | ? null 110 | : addWildcardParameters(clazz.getDeclaringClass()); 111 | return new ParameterizedTypeImpl(clazz, arguments, owner); 112 | } else { 113 | return clazz; 114 | } 115 | } 116 | 117 | /** 118 | * Finds the most specific supertype of type whose erasure is searchClass. In 119 | * other words, returns a type representing the class searchClass plus its exact type 120 | * parameters in type. 121 | * 122 | *
    123 | *
  • Returns an instance of {@link ParameterizedType} if searchClass is a real class 124 | * or interface and type has parameters for it 125 | *
  • Returns an instance of {@link GenericArrayType} if searchClass is an array type, 126 | * and type has type parameters for it 127 | *
  • Returns an instance of {@link Class} if type is a raw type, or has no type 128 | * parameters for searchClass 129 | *
  • Returns null if searchClass is not a superclass of type. 130 | *
131 | * 132 | *

For example, with class StringList implements List<String>, 133 | * getExactSuperType(StringList.class, Collection.class) returns a {@link 134 | * ParameterizedType} representing Collection<String>. 135 | */ 136 | public static Type getExactSuperType(Type type, Class searchClass) { 137 | if (type instanceof ParameterizedType 138 | || type instanceof Class 139 | || type instanceof GenericArrayType) { 140 | Class clazz = erase(type); 141 | 142 | if (searchClass == clazz) { 143 | return type; 144 | } 145 | 146 | if (!searchClass.isAssignableFrom(clazz)) return null; 147 | } 148 | 149 | for (Type superType : getExactDirectSuperTypes(type)) { 150 | Type result = getExactSuperType(superType, searchClass); 151 | if (result != null) return result; 152 | } 153 | 154 | return null; 155 | } 156 | 157 | /** 158 | * Gets the type parameter for a given type that is the value for a given type variable. For 159 | * example, with class StringList implements List<String>, 160 | * getTypeParameter(StringList.class, Collection.class.getTypeParameters()[0]) returns 161 | * String. 162 | * 163 | * @param type The type to inspect. 164 | * @param variable The type variable to find the value for. 165 | * @return The type parameter for the given variable. Or null if type is not a subtype of the type 166 | * that declares the variable, or if the variable isn't known (because of raw types). 167 | */ 168 | public static Type getTypeParameter(Type type, TypeVariable> variable) { 169 | Class clazz = variable.getGenericDeclaration(); 170 | Type superType = getExactSuperType(type, clazz); 171 | if (superType instanceof ParameterizedType) { 172 | int index = Arrays.asList(clazz.getTypeParameters()).indexOf(variable); 173 | return ((ParameterizedType) superType).getActualTypeArguments()[index]; 174 | } else { 175 | return null; 176 | } 177 | } 178 | 179 | /** Checks if the capture of subType is a subtype of superType */ 180 | public static boolean isSuperType(Type superType, Type subType) { 181 | if (superType instanceof ParameterizedType 182 | || superType instanceof Class 183 | || superType instanceof GenericArrayType) { 184 | Class superClass = erase(superType); 185 | Type mappedSubType = getExactSuperType(capture(subType), superClass); 186 | if (mappedSubType == null) { 187 | return false; 188 | } else if (superType instanceof Class) { 189 | return true; 190 | } else if (mappedSubType instanceof Class) { 191 | // TODO treat supertype by being raw type differently ("supertype, but with warnings") 192 | return true; // class has no parameters, or it's a raw type 193 | } else if (mappedSubType instanceof GenericArrayType) { 194 | Type superComponentType = getArrayComponentType(superType); 195 | assert superComponentType != null; 196 | Type mappedSubComponentType = getArrayComponentType(mappedSubType); 197 | assert mappedSubComponentType != null; 198 | return isSuperType(superComponentType, mappedSubComponentType); 199 | } else { 200 | assert mappedSubType instanceof ParameterizedType; 201 | ParameterizedType pMappedSubType = (ParameterizedType) mappedSubType; 202 | assert pMappedSubType.getRawType() == superClass; 203 | ParameterizedType pSuperType = (ParameterizedType) superType; 204 | 205 | Type[] superTypeArgs = pSuperType.getActualTypeArguments(); 206 | Type[] subTypeArgs = pMappedSubType.getActualTypeArguments(); 207 | assert superTypeArgs.length == subTypeArgs.length; 208 | for (int i = 0; i < superTypeArgs.length; i++) { 209 | if (!contains(superTypeArgs[i], subTypeArgs[i])) { 210 | return false; 211 | } 212 | } 213 | // params of the class itself match, so if the owner types are supertypes too, it's a supertype. 214 | return pSuperType.getOwnerType() == null 215 | || isSuperType(pSuperType.getOwnerType(), pMappedSubType.getOwnerType()); 216 | } 217 | } else if (superType instanceof CaptureType) { 218 | if (superType.equals(subType)) return true; 219 | for (Type lowerBound : ((CaptureType) superType).getLowerBounds()) { 220 | if (isSuperType(lowerBound, subType)) { 221 | return true; 222 | } 223 | } 224 | return false; 225 | } else if (superType instanceof GenericArrayType) { 226 | return isArraySupertype(superType, subType); 227 | } else { 228 | throw new RuntimeException("not implemented: " + superType.getClass()); 229 | } 230 | } 231 | 232 | private static boolean isArraySupertype(Type arraySuperType, Type subType) { 233 | Type superTypeComponent = getArrayComponentType(arraySuperType); 234 | assert superTypeComponent != null; 235 | Type subTypeComponent = getArrayComponentType(subType); 236 | if (subTypeComponent == null) { // subType is not an array type 237 | return false; 238 | } else { 239 | return isSuperType(superTypeComponent, subTypeComponent); 240 | } 241 | } 242 | 243 | /** 244 | * If type is an array type, returns the type of the component of the array. Otherwise, returns 245 | * null. 246 | */ 247 | public static Type getArrayComponentType(Type type) { 248 | if (type instanceof Class) { 249 | Class clazz = (Class) type; 250 | return clazz.getComponentType(); 251 | } else if (type instanceof GenericArrayType) { 252 | GenericArrayType aType = (GenericArrayType) type; 253 | return aType.getGenericComponentType(); 254 | } else { 255 | return null; 256 | } 257 | } 258 | 259 | private static boolean contains(Type containingType, Type containedType) { 260 | if (containingType instanceof WildcardType) { 261 | WildcardType wContainingType = (WildcardType) containingType; 262 | for (Type upperBound : wContainingType.getUpperBounds()) { 263 | if (!isSuperType(upperBound, containedType)) { 264 | return false; 265 | } 266 | } 267 | for (Type lowerBound : wContainingType.getLowerBounds()) { 268 | if (!isSuperType(containedType, lowerBound)) { 269 | return false; 270 | } 271 | } 272 | return true; 273 | } else { 274 | return containingType.equals(containedType); 275 | } 276 | } 277 | 278 | /** Returns the direct supertypes of the given type. Resolves type parameters. */ 279 | private static Type[] getExactDirectSuperTypes(Type type) { 280 | if (type instanceof ParameterizedType || type instanceof Class) { 281 | Class clazz; 282 | if (type instanceof ParameterizedType) { 283 | clazz = (Class) ((ParameterizedType) type).getRawType(); 284 | } else { 285 | // TODO primitive types? 286 | clazz = (Class) type; 287 | if (clazz.isArray()) return getArrayExactDirectSuperTypes(clazz); 288 | } 289 | 290 | Type[] superInterfaces = clazz.getGenericInterfaces(); 291 | Type superClass = clazz.getGenericSuperclass(); 292 | 293 | // the only supertype of an interface without superinterfaces is Object 294 | if (superClass == null && superInterfaces.length == 0 && clazz.isInterface()) { 295 | return new Type[] {Object.class}; 296 | } 297 | 298 | Type[] result; 299 | int resultIndex; 300 | if (superClass == null) { 301 | result = new Type[superInterfaces.length]; 302 | resultIndex = 0; 303 | } else { 304 | result = new Type[superInterfaces.length + 1]; 305 | resultIndex = 1; 306 | result[0] = mapTypeParameters(superClass, type); 307 | } 308 | for (Type superInterface : superInterfaces) { 309 | result[resultIndex++] = mapTypeParameters(superInterface, type); 310 | } 311 | 312 | return result; 313 | } else if (type instanceof TypeVariable) { 314 | TypeVariable tv = (TypeVariable) type; 315 | return tv.getBounds(); 316 | } else if (type instanceof WildcardType) { 317 | // This should be a rare case: normally this wildcard is already captured. 318 | // But it does happen if the upper bound of a type variable contains a wildcard 319 | // TODO shouldn't upper bound of type variable have been captured too? (making this case impossible?) 320 | return ((WildcardType) type).getUpperBounds(); 321 | } else if (type instanceof CaptureType) { 322 | return ((CaptureType) type).getUpperBounds(); 323 | } else if (type instanceof GenericArrayType) { 324 | return getArrayExactDirectSuperTypes(type); 325 | } else if (type == null) { 326 | throw new NullPointerException(); 327 | } else { 328 | throw new RuntimeException("not implemented type: " + type); 329 | } 330 | } 331 | 332 | private static Type[] getArrayExactDirectSuperTypes(Type arrayType) { 333 | // see http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.10.3 334 | Type typeComponent = getArrayComponentType(arrayType); 335 | 336 | Type[] result; 337 | int resultIndex; 338 | if (typeComponent instanceof Class && ((Class) typeComponent).isPrimitive()) { 339 | resultIndex = 0; 340 | result = new Type[3]; 341 | } else { 342 | Type[] componentSupertypes = getExactDirectSuperTypes(typeComponent); 343 | result = new Type[componentSupertypes.length + 3]; 344 | for (resultIndex = 0; resultIndex < componentSupertypes.length; resultIndex++) { 345 | result[resultIndex] = 346 | GenericArrayTypeImpl.createArrayType(componentSupertypes[resultIndex]); 347 | } 348 | } 349 | result[resultIndex++] = Object.class; 350 | result[resultIndex++] = Cloneable.class; 351 | result[resultIndex++] = Serializable.class; 352 | return result; 353 | } 354 | 355 | /** 356 | * Returns the exact return type of the given method in the given type. This may be different from 357 | * m.getGenericReturnType() when the method was declared in a superclass, or 358 | * type has a type parameter that is used in the return type, or type is a raw 359 | * type. 360 | */ 361 | public static Type getExactReturnType(Method m, Type type) { 362 | Type returnType = m.getGenericReturnType(); 363 | Type exactDeclaringType = getExactSuperType(capture(type), m.getDeclaringClass()); 364 | if (exactDeclaringType == null) { // capture(type) is not a subtype of m.getDeclaringClass() 365 | throw new IllegalArgumentException("The method " + m + " is not a member of type " + type); 366 | } 367 | return mapTypeParameters(returnType, exactDeclaringType); 368 | } 369 | 370 | /** 371 | * Returns the exact type of the given field in the given type. This may be different from 372 | * f.getGenericType() when the field was declared in a superclass, or type has a 373 | * type parameter that is used in the type of the field, or type is a raw type. 374 | */ 375 | public static Type getExactFieldType(Field f, Type type) { 376 | Type returnType = f.getGenericType(); 377 | Type exactDeclaringType = getExactSuperType(capture(type), f.getDeclaringClass()); 378 | if (exactDeclaringType == null) { // capture(type) is not a subtype of f.getDeclaringClass() 379 | throw new IllegalArgumentException("The field " + f + " is not a member of type " + type); 380 | } 381 | return mapTypeParameters(returnType, exactDeclaringType); 382 | } 383 | 384 | /** 385 | * Returns the exact parameter types of the given method in the given type. This may be different 386 | * from m.getGenericParameterTypes() when the method was declared in a superclass, or 387 | * type has a type parameter that is used in one of the parameters, or type is a 388 | * raw type. 389 | */ 390 | public static Type[] getExactParameterTypes(Method m, Type type) { 391 | Type[] parameterTypes = m.getGenericParameterTypes(); 392 | Type exactDeclaringType = getExactSuperType(capture(type), m.getDeclaringClass()); 393 | if (exactDeclaringType == null) { // capture(type) is not a subtype of m.getDeclaringClass() 394 | throw new IllegalArgumentException("The method " + m + " is not a member of type " + type); 395 | } 396 | 397 | Type[] result = new Type[parameterTypes.length]; 398 | for (int i = 0; i < parameterTypes.length; i++) { 399 | result[i] = mapTypeParameters(parameterTypes[i], exactDeclaringType); 400 | } 401 | return result; 402 | } 403 | 404 | /** Applies capture conversion to the given type. */ 405 | public static Type capture(Type type) { 406 | if (type instanceof ParameterizedType) { 407 | return capture((ParameterizedType) type); 408 | } else { 409 | return type; 410 | } 411 | } 412 | 413 | /** 414 | * Applies capture conversion to the given type. 415 | * 416 | * @see #capture(Type) 417 | */ 418 | public static ParameterizedType capture(ParameterizedType type) { 419 | // the map from parameters to their captured equivalent 420 | 421 | VarMap varMap = new VarMap(); 422 | // list of CaptureTypes we've created but aren't fully initialized yet 423 | // we can only initialize them *after* we've fully populated varMap 424 | List toInit = new ArrayList(); 425 | 426 | Class clazz = (Class) type.getRawType(); 427 | Type[] arguments = type.getActualTypeArguments(); 428 | TypeVariable[] vars = clazz.getTypeParameters(); 429 | Type[] capturedArguments = new Type[arguments.length]; 430 | 431 | assert arguments.length == vars.length; // NICE throw an explaining exception 432 | 433 | for (int i = 0; i < arguments.length; i++) { 434 | Type argument = arguments[i]; 435 | if (argument instanceof WildcardType) { 436 | CaptureTypeImpl captured = new CaptureTypeImpl((WildcardType) argument, vars[i]); 437 | argument = captured; 438 | toInit.add(captured); 439 | } 440 | capturedArguments[i] = argument; 441 | varMap.add(vars[i], argument); 442 | } 443 | for (CaptureTypeImpl captured : toInit) { 444 | captured.init(varMap); 445 | } 446 | Type ownerType = (type.getOwnerType() == null) ? null : capture(type.getOwnerType()); 447 | return new ParameterizedTypeImpl(clazz, capturedArguments, ownerType); 448 | } 449 | 450 | /** Returns the display name of a Type. */ 451 | public static String getTypeName(Type type) { 452 | if (type instanceof Class) { 453 | Class clazz = (Class) type; 454 | return clazz.isArray() ? (getTypeName(clazz.getComponentType()) + "[]") : clazz.getName(); 455 | } else { 456 | return type.toString(); 457 | } 458 | } 459 | 460 | /** 461 | * Returns list of classes and interfaces that are supertypes of the given type. For example given 462 | * this class: class Foo<A extends Number & Iterable<A>, B extends A>
463 | * calling this method on type parameters B (Foo.class.getTypeParameters()[1]) 464 | * returns a list containing Number and Iterable. 465 | * 466 | *

This is mostly useful if you get a type from one of the other methods in 467 | * GenericTypeReflector, but you don't want to deal with all the different sorts of 468 | * types, and you are only really interested in concrete classes and interfaces. 469 | * 470 | * @return A List of classes, each of them a supertype of the given type. If the given type is a 471 | * class or interface itself, returns a List with just the given type. The list contains no 472 | * duplicates, and is ordered in the order the upper bounds are defined on the type. 473 | */ 474 | public static List> getUpperBoundClassAndInterfaces(Type type) { 475 | LinkedHashSet> result = new LinkedHashSet>(); 476 | buildUpperBoundClassAndInterfaces(type, result); 477 | return new ArrayList>(result); 478 | } 479 | 480 | /** Helper method for getUpperBoundClassAndInterfaces, adding the result to the given set. */ 481 | private static void buildUpperBoundClassAndInterfaces(Type type, Set> result) { 482 | if (type instanceof ParameterizedType || type instanceof Class) { 483 | result.add(erase(type)); 484 | return; 485 | } 486 | 487 | for (Type superType : getExactDirectSuperTypes(type)) { 488 | buildUpperBoundClassAndInterfaces(superType, result); 489 | } 490 | } 491 | } 492 | -------------------------------------------------------------------------------- /src/test/java/com/coekie/gentyref/AbstractGenericsReflectorTest.java: -------------------------------------------------------------------------------- 1 | package com.coekie.gentyref; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import java.io.Serializable; 8 | import java.lang.reflect.Array; 9 | import java.lang.reflect.Field; 10 | import java.lang.reflect.GenericArrayType; 11 | import java.lang.reflect.ParameterizedType; 12 | import java.lang.reflect.Type; 13 | import java.util.ArrayList; 14 | import java.util.Collection; 15 | import java.util.List; 16 | import java.util.RandomAccess; 17 | import org.junit.Test; 18 | 19 | public abstract class AbstractGenericsReflectorTest { 20 | /** 21 | * A constant that's false, to use in an if() block for code that's only there to show that it 22 | * compiles. This code "proves" that the test is an actual valid test case, by showing the 23 | * compiler agrees. But some of the code should not actually be executed, because it might throw 24 | * exceptions (because we're too lazy to initialize everything). 25 | */ 26 | private static final boolean COMPILE_CHECK = false; 27 | 28 | private static final TypeToken> ARRAYLIST_OF_STRING = 29 | new TypeToken>() {}; 30 | private static final TypeToken> LIST_OF_STRING = new TypeToken>() {}; 31 | private static final TypeToken> COLLECTION_OF_STRING = 32 | new TypeToken>() {}; 33 | 34 | private static final TypeToken>> ARRAYLIST_OF_LIST_OF_STRING = 35 | new TypeToken>>() {}; 36 | private static final TypeToken>> LIST_OF_LIST_OF_STRING = 37 | new TypeToken>>() {}; 38 | private static final TypeToken>> COLLECTION_OF_LIST_OF_STRING = 39 | new TypeToken>>() {}; 40 | 41 | private static final TypeToken> ARRAYLIST_OF_EXT_STRING = 42 | new TypeToken>() {}; 43 | private static final TypeToken> COLLECTION_OF_EXT_STRING = 44 | new TypeToken>() {}; 45 | 46 | private static final TypeToken> COLLECTION_OF_SUPER_STRING = 47 | new TypeToken>() {}; 48 | 49 | private static final TypeToken>> 50 | ARRAYLIST_OF_LIST_OF_EXT_STRING = new TypeToken>>() {}; 51 | private static final TypeToken>> LIST_OF_LIST_OF_EXT_STRING = 52 | new TypeToken>>() {}; 53 | private static final TypeToken>> 54 | COLLECTION_OF_LIST_OF_EXT_STRING = new TypeToken>>() {}; 55 | 56 | private final ReflectionStrategy strategy; 57 | 58 | class Box implements WithF, WithFToken> { 59 | public T f; 60 | } 61 | 62 | public AbstractGenericsReflectorTest(ReflectionStrategy strategy) { 63 | this.strategy = strategy; 64 | } 65 | 66 | private boolean isSupertype(TypeToken supertype, TypeToken subtype) { 67 | return strategy.isSupertype(supertype.getType(), subtype.getType()); 68 | } 69 | 70 | /** Tests that the types given are not equal, but they are eachother's supertype. */ 71 | private void testMutualSupertypes(TypeToken... types) { 72 | for (int i = 0; i < types.length; i++) { 73 | for (int j = i + 1; j < types.length; j++) { 74 | assertFalse(types[i].equals(types[j])); 75 | assertTrue(isSupertype(types[i], types[j])); 76 | assertTrue(isSupertype(types[i], types[j])); 77 | } 78 | } 79 | } 80 | 81 | /** Tests that the types given are not equal, but they are eachother's supertype. */ 82 | private void checkedTestMutualSupertypes(TypeToken type1, TypeToken type2) { 83 | assertFalse(type1.equals(type2)); 84 | assertTrue(isSupertype(type1, type2)); 85 | assertTrue(isSupertype(type2, type1)); 86 | } 87 | 88 | /** Test if superType is seen as a real (not equal) supertype of subType. */ 89 | private void testRealSupertype(TypeToken superType, TypeToken subType) { 90 | // test if it's really seen as a supertype 91 | assertTrue(isSupertype(superType, subType)); 92 | 93 | // check if they're not seen as supertypes the other way around 94 | assertFalse(isSupertype(subType, superType)); 95 | } 96 | 97 | private void checkedTestInexactSupertype( 98 | TypeToken expectedSuperclass, TypeToken type) { 99 | testInexactSupertype(expectedSuperclass, type); 100 | } 101 | 102 | /** 103 | * Checks if the supertype is seen as a supertype of subType. But, if superType is a Class or 104 | * ParameterizedType, with different type parameters. 105 | */ 106 | private void testInexactSupertype(TypeToken superType, TypeToken subType) { 107 | testRealSupertype(superType, subType); 108 | strategy.testInexactSupertype(superType.getType(), subType.getType()); 109 | } 110 | 111 | /** 112 | * Like testExactSuperclass, but the types of the arguments are checked so only valid test cases 113 | * can be applied 114 | */ 115 | private void checkedTestExactSuperclass( 116 | TypeToken expectedSuperclass, TypeToken type) { 117 | testExactSuperclass(expectedSuperclass, type); 118 | } 119 | 120 | /** Checks if the given types are equal */ 121 | private void assertCheckedTypeEquals(TypeToken expected, TypeToken type) { 122 | assertEquals(expected, type); 123 | } 124 | 125 | /** 126 | * Checks if the supertype is seen as a supertype of subType. SuperType must be a Class or 127 | * ParameterizedType, with the right type parameters. 128 | */ 129 | private void testExactSuperclass(TypeToken expectedSuperclass, TypeToken type) { 130 | testRealSupertype(expectedSuperclass, type); 131 | strategy.testExactSuperclass(expectedSuperclass.getType(), type.getType()); 132 | } 133 | 134 | protected static Class getClassType(Type type) { 135 | if (type instanceof Class) { 136 | return (Class) type; 137 | } else if (type instanceof ParameterizedType) { 138 | ParameterizedType pType = (ParameterizedType) type; 139 | return (Class) pType.getRawType(); 140 | } else if (type instanceof GenericArrayType) { 141 | GenericArrayType aType = (GenericArrayType) type; 142 | Class componentType = getClassType(aType.getGenericComponentType()); 143 | return Array.newInstance(componentType, 0).getClass(); 144 | } else { 145 | throw new IllegalArgumentException( 146 | "Only supports Class, ParameterizedType and GenericArrayType. Not " + type.getClass()); 147 | } 148 | } 149 | 150 | private TypeToken getFieldType(TypeToken forType, String fieldName) { 151 | return getFieldType(forType.getType(), fieldName); 152 | } 153 | 154 | /** 155 | * Marker interface to mark the type of the field f. Note: we could use a method instead of a 156 | * field, so the method could be in the interface, enforcing correct usage, but that would 157 | * influence the actual test too much. 158 | */ 159 | interface WithF {} 160 | 161 | /** Variant on WithF, where the type parameter is a TypeToken. TODO do we really need this? */ 162 | interface WithFToken> {} 163 | 164 | /** 165 | * Uses the reflector being tested to get the type of the field named "f" in the given type. The 166 | * returned value is cased into a TypeToken assuming the WithF interface is used correctly, and 167 | * the reflector returned the correct result. 168 | */ 169 | @SuppressWarnings("unchecked") // assuming the WithT interface is used correctly 170 | private TypeToken getF(TypeToken> type) { 171 | return (TypeToken) getFieldType(type, "f"); 172 | } 173 | /** 174 | * Variant of {@link #getF(TypeToken)} that's stricter in arguments and return type, for checked 175 | * equals tests. 176 | * 177 | * @see #getF(TypeToken) 178 | */ 179 | @SuppressWarnings("unchecked") // assuming the WithT interface is used correctly 180 | private TypeToken getStrictF(TypeToken> type) { 181 | return (TypeToken) getFieldType(type, "f"); 182 | } 183 | 184 | @SuppressWarnings("unchecked") 185 | private > T getFToken(TypeToken> type) { 186 | return (T) getFieldType(type, "f"); 187 | } 188 | 189 | private TypeToken getFieldType(Type forType, String fieldName) { 190 | try { 191 | Class clazz = getClassType(forType); 192 | return getFieldType(forType, clazz.getField(fieldName)); 193 | } catch (NoSuchFieldException e) { 194 | throw new RuntimeException("Error in test: can't find field " + fieldName, e); 195 | } 196 | } 197 | 198 | private TypeToken getFieldType(Type forType, Field field) { 199 | return TypeToken.get(strategy.getFieldType(forType, field)); 200 | } 201 | 202 | // private Type getReturnType(String methodName, Type forType) { 203 | // try { 204 | // Class clazz = getClass(forType); 205 | // return strategy.getExactReturnType(clazz.getMethod(methodName), forType); 206 | // } catch (NoSuchMethodException e) { 207 | // throw new RuntimeException("Error in test: can't find method " + methodName, e); 208 | // } 209 | // } 210 | 211 | private void checkedTestExactSuperclassChain( 212 | TypeToken type1, TypeToken type2, TypeToken type3) { 213 | testExactSuperclassChain(type1, type2, type3); 214 | } 215 | 216 | private void testExactSuperclassChain(TypeToken... types) { 217 | for (int i = 0; i < types.length; i++) { 218 | assertTrue(isSupertype(types[i], types[i])); 219 | for (int j = i + 1; j < types.length; j++) { 220 | testExactSuperclass(types[i], types[j]); 221 | } 222 | } 223 | } 224 | 225 | private void checkedTestInexactSupertypeChain( 226 | TypeToken type1, TypeToken type2, TypeToken type3) { 227 | testInexactSupertypeChain(type1, type2, type3); 228 | } 229 | 230 | private void testInexactSupertypeChain(TypeToken... types) { 231 | for (int i = 0; i < types.length; i++) { 232 | assertTrue(isSupertype(types[i], types[i])); 233 | for (int j = i + 1; j < types.length; j++) { 234 | testInexactSupertype(types[i], types[j]); 235 | } 236 | } 237 | } 238 | 239 | /** 240 | * Test that type1 is not a supertype of type2 (and, while we're at it, not vice-versa either). 241 | */ 242 | private void testNotSupertypes(TypeToken... types) { 243 | for (int i = 0; i < types.length; i++) { 244 | for (int j = i + 1; j < types.length; j++) { 245 | assertFalse(isSupertype(types[i], types[j])); 246 | assertFalse(isSupertype(types[j], types[i])); 247 | } 248 | } 249 | } 250 | 251 | private TypeToken tt(Class t) { 252 | return TypeToken.get(t); 253 | } 254 | 255 | @Test 256 | public void testBasic() { 257 | checkedTestExactSuperclassChain(tt(Object.class), tt(Number.class), tt(Integer.class)); 258 | testNotSupertypes(tt(Integer.class), tt(Double.class)); 259 | } 260 | 261 | @Test 262 | public void testSimpleTypeParam() { 263 | checkedTestExactSuperclassChain(COLLECTION_OF_STRING, LIST_OF_STRING, ARRAYLIST_OF_STRING); 264 | testNotSupertypes(COLLECTION_OF_STRING, new TypeToken>() {}); 265 | } 266 | 267 | /** 268 | * Dummy method to force the compiler to see the reference to the given local class. Workaround 269 | * for Issue 15 (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7003595) 270 | */ 271 | private void use(Class clazz) {} 272 | 273 | public interface StringList extends List {} 274 | 275 | @Test 276 | public void testStringList() { 277 | checkedTestExactSuperclassChain(COLLECTION_OF_STRING, LIST_OF_STRING, tt(StringList.class)); 278 | } 279 | 280 | @Test 281 | public void testTextendsStringList() { 282 | class C implements WithF { 283 | public T f; 284 | } 285 | 286 | // raw 287 | if (COMPILE_CHECK) { 288 | @SuppressWarnings("rawtypes") 289 | C c = null; 290 | List listOfString = c.f; 291 | } 292 | testExactSuperclass(LIST_OF_STRING, getFieldType(C.class, "f")); 293 | 294 | // wildcard 295 | TypeToken ft = getF(new TypeToken>() {}); 296 | checkedTestExactSuperclassChain(LIST_OF_STRING, tt(StringList.class), ft); 297 | } 298 | 299 | @Test 300 | public void testExtendViaOtherTypeParam() { 301 | class C implements WithF { 302 | @SuppressWarnings("unused") 303 | public U f; 304 | } 305 | // raw 306 | testExactSuperclass(LIST_OF_STRING, getFieldType(C.class, "f")); 307 | // wildcard 308 | TypeToken ft = getF(new TypeToken>() {}); 309 | checkedTestExactSuperclassChain(LIST_OF_STRING, tt(StringList.class), ft); 310 | } 311 | 312 | @Test 313 | @SuppressWarnings({"unchecked", "rawtypes"}) 314 | public void testMultiBoundParametrizedStringList() { 315 | class C implements WithF { 316 | @SuppressWarnings("unused") 317 | public T f; 318 | } 319 | // raw 320 | new C().f = new Object(); // compile check 321 | assertEquals(tt(Object.class), getFieldType(C.class, "f")); 322 | // wildcard 323 | TypeToken ft = getF(new TypeToken>() {}); 324 | checkedTestExactSuperclassChain(LIST_OF_STRING, tt(StringList.class), ft); 325 | } 326 | 327 | @Test 328 | public void testFListOfT_String() { 329 | class C implements WithF> { 330 | @SuppressWarnings("unused") 331 | public List f; 332 | } 333 | use(C.class); 334 | 335 | TypeToken> ft = getStrictF(new TypeToken>() {}); 336 | assertCheckedTypeEquals(LIST_OF_STRING, ft); 337 | } 338 | 339 | @Test 340 | public void testOfListOfString() { 341 | checkedTestExactSuperclassChain( 342 | COLLECTION_OF_LIST_OF_STRING, LIST_OF_LIST_OF_STRING, ARRAYLIST_OF_LIST_OF_STRING); 343 | testNotSupertypes(COLLECTION_OF_LIST_OF_STRING, new TypeToken>>() {}); 344 | } 345 | 346 | @Test 347 | public void testFListOfListOfT_String() { 348 | class C implements WithF>> { 349 | @SuppressWarnings("unused") 350 | public List> f; 351 | } 352 | use(C.class); 353 | 354 | TypeToken>> ft = getStrictF(new TypeToken>() {}); 355 | assertCheckedTypeEquals(LIST_OF_LIST_OF_STRING, ft); 356 | } 357 | 358 | public interface ListOfListOfT extends List> {} 359 | 360 | @Test 361 | public void testListOfListOfT_String() { 362 | checkedTestExactSuperclassChain( 363 | COLLECTION_OF_LIST_OF_STRING, 364 | LIST_OF_LIST_OF_STRING, 365 | new TypeToken>() {}); 366 | } 367 | 368 | public interface ListOfListOfT_String extends ListOfListOfT {} 369 | 370 | @Test 371 | public void testListOfListOfT_StringInterface() { 372 | checkedTestExactSuperclassChain( 373 | COLLECTION_OF_LIST_OF_STRING, LIST_OF_LIST_OF_STRING, tt(ListOfListOfT_String.class)); 374 | } 375 | 376 | public interface ListOfListOfString extends List> {} 377 | 378 | @Test 379 | public void testListOfListOfStringInterface() { 380 | checkedTestExactSuperclassChain( 381 | COLLECTION_OF_LIST_OF_STRING, LIST_OF_LIST_OF_STRING, tt(ListOfListOfString.class)); 382 | } 383 | 384 | @Test 385 | public void testWildcardTExtendsListOfListOfString() { 386 | class C>> implements WithF { 387 | @SuppressWarnings("unused") 388 | public T f; 389 | } 390 | use(C.class); 391 | 392 | TypeToken>> ft = getF(new TypeToken>() {}); 393 | checkedTestExactSuperclass(COLLECTION_OF_LIST_OF_STRING, ft); 394 | } 395 | 396 | @Test 397 | public void testExtWildcard() { 398 | checkedTestExactSuperclass(COLLECTION_OF_EXT_STRING, ARRAYLIST_OF_EXT_STRING); 399 | checkedTestExactSuperclass(COLLECTION_OF_LIST_OF_EXT_STRING, ARRAYLIST_OF_LIST_OF_EXT_STRING); 400 | testNotSupertypes(COLLECTION_OF_EXT_STRING, new TypeToken>() {}); 401 | testNotSupertypes(COLLECTION_OF_EXT_STRING, new TypeToken>() {}); 402 | } 403 | 404 | public interface ListOfListOfExtT extends List> {} 405 | 406 | @Test 407 | public void testListOfListOfExtT_String() { 408 | checkedTestExactSuperclass( 409 | COLLECTION_OF_LIST_OF_EXT_STRING, new TypeToken>() {}); 410 | } 411 | 412 | @Test 413 | public void testUExtendsListOfExtT() { 414 | class C> implements WithF { 415 | @SuppressWarnings("unused") 416 | public U f; 417 | } 418 | 419 | // this doesn't compile in eclipse nor with sun compiler, so we hold the compilers hand by 420 | // adding some steps in between 421 | // TypeToken> ft = 422 | // getF(new TypeToken>(){}); 423 | TypeToken> tt = new TypeToken>() {}; 424 | TypeToken>> ttt = tt; 425 | TypeToken> ft = getF(ttt); 426 | 427 | checkedTestInexactSupertype(COLLECTION_OF_EXT_STRING, ft); 428 | } 429 | 430 | @Test 431 | public void testListOfExtT() { 432 | class C implements WithF> { 433 | @SuppressWarnings("unused") 434 | public List f; 435 | } 436 | use(C.class); 437 | 438 | TypeToken> ft = getF(new TypeToken>() {}); 439 | checkedTestExactSuperclass(COLLECTION_OF_EXT_STRING, ft); 440 | } 441 | 442 | @Test 443 | public void testListOfSuperT() { 444 | class C implements WithF> { 445 | @SuppressWarnings("unused") 446 | public List f; 447 | } 448 | use(C.class); 449 | 450 | TypeToken> ft = getF(new TypeToken>() {}); 451 | checkedTestExactSuperclass(COLLECTION_OF_SUPER_STRING, ft); 452 | } 453 | 454 | @Test 455 | public void testInnerFieldWithTypeOfOuter() { 456 | class Outer { 457 | @SuppressWarnings("unused") 458 | class Inner implements WithF { 459 | public T f; 460 | } 461 | 462 | class Inner2 implements WithF>> { 463 | @SuppressWarnings("unused") 464 | public List> f; 465 | } 466 | } 467 | use(Outer.class); 468 | 469 | TypeToken ft = getStrictF(new TypeToken.Inner>() {}); 470 | assertCheckedTypeEquals(tt(String.class), ft); 471 | 472 | TypeToken>> ft2 = 473 | getStrictF(new TypeToken.Inner2>() {}); 474 | assertCheckedTypeEquals(LIST_OF_LIST_OF_EXT_STRING, ft2); 475 | } 476 | 477 | @Test 478 | public void testInnerExtendsWithTypeOfOuter() { 479 | class Outer { 480 | class Inner extends ArrayList {} 481 | } 482 | use(Outer.class); 483 | 484 | checkedTestExactSuperclass(COLLECTION_OF_STRING, new TypeToken.Inner>() {}); 485 | } 486 | 487 | @Test 488 | public void testInnerDifferentParams() { 489 | class Outer { 490 | class Inner {} 491 | } 492 | use(Outer.class); 493 | 494 | // inner param different 495 | testNotSupertypes( 496 | new TypeToken.Inner>() {}, 497 | new TypeToken.Inner>() {}); 498 | // outer param different 499 | testNotSupertypes( 500 | new TypeToken.Inner>() {}, 501 | new TypeToken.Inner>() {}); 502 | } 503 | 504 | /** Supertype of a raw type is erased */ 505 | @Test 506 | @SuppressWarnings({"unchecked", "rawtypes"}) 507 | public void testSubclassRaw() { 508 | class Superclass { 509 | public T t; 510 | } 511 | class Subclass extends Superclass {} 512 | use(Superclass.class); 513 | 514 | assertEquals(tt(Number.class), getFieldType(Subclass.class, "t")); 515 | 516 | Number n = new Subclass().t; // compile check 517 | new Subclass().t = n; // compile check 518 | } 519 | 520 | /** 521 | * Supertype of a raw type is erased. (And there's no such thing as a ParameterizedType with some 522 | * type parameters raw and others not) 523 | */ 524 | @Test 525 | @SuppressWarnings({"unchecked", "rawtypes"}) 526 | public void testSubclassRawMix() { 527 | class Superclass { 528 | // public T t; 529 | public U u; 530 | } 531 | class Subclass extends Superclass {} 532 | use(Superclass.class); 533 | 534 | assertEquals(tt(Number.class), getFieldType(Subclass.class, "u")); 535 | 536 | Number n = new Subclass().u; // compile check 537 | new Subclass().u = n; // compile check 538 | } 539 | 540 | /** 541 | * If a type has no parameters, it doesn't matter that it got erased. So even though Middleclass 542 | * was erased, its supertype is not. 543 | */ 544 | @Test 545 | public void testSubclassRawViaUnparameterized() { 546 | class Superclass implements WithF { 547 | @SuppressWarnings("unused") 548 | public T f; 549 | } 550 | class Middleclass extends Superclass {} 551 | class Subclass extends Middleclass {} 552 | use(Superclass.class); 553 | 554 | // doesn't compile with sun compiler (but does work in eclipse) 555 | // TypeToken ft = getStrictF(tt(Subclass.class)); 556 | // assertCheckedTypeEquals(tt(Integer.class), ft); 557 | assertEquals(tt(Integer.class), getFieldType(Subclass.class, "f")); 558 | } 559 | 560 | /** Similar for inner types: the outer type of a raw inner type is also erased */ 561 | @Test 562 | @SuppressWarnings("unchecked") 563 | public void testInnerRaw() { 564 | class Outer { 565 | @SuppressWarnings("rawtypes") 566 | public Inner rawInner; 567 | 568 | class Inner { 569 | public T t; 570 | public U u; 571 | } 572 | } 573 | 574 | assertEquals(tt(Outer.Inner.class), getFieldType(Outer.class, "rawInner")); 575 | assertEquals(tt(Number.class), getFieldType(Outer.Inner.class, "t")); 576 | assertEquals(tt(Number.class), getFieldType(Outer.Inner.class, "u")); 577 | 578 | if (COMPILE_CHECK) { 579 | Number n = new Outer().rawInner.t; // compile check 580 | new Outer().rawInner.t = n; // compile check 581 | n = new Outer().rawInner.u; // compile check 582 | new Outer().rawInner.u = n; // compile check 583 | } 584 | } 585 | 586 | @Test 587 | public void testSuperWildcard() { 588 | Box b = new Box(); // compile check 589 | b.f = new Integer(0); // compile check 590 | 591 | testInexactSupertype( 592 | getFieldType(new TypeToken>() {}, "f"), tt(Integer.class)); 593 | 594 | TypeToken ft = getFToken(new TypeToken>() {}); 595 | checkedTestInexactSupertype(ft, tt(Integer.class)); 596 | } 597 | 598 | @Test 599 | public void testContainment() { 600 | checkedTestInexactSupertypeChain( 601 | new TypeToken>() {}, 602 | new TypeToken>() {}, 603 | new TypeToken>() {}); 604 | checkedTestInexactSupertypeChain( 605 | new TypeToken>() {}, 606 | new TypeToken>() {}, 607 | new TypeToken>() {}); 608 | } 609 | 610 | @Test 611 | public void testArrays() { 612 | checkedTestExactSuperclassChain(tt(Object[].class), tt(Number[].class), tt(Integer[].class)); 613 | testNotSupertypes(new TypeToken() {}, new TypeToken() {}); 614 | checkedTestExactSuperclassChain(tt(Object.class), tt(Object[].class), tt(Object[][].class)); 615 | checkedTestExactSuperclass(tt(Serializable.class), tt(Integer[].class)); 616 | checkedTestExactSuperclass(tt(Cloneable[].class), tt(Object[][].class)); 617 | } 618 | 619 | @Test 620 | public void testGenericArrays() { 621 | checkedTestExactSuperclass( 622 | new TypeToken[]>() {}, new TypeToken[]>() {}); 623 | checkedTestInexactSupertype( 624 | new TypeToken[]>() {}, 625 | new TypeToken[]>() {}); 626 | checkedTestExactSuperclass(tt(RandomAccess[].class), new TypeToken[]>() {}); 627 | assertTrue( 628 | isSupertype( 629 | tt(ArrayList[].class), 630 | new TypeToken< 631 | ArrayList 632 | []>() {})); // not checked* because we're avoiding the inverse test 633 | } 634 | 635 | @Test 636 | public void testArrayOfT() { 637 | class C implements WithF { 638 | @SuppressWarnings("unused") 639 | public T[] f; 640 | } 641 | use(C.class); 642 | 643 | TypeToken ft = getStrictF(new TypeToken>() {}); 644 | assertCheckedTypeEquals(tt(String[].class), ft); 645 | } 646 | 647 | @Test 648 | public void testArrayOfListOfT() { 649 | class C implements WithF[]> { 650 | @SuppressWarnings("unused") 651 | public List[] f; 652 | } 653 | use(C.class); 654 | 655 | TypeToken[]> ft = getStrictF(new TypeToken>() {}); 656 | assertCheckedTypeEquals(new TypeToken[]>() {}, ft); 657 | } 658 | 659 | @Test 660 | @SuppressWarnings({"unchecked", "rawtypes"}) 661 | public void testArrayRaw() { 662 | class C { 663 | @SuppressWarnings("unused") 664 | public List f; 665 | } 666 | new C().f = new ArrayList(); // compile check 667 | assertEquals(tt(List.class), getFieldType(new TypeToken() {}, "f")); 668 | } 669 | 670 | @Test 671 | public void testPrimitiveArray() { 672 | testNotSupertypes(tt(double[].class), tt(float[].class)); 673 | testNotSupertypes(tt(int[].class), tt(Integer[].class)); 674 | } 675 | 676 | @Test 677 | public void testCapture() { 678 | TypeToken> bw = new TypeToken>() {}; 679 | TypeToken capture1 = getF(bw); 680 | TypeToken capture2 = getF(bw); 681 | assertFalse(capture1.equals(capture2)); 682 | // if these were equal, this would be valid: 683 | // Box b1 = new Box(); 684 | // Box b2 = new Box(); 685 | // b1.f = b2.f; 686 | // but the capture is still equal to itself 687 | assertTrue(capture1.equals(capture1)); 688 | } 689 | 690 | @Test 691 | public void testCaptureBeforeReplaceSupertype() { 692 | class C extends ArrayList> {} 693 | use(C.class); 694 | testNotSupertypes(LIST_OF_LIST_OF_EXT_STRING, new TypeToken>() {}); 695 | // if it was a supertype, this would be valid: 696 | // List> o = new C(); 697 | } 698 | 699 | class Node, E extends Edge> implements WithF> { 700 | public List f; 701 | public E e; 702 | } 703 | 704 | class Edge, E extends Edge> implements WithF> { 705 | public List f; 706 | public N n; 707 | } 708 | 709 | @Test 710 | public void testGraphWildcard() { 711 | testInexactSupertype( 712 | new TypeToken, ?>>>() {}, 713 | getF(new TypeToken>() {})); 714 | } 715 | 716 | @Test 717 | public void testGraphCapture() throws NoSuchFieldException { 718 | Field e = Node.class.getField("e"); 719 | Field n = Edge.class.getField("n"); 720 | TypeToken node = new TypeToken>() {}; 721 | TypeToken edgeOfNode = getFieldType(node.getType(), e); 722 | TypeToken nodeOfEdgeOfNode = getFieldType(edgeOfNode.getType(), n); 723 | TypeToken edgeOfNodeOfEdgeOfNode = getFieldType(nodeOfEdgeOfNode.getType(), e); 724 | assertEquals(edgeOfNode, edgeOfNodeOfEdgeOfNode); 725 | assertFalse(node.equals(nodeOfEdgeOfNode)); // node is not captured, nodeOfEdgeOfNode is 726 | } 727 | 728 | /** 729 | * This test shows the need for capturing in isSupertype: the type parameters aren't contained, 730 | * but the capture of them is because of the bound on type variable 731 | */ 732 | @Test 733 | public void testCaptureContainment() { 734 | class C {} 735 | use(C.class); 736 | 737 | checkedTestMutualSupertypes(new TypeToken>() {}, new TypeToken>() {}); 738 | } 739 | 740 | @Test 741 | public void testCaptureContainmentViaOtherParam() { 742 | class C> {} 743 | 744 | C> c1 = null; 745 | C c2 = null; 746 | c1 = c2; 747 | c2 = c1; 748 | testMutualSupertypes( 749 | new TypeToken>>() {}, 750 | new TypeToken>() {}); 751 | } 752 | 753 | // Issue #4 754 | @Test 755 | public void testClassInMethod() throws NoSuchFieldException { 756 | class Outer { 757 | Class getInnerClass() { 758 | class Inner implements WithF { 759 | @SuppressWarnings("unused") 760 | public T f; 761 | } 762 | return Inner.class; 763 | } 764 | } 765 | Class inner = new Outer().getInnerClass(); 766 | assertEquals(WithF.class, GenericTypeReflector.getExactSuperType(inner, WithF.class)); 767 | assertEquals(Object.class, GenericTypeReflector.getExactFieldType(inner.getField("f"), inner)); 768 | } 769 | } 770 | --------------------------------------------------------------------------------

It does not follow the checks defined in the JLS 139 | * because there are several problems with those (see http://stackoverflow.com/questions/7003009 140 | * for one). Instead, this applies some intuition and follows what Java compilers seem to do. 141 | * 142 | * @param type possibly inconsistent type to check. 143 | * @throws IllegalArgumentException if the type arguments are not within the bounds 144 | */ 145 | private static void checkParametersWithinBound(ParameterizedType type) { 146 | Type[] arguments = type.getActualTypeArguments(); 147 | TypeVariable[] typeParameters = ((Class) type.getRawType()).getTypeParameters(); 148 | 149 | // a map of type arguments in the type, to fill in variables in the bounds 150 | VarMap varMap = new VarMap(type); 151 | 152 | // for every bound on every parameter 153 | for (int i = 0; i < arguments.length; i++) { 154 | for (Type bound : typeParameters[i].getBounds()) { 155 | // replace type variables in the bound by their value 156 | Type replacedBound = varMap.map(bound); 157 | 158 | if (arguments[i] instanceof WildcardType) { 159 | WildcardType wildcardTypeParameter = (WildcardType) arguments[i]; 160 | 161 | // Check if a type satisfying both the bounds of the variable and of the wildcard could exist 162 | 163 | // upper bounds must not be mutually exclusive 164 | for (Type wildcardUpperBound : wildcardTypeParameter.getUpperBounds()) { 165 | if (!couldHaveCommonSubtype(replacedBound, wildcardUpperBound)) { 166 | throw new TypeArgumentNotInBoundException(arguments[i], typeParameters[i], bound); 167 | } 168 | } 169 | // a lowerbound in the wildcard must satisfy every upperbound 170 | for (Type wildcardLowerBound : wildcardTypeParameter.getLowerBounds()) { 171 | if (!GenericTypeReflector.isSuperType(replacedBound, wildcardLowerBound)) { 172 | throw new TypeArgumentNotInBoundException(arguments[i], typeParameters[i], bound); 173 | } 174 | } 175 | } else { 176 | if (!GenericTypeReflector.isSuperType(replacedBound, arguments[i])) { 177 | throw new TypeArgumentNotInBoundException(arguments[i], typeParameters[i], bound); 178 | } 179 | } 180 | } 181 | } 182 | } 183 | 184 | /** Checks if the intersection of two types is not empty. */ 185 | private static boolean couldHaveCommonSubtype(Type type1, Type type2) { 186 | // this is an optimistically naive implementation. 187 | // if they are parameterized types their parameters need to be checked,... 188 | // so we're just a bit too lenient here 189 | 190 | Class erased1 = GenericTypeReflector.erase(type1); 191 | Class erased2 = GenericTypeReflector.erase(type2); 192 | // if they are both classes 193 | if (!erased1.isInterface() && !erased2.isInterface()) { 194 | // then one needs to be a subclass of another 195 | if (!erased1.isAssignableFrom(erased2) && !erased2.isAssignableFrom(erased1)) { 196 | return false; 197 | } 198 | } 199 | return true; 200 | } 201 | 202 | /** 203 | * Transforms the given owner type into an appropriate one when constructing a parameterized type. 204 | */ 205 | private static Type transformOwner(Type givenOwner, Class clazz) { 206 | if (givenOwner == null) { 207 | // be lenient: if this is an inner class but no owner was specified, assume a raw owner type 208 | // (or if there is no owner just return null) 209 | return clazz.getDeclaringClass(); 210 | } else { 211 | // If the specified owner is not of the declaring class' type, but instead a subtype, 212 | // transform it into the declaring class with the exact type parameters. 213 | // For example with "class StringOuter extends GenericOuter", transform 214 | // "StringOuter.Inner" into "GenericOuter.Inner", just like the Java compiler does. 215 | Type transformedOwner = 216 | GenericTypeReflector.getExactSuperType(givenOwner, clazz.getDeclaringClass()); 217 | 218 | if (transformedOwner == null) { // null means it's not a supertype 219 | throw new IllegalArgumentException( 220 | "Given owner type [" 221 | + givenOwner 222 | + "] is not appropriate for [" 223 | + clazz 224 | + "]: it should be a subtype of " 225 | + clazz.getDeclaringClass()); 226 | } 227 | 228 | if (Modifier.isStatic(clazz.getModifiers())) { 229 | // for a static inner class, the owner shouldn't have type parameters 230 | return GenericTypeReflector.erase(transformedOwner); 231 | } else { 232 | return transformedOwner; 233 | } 234 | } 235 | } 236 | 237 | /** 238 | * Returns the wildcard type without bounds. This is the '?' in for example 239 | * List<?>. 240 | * 241 | * @return The unbound wildcard type 242 | */ 243 | public static WildcardType unboundWildcard() { 244 | return UNBOUND_WILDCARD; 245 | } 246 | 247 | /** 248 | * Creates a wildcard type with an upper bound. 249 | * 250 | *