/* * Copyright (C) 2008 Google Inc. * * 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.inject; import static com.google.inject.Asserts.assertEqualsBothWays; import static com.google.inject.Asserts.assertNotSerializable; import static com.google.inject.util.Types.arrayOf; import static com.google.inject.util.Types.listOf; import static com.google.inject.util.Types.newParameterizedType; import static com.google.inject.util.Types.newParameterizedTypeWithOwner; import static com.google.inject.util.Types.setOf; import com.google.common.collect.ImmutableList; import com.google.inject.util.Types; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.AbstractCollection; import java.util.AbstractList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import junit.framework.TestCase; /** * This test checks that TypeLiteral can perform type resolution on its members. * * @author jessewilson@google.com (Jesse Wilson) */ public class TypeLiteralTypeResolutionTest extends TestCase { Type arrayListOfString = newParameterizedType(ArrayList.class, String.class); Type hasGenericFieldsOfShort = newParameterizedTypeWithOwner(getClass(), HasGenericFields.class, Short.class); Type hasGenericConstructorOfShort = newParameterizedTypeWithOwner(getClass(), GenericConstructor.class, Short.class); Type throwerOfNpe = newParameterizedTypeWithOwner(getClass(), Thrower.class, NullPointerException.class); Type hasArrayOfShort = newParameterizedTypeWithOwner(getClass(), HasArray.class, Short.class); Type hasRelatedOfString = newParameterizedTypeWithOwner(getClass(), HasRelated.class, String.class, String.class); Type mapK = Map.class.getTypeParameters()[0]; Type hashMapK = HashMap.class.getTypeParameters()[0]; Type setEntryKV; Type entryStringInteger = setOf(newParameterizedTypeWithOwner(Map.class, Map.Entry.class, String.class, Integer.class)); Field list; Field instance; Constructor<GenericConstructor> newHasGenericConstructor; Constructor<Thrower> newThrower; Constructor newString; Method stringIndexOf; Method comparableCompareTo; Method getArray; Method getSetOfArray; Method echo; Method throwS; @Override protected void setUp() throws Exception { super.setUp(); list = HasGenericFields.class.getField("list"); instance = HasGenericFields.class.getField("instance"); newHasGenericConstructor = GenericConstructor.class.getConstructor(Object.class, Object.class); newThrower = Thrower.class.getConstructor(); stringIndexOf = String.class.getMethod("indexOf", String.class); newString = String.class.getConstructor(String.class); comparableCompareTo = Comparable.class.getMethod("compareTo", Object.class); getArray = HasArray.class.getMethod("getArray"); getSetOfArray = HasArray.class.getMethod("getSetOfArray"); echo = HasRelated.class.getMethod("echo", Object.class); throwS = Thrower.class.getMethod("throwS"); setEntryKV = HashMap.class.getMethod("entrySet").getGenericReturnType(); } public void testDirectInheritance() throws NoSuchMethodException { TypeLiteral<?> resolver = TypeLiteral.get(arrayListOfString); assertEquals( listOf(String.class), resolver.getReturnType(List.class.getMethod("subList", int.class, int.class)).getType()); assertEquals( ImmutableList.<TypeLiteral<?>>of(TypeLiteral.get(String.class)), resolver.getParameterTypes(Collection.class.getMethod("add", Object.class))); } public void testGenericSupertype() { TypeLiteral<?> resolver = TypeLiteral.get(arrayListOfString); assertEquals( newParameterizedType(Collection.class, String.class), resolver.getSupertype(Collection.class).getType()); assertEquals( newParameterizedType(Iterable.class, String.class), resolver.getSupertype(Iterable.class).getType()); assertEquals( newParameterizedType(AbstractList.class, String.class), resolver.getSupertype(AbstractList.class).getType()); assertEquals(Object.class, resolver.getSupertype(Object.class).getType()); } public void testRecursiveTypeVariable() { TypeLiteral<?> resolver = TypeLiteral.get(MyInteger.class); assertEquals(MyInteger.class, resolver.getParameterTypes(comparableCompareTo).get(0).getType()); } interface MyComparable<E extends MyComparable<E>> extends Comparable<E> {} static class MyInteger implements MyComparable<MyInteger> { int value; @Override public int compareTo(MyInteger o) { return value - o.value; } } public void testFields() { TypeLiteral<?> resolver = TypeLiteral.get(hasGenericFieldsOfShort); assertEquals(listOf(Short.class), resolver.getFieldType(list).getType()); assertEquals(Short.class, resolver.getFieldType(instance).getType()); } static class HasGenericFields<T> { public List<T> list; public T instance; } public void testGenericConstructor() throws NoSuchMethodException { TypeLiteral<?> resolver = TypeLiteral.get(hasGenericConstructorOfShort); assertEquals( Short.class, resolver.getParameterTypes(newHasGenericConstructor).get(0).getType()); } static class GenericConstructor<S> { @SuppressWarnings("UnusedDeclaration") public <T> GenericConstructor(S s, T t) {} } public void testThrowsExceptions() { TypeLiteral<?> type = TypeLiteral.get(throwerOfNpe); assertEquals(NullPointerException.class, type.getExceptionTypes(newThrower).get(0).getType()); assertEquals(NullPointerException.class, type.getExceptionTypes(throwS).get(0).getType()); } static class Thrower<S extends Exception> { public Thrower() throws S {} public void throwS() throws S {} } public void testArrays() { TypeLiteral<?> resolver = TypeLiteral.get(hasArrayOfShort); assertEquals(arrayOf(Short.class), resolver.getReturnType(getArray).getType()); assertEquals(setOf(arrayOf(Short.class)), resolver.getReturnType(getSetOfArray).getType()); } static interface HasArray<T extends Number> { T[] getArray(); Set<T[]> getSetOfArray(); } public void testRelatedTypeVariables() { TypeLiteral<?> resolver = TypeLiteral.get(hasRelatedOfString); assertEquals(String.class, resolver.getParameterTypes(echo).get(0).getType()); assertEquals(String.class, resolver.getReturnType(echo).getType()); } interface HasRelated<T, R extends T> { T echo(R r); } /** Ensure the cache doesn't cache too much */ public void testCachingAndReindexing() throws NoSuchMethodException { TypeLiteral<?> resolver = TypeLiteral.get( newParameterizedTypeWithOwner(getClass(), HasLists.class, String.class, Short.class)); assertEquals( listOf(String.class), resolver.getReturnType(HasLists.class.getMethod("listS")).getType()); assertEquals( listOf(Short.class), resolver.getReturnType(HasLists.class.getMethod("listT")).getType()); } interface HasLists<S, T> { List<S> listS(); List<T> listT(); List<Map.Entry<S, T>> listEntries(); } public void testUnsupportedQueries() throws NoSuchMethodException { TypeLiteral<?> resolver = TypeLiteral.get(arrayListOfString); try { resolver.getExceptionTypes(stringIndexOf); fail(); } catch (IllegalArgumentException e) { assertEquals( "public int java.lang.String.indexOf(java.lang.String) is not defined by a " + "supertype of java.util.ArrayList<java.lang.String>", e.getMessage()); } try { resolver.getParameterTypes(stringIndexOf); fail(); } catch (Exception e) { assertEquals( "public int java.lang.String.indexOf(java.lang.String) is not defined by a " + "supertype of java.util.ArrayList<java.lang.String>", e.getMessage()); } try { resolver.getReturnType(stringIndexOf); fail(); } catch (Exception e) { assertEquals( "public int java.lang.String.indexOf(java.lang.String) is not defined by a " + "supertype of java.util.ArrayList<java.lang.String>", e.getMessage()); } try { resolver.getSupertype(String.class); fail(); } catch (Exception e) { assertEquals( "class java.lang.String is not a supertype of " + "java.util.ArrayList<java.lang.String>", e.getMessage()); } try { resolver.getExceptionTypes(newString); fail(); } catch (Exception e) { assertEquals( "public java.lang.String(java.lang.String) does not construct " + "a supertype of java.util.ArrayList<java.lang.String>", e.getMessage()); } try { resolver.getParameterTypes(newString); fail(); } catch (Exception e) { assertEquals( "public java.lang.String(java.lang.String) does not construct " + "a supertype of java.util.ArrayList<java.lang.String>", e.getMessage()); } } public void testResolve() { TypeLiteral<?> typeResolver = TypeLiteral.get(StringIntegerMap.class); assertEquals(String.class, typeResolver.resolveType(mapK)); typeResolver = new TypeLiteral<Map<String, Integer>>() {}; assertEquals(String.class, typeResolver.resolveType(mapK)); assertEquals( Types.mapOf(String.class, Integer.class), typeResolver.getSupertype(Map.class).getType()); typeResolver = new TypeLiteral<BetterMap<String, Integer>>() {}; assertEquals(String.class, typeResolver.resolveType(mapK)); typeResolver = new TypeLiteral<BestMap<String, Integer>>() {}; assertEquals(String.class, typeResolver.resolveType(mapK)); typeResolver = TypeLiteral.get(StringIntegerHashMap.class); assertEquals(String.class, typeResolver.resolveType(mapK)); assertEquals(String.class, typeResolver.resolveType(hashMapK)); assertEquals(entryStringInteger, typeResolver.resolveType(setEntryKV)); assertEquals(Object.class, typeResolver.getSupertype(Object.class).getType()); } public void testOnObject() { TypeLiteral<?> typeResolver = TypeLiteral.get(Object.class); assertEquals(Object.class, typeResolver.getSupertype(Object.class).getType()); assertEquals(Object.class, typeResolver.getRawType()); // interfaces also resolve Object typeResolver = TypeLiteral.get(Types.setOf(Integer.class)); assertEquals(Object.class, typeResolver.getSupertype(Object.class).getType()); } interface StringIntegerMap extends Map<String, Integer> {} interface BetterMap<K1, V1> extends Map<K1, V1> {} interface BestMap<K2, V2> extends BetterMap<K2, V2> {} static class StringIntegerHashMap extends HashMap<String, Integer> {} public void testGetSupertype() { TypeLiteral<AbstractList<String>> listOfString = new TypeLiteral<AbstractList<String>>() {}; assertEquals( Types.newParameterizedType(AbstractCollection.class, String.class), listOfString.getSupertype(AbstractCollection.class).getType()); TypeLiteral arrayListOfE = TypeLiteral.get(newParameterizedType(ArrayList.class, ArrayList.class.getTypeParameters())); assertEquals( newParameterizedType(AbstractCollection.class, ArrayList.class.getTypeParameters()), arrayListOfE.getSupertype(AbstractCollection.class).getType()); } public void testGetSupertypeForArraysAsList() { Class<? extends List> arraysAsListClass = Arrays.asList().getClass(); Type anotherE = arraysAsListClass.getTypeParameters()[0]; TypeLiteral type = TypeLiteral.get(newParameterizedType(AbstractList.class, anotherE)); assertEquals( newParameterizedType(AbstractCollection.class, anotherE), type.getSupertype(AbstractCollection.class).getType()); } public void testWildcards() throws NoSuchFieldException { TypeLiteral<Parameterized<String>> ofString = new TypeLiteral<Parameterized<String>>() {}; assertEquals( new TypeLiteral<List<String>>() {}.getType(), ofString.getFieldType(Parameterized.class.getField("t")).getType()); assertEquals( new TypeLiteral<List<? extends String>>() {}.getType(), ofString.getFieldType(Parameterized.class.getField("extendsT")).getType()); assertEquals( new TypeLiteral<List<? super String>>() {}.getType(), ofString.getFieldType(Parameterized.class.getField("superT")).getType()); } static class Parameterized<T> { public List<T> t; public List<? extends T> extendsT; public List<? super T> superT; } // TODO(jessewilson): tests for tricky bounded types like <T extends Collection, Serializable> public void testEqualsAndHashCode() throws IOException { TypeLiteral<?> a1 = TypeLiteral.get(arrayListOfString); TypeLiteral<?> a2 = TypeLiteral.get(arrayListOfString); TypeLiteral<?> b = TypeLiteral.get(listOf(String.class)); assertEqualsBothWays(a1, a2); assertNotSerializable(a1); assertFalse(a1.equals(b)); } }