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 com.google.common.truth.Truth.assertThat; 20 21import com.google.common.base.Predicate; 22import com.google.common.base.Supplier; 23 24import junit.framework.TestCase; 25 26import java.lang.reflect.GenericArrayType; 27import java.lang.reflect.ParameterizedType; 28import java.lang.reflect.Type; 29import java.lang.reflect.TypeVariable; 30import java.lang.reflect.WildcardType; 31import java.util.ArrayList; 32import java.util.List; 33import java.util.Map; 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 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} 564