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@google.com (Jesse Wilson)
44  */
45 public class BinderTestSuite extends TestCase {
46 
suite()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 {
configure(ScopedBindingBuilder sbb)245       void configure(ScopedBindingBuilder sbb) {}
apply(Builder builder)246       void apply(Builder builder) {}
247     },
248 
249     EAGER_SINGLETON {
configure(ScopedBindingBuilder sbb)250       void configure(ScopedBindingBuilder sbb) {
251         sbb.asEagerSingleton();
252       }
apply(Builder builder)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 {
configure(ScopedBindingBuilder sbb)260       void configure(ScopedBindingBuilder sbb) {
261         sbb.in(Scopes.SINGLETON);
262       }
apply(Builder builder)263       void apply(Builder builder) {
264         builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(201));
265       }
266     },
267 
268     SINGLETON_DOT_CLASS {
configure(ScopedBindingBuilder sbb)269       void configure(ScopedBindingBuilder sbb) {
270         sbb.in(Singleton.class);
271       }
apply(Builder builder)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 {
configure(ScopedBindingBuilder sbb)278       void configure(ScopedBindingBuilder sbb) {
279         sbb.in(TwoAtATimeScoped.class);
280       }
apply(Builder builder)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 {
configure(ScopedBindingBuilder sbb)287       void configure(ScopedBindingBuilder sbb) {
288         sbb.in(new TwoAtATimeScope());
289       }
apply(Builder builder)290       void apply(Builder builder) {
291         builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202));
292       }
293     };
294 
configure(ScopedBindingBuilder sbb)295     abstract void configure(ScopedBindingBuilder sbb);
apply(Builder builder)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 
module(Module module)319     public Builder module(Module module) {
320       this.modules.add(module);
321       return this;
322     }
323 
creationTime(CreationTime creationTime)324     public Builder creationTime(CreationTime creationTime) {
325       this.creationTime = creationTime;
326       return this;
327     }
328 
name(String name)329     public Builder name(String name) {
330       this.name = name;
331       return this;
332     }
333 
key(Key<?> key, Class<? extends Injectable> injectsKey)334     public Builder key(Key<?> key, Class<? extends Injectable> injectsKey) {
335       this.key = key;
336       this.injectsKey = injectsKey;
337       return this;
338     }
339 
creationException(String message, Object... args)340     private Builder creationException(String message, Object... args) {
341       this.creationException = String.format(message, args);
342       return this;
343     }
344 
configurationException(String message, Object... args)345     private Builder configurationException(String message, Object... args) {
346       configurationException = String.format(message, args);
347       return this;
348     }
349 
scoper(Scoper scoper)350     private Builder scoper(Scoper scoper) {
351       name(name + " in " + scoper);
352       scoper.apply(this);
353       return this;
354     }
355 
expectedValues(T... values)356     private <T> Builder expectedValues(T... values) {
357       this.expectedValues.clear();
358       Collections.addAll(this.expectedValues, values);
359       return this;
360     }
361 
addToSuite(TestSuite suite)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 
SuccessTest(Builder builder)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 
getName()394     public String getName() {
395       return name;
396     }
397 
newInjector()398     Injector newInjector() {
399       nextId.set(101);
400       return Guice.createInjector(modules);
401     }
402 
test()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 
CreationExceptionTest(Builder builder)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 
getName()462     public String getName() {
463       return "creation errors:" + name;
464     }
465 
test()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 
ConfigurationExceptionTest(Builder builder)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 
getName()492     public String getName() {
493       return "provision errors:" + name;
494     }
495 
newInjector()496     Injector newInjector() {
497       return Guice.createInjector(modules);
498     }
499 
test()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 
UserExceptionsTest(Builder builder)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 
getName()563     public String getName() {
564       return "provision errors:" + name;
565     }
566 
newInjector()567     Injector newInjector() {
568       return Guice.createInjector(modules);
569     }
570 
test()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 {
inject(AWithProvidedBy aWithProvidedBy, Provider<AWithProvidedBy> aWithProvidedByProvider)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 {
inject(@amed"apple") AWithProvidedBy aWithProvidedBy, @Named("apple") Provider<AWithProvidedBy> aWithProvidedByProvider)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 {
inject(AWithImplementedBy aWithImplementedBy, Provider<AWithImplementedBy> aWithImplementedByProvider)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 {
inject(@amed"apple") AWithImplementedBy aWithImplementedBy, @Named("apple") Provider<AWithImplementedBy> aWithImplementedByProvider)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 {
inject(A a, Provider<A> aProvider)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;
PlainA()691     PlainA() {
692       value = nextId.getAndIncrement();
693       if (value < 0) {
694         throw new RuntimeException("Illegal value: " + value);
695       }
696     }
PlainA(int value)697     PlainA(int value) {
698       this.value = value;
699     }
equals(Object obj)700     public boolean equals(Object obj) {
701       return obj instanceof PlainA
702           && value == ((PlainA) obj).value;
703     }
hashCode()704     public int hashCode() {
705       return value;
706     }
toString()707     public String toString() {
708       return "PlainA#" + value;
709     }
710   }
711 
712   static class PlainAProvider implements Provider<A> {
get()713     public A get() {
714       return new PlainA();
715     }
716   }
717 
718   static class InjectsPlainA extends Injectable {
inject(PlainA plainA, Provider<PlainA> plainAProvider)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 {
scope(Key<T> key, final Provider<T> unscoped)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 {
inject(ScopedA scopedA, Provider<ScopedA> scopedAProvider)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 {
inject(@amed"apple") ScopedA scopedA, @Named("apple") Provider<ScopedA> scopedAProvider)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