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
17package com.google.inject;
18
19import static com.google.common.collect.ImmutableList.of;
20import static com.google.inject.Asserts.assertContains;
21import static com.google.inject.name.Names.named;
22
23import com.google.common.collect.ImmutableList;
24import com.google.common.collect.ImmutableSet;
25import com.google.common.collect.Lists;
26import com.google.inject.matcher.AbstractMatcher;
27import com.google.inject.matcher.Matcher;
28import com.google.inject.matcher.Matchers;
29import com.google.inject.name.Named;
30import com.google.inject.spi.DependencyAndSource;
31import com.google.inject.spi.InstanceBinding;
32import com.google.inject.spi.ProvisionListener;
33import com.google.inject.util.Providers;
34
35import junit.framework.TestCase;
36
37import java.util.List;
38import java.util.Set;
39import java.util.concurrent.atomic.AtomicBoolean;
40import java.util.concurrent.atomic.AtomicInteger;
41import 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.
49public class ProvisionListenerTest extends TestCase {
50
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
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
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
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
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
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
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
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
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
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
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
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
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
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> {
422    public Foo get() {
423      return new Foo();
424    }
425  }
426  static class JitFoo2P implements Provider<JitFoo2> {
427    public JitFoo2 get() {
428      return new JitFoo2();
429    }
430  }
431
432  static class 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 {
443    @Inject DependsOnFooBombInCxtor(FooBomb fooBomb) {}
444  }
445
446  private static class Counter implements ProvisionListener {
447    int count = 0;
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>();
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();
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
487    Set<Key> getAsSetAndClear() {
488      Set<Key> copy = ImmutableSet.copyOf(keys);
489      keys.clear();
490      return copy;
491    }
492
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 {
501    public <T> void onProvision(ProvisionInvocation<T> provision) {
502      throw new RuntimeException("boo");
503    }
504  }
505  private static class FailAfterProvision implements ProvisionListener {
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 {
512    public <T> void onProvision(ProvisionInvocation<T> provision) {
513      provision.provision();
514    }
515  }
516  private static class NoProvision implements ProvisionListener {
517    public <T> void onProvision(ProvisionInvocation<T> provision) {
518    }
519  }
520  private static class ProvisionTwice implements ProvisionListener {
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
531    public ChainAsserter(List<Class<?>> provisionList, Iterable<Class<?>> expected) {
532      this.provisionList = provisionList;
533      this.expected = ImmutableList.copyOf(expected);
534    }
535
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
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