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 static com.google.inject.util.Types.arrayOf;
22import static com.google.inject.util.Types.listOf;
23import static com.google.inject.util.Types.newParameterizedType;
24import static com.google.inject.util.Types.newParameterizedTypeWithOwner;
25import static com.google.inject.util.Types.setOf;
26
27import com.google.common.collect.ImmutableList;
28import com.google.inject.util.Types;
29
30import junit.framework.TestCase;
31
32import java.io.IOException;
33import java.lang.reflect.Constructor;
34import java.lang.reflect.Field;
35import java.lang.reflect.Method;
36import java.lang.reflect.Type;
37import java.util.AbstractCollection;
38import java.util.AbstractList;
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.Collection;
42import java.util.HashMap;
43import java.util.List;
44import java.util.Map;
45import 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 */
52public 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
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
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
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
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;
125    public int compareTo(MyInteger o) {
126      return value - o.value;
127    }
128  }
129
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
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")
149    public <T> GenericConstructor(S s, T t) {}
150  }
151
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> {
159    public Thrower() throws S {}
160    public void throwS() throws S {}
161  }
162
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> {
170    T[] getArray();
171    Set<T[]> getSetOfArray();
172  }
173
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> {
181    T echo(R r);
182  }
183
184  /** Ensure the cache doesn't cache too much */
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> {
195    List<S> listS();
196    List<T> listT();
197    List<Map.Entry<S, T>> listEntries();
198  }
199
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
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
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
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
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
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
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