NavigableMapTestSuiteBuilder.java revision 7dd252788645e940eada959bdde927426e2531c9
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
17package com.google.common.reflect;
18
19import static org.truth0.Truth.ASSERT;
20
21import com.google.common.base.Predicate;
22import com.google.common.base.Supplier;
23
24import java.lang.reflect.GenericArrayType;
25import java.lang.reflect.ParameterizedType;
26import java.lang.reflect.Type;
27import java.lang.reflect.TypeVariable;
28import java.lang.reflect.WildcardType;
29import java.util.ArrayList;
30import java.util.List;
31import java.util.Map;
32
33import junit.framework.TestCase;
34
35/**
36 * Unit test for {@link TypeToken} and {@link TypeResolver}.
37 *
38 * @author Ben Yu
39 */
40public 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    ASSERT.<Type, List<Type>>that(resolved.getActualTypeArguments()).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