blob: 69e59ff18895b8e0e687d61c2a37f76b16001567 [file] [log] [blame]
/*
* 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 com.google.common.truth.Truth.assertThat;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
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;
import junit.framework.TestCase;
/**
* Unit test for {@link TypeToken} and {@link TypeResolver}.
*
* @author Ben Yu
*/
@AndroidIncompatible // lots of failures, possibly some related to bad equals() implementations?
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<>();
assertEquals(String.class, foo.getClassA());
assertEquals(String[].class, foo.getArrayClassA());
assertEquals(Object.class, foo.getClassB());
assertEquals(Object[].class, foo.getArrayClassB());
}
private abstract static 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.covariantly(parameterized.getClass());
ParameterizedType resolved =
(ParameterizedType) typeResolver.resolveType(parameterized.parameterizedType());
assertEquals(TypeTokenResolutionTest.class, resolved.getOwnerType());
assertEquals(Bar.class, resolved.getRawType());
assertThat(resolved.getActualTypeArguments()).asList().contains(String.class);
}
private interface StringListPredicate extends Predicate<List<String>> {}
private interface IntegerSupplier extends Supplier<Integer> {}
// Intentionally duplicate the Predicate interface to test that it won't cause
// exceptions
private interface IntegerStringFunction
extends IntegerSupplier, Predicate<List<String>>, StringListPredicate {}
public void testGenericInterface() {
// test the 1st generic interface on the class
Type fType = Supplier.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 abstract static 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 abstract static 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<>();
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<>();
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<>();
Red<Integer> redInteger = new Red<>();
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);
}
}
public void testTwoStageResolution() {
class ForTwoStageResolution<A extends Number> {
<B extends A> void verifyTwoStageResolution() {
@SuppressWarnings({"unchecked", "rawtypes"})
Type type =
new TypeToken<B>(getClass()) {}
// B's bound may have already resolved to something.
// Make sure it can still further resolve when given a context.
.where(new TypeParameter<B>() {}, (Class) Integer.class).getType();
assertEquals(Integer.class, type);
}
}
new ForTwoStageResolution<Integer>().verifyTwoStageResolution();
new ForTwoStageResolution<Integer>() {}.verifyTwoStageResolution();
}
}