/* * Copyright (C) 2009 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.common.reflect; import static org.junit.contrib.truth.Truth.ASSERT; import com.google.common.base.Function; import com.google.common.base.Predicate; import junit.framework.TestCase; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Unit test for {@link TypeToken} and {@link TypeResolver}. * * @author Ben Yu */ public class TypeTokenResolutionTest extends TestCase { private static class Foo<A, B> { Class<? super A> getClassA() { return new TypeToken<A>(getClass()) {}.getRawType(); } Class<? super B> getClassB() { return new TypeToken<B>(getClass()) {}.getRawType(); } Class<? super A[]> getArrayClassA() { return new TypeToken<A[]>(getClass()) {}.getRawType(); } Type getArrayTypeA() { return new TypeToken<A[]>(getClass()) {}.getType(); } Class<? super B[]> getArrayClassB() { return new TypeToken<B[]>(getClass()) {}.getRawType(); } } public void testSimpleTypeToken() { Foo<String, Integer> foo = new Foo<String, Integer>() {}; assertEquals(String.class, foo.getClassA()); assertEquals(Integer.class, foo.getClassB()); assertEquals(String[].class, foo.getArrayClassA()); assertEquals(Integer[].class, foo.getArrayClassB()); } public void testCompositeTypeToken() { Foo<String[], List<int[]>> foo = new Foo<String[], List<int[]>>() {}; assertEquals(String[].class, foo.getClassA()); assertEquals(List.class, foo.getClassB()); assertEquals(String[][].class, foo.getArrayClassA()); assertEquals(List[].class, foo.getArrayClassB()); } private static class StringFoo<T> extends Foo<String, T> {} public void testPartialSpecialization() { StringFoo<Integer> foo = new StringFoo<Integer>() {}; assertEquals(String.class, foo.getClassA()); assertEquals(Integer.class, foo.getClassB()); assertEquals(String[].class, foo.getArrayClassA()); assertEquals(Integer[].class, foo.getArrayClassB()); assertEquals(new TypeToken<String[]>() {}.getType(), foo.getArrayTypeA()); } public void testTypeArgNotFound() { StringFoo<Integer> foo = new StringFoo<Integer>(); assertEquals(String.class, foo.getClassA()); assertEquals(String[].class, foo.getArrayClassA()); assertEquals(Object.class, foo.getClassB()); assertEquals(Object[].class, foo.getArrayClassB()); } private static abstract class Bar<T> {} private abstract static class Parameterized<O, T, P> { ParameterizedType parameterizedType() { return new ParameterizedType() { @Override public Type[] getActualTypeArguments() { return new Type[]{new TypeCapture<P>() {}.capture()}; } @Override public Type getOwnerType() { return new TypeCapture<O>() {}.capture(); } @Override public Type getRawType() { return new TypeCapture<T>() {}.capture(); } }; } } public void testResolveType_parameterizedType() { @SuppressWarnings("rawtypes") // trying to test raw type Parameterized<?, ?, ?> parameterized = new Parameterized<TypeTokenResolutionTest, Bar, String>() {}; TypeResolver typeResolver = TypeResolver.accordingTo(parameterized.getClass()); ParameterizedType resolved = (ParameterizedType) typeResolver.resolveType( parameterized.parameterizedType()); assertEquals(TypeTokenResolutionTest.class, resolved.getOwnerType()); assertEquals(Bar.class, resolved.getRawType()); ASSERT.that(resolved.getActualTypeArguments()) .hasContentsInOrder(String.class); } private interface StringListPredicate extends Predicate<List<String>> {} // Intentionally duplicate the Predicate interface to test that it won't cause // exceptions private interface IntegerStringFunction extends Function<Integer, Boolean>, Predicate<List<String>>, StringListPredicate {} public void testGenericInterface() { // test the 1st generic interface on the class Type fType = Function.class.getTypeParameters()[0]; assertEquals(Integer.class, TypeToken.of(IntegerStringFunction.class).resolveType(fType) .getRawType()); // test the 2nd generic interface on the class Type predicateParameterType = Predicate.class.getTypeParameters()[0]; assertEquals(new TypeToken<List<String>>() {}.getType(), TypeToken.of(IntegerStringFunction.class).resolveType(predicateParameterType) .getType()); } private static abstract class StringIntegerFoo extends Foo<String, Integer> {} public void testConstructor_typeArgsResolvedFromAncestorClass() { assertEquals(String.class, new StringIntegerFoo() {}.getClassA()); assertEquals(Integer.class, new StringIntegerFoo() {}.getClassB()); } private static class Owner<T> { private static abstract class Nested<X> { Class<? super X> getTypeArgument() { return new TypeToken<X>(getClass()) {}.getRawType(); } } private abstract class Inner<Y> extends Nested<Y> { Class<? super T> getOwnerType() { return new TypeToken<T>(getClass()) {}.getRawType(); } } } public void testResolveNestedClass() { assertEquals(String.class, new Owner.Nested<String>() {}.getTypeArgument()); } public void testResolveInnerClass() { assertEquals(String.class, new Owner<Integer>().new Inner<String>() {}.getTypeArgument()); } public void testResolveOwnerClass() { assertEquals(Integer.class, new Owner<Integer>().new Inner<String>() {}.getOwnerType()); } private static class Mapping<F, T> { final Type f = new TypeToken<F>(getClass()) {}.getType(); final Type t = new TypeToken<T>(getClass()) {}.getType(); Type getFromType() { return new TypeToken<F>(getClass()) {}.getType(); } Type getToType() { return new TypeToken<T>(getClass()) {}.getType(); } Mapping<T, F> flip() { return new Mapping<T, F>() {}; } Mapping<F, T> selfMapping() { return new Mapping<F, T>() {}; } } public void testCyclicMapping() { Mapping<Integer, String> mapping = new Mapping<Integer, String>(); assertEquals(mapping.f, mapping.getFromType()); assertEquals(mapping.t, mapping.getToType()); assertEquals(mapping.f, mapping.flip().getFromType()); assertEquals(mapping.t, mapping.flip().getToType()); assertEquals(mapping.f, mapping.selfMapping().getFromType()); assertEquals(mapping.t, mapping.selfMapping().getToType()); } private static class ParameterizedOuter<T> { @SuppressWarnings("unused") // used by reflection public Inner field; class Inner {} } public void testInnerClassWithParameterizedOwner() throws Exception { Type fieldType = ParameterizedOuter.class.getField("field") .getGenericType(); assertEquals(fieldType, TypeToken.of(ParameterizedOuter.class).resolveType(fieldType).getType()); } private interface StringIterable extends Iterable<String> {} public void testResolveType() { assertEquals(String.class, TypeToken.of(this.getClass()).resolveType(String.class).getType()); assertEquals(String.class, TypeToken.of(StringIterable.class) .resolveType(Iterable.class.getTypeParameters()[0]).getType()); assertEquals(String.class, TypeToken.of(StringIterable.class) .resolveType(Iterable.class.getTypeParameters()[0]).getType()); try { TypeToken.of(this.getClass()).resolveType(null); fail(); } catch (NullPointerException expected) {} } public void testConextIsParameterizedType() throws Exception { class Context { @SuppressWarnings("unused") // used by reflection Map<String, Integer> returningMap() { throw new AssertionError(); } } Type context = Context.class.getDeclaredMethod("returningMap") .getGenericReturnType(); Type keyType = Map.class.getTypeParameters()[0]; Type valueType = Map.class.getTypeParameters()[1]; // context is parameterized type assertEquals(String.class, TypeToken.of(context).resolveType(keyType).getType()); assertEquals(Integer.class, TypeToken.of(context).resolveType(valueType).getType()); // context is type variable assertEquals(keyType, TypeToken.of(keyType).resolveType(keyType).getType()); assertEquals(valueType, TypeToken.of(valueType).resolveType(valueType).getType()); } private static final class GenericArray<T> { final Type t = new TypeToken<T>(getClass()) {}.getType(); final Type array = new TypeToken<T[]>(getClass()) {}.getType(); } public void testGenericArrayType() { GenericArray<?> genericArray = new GenericArray<Integer>(); assertEquals(GenericArray.class.getTypeParameters()[0], genericArray.t); assertEquals(Types.newArrayType(genericArray.t), genericArray.array); } public void testClassWrapper() { TypeToken<String> typeExpression = TypeToken.of(String.class); assertEquals(String.class, typeExpression.getType()); assertEquals(String.class, typeExpression.getRawType()); } private static class Red<A> { private class Orange { Class<?> getClassA() { return new TypeToken<A>(getClass()) {}.getRawType(); } Red<A> getSelfB() { return Red.this; } } Red<A> getSelfA() { return this; } private class Yellow<B> extends Red<B>.Orange { Yellow(Red<B> red) { red.super(); } Class<?> getClassB() { return new TypeToken<B>(getClass()) {}.getRawType(); } Red<A> getA() { return getSelfA(); } Red<B> getB() { return getSelfB(); } } Class<?> getClassDirect() { return new TypeToken<A>(getClass()) {}.getRawType(); } } public void test1() { Red<String> redString = new Red<String>() {}; Red<Integer> redInteger = new Red<Integer>() {}; assertEquals(String.class, redString.getClassDirect()); assertEquals(Integer.class, redInteger.getClassDirect()); Red<String>.Yellow<Integer> yellowInteger = redString.new Yellow<Integer>(redInteger) {}; assertEquals(Integer.class, yellowInteger.getClassA()); assertEquals(Integer.class, yellowInteger.getClassB()); assertEquals(String.class, yellowInteger.getA().getClassDirect()); assertEquals(Integer.class, yellowInteger.getB().getClassDirect()); } public void test2() { Red<String> redString = new Red<String>(); Red<Integer> redInteger = new Red<Integer>(); Red<String>.Yellow<Integer> yellowInteger = redString.new Yellow<Integer>(redInteger) {}; assertEquals(Integer.class, yellowInteger.getClassA()); assertEquals(Integer.class, yellowInteger.getClassB()); } private static <T> Type staticMethodWithLocalClass() { class MyLocalClass { Type getType() { return new TypeToken<T>(getClass()) {}.getType(); } } return new MyLocalClass().getType(); } public void testLocalClassInsideStaticMethod() { assertNotNull(staticMethodWithLocalClass()); } public void testLocalClassInsideNonStaticMethod() { class MyLocalClass<T> { Type getType() { return new TypeToken<T>(getClass()) {}.getType(); } } assertNotNull(new MyLocalClass<String>().getType()); } private static <T> Type staticMethodWithAnonymousClass() { return new Object() { Type getType() { return new TypeToken<T>(getClass()) {}.getType(); } }.getType(); } public void testAnonymousClassInsideStaticMethod() { assertNotNull(staticMethodWithAnonymousClass()); } public void testAnonymousClassInsideNonStaticMethod() { assertNotNull(new Object() { Type getType() { return new TypeToken<Object>() {}.getType(); } }.getType()); } public void testStaticContext() { assertEquals(Map.class, mapType().getRawType()); } private abstract static class Holder<T> { Type getContentType() { return new TypeToken<T>(getClass()) {}.getType(); } } public void testResolvePrimitiveArrayType() { assertEquals(new TypeToken<int[]>() {}.getType(), new Holder<int[]>() {}.getContentType()); assertEquals(new TypeToken<int[][]> () {}.getType(), new Holder<int[][]>() {}.getContentType()); } public void testResolveToGenericArrayType() { GenericArrayType arrayType = (GenericArrayType) new Holder<List<int[][]>[]>() {}.getContentType(); ParameterizedType listType = (ParameterizedType) arrayType.getGenericComponentType(); assertEquals(List.class, listType.getRawType()); assertEquals(Types.newArrayType(int[].class), listType.getActualTypeArguments()[0]); } private abstract class WithGenericBound<A> { @SuppressWarnings("unused") public <B extends A> void withTypeVariable(List<B> list) {} @SuppressWarnings("unused") public <E extends Enum<E>> void withRecursiveBound(List<E> list) {} @SuppressWarnings("unused") public <K extends List<V>, V extends List<K>> void withMutualRecursiveBound( List<Map<K, V>> list) {} @SuppressWarnings("unused") void withWildcardLowerBound(List<? super A> list) {} @SuppressWarnings("unused") void withWildcardUpperBound(List<? extends A> list) {} Type getTargetType(String methodName) throws Exception { ParameterizedType parameterType = (ParameterizedType) WithGenericBound.class.getDeclaredMethod(methodName, List.class) .getGenericParameterTypes()[0]; parameterType = (ParameterizedType) TypeToken.of(this.getClass()).resolveType(parameterType).getType(); return parameterType.getActualTypeArguments()[0]; } } public void testWithGenericBoundInTypeVariable() throws Exception { TypeVariable<?> typeVariable = (TypeVariable<?>) new WithGenericBound<String>() {}.getTargetType("withTypeVariable"); assertEquals(String.class, typeVariable.getBounds()[0]); } public void testWithRecursiveBoundInTypeVariable() throws Exception { TypeVariable<?> typeVariable = (TypeVariable<?>) new WithGenericBound<String>() {}.getTargetType("withRecursiveBound"); assertEquals(Types.newParameterizedType(Enum.class, typeVariable), typeVariable.getBounds()[0]); } public void testWithMutualRecursiveBoundInTypeVariable() throws Exception { ParameterizedType paramType = (ParameterizedType) new WithGenericBound<String>() {} .getTargetType("withMutualRecursiveBound"); TypeVariable<?> k = (TypeVariable<?>) paramType.getActualTypeArguments()[0]; TypeVariable<?> v = (TypeVariable<?>) paramType.getActualTypeArguments()[1]; assertEquals(Types.newParameterizedType(List.class, v), k.getBounds()[0]); assertEquals(Types.newParameterizedType(List.class, k), v.getBounds()[0]); } public void testWithGenericLowerBoundInWildcard() throws Exception { WildcardType wildcardType = (WildcardType) new WithGenericBound<String>() {} .getTargetType("withWildcardLowerBound"); assertEquals(String.class, wildcardType.getLowerBounds()[0]); } public void testWithGenericUpperBoundInWildcard() throws Exception { WildcardType wildcardType = (WildcardType) new WithGenericBound<String>() {} .getTargetType("withWildcardUpperBound"); assertEquals(String.class, wildcardType.getUpperBounds()[0]); } public void testInterfaceTypeParameterResolution() throws Exception { assertEquals(String.class, TypeToken.of(new TypeToken<ArrayList<String>>() {}.getType()) .resolveType(List.class.getTypeParameters()[0]).getType()); } private static TypeToken<Map<Object, Object>> mapType() { return new TypeToken<Map<Object, Object>>() {}; } // Looks like recursive, but legit. private interface WithFalseRecursiveType<K, V> { WithFalseRecursiveType<List<V>, String> keyShouldNotResolveToStringList(); WithFalseRecursiveType<List<K>, List<V>> shouldNotCauseInfiniteLoop(); SubTypeOfWithFalseRecursiveType<List<V>, List<K>> evenSubTypeWorks(); } private interface SubTypeOfWithFalseRecursiveType<K1, V1> extends WithFalseRecursiveType<List<K1>, List<V1>> { SubTypeOfWithFalseRecursiveType<V1, K1> revertKeyAndValueTypes(); } public void testFalseRecursiveType_mappingOnTheSameDeclarationNotUsed() { Type returnType = genericReturnType( WithFalseRecursiveType.class, "keyShouldNotResolveToStringList"); TypeToken<?> keyType = TypeToken.of(returnType) .resolveType(WithFalseRecursiveType.class.getTypeParameters()[0]); assertEquals("java.util.List<V>", keyType.getType().toString()); } public void testFalseRecursiveType_notRealRecursiveMapping() { Type returnType = genericReturnType( WithFalseRecursiveType.class, "shouldNotCauseInfiniteLoop"); TypeToken<?> keyType = TypeToken.of(returnType) .resolveType(WithFalseRecursiveType.class.getTypeParameters()[0]); assertEquals("java.util.List<K>", keyType.getType().toString()); } public void testFalseRecursiveType_referenceOfSubtypeDoesNotConfuseMe() { Type returnType = genericReturnType( WithFalseRecursiveType.class, "evenSubTypeWorks"); TypeToken<?> keyType = TypeToken.of(returnType) .resolveType(WithFalseRecursiveType.class.getTypeParameters()[0]); assertEquals("java.util.List<java.util.List<V>>", keyType.getType().toString()); } public void testFalseRecursiveType_intermediaryTypeMappingDoesNotConfuseMe() { Type returnType = genericReturnType( SubTypeOfWithFalseRecursiveType.class, "revertKeyAndValueTypes"); TypeToken<?> keyType = TypeToken.of(returnType) .resolveType(WithFalseRecursiveType.class.getTypeParameters()[0]); assertEquals("java.util.List<K1>", keyType.getType().toString()); } private static Type genericReturnType(Class<?> cls, String methodName) { try { return cls.getMethod(methodName).getGenericReturnType(); } catch (Exception e) { throw new RuntimeException(e); } } }