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