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