1 /** 2 * Copyright (C) 2009 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 17 package com.google.inject; 18 19 import static com.google.inject.Asserts.asModuleChain; 20 import static com.google.inject.Asserts.assertContains; 21 import static com.google.inject.Asserts.getDeclaringSourcePart; 22 import static com.google.inject.matcher.Matchers.any; 23 import static com.google.inject.matcher.Matchers.only; 24 import static com.google.inject.name.Names.named; 25 26 import com.google.common.collect.ImmutableList; 27 import com.google.common.collect.Lists; 28 import com.google.inject.matcher.Matcher; 29 import com.google.inject.matcher.Matchers; 30 import com.google.inject.spi.InjectionListener; 31 import com.google.inject.spi.Message; 32 import com.google.inject.spi.TypeEncounter; 33 import com.google.inject.spi.TypeListener; 34 35 import junit.framework.TestCase; 36 37 import java.util.List; 38 import java.util.concurrent.atomic.AtomicInteger; 39 import java.util.concurrent.atomic.AtomicReference; 40 41 /** 42 * @author jessewilson (at) google.com (Jesse Wilson) 43 */ 44 public class TypeListenerTest extends TestCase { 45 46 private final Matcher<Object> onlyAbcd = Matchers.only(new TypeLiteral<A>() {}) 47 .or(only(new TypeLiteral<B>() {})) 48 .or(only(new TypeLiteral<C>() {})) 49 .or(only(new TypeLiteral<D>() {})); 50 51 final TypeListener failingTypeListener = new TypeListener() { 52 int failures = 0; 53 54 public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { 55 throw new ClassCastException("whoops, failure #" + (++failures)); 56 } 57 58 @Override public String toString() { 59 return "clumsy"; 60 } 61 }; 62 63 final InjectionListener<Object> failingInjectionListener = new InjectionListener<Object>() { 64 int failures = 0; 65 66 public void afterInjection(Object injectee) { 67 throw new ClassCastException("whoops, failure #" + (++failures)); 68 } 69 70 @Override public String toString() { 71 return "goofy"; 72 } 73 }; 74 75 final MembersInjector<Object> failingMembersInjector = new MembersInjector<Object>() { 76 int failures = 0; 77 78 public void injectMembers(Object instance) { 79 throw new ClassCastException("whoops, failure #" + (++failures)); 80 } 81 82 @Override public String toString() { 83 return "awkward"; 84 } 85 }; 86 87 public void testTypeListenersAreFired() { 88 final AtomicInteger firedCount = new AtomicInteger(); 89 90 final TypeListener typeListener = new TypeListener() { 91 public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { 92 assertEquals(new TypeLiteral<A>() {}, type); 93 firedCount.incrementAndGet(); 94 } 95 }; 96 97 Guice.createInjector(new AbstractModule() { 98 @Override protected void configure() { 99 bindListener(onlyAbcd, typeListener); 100 bind(A.class); 101 } 102 }); 103 104 assertEquals(1, firedCount.get()); 105 } 106 107 public void testInstallingInjectionListener() { 108 final List<Object> injectees = Lists.newArrayList(); 109 final InjectionListener<Object> injectionListener = new InjectionListener<Object>() { 110 public void afterInjection(Object injectee) { 111 injectees.add(injectee); 112 } 113 }; 114 115 Injector injector = Guice.createInjector(new AbstractModule() { 116 @Override protected void configure() { 117 bindListener(onlyAbcd, new TypeListener() { 118 public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { 119 encounter.register(injectionListener); 120 } 121 }); 122 bind(A.class); 123 } 124 }); 125 126 assertEquals(ImmutableList.of(), injectees); 127 128 Object a1 = injector.getInstance(A.class); 129 assertEquals(ImmutableList.of(a1), injectees); 130 131 Object a2 = injector.getInstance(A.class); 132 assertEquals(ImmutableList.of(a1, a2), injectees); 133 134 Object b1 = injector.getInstance(B.class); 135 assertEquals(ImmutableList.of(a1, a2, b1), injectees); 136 137 Provider<A> aProvider = injector.getProvider(A.class); 138 assertEquals(ImmutableList.of(a1, a2, b1), injectees); 139 A a3 = aProvider.get(); 140 A a4 = aProvider.get(); 141 assertEquals(ImmutableList.of(a1, a2, b1, a3, a4), injectees); 142 } 143 144 /*if[AOP]*/ 145 private static org.aopalliance.intercept.MethodInterceptor prefixInterceptor( 146 final String prefix) { 147 return new org.aopalliance.intercept.MethodInterceptor() { 148 public Object invoke(org.aopalliance.intercept.MethodInvocation methodInvocation) 149 throws Throwable { 150 return prefix + methodInvocation.proceed(); 151 } 152 }; 153 } 154 155 public void testAddingInterceptors() throws NoSuchMethodException { 156 final Matcher<Object> buzz = only(C.class.getMethod("buzz")); 157 158 Injector injector = Guice.createInjector(new AbstractModule() { 159 @Override protected void configure() { 160 bindInterceptor(any(), buzz, prefixInterceptor("ka")); 161 bindInterceptor(any(), any(), prefixInterceptor("fe")); 162 163 bindListener(onlyAbcd, new TypeListener() { 164 public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { 165 encounter.bindInterceptor(any(), prefixInterceptor("li")); 166 encounter.bindInterceptor(buzz, prefixInterceptor("no")); 167 } 168 }); 169 } 170 }); 171 172 // interceptors must be invoked in the order they're bound. 173 C c = injector.getInstance(C.class); 174 assertEquals("kafelinobuzz", c.buzz()); 175 assertEquals("felibeep", c.beep()); 176 } 177 /*end[AOP]*/ 178 179 class OuterThrowsModule extends AbstractModule { 180 @Override protected void configure() { 181 install(new InnerThrowsModule()); 182 } 183 } 184 class InnerThrowsModule extends AbstractModule { 185 @Override protected void configure() { 186 bindListener(onlyAbcd, failingTypeListener); 187 bind(B.class); 188 bind(C.class); 189 } 190 } 191 public void testTypeListenerThrows() { 192 try { 193 Guice.createInjector(new OuterThrowsModule()); 194 fail(); 195 } catch (CreationException expected) { 196 assertContains(expected.getMessage(), 197 "1) Error notifying TypeListener clumsy (bound at " + getClass().getName(), 198 getDeclaringSourcePart(getClass()), 199 asModuleChain(OuterThrowsModule.class, InnerThrowsModule.class), 200 "of " + B.class.getName(), 201 "Reason: java.lang.ClassCastException: whoops, failure #1", 202 "2) Error notifying TypeListener clumsy (bound at " + getClass().getName(), 203 getDeclaringSourcePart(getClass()), 204 asModuleChain(OuterThrowsModule.class, InnerThrowsModule.class), 205 "of " + C.class.getName(), 206 "Reason: java.lang.ClassCastException: whoops, failure #2"); 207 } 208 209 Injector injector = Guice.createInjector(new AbstractModule() { 210 @Override protected void configure() { 211 bindListener(onlyAbcd, failingTypeListener); 212 } 213 }); 214 try { 215 injector.getProvider(B.class); 216 fail(); 217 } catch (ConfigurationException expected) { 218 assertContains(expected.getMessage(), 219 "1) Error notifying TypeListener clumsy (bound at " + getClass().getName(), 220 getDeclaringSourcePart(getClass()), 221 "of " + B.class.getName(), 222 "Reason: java.lang.ClassCastException: whoops, failure #3"); 223 } 224 225 // getting it again should yield the same exception #3 226 try { 227 injector.getInstance(B.class); 228 fail(); 229 } catch (ConfigurationException expected) { 230 assertContains(expected.getMessage(), 231 "1) Error notifying TypeListener clumsy (bound at " + getClass().getName(), 232 getDeclaringSourcePart(getClass()), 233 "of " + B.class.getName(), 234 "Reason: java.lang.ClassCastException: whoops, failure #3"); 235 } 236 237 // non-injected types do not participate 238 assertSame(Stage.DEVELOPMENT, injector.getInstance(Stage.class)); 239 } 240 241 public void testInjectionListenerThrows() { 242 Injector injector = Guice.createInjector(new AbstractModule() { 243 @Override protected void configure() { 244 bindListener(onlyAbcd, new TypeListener() { 245 public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { 246 encounter.register(failingInjectionListener); 247 } 248 }); 249 bind(B.class); 250 } 251 }); 252 253 try { 254 injector.getInstance(A.class); 255 fail(); 256 } catch (ProvisionException e) { 257 assertContains(e.getMessage(), 258 "1) Error notifying InjectionListener goofy of " + A.class.getName(), 259 " Reason: java.lang.ClassCastException: whoops, failure #1"); 260 } 261 262 // second time through should be a new cause (#2) 263 try { 264 injector.getInstance(A.class); 265 fail(); 266 } catch (ProvisionException e) { 267 assertContains(e.getMessage(), 268 "1) Error notifying InjectionListener goofy of " + A.class.getName(), 269 " Reason: java.lang.ClassCastException: whoops, failure #2"); 270 } 271 272 // we should get errors for all types, but only on getInstance() 273 Provider<B> bProvider = injector.getProvider(B.class); 274 try { 275 bProvider.get(); 276 fail(); 277 } catch (ProvisionException e) { 278 assertContains(e.getMessage(), 279 "1) Error notifying InjectionListener goofy of " + B.class.getName(), 280 " Reason: java.lang.ClassCastException: whoops, failure #3"); 281 } 282 283 // non-injected types do not participate 284 assertSame(Stage.DEVELOPMENT, injector.getInstance(Stage.class)); 285 } 286 287 public void testInjectMembersTypeListenerFails() { 288 try { 289 Guice.createInjector(new AbstractModule() { 290 @Override protected void configure() { 291 getMembersInjector(A.class); 292 bindListener(onlyAbcd, failingTypeListener); 293 } 294 }); 295 fail(); 296 } catch (CreationException expected) { 297 assertContains(expected.getMessage(), 298 "1) Error notifying TypeListener clumsy (bound at ", 299 TypeListenerTest.class.getName(), getDeclaringSourcePart(getClass()), 300 "of " + A.class.getName(), 301 " Reason: java.lang.ClassCastException: whoops, failure #1"); 302 } 303 } 304 305 public void testConstructedTypeListenerIsTheSameAsMembersInjectorListener() { 306 final AtomicInteger typeEncounters = new AtomicInteger(); 307 final AtomicInteger injections = new AtomicInteger(); 308 309 final InjectionListener<A> listener = new InjectionListener<A>() { 310 public void afterInjection(A injectee) { 311 injections.incrementAndGet(); 312 assertNotNull(injectee.injector); 313 } 314 }; 315 316 Injector injector = Guice.createInjector(new AbstractModule() { 317 @Override protected void configure() { 318 bindListener(onlyAbcd, new TypeListener() { 319 @SuppressWarnings("unchecked") 320 public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { 321 typeEncounters.incrementAndGet(); 322 encounter.register((InjectionListener) listener); 323 } 324 }); 325 326 bind(A.class); 327 getMembersInjector(A.class); 328 } 329 }); 330 331 // creating the injector shouldn't trigger injections 332 assertEquals(0, injections.getAndSet(0)); 333 334 // constructing an A should trigger an injection 335 injector.getInstance(A.class); 336 assertEquals(1, injections.getAndSet(0)); 337 338 // injecting an A should trigger an injection 339 injector.injectMembers(new A()); 340 assertEquals(1, injections.getAndSet(0)); 341 342 // getting a provider shouldn't 343 Provider<A> aProvider = injector.getProvider(A.class); 344 MembersInjector<A> aMembersInjector = injector.getMembersInjector(A.class); 345 assertEquals(0, injections.getAndSet(0)); 346 347 // exercise the provider 348 aProvider.get(); 349 aProvider.get(); 350 assertEquals(2, injections.getAndSet(0)); 351 352 // exercise the members injector 353 aMembersInjector.injectMembers(new A()); 354 aMembersInjector.injectMembers(new A()); 355 assertEquals(2, injections.getAndSet(0)); 356 357 // we should only have encountered one type 358 assertEquals(1, typeEncounters.getAndSet(0)); 359 } 360 361 public void testLookupsAtInjectorCreateTime() { 362 final AtomicReference<Provider<B>> bProviderReference = new AtomicReference<Provider<B>>(); 363 final AtomicReference<MembersInjector<A>> aMembersInjectorReference 364 = new AtomicReference<MembersInjector<A>>(); 365 366 final InjectionListener<Object> lookupsTester = new InjectionListener<Object>() { 367 public void afterInjection(Object injectee) { 368 assertNotNull(bProviderReference.get().get()); 369 370 A a = new A(); 371 aMembersInjectorReference.get().injectMembers(a); 372 assertNotNull(a.injector); 373 } 374 }; 375 376 Guice.createInjector(new AbstractModule() { 377 @Override protected void configure() { 378 bindListener(only(TypeLiteral.get(C.class)), new TypeListener() { 379 public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { 380 Provider<B> bProvider = encounter.getProvider(B.class); 381 try { 382 bProvider.get(); 383 fail(); 384 } catch (IllegalStateException expected) { 385 assertEquals("This Provider cannot be used until the Injector has been created.", 386 expected.getMessage()); 387 } 388 bProviderReference.set(bProvider); 389 390 MembersInjector<A> aMembersInjector = encounter.getMembersInjector(A.class); 391 try { 392 aMembersInjector.injectMembers(new A()); 393 fail(); 394 } catch (IllegalStateException expected) { 395 assertEquals( 396 "This MembersInjector cannot be used until the Injector has been created.", 397 expected.getMessage()); 398 } 399 aMembersInjectorReference.set(aMembersInjector); 400 401 encounter.register(lookupsTester); 402 } 403 }); 404 405 // this ensures the type listener fires, and also the afterInjection() listener 406 bind(C.class).asEagerSingleton(); 407 } 408 }); 409 410 lookupsTester.afterInjection(null); 411 } 412 413 public void testLookupsPostCreate() { 414 Injector injector = Guice.createInjector(new AbstractModule() { 415 @Override protected void configure() { 416 bindListener(only(TypeLiteral.get(C.class)), new TypeListener() { 417 public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { 418 assertNotNull(encounter.getProvider(B.class).get()); 419 420 A a = new A(); 421 encounter.getMembersInjector(A.class).injectMembers(a); 422 assertNotNull(a.injector); 423 } 424 }); 425 } 426 }); 427 428 injector.getInstance(C.class); 429 } 430 431 public void testMembersInjector() { 432 final MembersInjector<D> membersInjector = new MembersInjector<D>() { 433 public void injectMembers(D instance) { 434 instance.userInjected++; 435 assertEquals(instance.guiceInjected, instance.userInjected); 436 } 437 }; 438 439 final InjectionListener<D> injectionListener = new InjectionListener<D>() { 440 public void afterInjection(D injectee) { 441 assertTrue(injectee.userInjected > 0); 442 injectee.listenersNotified++; 443 assertEquals(injectee.guiceInjected, injectee.listenersNotified); 444 } 445 }; 446 447 Injector injector = Guice.createInjector(new AbstractModule() { 448 @Override protected void configure() { 449 bindListener(onlyAbcd, new TypeListener() { 450 @SuppressWarnings("unchecked") 451 public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { 452 encounter.register((MembersInjector) membersInjector); 453 encounter.register((InjectionListener) injectionListener); 454 } 455 }); 456 457 D boundThreeTimes = new D(); 458 bind(D.class).annotatedWith(named("i")).toInstance(boundThreeTimes); 459 bind(D.class).annotatedWith(named("ii")).toInstance(boundThreeTimes); 460 bind(D.class).annotatedWith(named("iii")).toInstance(boundThreeTimes); 461 } 462 }); 463 464 D boundThreeTimes = injector.getInstance(Key.get(D.class, named("iii"))); 465 boundThreeTimes.assertAllCounts(1); 466 467 D getInstance = injector.getInstance(D.class); 468 getInstance.assertAllCounts(1); 469 470 D memberInjection = new D(); 471 injector.injectMembers(memberInjection); 472 memberInjection.assertAllCounts(1); 473 474 injector.injectMembers(memberInjection); 475 injector.injectMembers(memberInjection); 476 memberInjection.assertAllCounts(3); 477 478 injector.getMembersInjector(D.class).injectMembers(memberInjection); 479 memberInjection.assertAllCounts(4); 480 } 481 482 public void testMembersInjectorThrows() { 483 Injector injector = Guice.createInjector(new AbstractModule() { 484 @Override protected void configure() { 485 bindListener(onlyAbcd, new TypeListener() { 486 public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { 487 encounter.register(failingMembersInjector); 488 } 489 }); 490 bind(B.class); 491 } 492 }); 493 494 try { 495 injector.getInstance(A.class); 496 fail(); 497 } catch (ProvisionException e) { 498 assertContains(e.getMessage(), 499 "1) Error injecting " + A.class.getName() + " using awkward.", 500 "Reason: java.lang.ClassCastException: whoops, failure #1"); 501 } 502 503 // second time through should be a new cause (#2) 504 try { 505 injector.getInstance(A.class); 506 fail(); 507 } catch (ProvisionException e) { 508 assertContains(e.getMessage(), 509 "1) Error injecting " + A.class.getName() + " using awkward.", 510 "Reason: java.lang.ClassCastException: whoops, failure #2"); 511 } 512 513 // we should get errors for all types, but only on getInstance() 514 Provider<B> bProvider = injector.getProvider(B.class); 515 try { 516 bProvider.get(); 517 fail(); 518 } catch (ProvisionException e) { 519 assertContains(e.getMessage(), 520 "1) Error injecting " + B.class.getName() + " using awkward.", 521 "Reason: java.lang.ClassCastException: whoops, failure #3"); 522 } 523 524 // non-injected types do not participate 525 assertSame(Stage.DEVELOPMENT, injector.getInstance(Stage.class)); 526 } 527 528 /** 529 * We had a bug where we weren't notifying of types encountered for member injection when those 530 * types had no members to be injected. Constructed types are always injected because they always 531 * have at least one injection point: the class constructor. 532 */ 533 public void testTypesWithNoInjectableMembersAreNotified() { 534 final AtomicInteger notificationCount = new AtomicInteger(); 535 536 Guice.createInjector(new AbstractModule() { 537 @Override protected void configure() { 538 bindListener(onlyAbcd, new TypeListener() { 539 public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { 540 notificationCount.incrementAndGet(); 541 } 542 }); 543 544 bind(C.class).toInstance(new C()); 545 } 546 }); 547 548 assertEquals(1, notificationCount.get()); 549 } 550 551 public void testEncounterCannotBeUsedAfterHearReturns() { 552 final AtomicReference<TypeEncounter<?>> encounterReference = new AtomicReference<TypeEncounter<?>>(); 553 554 Guice.createInjector(new AbstractModule() { 555 @Override protected void configure() { 556 bindListener(any(), new TypeListener() { 557 public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { 558 encounterReference.set(encounter); 559 } 560 }); 561 562 bind(C.class); 563 } 564 }); 565 TypeEncounter<?> encounter = encounterReference.get(); 566 567 try { 568 encounter.register(new InjectionListener<Object>() { 569 public void afterInjection(Object injectee) {} 570 }); 571 fail(); 572 } catch (IllegalStateException expected) { 573 } 574 575 /*if[AOP]*/ 576 try { 577 encounter.bindInterceptor(any(), new org.aopalliance.intercept.MethodInterceptor() { 578 public Object invoke(org.aopalliance.intercept.MethodInvocation methodInvocation) 579 throws Throwable { 580 return methodInvocation.proceed(); 581 } 582 }); 583 fail(); 584 } catch (IllegalStateException expected) { 585 } 586 /*end[AOP]*/ 587 588 try { 589 encounter.addError(new Exception()); 590 fail(); 591 } catch (IllegalStateException expected) { 592 } 593 594 try { 595 encounter.getMembersInjector(A.class); 596 fail(); 597 } catch (IllegalStateException expected) { 598 } 599 600 try { 601 encounter.getProvider(B.class); 602 fail(); 603 } catch (IllegalStateException expected) { 604 } 605 } 606 607 public void testAddErrors() { 608 try { 609 Guice.createInjector(new AbstractModule() { 610 @Override protected void configure() { 611 requestInjection(new Object()); 612 bindListener(Matchers.any(), new TypeListener() { 613 public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { 614 encounter.addError("There was an error on %s", type); 615 encounter.addError(new IllegalArgumentException("whoops!")); 616 encounter.addError(new Message("And another problem")); 617 encounter.addError(new IllegalStateException()); 618 } 619 }); 620 } 621 }); 622 fail(); 623 } catch (CreationException expected) { 624 assertContains(expected.getMessage(), 625 "1) There was an error on java.lang.Object", 626 "2) An exception was caught and reported. Message: whoops!", 627 "3) And another problem", 628 "4) An exception was caught and reported. Message: null", 629 "4 errors"); 630 } 631 } 632 633 private static class CountingMembersInjector implements MembersInjector<D> { 634 public void injectMembers(D instance) { 635 ++instance.userInjected; 636 } 637 } 638 639 private static class CountingInjectionListener implements InjectionListener<D> { 640 public void afterInjection(D injectee) { 641 ++injectee.listenersNotified; 642 } 643 } 644 645 private static class DuplicatingTypeListener implements TypeListener { 646 int count = 0; 647 648 @SuppressWarnings({"rawtypes", "unchecked"}) 649 public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { 650 ++count; 651 652 MembersInjector membersInjector = new CountingMembersInjector(); 653 encounter.register(membersInjector); 654 encounter.register(membersInjector); 655 656 InjectionListener injectionListener = new CountingInjectionListener(); 657 encounter.register(injectionListener); 658 encounter.register(injectionListener); 659 } 660 } 661 662 public void testDeDuplicateTypeListeners() { 663 final DuplicatingTypeListener typeListener = new DuplicatingTypeListener(); 664 Injector injector = Guice.createInjector(new AbstractModule() { 665 @Override 666 protected void configure() { 667 bindListener(any(), typeListener); 668 bindListener(only(new TypeLiteral<D>() {}), typeListener); 669 } 670 }); 671 D d = injector.getInstance(D.class); 672 d.assertAllCounts(1); 673 assertEquals(1, typeListener.count); 674 } 675 676 // TODO: recursively accessing a lookup should fail 677 678 static class A { 679 @Inject Injector injector; 680 @Inject Stage stage; 681 } 682 683 static class B {} 684 685 public static class C { 686 public String buzz() { 687 return "buzz"; 688 } 689 690 public String beep() { 691 return "beep"; 692 } 693 } 694 695 static class D { 696 int guiceInjected = 0; 697 int userInjected = 0; 698 int listenersNotified = 0; 699 700 @Inject void guiceInjected() { 701 guiceInjected++; 702 } 703 704 void assertAllCounts(int expected) { 705 assertEquals(expected, guiceInjected); 706 assertEquals(expected, userInjected); 707 assertEquals(expected, listenersNotified); 708 } 709 } 710 } 711