View Javadoc
1   /*
2    * Copyright (C) 2009 The Guava Authors
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.common.reflect;
18  
19  import static com.google.common.truth.Truth.assertThat;
20  
21  import com.google.common.base.Predicate;
22  import com.google.common.base.Supplier;
23  
24  import junit.framework.TestCase;
25  
26  import java.lang.reflect.GenericArrayType;
27  import java.lang.reflect.ParameterizedType;
28  import java.lang.reflect.Type;
29  import java.lang.reflect.TypeVariable;
30  import java.lang.reflect.WildcardType;
31  import java.util.ArrayList;
32  import java.util.List;
33  import java.util.Map;
34  
35  /**
36   * Unit test for {@link TypeToken} and {@link TypeResolver}.
37   * 
38   * @author Ben Yu
39   */
40  public class TypeTokenResolutionTest extends TestCase {
41  
42    private static class Foo<A, B> {
43      
44      Class<? super A> getClassA() {
45        return new TypeToken<A>(getClass()) {}.getRawType();
46      }
47      
48      Class<? super B> getClassB() {
49        return new TypeToken<B>(getClass()) {}.getRawType();
50      }
51      
52      Class<? super A[]> getArrayClassA() {
53        return new TypeToken<A[]>(getClass()) {}.getRawType();
54      }
55     
56      Type getArrayTypeA() {
57        return new TypeToken<A[]>(getClass()) {}.getType();
58      }
59      
60      Class<? super B[]> getArrayClassB() {
61        return new TypeToken<B[]>(getClass()) {}.getRawType();
62      }
63    }
64    
65    public void testSimpleTypeToken() {
66      Foo<String, Integer> foo = new Foo<String, Integer>() {};
67      assertEquals(String.class, foo.getClassA());
68      assertEquals(Integer.class, foo.getClassB());
69      assertEquals(String[].class, foo.getArrayClassA());
70      assertEquals(Integer[].class, foo.getArrayClassB());
71    }
72    
73    public void testCompositeTypeToken() {
74      Foo<String[], List<int[]>> foo = new Foo<String[], List<int[]>>() {};
75      assertEquals(String[].class, foo.getClassA());
76      assertEquals(List.class, foo.getClassB());
77      assertEquals(String[][].class, foo.getArrayClassA());
78      assertEquals(List[].class, foo.getArrayClassB());
79    }
80    
81    private static class StringFoo<T> extends Foo<String, T> {}
82    
83    public void testPartialSpecialization() {
84      StringFoo<Integer> foo = new StringFoo<Integer>() {};
85      assertEquals(String.class, foo.getClassA());
86      assertEquals(Integer.class, foo.getClassB());
87      assertEquals(String[].class, foo.getArrayClassA());
88      assertEquals(Integer[].class, foo.getArrayClassB());
89      assertEquals(new TypeToken<String[]>() {}.getType(), foo.getArrayTypeA());
90    }
91    
92    public void testTypeArgNotFound() {
93      StringFoo<Integer> foo = new StringFoo<Integer>();
94      assertEquals(String.class, foo.getClassA());
95      assertEquals(String[].class, foo.getArrayClassA());
96      assertEquals(Object.class, foo.getClassB());
97      assertEquals(Object[].class, foo.getArrayClassB());
98    }
99    
100   private static abstract class Bar<T> {}
101   
102   private abstract static class Parameterized<O, T, P> {
103     ParameterizedType parameterizedType() {
104       return new ParameterizedType() {
105         @Override public Type[] getActualTypeArguments() {
106           return new Type[]{new TypeCapture<P>() {}.capture()};
107         }
108         @Override public Type getOwnerType() {
109           return new TypeCapture<O>() {}.capture();
110         }
111         @Override public Type getRawType() {
112           return new TypeCapture<T>() {}.capture();
113         }
114       };
115     }
116   }
117   
118   public void testResolveType_parameterizedType() {
119     @SuppressWarnings("rawtypes") // trying to test raw type
120     Parameterized<?, ?, ?> parameterized =
121         new Parameterized<TypeTokenResolutionTest, Bar, String>() {};
122     TypeResolver typeResolver = TypeResolver.accordingTo(parameterized.getClass());
123     ParameterizedType resolved = (ParameterizedType) typeResolver.resolveType(
124         parameterized.parameterizedType());
125     assertEquals(TypeTokenResolutionTest.class, resolved.getOwnerType());
126     assertEquals(Bar.class, resolved.getRawType());
127     assertThat(resolved.getActualTypeArguments()).asList().has().item(String.class);
128   }
129   
130   private interface StringListPredicate extends Predicate<List<String>> {}
131 
132   private interface IntegerSupplier extends Supplier<Integer> {}
133   
134   // Intentionally duplicate the Predicate interface to test that it won't cause
135   // exceptions
136   private interface IntegerStringFunction extends IntegerSupplier,
137       Predicate<List<String>>, StringListPredicate {}
138   
139   public void testGenericInterface() {
140     // test the 1st generic interface on the class
141     Type fType = Supplier.class.getTypeParameters()[0];
142     assertEquals(Integer.class,
143         TypeToken.of(IntegerStringFunction.class).resolveType(fType)
144             .getRawType());
145     
146     // test the 2nd generic interface on the class
147     Type predicateParameterType = Predicate.class.getTypeParameters()[0];
148     assertEquals(new TypeToken<List<String>>() {}.getType(),
149         TypeToken.of(IntegerStringFunction.class).resolveType(predicateParameterType)
150             .getType());
151   }
152   
153   private static abstract class StringIntegerFoo extends Foo<String, Integer> {}
154   
155   public void testConstructor_typeArgsResolvedFromAncestorClass() {
156     assertEquals(String.class, new StringIntegerFoo() {}.getClassA());
157     assertEquals(Integer.class, new StringIntegerFoo() {}.getClassB());
158   }
159   
160   private static class Owner<T> {
161     private static abstract class Nested<X> {
162       Class<? super X> getTypeArgument() {
163         return new TypeToken<X>(getClass()) {}.getRawType();
164       }
165     }
166     
167     private abstract class Inner<Y> extends Nested<Y> {
168       Class<? super T> getOwnerType() {
169         return new TypeToken<T>(getClass()) {}.getRawType();
170       }
171     }
172   }
173   
174   public void testResolveNestedClass() {
175     assertEquals(String.class, new Owner.Nested<String>() {}.getTypeArgument());
176   }
177   
178   public void testResolveInnerClass() {
179     assertEquals(String.class,
180         new Owner<Integer>().new Inner<String>() {}.getTypeArgument());
181   }
182   
183   public void testResolveOwnerClass() {
184     assertEquals(Integer.class,
185         new Owner<Integer>().new Inner<String>() {}.getOwnerType());
186   }
187   
188   private static class Mapping<F, T> {
189     
190     final Type f = new TypeToken<F>(getClass()) {}.getType();
191     final Type t = new TypeToken<T>(getClass()) {}.getType();
192     
193     Type getFromType() {
194       return new TypeToken<F>(getClass()) {}.getType();
195     }
196     
197     Type getToType() {
198       return new TypeToken<T>(getClass()) {}.getType();
199     }
200     
201     Mapping<T, F> flip() {
202       return new Mapping<T, F>() {};
203     }
204     
205     Mapping<F, T> selfMapping() {
206       return new Mapping<F, T>() {};
207     }
208   }
209   
210   public void testCyclicMapping() {
211     Mapping<Integer, String> mapping = new Mapping<Integer, String>();
212     assertEquals(mapping.f, mapping.getFromType());
213     assertEquals(mapping.t, mapping.getToType());
214     assertEquals(mapping.f, mapping.flip().getFromType());
215     assertEquals(mapping.t, mapping.flip().getToType());
216     assertEquals(mapping.f, mapping.selfMapping().getFromType());
217     assertEquals(mapping.t, mapping.selfMapping().getToType());
218   }
219   
220   private static class ParameterizedOuter<T> {
221     
222     @SuppressWarnings("unused") // used by reflection
223     public Inner field;
224     
225     class Inner {}
226   }
227   
228   public void testInnerClassWithParameterizedOwner() throws Exception {
229     Type fieldType = ParameterizedOuter.class.getField("field")
230         .getGenericType();
231     assertEquals(fieldType,
232         TypeToken.of(ParameterizedOuter.class).resolveType(fieldType).getType());
233   }
234   
235   private interface StringIterable extends Iterable<String> {}
236 
237   public void testResolveType() {
238     assertEquals(String.class, TypeToken.of(this.getClass()).resolveType(String.class).getType());
239     assertEquals(String.class,
240         TypeToken.of(StringIterable.class)
241             .resolveType(Iterable.class.getTypeParameters()[0]).getType());
242     assertEquals(String.class,
243         TypeToken.of(StringIterable.class)
244             .resolveType(Iterable.class.getTypeParameters()[0]).getType());
245     try {
246       TypeToken.of(this.getClass()).resolveType(null);
247       fail();
248     } catch (NullPointerException expected) {}
249   }
250   
251   public void testConextIsParameterizedType() throws Exception {
252     class Context {
253       @SuppressWarnings("unused") // used by reflection
254       Map<String, Integer> returningMap() {
255         throw new AssertionError();
256       }
257     }
258     Type context = Context.class.getDeclaredMethod("returningMap")
259         .getGenericReturnType();
260     Type keyType = Map.class.getTypeParameters()[0];
261     Type valueType = Map.class.getTypeParameters()[1];
262     
263     // context is parameterized type
264     assertEquals(String.class, TypeToken.of(context).resolveType(keyType).getType());
265     assertEquals(Integer.class,
266         TypeToken.of(context).resolveType(valueType).getType());
267     
268     // context is type variable
269     assertEquals(keyType, TypeToken.of(keyType).resolveType(keyType).getType());
270     assertEquals(valueType, TypeToken.of(valueType).resolveType(valueType).getType());
271   }
272   
273   private static final class GenericArray<T> {
274     final Type t = new TypeToken<T>(getClass()) {}.getType();
275     final Type array = new TypeToken<T[]>(getClass()) {}.getType();
276   }
277   
278   public void testGenericArrayType() {
279     GenericArray<?> genericArray = new GenericArray<Integer>();
280     assertEquals(GenericArray.class.getTypeParameters()[0], genericArray.t);
281     assertEquals(Types.newArrayType(genericArray.t),
282         genericArray.array);
283   }
284   
285   public void testClassWrapper() {
286     TypeToken<String> typeExpression = TypeToken.of(String.class);
287     assertEquals(String.class, typeExpression.getType());
288     assertEquals(String.class, typeExpression.getRawType());
289   }
290   
291   private static class Red<A> {
292     private class Orange {
293       Class<?> getClassA() {
294         return new TypeToken<A>(getClass()) {}.getRawType();
295       }
296 
297       Red<A> getSelfB() {
298         return Red.this;
299       }
300     }
301 
302     Red<A> getSelfA() {
303       return this;
304     }
305 
306     private class Yellow<B> extends Red<B>.Orange {
307       Yellow(Red<B> red) {
308         red.super();
309       }
310 
311       Class<?> getClassB() {
312         return new TypeToken<B>(getClass()) {}.getRawType();
313       }
314 
315       Red<A> getA() {
316         return getSelfA();
317       }
318 
319       Red<B> getB() {
320         return getSelfB();
321       }
322     }
323 
324     Class<?> getClassDirect() {
325       return new TypeToken<A>(getClass()) {}.getRawType();
326     }
327   }
328 
329   public void test1() {
330     Red<String> redString = new Red<String>() {};
331     Red<Integer> redInteger = new Red<Integer>() {};
332     assertEquals(String.class, redString.getClassDirect());
333     assertEquals(Integer.class, redInteger.getClassDirect());
334 
335     Red<String>.Yellow<Integer> yellowInteger =
336         redString.new Yellow<Integer>(redInteger) {};
337     assertEquals(Integer.class, yellowInteger.getClassA());
338     assertEquals(Integer.class, yellowInteger.getClassB());
339     assertEquals(String.class, yellowInteger.getA().getClassDirect());
340     assertEquals(Integer.class, yellowInteger.getB().getClassDirect());
341   }
342 
343   public void test2() {
344     Red<String> redString = new Red<String>();
345     Red<Integer> redInteger = new Red<Integer>();
346     Red<String>.Yellow<Integer> yellowInteger =
347         redString.new Yellow<Integer>(redInteger) {};
348     assertEquals(Integer.class, yellowInteger.getClassA());
349     assertEquals(Integer.class, yellowInteger.getClassB());
350   }
351   
352   private static <T> Type staticMethodWithLocalClass() {
353     class MyLocalClass {
354       Type getType() {
355         return new TypeToken<T>(getClass()) {}.getType();
356       }
357     }
358     return new MyLocalClass().getType();
359   }
360   
361   public void testLocalClassInsideStaticMethod() {
362     assertNotNull(staticMethodWithLocalClass());
363   }
364   
365   public void testLocalClassInsideNonStaticMethod() {
366     class MyLocalClass<T> {
367       Type getType() {
368         return new TypeToken<T>(getClass()) {}.getType();
369       }
370     }
371     assertNotNull(new MyLocalClass<String>().getType());
372   }
373   
374   private static <T> Type staticMethodWithAnonymousClass() {
375     return new Object() {
376       Type getType() {
377         return new TypeToken<T>(getClass()) {}.getType();
378       }
379     }.getType();
380   }
381   
382   public void testAnonymousClassInsideStaticMethod() {
383     assertNotNull(staticMethodWithAnonymousClass());
384   }
385   
386   public void testAnonymousClassInsideNonStaticMethod() {
387     assertNotNull(new Object() {
388       Type getType() {
389         return new TypeToken<Object>() {}.getType();
390       }
391     }.getType());
392   }
393   
394   public void testStaticContext() {
395     assertEquals(Map.class, mapType().getRawType());
396   }
397   
398   private abstract static class Holder<T> {
399     Type getContentType() {
400       return new TypeToken<T>(getClass()) {}.getType();
401     }
402   }
403   
404   public void testResolvePrimitiveArrayType() {
405     assertEquals(new TypeToken<int[]>() {}.getType(),
406         new Holder<int[]>() {}.getContentType());
407     assertEquals(new TypeToken<int[][]> () {}.getType(),
408         new Holder<int[][]>() {}.getContentType());
409   }
410   
411   public void testResolveToGenericArrayType() {
412     GenericArrayType arrayType = (GenericArrayType)
413         new Holder<List<int[][]>[]>() {}.getContentType();
414     ParameterizedType listType = (ParameterizedType)
415         arrayType.getGenericComponentType();
416     assertEquals(List.class, listType.getRawType());
417     assertEquals(Types.newArrayType(int[].class),
418         listType.getActualTypeArguments()[0]);
419   }
420   
421   private abstract class WithGenericBound<A> {
422 
423     @SuppressWarnings("unused")
424     public <B extends A> void withTypeVariable(List<B> list) {}
425 
426     @SuppressWarnings("unused")
427     public <E extends Enum<E>> void withRecursiveBound(List<E> list) {}
428 
429     @SuppressWarnings("unused")
430     public <K extends List<V>, V extends List<K>> void withMutualRecursiveBound(
431         List<Map<K, V>> list) {}
432 
433     @SuppressWarnings("unused")
434     void withWildcardLowerBound(List<? super A> list) {}
435 
436     @SuppressWarnings("unused")
437     void withWildcardUpperBound(List<? extends A> list) {}
438     
439     Type getTargetType(String methodName) throws Exception {
440       ParameterizedType parameterType = (ParameterizedType)
441           WithGenericBound.class.getDeclaredMethod(methodName, List.class)
442               .getGenericParameterTypes()[0];
443       parameterType = (ParameterizedType)
444           TypeToken.of(this.getClass()).resolveType(parameterType).getType();
445       return parameterType.getActualTypeArguments()[0];
446     }
447   }
448   
449   public void testWithGenericBoundInTypeVariable() throws Exception {
450     TypeVariable<?> typeVariable = (TypeVariable<?>)
451         new WithGenericBound<String>() {}.getTargetType("withTypeVariable");
452     assertEquals(String.class, typeVariable.getBounds()[0]);
453   }
454   
455   public void testWithRecursiveBoundInTypeVariable() throws Exception {
456     TypeVariable<?> typeVariable = (TypeVariable<?>)
457         new WithGenericBound<String>() {}.getTargetType("withRecursiveBound");
458     assertEquals(Types.newParameterizedType(Enum.class, typeVariable),
459         typeVariable.getBounds()[0]);
460   }
461   
462   public void testWithMutualRecursiveBoundInTypeVariable() throws Exception {
463     ParameterizedType paramType = (ParameterizedType)
464         new WithGenericBound<String>() {}
465             .getTargetType("withMutualRecursiveBound");
466     TypeVariable<?> k = (TypeVariable<?>) paramType.getActualTypeArguments()[0];
467     TypeVariable<?> v = (TypeVariable<?>) paramType.getActualTypeArguments()[1];
468     assertEquals(Types.newParameterizedType(List.class, v), k.getBounds()[0]);
469     assertEquals(Types.newParameterizedType(List.class, k), v.getBounds()[0]);
470   }
471   
472   public void testWithGenericLowerBoundInWildcard() throws Exception {
473     WildcardType wildcardType = (WildcardType)
474         new WithGenericBound<String>() {}
475             .getTargetType("withWildcardLowerBound");
476     assertEquals(String.class, wildcardType.getLowerBounds()[0]);
477   }
478   
479   public void testWithGenericUpperBoundInWildcard() throws Exception {
480     WildcardType wildcardType = (WildcardType)
481         new WithGenericBound<String>() {}
482             .getTargetType("withWildcardUpperBound");
483     assertEquals(String.class, wildcardType.getUpperBounds()[0]);
484   }
485   
486   public void testInterfaceTypeParameterResolution() throws Exception {
487     assertEquals(String.class,
488         TypeToken.of(new TypeToken<ArrayList<String>>() {}.getType())
489             .resolveType(List.class.getTypeParameters()[0]).getType());
490   }
491   
492   private static TypeToken<Map<Object, Object>> mapType() {
493     return new TypeToken<Map<Object, Object>>() {};
494   }
495   
496   // Looks like recursive, but legit.
497   private interface WithFalseRecursiveType<K, V> {
498     WithFalseRecursiveType<List<V>, String> keyShouldNotResolveToStringList();
499     WithFalseRecursiveType<List<K>, List<V>> shouldNotCauseInfiniteLoop();
500     SubTypeOfWithFalseRecursiveType<List<V>, List<K>> evenSubTypeWorks();
501   }
502   
503   private interface SubTypeOfWithFalseRecursiveType<K1, V1>
504       extends WithFalseRecursiveType<List<K1>, List<V1>> {
505     SubTypeOfWithFalseRecursiveType<V1, K1> revertKeyAndValueTypes();
506   }
507   
508   public void testFalseRecursiveType_mappingOnTheSameDeclarationNotUsed() {
509     Type returnType = genericReturnType(
510         WithFalseRecursiveType.class, "keyShouldNotResolveToStringList");
511     TypeToken<?> keyType = TypeToken.of(returnType)
512         .resolveType(WithFalseRecursiveType.class.getTypeParameters()[0]);
513     assertEquals("java.util.List<V>", keyType.getType().toString());
514   }
515   
516   public void testFalseRecursiveType_notRealRecursiveMapping() {
517     Type returnType = genericReturnType(
518         WithFalseRecursiveType.class, "shouldNotCauseInfiniteLoop");
519     TypeToken<?> keyType = TypeToken.of(returnType)
520         .resolveType(WithFalseRecursiveType.class.getTypeParameters()[0]);
521     assertEquals("java.util.List<K>", keyType.getType().toString());
522   }
523   
524   public void testFalseRecursiveType_referenceOfSubtypeDoesNotConfuseMe() {
525     Type returnType = genericReturnType(
526         WithFalseRecursiveType.class, "evenSubTypeWorks");
527     TypeToken<?> keyType = TypeToken.of(returnType)
528         .resolveType(WithFalseRecursiveType.class.getTypeParameters()[0]);
529     assertEquals("java.util.List<java.util.List<V>>", keyType.getType().toString());
530   }
531   
532   public void testFalseRecursiveType_intermediaryTypeMappingDoesNotConfuseMe() {
533     Type returnType = genericReturnType(
534         SubTypeOfWithFalseRecursiveType.class, "revertKeyAndValueTypes");
535     TypeToken<?> keyType = TypeToken.of(returnType)
536         .resolveType(WithFalseRecursiveType.class.getTypeParameters()[0]);
537     assertEquals("java.util.List<K1>", keyType.getType().toString());
538   }
539   
540   private static Type genericReturnType(Class<?> cls, String methodName) {
541     try {
542       return cls.getMethod(methodName).getGenericReturnType();
543     } catch (Exception e) {
544       throw new RuntimeException(e);
545     }
546   }
547 
548   public void testTwoStageResolution() {
549     class ForTwoStageResolution<A extends Number> {
550       <B extends A> void verifyTwoStageResolution() {
551         @SuppressWarnings({"unchecked", "rawtypes"})
552         Type type = new TypeToken<B>(getClass()) {}
553             // B's bound may have already resolved to something.
554             // Make sure it can still further resolve when given a context.
555             .where(new TypeParameter<B>() {}, (Class) Integer.class)
556             .getType();
557         assertEquals(Integer.class, type);
558       }
559     }
560     new ForTwoStageResolution<Integer>().verifyTwoStageResolution();
561     new ForTwoStageResolution<Integer>() {}.verifyTwoStageResolution();
562   }
563 }