PageRenderTime 1516ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/spice-inject/guice-patches/vanilla.test/com/google/inject/TypeListenerTest.java

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