Home | History | Annotate | Download | only in inject
      1 /*
      2  * Copyright (C) 2008 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.inject;
     18 
     19 import static com.google.inject.Asserts.assertContains;
     20 import static com.google.inject.name.Names.named;
     21 import static java.lang.annotation.ElementType.METHOD;
     22 import static java.lang.annotation.ElementType.TYPE;
     23 import static java.lang.annotation.RetentionPolicy.RUNTIME;
     24 
     25 import com.google.common.collect.ImmutableList;
     26 import com.google.common.collect.Lists;
     27 import com.google.inject.binder.AnnotatedBindingBuilder;
     28 import com.google.inject.binder.ScopedBindingBuilder;
     29 import com.google.inject.name.Named;
     30 import com.google.inject.util.Providers;
     31 import java.lang.annotation.Retention;
     32 import java.lang.annotation.Target;
     33 import java.util.Collections;
     34 import java.util.List;
     35 import java.util.concurrent.atomic.AtomicInteger;
     36 import junit.framework.Test;
     37 import junit.framework.TestCase;
     38 import junit.framework.TestSuite;
     39 
     40 /** @author jessewilson (at) google.com (Jesse Wilson) */
     41 public class BinderTestSuite extends TestCase {
     42 
     43   public static Test suite() {
     44     TestSuite suite = new TestSuite();
     45 
     46     new Builder()
     47         .name("bind A")
     48         .module(
     49             new AbstractModule() {
     50               @Override
     51               protected void configure() {
     52                 bind(A.class);
     53               }
     54             })
     55         .creationException("No implementation for %s was bound", A.class.getName())
     56         .addToSuite(suite);
     57 
     58     new Builder()
     59         .name("bind PlainA named apple")
     60         .module(
     61             new AbstractModule() {
     62               @Override
     63               protected void configure() {
     64                 bind(PlainA.class).annotatedWith(named("apple"));
     65               }
     66             })
     67         .creationException(
     68             "No implementation for %s annotated with %s was bound",
     69             PlainA.class.getName(), named("apple"))
     70         .addToSuite(suite);
     71 
     72     new Builder()
     73         .name("bind A to new PlainA(1)")
     74         .module(
     75             new AbstractModule() {
     76               @Override
     77               protected void configure() {
     78                 bind(A.class).toInstance(new PlainA(1));
     79               }
     80             })
     81         .creationTime(CreationTime.NONE)
     82         .expectedValues(new PlainA(1), new PlainA(1), new PlainA(1))
     83         .addToSuite(suite);
     84 
     85     new Builder()
     86         .name("no binding, AWithProvidedBy")
     87         .key(Key.get(AWithProvidedBy.class), InjectsAWithProvidedBy.class)
     88         .addToSuite(suite);
     89 
     90     new Builder()
     91         .name("no binding, AWithImplementedBy")
     92         .key(Key.get(AWithImplementedBy.class), InjectsAWithImplementedBy.class)
     93         .addToSuite(suite);
     94 
     95     new Builder()
     96         .name("no binding, ScopedA")
     97         .key(Key.get(ScopedA.class), InjectsScopedA.class)
     98         .expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202))
     99         .addToSuite(suite);
    100 
    101     new Builder()
    102         .name("no binding, AWithProvidedBy named apple")
    103         .key(Key.get(AWithProvidedBy.class, named("apple")), InjectsAWithProvidedByNamedApple.class)
    104         .configurationException(
    105             "No implementation for %s annotated with %s was bound",
    106             AWithProvidedBy.class.getName(), named("apple"))
    107         .addToSuite(suite);
    108 
    109     new Builder()
    110         .name("no binding, AWithImplementedBy named apple")
    111         .key(
    112             Key.get(AWithImplementedBy.class, named("apple")),
    113             InjectsAWithImplementedByNamedApple.class)
    114         .configurationException(
    115             "No implementation for %s annotated with %s was bound",
    116             AWithImplementedBy.class.getName(), named("apple"))
    117         .addToSuite(suite);
    118 
    119     new Builder()
    120         .name("no binding, ScopedA named apple")
    121         .key(Key.get(ScopedA.class, named("apple")), InjectsScopedANamedApple.class)
    122         .configurationException(
    123             "No implementation for %s annotated with %s was bound",
    124             ScopedA.class.getName(), named("apple"))
    125         .addToSuite(suite);
    126 
    127     for (final Scoper scoper : Scoper.values()) {
    128       new Builder()
    129           .name("bind PlainA")
    130           .key(Key.get(PlainA.class), InjectsPlainA.class)
    131           .module(
    132               new AbstractModule() {
    133                 @Override
    134                 protected void configure() {
    135                   AnnotatedBindingBuilder<PlainA> abb = bind(PlainA.class);
    136                   scoper.configure(abb);
    137                 }
    138               })
    139           .scoper(scoper)
    140           .addToSuite(suite);
    141 
    142       new Builder()
    143           .name("bind A to PlainA")
    144           .module(
    145               new AbstractModule() {
    146                 @Override
    147                 protected void configure() {
    148                   ScopedBindingBuilder sbb = bind(A.class).to(PlainA.class);
    149                   scoper.configure(sbb);
    150                 }
    151               })
    152           .scoper(scoper)
    153           .addToSuite(suite);
    154 
    155       new Builder()
    156           .name("bind A to PlainAProvider.class")
    157           .module(
    158               new AbstractModule() {
    159                 @Override
    160                 protected void configure() {
    161                   ScopedBindingBuilder sbb = bind(A.class).toProvider(PlainAProvider.class);
    162                   scoper.configure(sbb);
    163                 }
    164               })
    165           .scoper(scoper)
    166           .addToSuite(suite);
    167 
    168       new Builder()
    169           .name("bind A to new PlainAProvider()")
    170           .module(
    171               new AbstractModule() {
    172                 @Override
    173                 protected void configure() {
    174                   ScopedBindingBuilder sbb = bind(A.class).toProvider(new PlainAProvider());
    175                   scoper.configure(sbb);
    176                 }
    177               })
    178           .scoper(scoper)
    179           .addToSuite(suite);
    180 
    181       new Builder()
    182           .name("bind AWithProvidedBy")
    183           .key(Key.get(AWithProvidedBy.class), InjectsAWithProvidedBy.class)
    184           .module(
    185               new AbstractModule() {
    186                 @Override
    187                 protected void configure() {
    188                   ScopedBindingBuilder sbb = bind(AWithProvidedBy.class);
    189                   scoper.configure(sbb);
    190                 }
    191               })
    192           .scoper(scoper)
    193           .addToSuite(suite);
    194 
    195       new Builder()
    196           .name("bind AWithImplementedBy")
    197           .key(Key.get(AWithImplementedBy.class), InjectsAWithImplementedBy.class)
    198           .module(
    199               new AbstractModule() {
    200                 @Override
    201                 protected void configure() {
    202                   ScopedBindingBuilder sbb = bind(AWithImplementedBy.class);
    203                   scoper.configure(sbb);
    204                 }
    205               })
    206           .scoper(scoper)
    207           .addToSuite(suite);
    208 
    209       new Builder()
    210           .name("bind ScopedA")
    211           .key(Key.get(ScopedA.class), InjectsScopedA.class)
    212           .module(
    213               new AbstractModule() {
    214                 @Override
    215                 protected void configure() {
    216                   ScopedBindingBuilder sbb = bind(ScopedA.class);
    217                   scoper.configure(sbb);
    218                 }
    219               })
    220           .expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202))
    221           .scoper(scoper)
    222           .addToSuite(suite);
    223 
    224       new Builder()
    225           .name("bind AWithProvidedBy named apple")
    226           .module(
    227               new AbstractModule() {
    228                 @Override
    229                 protected void configure() {
    230                   scoper.configure(bind(AWithProvidedBy.class).annotatedWith(named("apple")));
    231                 }
    232               })
    233           .creationException(
    234               "No implementation for %s annotated with %s was bound",
    235               AWithProvidedBy.class.getName(), named("apple"))
    236           .addToSuite(suite);
    237 
    238       new Builder()
    239           .name("bind AWithImplementedBy named apple")
    240           .module(
    241               new AbstractModule() {
    242                 @Override
    243                 protected void configure() {
    244                   scoper.configure(bind(AWithImplementedBy.class).annotatedWith(named("apple")));
    245                 }
    246               })
    247           .creationException(
    248               "No implementation for %s annotated with %s was bound",
    249               AWithImplementedBy.class.getName(), named("apple"))
    250           .addToSuite(suite);
    251 
    252       new Builder()
    253           .name("bind ScopedA named apple")
    254           .module(
    255               new AbstractModule() {
    256                 @Override
    257                 protected void configure() {
    258                   scoper.configure(bind(ScopedA.class).annotatedWith(named("apple")));
    259                 }
    260               })
    261           .creationException(
    262               "No implementation for %s annotated with %s was bound",
    263               ScopedA.class.getName(), named("apple"))
    264           .addToSuite(suite);
    265     }
    266 
    267     return suite;
    268   }
    269 
    270   enum Scoper {
    271     UNSCOPED {
    272       @Override
    273       void configure(ScopedBindingBuilder sbb) {}
    274 
    275       @Override
    276       void apply(Builder builder) {}
    277     },
    278 
    279     EAGER_SINGLETON {
    280       @Override
    281       void configure(ScopedBindingBuilder sbb) {
    282         sbb.asEagerSingleton();
    283       }
    284 
    285       @Override
    286       void apply(Builder builder) {
    287         builder.expectedValues(new PlainA(101), new PlainA(101), new PlainA(101));
    288         builder.creationTime(CreationTime.EAGER);
    289       }
    290     },
    291 
    292     SCOPES_SINGLETON {
    293       @Override
    294       void configure(ScopedBindingBuilder sbb) {
    295         sbb.in(Scopes.SINGLETON);
    296       }
    297 
    298       @Override
    299       void apply(Builder builder) {
    300         builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(201));
    301       }
    302     },
    303 
    304     SINGLETON_DOT_CLASS {
    305       @Override
    306       void configure(ScopedBindingBuilder sbb) {
    307         sbb.in(Singleton.class);
    308       }
    309 
    310       @Override
    311       void apply(Builder builder) {
    312         builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(201));
    313       }
    314     },
    315 
    316     TWO_AT_A_TIME_SCOPED_DOT_CLASS {
    317       @Override
    318       void configure(ScopedBindingBuilder sbb) {
    319         sbb.in(TwoAtATimeScoped.class);
    320       }
    321 
    322       @Override
    323       void apply(Builder builder) {
    324         builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202));
    325       }
    326     },
    327 
    328     TWO_AT_A_TIME_SCOPE {
    329       @Override
    330       void configure(ScopedBindingBuilder sbb) {
    331         sbb.in(new TwoAtATimeScope());
    332       }
    333 
    334       @Override
    335       void apply(Builder builder) {
    336         builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202));
    337       }
    338     };
    339 
    340     abstract void configure(ScopedBindingBuilder sbb);
    341 
    342     abstract void apply(Builder builder);
    343   }
    344 
    345   /** When Guice creates a value, directly or via a provider */
    346   enum CreationTime {
    347     NONE,
    348     EAGER,
    349     LAZY
    350   }
    351 
    352   public static class Builder {
    353     private String name = "test";
    354     private Key<?> key = Key.get(A.class);
    355     private Class<? extends Injectable> injectsKey = InjectsA.class;
    356     private List<Module> modules =
    357         Lists.<Module>newArrayList(
    358             new AbstractModule() {
    359               @Override
    360               protected void configure() {
    361                 bindScope(TwoAtATimeScoped.class, new TwoAtATimeScope());
    362               }
    363             });
    364     private List<Object> expectedValues =
    365         Lists.<Object>newArrayList(new PlainA(201), new PlainA(202), new PlainA(203));
    366     private CreationTime creationTime = CreationTime.LAZY;
    367     private String creationException;
    368     private String configurationException;
    369 
    370     public Builder module(Module module) {
    371       this.modules.add(module);
    372       return this;
    373     }
    374 
    375     public Builder creationTime(CreationTime creationTime) {
    376       this.creationTime = creationTime;
    377       return this;
    378     }
    379 
    380     public Builder name(String name) {
    381       this.name = name;
    382       return this;
    383     }
    384 
    385     public Builder key(Key<?> key, Class<? extends Injectable> injectsKey) {
    386       this.key = key;
    387       this.injectsKey = injectsKey;
    388       return this;
    389     }
    390 
    391     private Builder creationException(String message, Object... args) {
    392       this.creationException = String.format(message, args);
    393       return this;
    394     }
    395 
    396     private Builder configurationException(String message, Object... args) {
    397       configurationException = String.format(message, args);
    398       return this;
    399     }
    400 
    401     private Builder scoper(Scoper scoper) {
    402       name(name + " in " + scoper);
    403       scoper.apply(this);
    404       return this;
    405     }
    406 
    407     private <T> Builder expectedValues(T... values) {
    408       this.expectedValues.clear();
    409       Collections.addAll(this.expectedValues, values);
    410       return this;
    411     }
    412 
    413     public void addToSuite(TestSuite suite) {
    414       if (creationException != null) {
    415         suite.addTest(new CreationExceptionTest(this));
    416 
    417       } else if (configurationException != null) {
    418         suite.addTest(new ConfigurationExceptionTest(this));
    419 
    420       } else {
    421         suite.addTest(new SuccessTest(this));
    422         if (creationTime != CreationTime.NONE) {
    423           suite.addTest(new UserExceptionsTest(this));
    424         }
    425       }
    426     }
    427   }
    428 
    429   public static class SuccessTest extends TestCase {
    430     final String name;
    431     final Key<?> key;
    432     final Class<? extends Injectable> injectsKey;
    433     final ImmutableList<Module> modules;
    434     final ImmutableList<Object> expectedValues;
    435 
    436     public SuccessTest(Builder builder) {
    437       super("test");
    438       name = builder.name;
    439       key = builder.key;
    440       injectsKey = builder.injectsKey;
    441       modules = ImmutableList.copyOf(builder.modules);
    442       expectedValues = ImmutableList.copyOf(builder.expectedValues);
    443     }
    444 
    445     @Override
    446     public String getName() {
    447       return name;
    448     }
    449 
    450     Injector newInjector() {
    451       nextId.set(101);
    452       return Guice.createInjector(modules);
    453     }
    454 
    455     public void test() throws IllegalAccessException, InstantiationException {
    456       Injector injector = newInjector();
    457       nextId.set(201);
    458       for (Object value : expectedValues) {
    459         assertEquals(value, injector.getInstance(key));
    460       }
    461 
    462       Provider<?> provider = newInjector().getProvider(key);
    463       nextId.set(201);
    464       for (Object value : expectedValues) {
    465         assertEquals(value, provider.get());
    466       }
    467 
    468       Provider<?> bindingProvider = newInjector().getBinding(key).getProvider();
    469       nextId.set(201);
    470       for (Object value : expectedValues) {
    471         assertEquals(value, bindingProvider.get());
    472       }
    473 
    474       injector = newInjector();
    475       nextId.set(201);
    476       for (Object value : expectedValues) {
    477         Injectable instance = injector.getInstance(injectsKey);
    478         assertEquals(value, instance.value);
    479       }
    480 
    481       injector = newInjector();
    482       nextId.set(201);
    483       for (Object value : expectedValues) {
    484         Injectable injectable = injectsKey.newInstance();
    485         injector.injectMembers(injectable);
    486         assertEquals(value, injectable.value);
    487       }
    488 
    489       Injector injector1 = newInjector();
    490       nextId.set(201);
    491       Injectable hasProvider = injector1.getInstance(injectsKey);
    492       hasProvider.provider.get();
    493       nextId.set(201);
    494       for (Object value : expectedValues) {
    495         assertEquals(value, hasProvider.provider.get());
    496       }
    497     }
    498   }
    499 
    500   public static class CreationExceptionTest extends TestCase {
    501     final String name;
    502     final Key<?> key;
    503     final ImmutableList<Module> modules;
    504     final String creationException;
    505 
    506     public CreationExceptionTest(Builder builder) {
    507       super("test");
    508       name = builder.name;
    509       key = builder.key;
    510       modules = ImmutableList.copyOf(builder.modules);
    511       creationException = builder.creationException;
    512     }
    513 
    514     @Override
    515     public String getName() {
    516       return "creation errors:" + name;
    517     }
    518 
    519     public void test() {
    520       try {
    521         Guice.createInjector(modules);
    522         fail();
    523       } catch (CreationException expected) {
    524         assertContains(expected.getMessage(), creationException);
    525       }
    526     }
    527   }
    528 
    529   public static class ConfigurationExceptionTest extends TestCase {
    530     final String name;
    531     final Key<?> key;
    532     final Class<? extends Injectable> injectsKey;
    533     final ImmutableList<Module> modules;
    534     final String configurationException;
    535 
    536     public ConfigurationExceptionTest(Builder builder) {
    537       super("test");
    538       name = builder.name;
    539       key = builder.key;
    540       injectsKey = builder.injectsKey;
    541       modules = ImmutableList.copyOf(builder.modules);
    542       configurationException = builder.configurationException;
    543     }
    544 
    545     @Override
    546     public String getName() {
    547       return "provision errors:" + name;
    548     }
    549 
    550     Injector newInjector() {
    551       return Guice.createInjector(modules);
    552     }
    553 
    554     public void test() throws IllegalAccessException, InstantiationException {
    555       try {
    556         newInjector().getProvider(key);
    557         fail();
    558       } catch (ConfigurationException expected) {
    559         assertContains(expected.getMessage(), configurationException);
    560       }
    561 
    562       try {
    563         newInjector().getBinding(key).getProvider();
    564         fail();
    565       } catch (ConfigurationException expected) {
    566         assertContains(expected.getMessage(), configurationException);
    567       }
    568 
    569       try {
    570         newInjector().getInstance(key);
    571         fail();
    572       } catch (ConfigurationException expected) {
    573         assertContains(expected.getMessage(), configurationException);
    574       }
    575 
    576       try {
    577         newInjector().getInstance(injectsKey);
    578         fail();
    579       } catch (ConfigurationException expected) {
    580         assertContains(
    581             expected.getMessage(),
    582             configurationException,
    583             injectsKey.getName() + ".inject",
    584             configurationException,
    585             injectsKey.getName() + ".inject",
    586             "2 errors");
    587       }
    588 
    589       try {
    590         Injectable injectable = injectsKey.newInstance();
    591         newInjector().injectMembers(injectable);
    592         fail();
    593       } catch (ConfigurationException expected) {
    594         assertContains(
    595             expected.getMessage(),
    596             configurationException,
    597             injectsKey.getName() + ".inject",
    598             configurationException,
    599             injectsKey.getName() + ".inject",
    600             "2 errors");
    601       }
    602     }
    603   }
    604 
    605   public static class UserExceptionsTest extends TestCase {
    606     final String name;
    607     final Key<?> key;
    608     final Class<? extends Injectable> injectsKey;
    609     final ImmutableList<Module> modules;
    610     final ImmutableList<Object> expectedValues;
    611     final CreationTime creationTime;
    612 
    613     public UserExceptionsTest(Builder builder) {
    614       super("test");
    615       name = builder.name;
    616       key = builder.key;
    617       injectsKey = builder.injectsKey;
    618       modules = ImmutableList.copyOf(builder.modules);
    619       expectedValues = ImmutableList.copyOf(builder.expectedValues);
    620       creationTime = builder.creationTime;
    621     }
    622 
    623     @Override
    624     public String getName() {
    625       return "provision errors:" + name;
    626     }
    627 
    628     Injector newInjector() {
    629       return Guice.createInjector(modules);
    630     }
    631 
    632     public void test() throws IllegalAccessException, InstantiationException {
    633       nextId.set(-1);
    634       try {
    635         newInjector();
    636         assertEquals(CreationTime.LAZY, creationTime);
    637       } catch (CreationException expected) {
    638         assertEquals(CreationTime.EAGER, creationTime);
    639         assertContains(expected.getMessage(), "Illegal value: -1");
    640         return;
    641       }
    642 
    643       Provider<?> provider = newInjector().getProvider(key);
    644       Provider<?> bindingProvider = newInjector().getBinding(key).getProvider();
    645 
    646       nextId.set(-1);
    647       try {
    648         newInjector().getInstance(key);
    649         fail();
    650       } catch (ProvisionException expected) {
    651         assertContains(expected.getMessage(), "Illegal value: -1");
    652       }
    653 
    654       nextId.set(-1);
    655       try {
    656         provider.get();
    657         fail();
    658       } catch (ProvisionException expected) {
    659         assertContains(expected.getMessage(), "Illegal value: -1");
    660       }
    661 
    662       nextId.set(-1);
    663       try {
    664         bindingProvider.get();
    665         fail();
    666       } catch (ProvisionException expected) {
    667         assertContains(expected.getMessage(), "Illegal value: -1");
    668       }
    669 
    670       try {
    671         nextId.set(-1);
    672         newInjector().getInstance(injectsKey);
    673         fail("Expected ProvisionException");
    674       } catch (ProvisionException expected) {
    675         assertContains(
    676             expected.getMessage(),
    677             "Illegal value: -1",
    678             "for the 1st parameter of " + injectsKey.getName() + ".inject");
    679       }
    680 
    681       nextId.set(201);
    682       Injectable injectable = injectsKey.newInstance();
    683       try {
    684         nextId.set(-1);
    685         newInjector().injectMembers(injectable);
    686       } catch (ProvisionException expected) {
    687         assertContains(
    688             expected.getMessage(),
    689             "Illegal value: -1",
    690             "for the 1st parameter of " + injectsKey.getName() + ".inject");
    691       }
    692 
    693       nextId.set(201);
    694       Injectable hasProvider = newInjector().getInstance(injectsKey);
    695       hasProvider.provider.get();
    696       try {
    697         nextId.set(-1);
    698         hasProvider.provider.get();
    699         // TODO(lukes): insert fail() call here
    700       } catch (ProvisionException expected) {
    701         assertContains(expected.getMessage(), "Illegal value: -1");
    702       }
    703     }
    704   }
    705 
    706   /** negative to throw, 101... for eager singletons, 201... for everything else */
    707   static final AtomicInteger nextId = new AtomicInteger();
    708 
    709   @ProvidedBy(PlainAProvider.class)
    710   interface AWithProvidedBy {}
    711 
    712   static class InjectsAWithProvidedBy extends Injectable {
    713     @Inject
    714     public void inject(
    715         AWithProvidedBy aWithProvidedBy, Provider<AWithProvidedBy> aWithProvidedByProvider) {
    716       this.value = aWithProvidedBy;
    717       this.provider = aWithProvidedByProvider;
    718     }
    719   }
    720 
    721   static class InjectsAWithProvidedByNamedApple extends Injectable {
    722     @Inject
    723     public void inject(
    724         @Named("apple") AWithProvidedBy aWithProvidedBy,
    725         @Named("apple") Provider<AWithProvidedBy> aWithProvidedByProvider) {
    726       this.value = aWithProvidedBy;
    727       this.provider = aWithProvidedByProvider;
    728     }
    729   }
    730 
    731   @ImplementedBy(PlainA.class)
    732   interface AWithImplementedBy {}
    733 
    734   static class InjectsAWithImplementedBy extends Injectable {
    735     @Inject
    736     public void inject(
    737         AWithImplementedBy aWithImplementedBy,
    738         Provider<AWithImplementedBy> aWithImplementedByProvider) {
    739       this.value = aWithImplementedBy;
    740       this.provider = aWithImplementedByProvider;
    741     }
    742   }
    743 
    744   static class InjectsAWithImplementedByNamedApple extends Injectable {
    745     @Inject
    746     public void inject(
    747         @Named("apple") AWithImplementedBy aWithImplementedBy,
    748         @Named("apple") Provider<AWithImplementedBy> aWithImplementedByProvider) {
    749       this.value = aWithImplementedBy;
    750       this.provider = aWithImplementedByProvider;
    751     }
    752   }
    753 
    754   interface A extends AWithProvidedBy, AWithImplementedBy {}
    755 
    756   static class InjectsA extends Injectable {
    757     @Inject
    758     public void inject(A a, Provider<A> aProvider) {
    759       this.value = a;
    760       this.provider = aProvider;
    761     }
    762   }
    763 
    764   static class PlainA implements A {
    765     final int value;
    766 
    767     PlainA() {
    768       value = nextId.getAndIncrement();
    769       if (value < 0) {
    770         throw new RuntimeException("Illegal value: " + value);
    771       }
    772     }
    773 
    774     PlainA(int value) {
    775       this.value = value;
    776     }
    777 
    778     @Override
    779     public boolean equals(Object obj) {
    780       return obj instanceof PlainA && value == ((PlainA) obj).value;
    781     }
    782 
    783     @Override
    784     public int hashCode() {
    785       return value;
    786     }
    787 
    788     @Override
    789     public String toString() {
    790       return "PlainA#" + value;
    791     }
    792   }
    793 
    794   static class PlainAProvider implements Provider<A> {
    795     @Override
    796     public A get() {
    797       return new PlainA();
    798     }
    799   }
    800 
    801   static class InjectsPlainA extends Injectable {
    802     @Inject
    803     public void inject(PlainA plainA, Provider<PlainA> plainAProvider) {
    804       this.value = plainA;
    805       this.provider = plainAProvider;
    806     }
    807   }
    808 
    809   /** This scope hands out each value exactly twice */
    810   static class TwoAtATimeScope implements Scope {
    811     @Override
    812     public <T> Provider<T> scope(Key<T> key, final Provider<T> unscoped) {
    813       return new Provider<T>() {
    814         T instance;
    815 
    816         @Override
    817         public T get() {
    818           if (instance == null) {
    819             instance = unscoped.get();
    820             return instance;
    821           } else {
    822             T result = instance;
    823             instance = null;
    824             return result;
    825           }
    826         }
    827       };
    828     }
    829   }
    830 
    831   @Target({TYPE, METHOD})
    832   @Retention(RUNTIME)
    833   @ScopeAnnotation
    834   public @interface TwoAtATimeScoped {}
    835 
    836   @TwoAtATimeScoped
    837   static class ScopedA extends PlainA {}
    838 
    839   static class InjectsScopedA extends Injectable {
    840     @Inject
    841     public void inject(ScopedA scopedA, Provider<ScopedA> scopedAProvider) {
    842       this.value = scopedA;
    843       this.provider = scopedAProvider;
    844     }
    845   }
    846 
    847   static class InjectsScopedANamedApple extends Injectable {
    848     @Inject
    849     public void inject(
    850         @Named("apple") ScopedA scopedA, @Named("apple") Provider<ScopedA> scopedAProvider) {
    851       this.value = scopedA;
    852       this.provider = scopedAProvider;
    853     }
    854   }
    855 
    856   static class Injectable {
    857     Object value = new Object();
    858     Provider<?> provider = Providers.of(new Object());
    859   }
    860 }
    861