1 /**
2  * Copyright (C) 2011 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.common.collect.ImmutableList.of;
20 import static com.google.inject.Asserts.assertContains;
21 import static com.google.inject.name.Names.named;
22 
23 import com.google.common.collect.ImmutableList;
24 import com.google.common.collect.ImmutableSet;
25 import com.google.common.collect.Lists;
26 import com.google.inject.matcher.AbstractMatcher;
27 import com.google.inject.matcher.Matcher;
28 import com.google.inject.matcher.Matchers;
29 import com.google.inject.name.Named;
30 import com.google.inject.spi.DependencyAndSource;
31 import com.google.inject.spi.InstanceBinding;
32 import com.google.inject.spi.ProvisionListener;
33 import com.google.inject.util.Providers;
34 
35 import junit.framework.TestCase;
36 
37 import java.util.List;
38 import java.util.Set;
39 import java.util.concurrent.atomic.AtomicBoolean;
40 import java.util.concurrent.atomic.AtomicInteger;
41 import java.util.concurrent.atomic.AtomicReference;
42 
43 /**
44  * Tests for {@link Binder#bindListener(Matcher, ProvisionListener...)}
45  *
46  * @author sameb@google.com (Sam Berlin)
47  */
48 // TODO(sameb): Add some tests for private modules & child injectors.
49 public class ProvisionListenerTest extends TestCase {
50 
testExceptionInListenerBeforeProvisioning()51   public void testExceptionInListenerBeforeProvisioning() {
52     Injector injector = Guice.createInjector(new AbstractModule() {
53       @Override
54       protected void configure() {
55         bindListener(Matchers.any(), new FailBeforeProvision());
56       }
57     });
58     try {
59       injector.getInstance(Foo.class);
60       fail();
61     } catch(ProvisionException pe) {
62       assertEquals(1, pe.getErrorMessages().size());
63       assertContains(pe.getMessage(),
64           "1) Error notifying ProvisionListener " + FailBeforeProvision.class.getName()
65           + " of " + Foo.class.getName(),
66           "Reason: java.lang.RuntimeException: boo",
67           "while locating " + Foo.class.getName());
68       assertEquals("boo", pe.getCause().getMessage());
69     }
70   }
71 
testExceptionInListenerAfterProvisioning()72   public void testExceptionInListenerAfterProvisioning() {
73     Injector injector = Guice.createInjector(new AbstractModule() {
74       @Override
75       protected void configure() {
76         bindListener(Matchers.any(), new FailAfterProvision());
77       }
78     });
79     try {
80       injector.getInstance(Foo.class);
81       fail();
82     } catch(ProvisionException pe) {
83       assertEquals(1, pe.getErrorMessages().size());
84       assertContains(pe.getMessage(),
85           "1) Error notifying ProvisionListener " + FailAfterProvision.class.getName()
86           + " of " + Foo.class.getName(),
87           "Reason: java.lang.RuntimeException: boo",
88           "while locating " + Foo.class.getName());
89       assertEquals("boo", pe.getCause().getMessage());
90     }
91   }
92 
testExceptionInProvisionExplicitlyCalled()93   public void testExceptionInProvisionExplicitlyCalled() {
94     Injector injector = Guice.createInjector(new AbstractModule() {
95       @Override
96       protected void configure() {
97         bindListener(Matchers.any(), new JustProvision());
98       }
99     });
100     try {
101       injector.getInstance(FooBomb.class);
102       fail();
103     } catch(ProvisionException pe) {
104       assertEquals(1, pe.getErrorMessages().size());
105       assertContains(pe.getMessage(),
106           "1) Error injecting constructor, java.lang.RuntimeException: Retry, Abort, Fail",
107           " at " + FooBomb.class.getName(),
108           " while locating " + FooBomb.class.getName());
109       assertEquals("Retry, Abort, Fail", pe.getCause().getMessage());
110     }
111   }
112 
testExceptionInProvisionAutomaticallyCalled()113   public void testExceptionInProvisionAutomaticallyCalled() {
114     Injector injector = Guice.createInjector(new AbstractModule() {
115       @Override
116       protected void configure() {
117         bindListener(Matchers.any(), new NoProvision());
118       }
119     });
120     try {
121       injector.getInstance(FooBomb.class);
122       fail();
123     } catch(ProvisionException pe) {
124       assertEquals(1, pe.getErrorMessages().size());
125       assertContains(pe.getMessage(),
126           "1) Error injecting constructor, java.lang.RuntimeException: Retry, Abort, Fail",
127           " at " + FooBomb.class.getName(),
128           " while locating " + FooBomb.class.getName());
129       assertEquals("Retry, Abort, Fail", pe.getCause().getMessage());
130     }
131   }
132 
testExceptionInFieldProvision()133   public void testExceptionInFieldProvision() throws Exception {
134     final CountAndCaptureExceptionListener listener = new CountAndCaptureExceptionListener();
135     Injector injector = Guice.createInjector(new AbstractModule() {
136       @Override protected void configure() {
137         bindListener(new AbstractMatcher<Binding<?>>() {
138           @Override public boolean matches(Binding<?> binding) {
139             return binding.getKey().getRawType().equals(DependsOnFooBombInField.class);
140           }
141         }, listener);
142       }
143     });
144     assertEquals(0, listener.beforeProvision);
145     String expectedMsg = null;
146     try {
147       injector.getInstance(DependsOnFooBombInField.class);
148       fail();
149     } catch (ProvisionException expected) {
150       assertEquals(1, expected.getErrorMessages().size());
151       expectedMsg = expected.getMessage();
152       assertContains(listener.capture.get().getMessage(),
153           "1) Error injecting constructor, java.lang.RuntimeException: Retry, Abort, Fail",
154           " at " + FooBomb.class.getName(),
155           " while locating " + FooBomb.class.getName(),
156           " while locating " + DependsOnFooBombInField.class.getName());
157     }
158     assertEquals(1, listener.beforeProvision);
159     assertEquals(expectedMsg, listener.capture.get().getMessage());
160     assertEquals(0, listener.afterProvision);
161   }
162 
testExceptionInCxtorProvision()163   public void testExceptionInCxtorProvision() throws Exception {
164     final CountAndCaptureExceptionListener listener = new CountAndCaptureExceptionListener();
165     Injector injector = Guice.createInjector(new AbstractModule() {
166       @Override protected void configure() {
167         bindListener(new AbstractMatcher<Binding<?>>() {
168           @Override public boolean matches(Binding<?> binding) {
169             return binding.getKey().getRawType().equals(DependsOnFooBombInCxtor.class);
170           }
171         }, listener);
172       }
173     });
174     assertEquals(0, listener.beforeProvision);
175     String expectedMsg = null;
176     try {
177       injector.getInstance(DependsOnFooBombInCxtor.class);
178       fail();
179     } catch (ProvisionException expected) {
180       assertEquals(1, expected.getErrorMessages().size());
181       expectedMsg = expected.getMessage();
182       assertContains(listener.capture.get().getMessage(),
183           "1) Error injecting constructor, java.lang.RuntimeException: Retry, Abort, Fail",
184           " at " + FooBomb.class.getName(),
185           " while locating " + FooBomb.class.getName(),
186           " while locating " + DependsOnFooBombInCxtor.class.getName());
187     }
188     assertEquals(1, listener.beforeProvision);
189     assertEquals(expectedMsg, listener.capture.get().getMessage());
190     assertEquals(0, listener.afterProvision);
191   }
192 
testListenerCallsProvisionTwice()193   public void testListenerCallsProvisionTwice() {
194     Injector injector = Guice.createInjector(new AbstractModule() {
195       @Override
196       protected void configure() {
197         bindListener(Matchers.any(), new ProvisionTwice());
198       }
199     });
200     try {
201       injector.getInstance(Foo.class);
202       fail();
203     } catch(ProvisionException pe) {
204       assertEquals(1, pe.getErrorMessages().size());
205       assertContains(pe.getMessage(),
206           "1) Error notifying ProvisionListener " + ProvisionTwice.class.getName()
207           + " of " + Foo.class.getName(),
208           "Reason: java.lang.IllegalStateException: Already provisioned in this listener.",
209           "while locating " + Foo.class.getName());
210       assertEquals("Already provisioned in this listener.", pe.getCause().getMessage());
211     }
212   }
213 
testCachedInScopePreventsProvisionNotify()214   public void testCachedInScopePreventsProvisionNotify() {
215     final Counter count1 = new Counter();
216     Injector injector = Guice.createInjector(new AbstractModule() {
217       @Override
218       protected void configure() {
219         bindListener(Matchers.any(), count1);
220         bind(Foo.class).in(Scopes.SINGLETON);
221       }
222     });
223     Foo foo = injector.getInstance(Foo.class);
224     assertNotNull(foo);
225     assertEquals(1, count1.count);
226 
227     // not notified the second time because nothing is provisioned
228     // (it's cached in the scope)
229     count1.count = 0;
230     assertSame(foo, injector.getInstance(Foo.class));
231     assertEquals(0, count1.count);
232   }
233 
testCombineAllBindListenerCalls()234   public void testCombineAllBindListenerCalls() {
235     final Counter count1 = new Counter();
236     final Counter count2 = new Counter();
237     Injector injector = Guice.createInjector(new AbstractModule() {
238       @Override
239       protected void configure() {
240         bindListener(Matchers.any(), count1);
241         bindListener(Matchers.any(), count2);
242       }
243     });
244     assertNotNull(injector.getInstance(Foo.class));
245     assertEquals(1, count1.count);
246     assertEquals(1, count2.count);
247   }
248 
testNotifyEarlyListenersIfFailBeforeProvision()249   public void testNotifyEarlyListenersIfFailBeforeProvision() {
250     final Counter count1 = new Counter();
251     final Counter count2 = new Counter();
252     Injector injector = Guice.createInjector(new AbstractModule() {
253       @Override
254       protected void configure() {
255         bindListener(Matchers.any(), count1, new FailBeforeProvision(), count2);
256       }
257     });
258     try {
259       injector.getInstance(Foo.class);
260       fail();
261     } catch(ProvisionException pe) {
262       assertEquals(1, pe.getErrorMessages().size());
263       assertContains(pe.getMessage(),
264           "1) Error notifying ProvisionListener " + FailBeforeProvision.class.getName()
265           + " of " + Foo.class.getName(),
266           "Reason: java.lang.RuntimeException: boo",
267           "while locating " + Foo.class.getName());
268       assertEquals("boo", pe.getCause().getMessage());
269 
270       assertEquals(1, count1.count);
271       assertEquals(0, count2.count);
272     }
273   }
274 
testNotifyLaterListenersIfFailAfterProvision()275   public void testNotifyLaterListenersIfFailAfterProvision() {
276     final Counter count1 = new Counter();
277     final Counter count2 = new Counter();
278     Injector injector = Guice.createInjector(new AbstractModule() {
279       @Override
280       protected void configure() {
281         bindListener(Matchers.any(), count1, new FailAfterProvision(), count2);
282       }
283     });
284     try {
285       injector.getInstance(Foo.class);
286       fail();
287     } catch(ProvisionException pe) {
288       assertEquals(1, pe.getErrorMessages().size());
289       assertContains(pe.getMessage(),
290           "1) Error notifying ProvisionListener " + FailAfterProvision.class.getName()
291           + " of " + Foo.class.getName(),
292           "Reason: java.lang.RuntimeException: boo",
293           "while locating " + Foo.class.getName());
294       assertEquals("boo", pe.getCause().getMessage());
295 
296       assertEquals(1, count1.count);
297       assertEquals(1, count2.count);
298     }
299   }
300 
testNotifiedKeysOfAllBindTypes()301   public void testNotifiedKeysOfAllBindTypes() {
302     final Capturer capturer = new Capturer();
303     Injector injector = Guice.createInjector(new AbstractModule() {
304       @Override
305       protected void configure() {
306         bindListener(Matchers.any(), capturer);
307         bind(Foo.class).annotatedWith(named("pk")).toProvider(FooP.class);
308         try {
309           bind(Foo.class).annotatedWith(named("cxtr")).toConstructor(Foo.class.getDeclaredConstructor());
310         } catch (Exception ex) {
311           throw new RuntimeException(ex);
312         }
313         bind(LinkedFoo.class).to(Foo.class);
314         bind(Interface.class).toInstance(new Implementation());
315         bindConstant().annotatedWith(named("constant")).to("MyConstant");
316       }
317 
318       @Provides @Named("pi") Foo provideFooBar() {
319         return new Foo();
320       }
321     });
322 
323     // toInstance & constant bindings are notified in random order, at the very beginning.
324     assertEquals(
325         ImmutableSet.of(Key.get(Interface.class), Key.get(String.class, named("constant"))),
326         capturer.getAsSetAndClear());
327 
328     // simple binding
329     assertNotNull(injector.getInstance(Foo.class));
330     assertEquals(of(Key.get(Foo.class)), capturer.getAndClear());
331 
332     // provider key binding -- notifies about provider & the object, always
333     assertNotNull(injector.getInstance(Key.get(Foo.class, named("pk"))));
334     assertEquals(of(Key.get(FooP.class), Key.get(Foo.class, named("pk"))), capturer.getAndClear());
335     assertNotNull(injector.getInstance(Key.get(Foo.class, named("pk"))));
336     assertEquals(of(Key.get(FooP.class), Key.get(Foo.class, named("pk"))), capturer.getAndClear());
337 
338     // JIT provider key binding -- notifies about provider & the object, always
339     assertNotNull(injector.getInstance(JitFoo2.class));
340     assertEquals(of(Key.get(JitFoo2P.class), Key.get(JitFoo2.class)), capturer.getAndClear());
341     assertNotNull(injector.getInstance(JitFoo2.class));
342     assertEquals(of(Key.get(JitFoo2P.class), Key.get(JitFoo2.class)), capturer.getAndClear());
343 
344     // provider instance binding -- just the object (not the provider)
345     assertNotNull(injector.getInstance(Key.get(Foo.class, named("pi"))));
346     assertEquals(of(Key.get(Foo.class, named("pi"))), capturer.getAndClear());
347 
348     // toConstructor binding
349     assertNotNull(injector.getInstance(Key.get(Foo.class, named("cxtr"))));
350     assertEquals(of(Key.get(Foo.class, named("cxtr"))), capturer.getAndClear());
351 
352     // linked binding -- notifies about the target (that's what's provisioned), not the link
353     assertNotNull(injector.getInstance(LinkedFoo.class));
354     assertEquals(of(Key.get(Foo.class)), capturer.getAndClear());
355 
356     // JIT linked binding -- notifies about the target (that's what's provisioned), not the link
357     assertNotNull(injector.getInstance(JitFoo.class));
358     assertEquals(of(Key.get(Foo.class)), capturer.getAndClear());
359   }
360 
testSingletonMatcher()361   public void testSingletonMatcher() {
362     final Counter counter = new Counter();
363     Injector injector = Guice.createInjector(new AbstractModule() {
364       @Override
365       protected void configure() {
366         bindListener(new AbstractMatcher<Binding<?>>() {
367           @Override
368           public boolean matches(Binding<?> t) {
369             return Scopes.isSingleton(t);
370           }
371         }, counter);
372       }
373     });
374     assertEquals(0, counter.count);
375     // no increment for getting Many.
376     injector.getInstance(Many.class);
377     assertEquals(0, counter.count);
378     // but an increment for getting Sole, since it's a singleton.
379     injector.getInstance(Sole.class);
380     assertEquals(1, counter.count);
381   }
382 
testCallingBindingDotGetProviderDotGet()383   public void testCallingBindingDotGetProviderDotGet() {
384     Injector injector = Guice.createInjector(new AbstractModule() {
385       @Override
386       protected void configure() {
387         bindListener(Matchers.any(), new ProvisionListener() {
388           @Override
389           public <T> void onProvision(ProvisionInvocation<T> provision) {
390             provision.getBinding().getProvider().get(); // AGH!
391           }
392         });
393       }
394     });
395 
396     try {
397       injector.getInstance(Sole.class);
398       fail();
399     } catch(ProvisionException expected) {
400       // We don't really care what kind of error you get, we only care you get an error.
401     }
402 
403     try {
404       injector.getInstance(Many.class);
405       fail();
406     } catch(ProvisionException expected) {
407       // We don't really care what kind of error you get, we only care you get an error.
408     }
409   }
410 
411   interface Interface {}
412   class Implementation implements Interface {}
413 
414   @Singleton static class Sole {}
415   static class Many {}
416 
417   @ImplementedBy(Foo.class) static interface JitFoo {}
418   @ProvidedBy(JitFoo2P.class) static class JitFoo2 {}
419   static interface LinkedFoo {}
420   static class Foo implements JitFoo, LinkedFoo {}
421   static class FooP implements Provider<Foo> {
get()422     public Foo get() {
423       return new Foo();
424     }
425   }
426   static class JitFoo2P implements Provider<JitFoo2> {
get()427     public JitFoo2 get() {
428       return new JitFoo2();
429     }
430   }
431 
432   static class FooBomb {
FooBomb()433     FooBomb() {
434       throw new RuntimeException("Retry, Abort, Fail");
435     }
436   }
437 
438   static class DependsOnFooBombInField {
439     @Inject FooBomb fooBomb;
440   }
441 
442   static class DependsOnFooBombInCxtor {
DependsOnFooBombInCxtor(FooBomb fooBomb)443     @Inject DependsOnFooBombInCxtor(FooBomb fooBomb) {}
444   }
445 
446   private static class Counter implements ProvisionListener {
447     int count = 0;
onProvision(ProvisionInvocation<T> provision)448     public <T> void onProvision(ProvisionInvocation<T> provision) {
449       count++;
450     }
451   }
452 
453   private static class CountAndCaptureExceptionListener implements ProvisionListener {
454     int beforeProvision = 0;
455     int afterProvision = 0;
456     AtomicReference<RuntimeException> capture = new AtomicReference<RuntimeException>();
onProvision(ProvisionInvocation<T> provision)457     public <T> void onProvision(ProvisionInvocation<T> provision) {
458       beforeProvision++;
459       try {
460         provision.provision();
461       } catch (RuntimeException re) {
462         capture.set(re);
463         throw re;
464       }
465       afterProvision++;
466     }
467   }
468 
469   private static class Capturer implements ProvisionListener {
470     List<Key> keys = Lists.newArrayList();
onProvision(ProvisionInvocation<T> provision)471     public <T> void onProvision(ProvisionInvocation<T> provision) {
472       keys.add(provision.getBinding().getKey());
473       T provisioned = provision.provision();
474       // InstanceBindings are the only kind of binding where the key can
475       // be an instanceof the provisioned, because it isn't linked to any
476       // direct implementation.  I guess maybe it'd also be possible
477       // with a toConstructor binding... but we don't use that in our tests.
478       if (provision.getBinding() instanceof InstanceBinding) {
479         Class<? super T> expected = provision.getBinding().getKey().getRawType();
480         assertTrue("expected instanceof: " + expected + ", but was: " + provisioned,
481             expected.isInstance(provisioned));
482       } else {
483         assertEquals(provision.getBinding().getKey().getRawType(), provisioned.getClass());
484       }
485     }
486 
getAsSetAndClear()487     Set<Key> getAsSetAndClear() {
488       Set<Key> copy = ImmutableSet.copyOf(keys);
489       keys.clear();
490       return copy;
491     }
492 
getAndClear()493     List<Key> getAndClear() {
494       List<Key> copy = ImmutableList.copyOf(keys);
495       keys.clear();
496       return copy;
497     }
498   }
499 
500   private static class FailBeforeProvision implements ProvisionListener {
onProvision(ProvisionInvocation<T> provision)501     public <T> void onProvision(ProvisionInvocation<T> provision) {
502       throw new RuntimeException("boo");
503     }
504   }
505   private static class FailAfterProvision implements ProvisionListener {
onProvision(ProvisionInvocation<T> provision)506     public <T> void onProvision(ProvisionInvocation<T> provision) {
507       provision.provision();
508       throw new RuntimeException("boo");
509     }
510   }
511   private static class JustProvision implements ProvisionListener {
onProvision(ProvisionInvocation<T> provision)512     public <T> void onProvision(ProvisionInvocation<T> provision) {
513       provision.provision();
514     }
515   }
516   private static class NoProvision implements ProvisionListener {
onProvision(ProvisionInvocation<T> provision)517     public <T> void onProvision(ProvisionInvocation<T> provision) {
518     }
519   }
520   private static class ProvisionTwice implements ProvisionListener {
onProvision(ProvisionInvocation<T> provision)521     public <T> void onProvision(ProvisionInvocation<T> provision) {
522       provision.provision();
523       provision.provision();
524     }
525   }
526 
527   private static class ChainAsserter implements ProvisionListener {
528     private final List<Class<?>> provisionList;
529     private final List<Class<?>> expected;
530 
ChainAsserter(List<Class<?>> provisionList, Iterable<Class<?>> expected)531     public ChainAsserter(List<Class<?>> provisionList, Iterable<Class<?>> expected) {
532       this.provisionList = provisionList;
533       this.expected = ImmutableList.copyOf(expected);
534     }
535 
onProvision(ProvisionInvocation<T> provision)536     public <T> void onProvision(ProvisionInvocation<T> provision) {
537       List<Class<?>> actual = Lists.newArrayList();
538       for (DependencyAndSource dep : provision.getDependencyChain()) {
539         actual.add(dep.getDependency().getKey().getRawType());
540       }
541       assertEquals(expected, actual);
542       provisionList.add(provision.getBinding().getKey().getRawType());
543     }
544   }
545 
keyMatcher(final Class<?> clazz)546   private static Matcher<Binding<?>> keyMatcher(final Class<?> clazz) {
547     return new AbstractMatcher<Binding<?>>() {
548       @Override
549       public boolean matches(Binding<?> t) {
550         return t.getKey().equals(Key.get(clazz));
551       }
552     };
553   }
554 
555   @SuppressWarnings("unchecked")
556   public void testDependencyChain() {
557     final List<Class<?>> pList = Lists.newArrayList();
558     final List<Class<?>> totalList = Lists.newArrayList();
559     Injector injector = Guice.createInjector(new AbstractModule() {
560       @Override
561       protected void configure() {
562         bind(Instance.class).toInstance(new Instance());
563         bind(B.class).to(BImpl.class);
564         bind(D.class).toProvider(DP.class);
565 
566         bindListener(Matchers.any(), new ProvisionListener() {
567           public <T> void onProvision(ProvisionInvocation<T> provision) {
568             totalList.add(provision.getBinding().getKey().getRawType());
569           }
570         });
571 
572         // Build up a list of asserters for our dependency chains.
573         ImmutableList.Builder<Class<?>> chain = ImmutableList.builder();
574         chain.add(Instance.class);
575         bindListener(keyMatcher(Instance.class), new ChainAsserter(pList, chain.build()));
576 
577         chain.add(A.class);
578         bindListener(keyMatcher(A.class), new ChainAsserter(pList, chain.build()));
579 
580         chain.add(B.class).add(BImpl.class);
581         bindListener(keyMatcher(BImpl.class), new ChainAsserter(pList, chain.build()));
582 
583         chain.add(C.class);
584         bindListener(keyMatcher(C.class), new ChainAsserter(pList, chain.build()));
585 
586         // the chain has D before DP even though DP is provisioned & notified first
587         // because we do DP because of D, and need DP to provision D.
588         chain.add(D.class).add(DP.class);
589         bindListener(keyMatcher(D.class), new ChainAsserter(pList, chain.build()));
590         bindListener(keyMatcher(DP.class), new ChainAsserter(pList, chain.build()));
591 
592         chain.add(E.class);
593         bindListener(keyMatcher(E.class), new ChainAsserter(pList, chain.build()));
594 
595         chain.add(F.class);
596         bindListener(keyMatcher(F.class), new ChainAsserter(pList, chain.build()));
597       }
598       @Provides C c(D d) {
599         return new C() {};
600       }
601     });
602     Instance instance = injector.getInstance(Instance.class);
603     // make sure we're checking all of the chain asserters..
604     assertEquals(
605         of(Instance.class, A.class, BImpl.class, C.class, DP.class, D.class, E.class, F.class),
606         pList);
607     // and make sure that nothing else was notified that we didn't expect.
608     assertEquals(totalList, pList);
609   }
610 
611   public void testModuleRequestInjection() {
612     final AtomicBoolean notified = new AtomicBoolean();
613     Guice.createInjector(new AbstractModule() {
614       @Override
615       protected void configure() {
616         requestInjection(new Object() {
617           @Inject Foo foo;
618         });
619         bindListener(Matchers.any(),
620             new SpecialChecker(Foo.class, getClass().getName() + ".configure(", notified));
621       }
622     });
623     assertTrue(notified.get());
624   }
625 
626   public void testToProviderInstance() {
627     final AtomicBoolean notified = new AtomicBoolean();
628     Guice.createInjector(new AbstractModule() {
629       @Override
630       protected void configure() {
631         bind(Object.class).toProvider(new Provider<Object>() {
632           @Inject Foo foo;
633           public Object get() {
634             return null;
635           }
636         });
637         bindListener(Matchers.any(),
638             new SpecialChecker(Foo.class, getClass().getName() + ".configure(", notified));
639       }
640     });
641     assertTrue(notified.get());
642   }
643 
644   public void testInjectorInjectMembers() {
645     final Object object = new Object() {
646       @Inject Foo foo;
647     };
648     final AtomicBoolean notified = new AtomicBoolean();
649     Guice.createInjector(new AbstractModule() {
650       @Override
651       protected void configure() {
652         bindListener(Matchers.any(),
653             new SpecialChecker(Foo.class, object.getClass().getName(), notified));
654       }
655     }).injectMembers(object);
656     assertTrue(notified.get());
657   }
658 
659   private static class SpecialChecker implements ProvisionListener {
660     private final Class<?> notifyType;
661     private final String firstSource;
662     private final AtomicBoolean notified;
663 
664     public SpecialChecker(Class<?> notifyType, String firstSource, AtomicBoolean notified) {
665       this.notifyType = notifyType;
666       this.firstSource = firstSource;
667       this.notified = notified;
668     }
669 
670     public <T> void onProvision(ProvisionInvocation<T> provision) {
671       notified.set(true);
672       assertEquals(notifyType, provision.getBinding().getKey().getRawType());
673       assertEquals(2, provision.getDependencyChain().size());
674 
675       assertNull(provision.getDependencyChain().get(0).getDependency());
676       assertContains(provision.getDependencyChain().get(0).getBindingSource(), firstSource);
677 
678       assertEquals(notifyType,
679           provision.getDependencyChain().get(1).getDependency().getKey().getRawType());
680       assertContains(provision.getDependencyChain().get(1).getBindingSource(),
681           notifyType.getName() + ".class(");
682     }
683   }
684 
685   private static class Instance {
686     @Inject A a;
687   }
688   private static class A {
689     @Inject A(B b) {}
690   }
691   private interface B {}
692   private static class BImpl implements B {
693     @Inject void inject(C c) {}
694   }
695   private interface C {}
696   private interface D {}
697   private static class DP implements Provider<D> {
698     @Inject Provider<E> ep;
699     public D get() {
700       ep.get();
701       return new D() {};
702     }
703   }
704   private static class E {
705     @SuppressWarnings("unused")
706     @Inject F f;
707   }
708   private static class F {
709   }
710 
711   public void testBindToInjectorWithListeningGivesSaneException() {
712     try {
713       Guice.createInjector(new AbstractModule() {
714         @Override
715         protected void configure() {
716           bindListener(Matchers.any(), new Counter());
717           bind(Injector.class).toProvider(Providers.<Injector>of(null));
718         }
719       });
720       fail();
721     } catch (CreationException ce) {
722       assertContains(
723           ce.getMessage(), "Binding to core guice framework type is not allowed: Injector.");
724     }
725   }
726 
727   public void testProvisionIsNotifiedAfterContextsClear() {
728     Injector injector = Guice.createInjector(new AbstractModule() {
729       @Override
730       protected void configure() {
731         bindListener(Matchers.any(), new ProvisionListener() {
732           @Override
733           public <T> void onProvision(ProvisionInvocation<T> provision) {
734             Object provisioned = provision.provision();
735             if (provisioned instanceof X) {
736               ((X)provisioned).init();
737             } else if (provisioned instanceof Y) {
738               X.createY = false;
739               ((Y)provisioned).init();
740             }
741           }
742         });
743       }
744     });
745 
746     X.createY = true;
747     X x = injector.getInstance(X.class);
748     assertNotSame(x, x.y.x);
749     assertFalse("x.ID: " + x.ID + ", x.y.x.iD: " + x.y.x.ID, x.ID == x.y.x.ID);
750   }
751 
752   private static class X {
753     final static AtomicInteger COUNTER = new AtomicInteger();
754     static boolean createY;
755 
756     final int ID = COUNTER.getAndIncrement();
757     final Provider<Y> yProvider;
758     Y y;
759 
760     @Inject X(Provider<Y> yProvider) {
761       this.yProvider = yProvider;
762     }
763 
764     void init() {
765       if (createY) {
766         this.y = yProvider.get();
767       }
768     }
769   }
770 
771   private static class Y {
772     final Provider<X> xProvider;
773     X x;
774 
775     @Inject Y(Provider<X> xProvider) {
776       this.xProvider = xProvider;
777     }
778 
779     void init() {
780       this.x = xProvider.get();
781     }
782   }
783 
784   public void testDeDuplicateProvisionListeners() {
785     final Counter counter = new Counter();
786     Injector injector = Guice.createInjector(new AbstractModule() {
787       @Override
788       protected void configure() {
789         bindListener(Matchers.any(), counter);
790         bindListener(Matchers.any(), counter);
791       }
792     });
793     injector.getInstance(Many.class);
794     assertEquals("ProvisionListener not de-duplicated", 1, counter.count);
795   }
796 }
797