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