1 /**
2  * Copyright (C) 2008 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.inject;
18 
19 import static com.google.inject.Asserts.assertEqualsBothWays;
20 import static com.google.inject.Asserts.assertNotSerializable;
21 import static com.google.inject.util.Types.arrayOf;
22 import static com.google.inject.util.Types.listOf;
23 import static com.google.inject.util.Types.newParameterizedType;
24 import static com.google.inject.util.Types.newParameterizedTypeWithOwner;
25 import static com.google.inject.util.Types.setOf;
26 
27 import com.google.common.collect.ImmutableList;
28 import com.google.inject.util.Types;
29 
30 import junit.framework.TestCase;
31 
32 import java.io.IOException;
33 import java.lang.reflect.Constructor;
34 import java.lang.reflect.Field;
35 import java.lang.reflect.Method;
36 import java.lang.reflect.Type;
37 import java.util.AbstractCollection;
38 import java.util.AbstractList;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.Collection;
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Set;
46 
47 /**
48  * This test checks that TypeLiteral can perform type resolution on its members.
49  *
50  * @author jessewilson@google.com (Jesse Wilson)
51  */
52 public class TypeLiteralTypeResolutionTest extends TestCase {
53   Type arrayListOfString = newParameterizedType(ArrayList.class, String.class);
54   Type hasGenericFieldsOfShort = newParameterizedTypeWithOwner(
55       getClass(), HasGenericFields.class, Short.class);
56   Type hasGenericConstructorOfShort = newParameterizedTypeWithOwner(
57       getClass(), GenericConstructor.class, Short.class);
58   Type throwerOfNpe = newParameterizedTypeWithOwner(
59       getClass(), Thrower.class, NullPointerException.class);
60   Type hasArrayOfShort = newParameterizedTypeWithOwner(getClass(), HasArray.class, Short.class);
61   Type hasRelatedOfString = newParameterizedTypeWithOwner(
62       getClass(), HasRelated.class, String.class, String.class);
63   Type mapK = Map.class.getTypeParameters()[0];
64   Type hashMapK = HashMap.class.getTypeParameters()[0];
65   Type setEntryKV;
66   Type entryStringInteger = setOf(newParameterizedTypeWithOwner(
67       Map.class, Map.Entry.class, String.class, Integer.class));
68   Field list;
69   Field instance;
70   Constructor<GenericConstructor> newHasGenericConstructor;
71   Constructor<Thrower> newThrower;
72   Constructor newString;
73   Method stringIndexOf;
74   Method comparableCompareTo;
75   Method getArray;
76   Method getSetOfArray;
77   Method echo;
78   Method throwS;
79 
setUp()80   @Override protected void setUp() throws Exception {
81     super.setUp();
82 
83     list = HasGenericFields.class.getField("list");
84     instance = HasGenericFields.class.getField("instance");
85     newHasGenericConstructor = GenericConstructor.class.getConstructor(Object.class, Object.class);
86     newThrower = Thrower.class.getConstructor();
87     stringIndexOf = String.class.getMethod("indexOf", String.class);
88     newString = String.class.getConstructor(String.class);
89     comparableCompareTo = Comparable.class.getMethod("compareTo", Object.class);
90     getArray = HasArray.class.getMethod("getArray");
91     getSetOfArray = HasArray.class.getMethod("getSetOfArray");
92     echo = HasRelated.class.getMethod("echo", Object.class);
93     throwS = Thrower.class.getMethod("throwS");
94     setEntryKV = HashMap.class.getMethod("entrySet").getGenericReturnType();
95   }
96 
testDirectInheritance()97   public void testDirectInheritance() throws NoSuchMethodException {
98     TypeLiteral<?> resolver = TypeLiteral.get(arrayListOfString);
99     assertEquals(listOf(String.class),
100         resolver.getReturnType(List.class.getMethod("subList", int.class, int.class)).getType());
101     assertEquals(ImmutableList.<TypeLiteral<?>>of(TypeLiteral.get(String.class)),
102         resolver.getParameterTypes(Collection.class.getMethod("add", Object.class)));
103   }
104 
testGenericSupertype()105   public void testGenericSupertype() {
106     TypeLiteral<?> resolver = TypeLiteral.get(arrayListOfString);
107     assertEquals(newParameterizedType(Collection.class, String.class),
108         resolver.getSupertype(Collection.class).getType());
109     assertEquals(newParameterizedType(Iterable.class, String.class),
110         resolver.getSupertype(Iterable.class).getType());
111     assertEquals(newParameterizedType(AbstractList.class, String.class),
112         resolver.getSupertype(AbstractList.class).getType());
113     assertEquals(Object.class, resolver.getSupertype(Object.class).getType());
114   }
115 
testRecursiveTypeVariable()116   public void testRecursiveTypeVariable() {
117     TypeLiteral<?> resolver = TypeLiteral.get(MyInteger.class);
118     assertEquals(MyInteger.class, resolver.getParameterTypes(comparableCompareTo).get(0).getType());
119   }
120 
121   interface MyComparable<E extends MyComparable<E>> extends Comparable<E> {}
122 
123   static class MyInteger implements MyComparable<MyInteger> {
124     int value;
compareTo(MyInteger o)125     public int compareTo(MyInteger o) {
126       return value - o.value;
127     }
128   }
129 
testFields()130   public void testFields() {
131     TypeLiteral<?> resolver = TypeLiteral.get(hasGenericFieldsOfShort);
132     assertEquals(listOf(Short.class), resolver.getFieldType(list).getType());
133     assertEquals(Short.class, resolver.getFieldType(instance).getType());
134   }
135 
136   static class HasGenericFields<T> {
137     public List<T> list;
138     public T instance;
139   }
140 
testGenericConstructor()141   public void testGenericConstructor() throws NoSuchMethodException {
142     TypeLiteral<?> resolver = TypeLiteral.get(hasGenericConstructorOfShort);
143     assertEquals(Short.class,
144         resolver.getParameterTypes(newHasGenericConstructor).get(0).getType());
145   }
146 
147   static class GenericConstructor<S> {
148     @SuppressWarnings("UnusedDeclaration")
GenericConstructor(S s, T t)149     public <T> GenericConstructor(S s, T t) {}
150   }
151 
testThrowsExceptions()152   public void testThrowsExceptions() {
153     TypeLiteral<?> type = TypeLiteral.get(throwerOfNpe);
154     assertEquals(NullPointerException.class, type.getExceptionTypes(newThrower).get(0).getType());
155     assertEquals(NullPointerException.class, type.getExceptionTypes(throwS).get(0).getType());
156   }
157 
158   static class Thrower<S extends Exception> {
Thrower()159     public Thrower() throws S {}
throwS()160     public void throwS() throws S {}
161   }
162 
testArrays()163   public void testArrays() {
164     TypeLiteral<?> resolver = TypeLiteral.get(hasArrayOfShort);
165     assertEquals(arrayOf(Short.class), resolver.getReturnType(getArray).getType());
166     assertEquals(setOf(arrayOf(Short.class)), resolver.getReturnType(getSetOfArray).getType());
167   }
168 
169   static interface HasArray<T extends Number> {
getArray()170     T[] getArray();
getSetOfArray()171     Set<T[]> getSetOfArray();
172   }
173 
testRelatedTypeVariables()174   public void testRelatedTypeVariables() {
175     TypeLiteral<?> resolver = TypeLiteral.get(hasRelatedOfString);
176     assertEquals(String.class, resolver.getParameterTypes(echo).get(0).getType());
177     assertEquals(String.class, resolver.getReturnType(echo).getType());
178   }
179 
180   interface HasRelated<T, R extends T> {
echo(R r)181     T echo(R r);
182   }
183 
184   /** Ensure the cache doesn't cache too much */
testCachingAndReindexing()185   public void testCachingAndReindexing() throws NoSuchMethodException {
186     TypeLiteral<?> resolver = TypeLiteral.get(
187         newParameterizedTypeWithOwner(getClass(), HasLists.class, String.class, Short.class));
188     assertEquals(listOf(String.class),
189         resolver.getReturnType(HasLists.class.getMethod("listS")).getType());
190     assertEquals(listOf(Short.class),
191         resolver.getReturnType(HasLists.class.getMethod("listT")).getType());
192   }
193 
194   interface HasLists<S, T> {
listS()195     List<S> listS();
listT()196     List<T> listT();
listEntries()197     List<Map.Entry<S, T>> listEntries();
198   }
199 
testUnsupportedQueries()200   public void testUnsupportedQueries() throws NoSuchMethodException {
201     TypeLiteral<?> resolver = TypeLiteral.get(arrayListOfString);
202 
203     try {
204       resolver.getExceptionTypes(stringIndexOf);
205       fail();
206     } catch (IllegalArgumentException e) {
207       assertEquals("public int java.lang.String.indexOf(java.lang.String) is not defined by a "
208           + "supertype of java.util.ArrayList<java.lang.String>", e.getMessage());
209     }
210     try {
211       resolver.getParameterTypes(stringIndexOf);
212       fail();
213     } catch (Exception e) {
214       assertEquals("public int java.lang.String.indexOf(java.lang.String) is not defined by a "
215           + "supertype of java.util.ArrayList<java.lang.String>", e.getMessage());
216     }
217     try {
218       resolver.getReturnType(stringIndexOf);
219       fail();
220     } catch (Exception e) {
221       assertEquals("public int java.lang.String.indexOf(java.lang.String) is not defined by a "
222           + "supertype of java.util.ArrayList<java.lang.String>", e.getMessage());
223     }
224     try {
225       resolver.getSupertype(String.class);
226       fail();
227     } catch (Exception e) {
228       assertEquals("class java.lang.String is not a supertype of "
229           + "java.util.ArrayList<java.lang.String>", e.getMessage());
230     }
231     try {
232       resolver.getExceptionTypes(newString);
233       fail();
234     } catch (Exception e) {
235       assertEquals("public java.lang.String(java.lang.String) does not construct "
236           + "a supertype of java.util.ArrayList<java.lang.String>", e.getMessage());
237     }
238     try {
239       resolver.getParameterTypes(newString);
240       fail();
241     } catch (Exception e) {
242       assertEquals("public java.lang.String(java.lang.String) does not construct "
243           + "a supertype of java.util.ArrayList<java.lang.String>", e.getMessage());
244     }
245   }
246 
testResolve()247   public void testResolve() {
248     TypeLiteral<?> typeResolver = TypeLiteral.get(StringIntegerMap.class);
249     assertEquals(String.class, typeResolver.resolveType(mapK));
250 
251     typeResolver = new TypeLiteral<Map<String, Integer>>() {};
252     assertEquals(String.class, typeResolver.resolveType(mapK));
253     assertEquals(Types.mapOf(String.class, Integer.class),
254         typeResolver.getSupertype(Map.class).getType());
255 
256     typeResolver = new TypeLiteral<BetterMap<String, Integer>>() {};
257     assertEquals(String.class, typeResolver.resolveType(mapK));
258 
259     typeResolver = new TypeLiteral<BestMap<String, Integer>>() {};
260     assertEquals(String.class, typeResolver.resolveType(mapK));
261 
262     typeResolver = TypeLiteral.get(StringIntegerHashMap.class);
263     assertEquals(String.class, typeResolver.resolveType(mapK));
264     assertEquals(String.class, typeResolver.resolveType(hashMapK));
265     assertEquals(entryStringInteger, typeResolver.resolveType(setEntryKV));
266     assertEquals(Object.class, typeResolver.getSupertype(Object.class).getType());
267   }
268 
testOnObject()269   public void testOnObject() {
270     TypeLiteral<?> typeResolver = TypeLiteral.get(Object.class);
271     assertEquals(Object.class, typeResolver.getSupertype(Object.class).getType());
272     assertEquals(Object.class, typeResolver.getRawType());
273 
274     // interfaces also resolve Object
275     typeResolver = TypeLiteral.get(Types.setOf(Integer.class));
276     assertEquals(Object.class, typeResolver.getSupertype(Object.class).getType());
277   }
278 
279   interface StringIntegerMap extends Map<String, Integer> {}
280   interface BetterMap<K1, V1> extends Map<K1, V1> {}
281   interface BestMap<K2, V2> extends BetterMap<K2, V2> {}
282   static class StringIntegerHashMap extends HashMap<String, Integer> {}
283 
testGetSupertype()284   public void testGetSupertype() {
285     TypeLiteral<AbstractList<String>> listOfString = new TypeLiteral<AbstractList<String>>() {};
286     assertEquals(Types.newParameterizedType(AbstractCollection.class, String.class),
287         listOfString.getSupertype(AbstractCollection.class).getType());
288 
289     TypeLiteral arrayListOfE = TypeLiteral.get(newParameterizedType(
290         ArrayList.class, ArrayList.class.getTypeParameters()));
291     assertEquals(
292         newParameterizedType(AbstractCollection.class, ArrayList.class.getTypeParameters()),
293         arrayListOfE.getSupertype(AbstractCollection.class).getType());
294   }
295 
testGetSupertypeForArraysAsList()296   public void testGetSupertypeForArraysAsList() {
297     Class<? extends List> arraysAsListClass = Arrays.asList().getClass();
298     Type anotherE = arraysAsListClass.getTypeParameters()[0];
299     TypeLiteral type = TypeLiteral.get(newParameterizedType(AbstractList.class, anotherE));
300     assertEquals(newParameterizedType(AbstractCollection.class, anotherE),
301         type.getSupertype(AbstractCollection.class).getType());
302   }
303 
testWildcards()304   public void testWildcards() throws NoSuchFieldException {
305     TypeLiteral<Parameterized<String>> ofString = new TypeLiteral<Parameterized<String>>() {};
306 
307     assertEquals(new TypeLiteral<List<String>>() {}.getType(),
308         ofString.getFieldType(Parameterized.class.getField("t")).getType());
309     assertEquals(new TypeLiteral<List<? extends String>>() {}.getType(),
310         ofString.getFieldType(Parameterized.class.getField("extendsT")).getType());
311     assertEquals(new TypeLiteral<List<? super String>>() {}.getType(),
312         ofString.getFieldType(Parameterized.class.getField("superT")).getType());
313   }
314 
315   static class Parameterized<T> {
316     public List<T> t;
317     public List<? extends T> extendsT;
318     public List<? super T> superT;
319   }
320 
321   // TODO(jessewilson): tests for tricky bounded types like <T extends Collection, Serializable>
322 
testEqualsAndHashCode()323   public void testEqualsAndHashCode() throws IOException {
324     TypeLiteral<?> a1 = TypeLiteral.get(arrayListOfString);
325     TypeLiteral<?> a2 = TypeLiteral.get(arrayListOfString);
326     TypeLiteral<?> b = TypeLiteral.get(listOf(String.class));
327     assertEqualsBothWays(a1, a2);
328     assertNotSerializable(a1);
329     assertFalse(a1.equals(b));
330   }
331 }
332