• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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@google.com (Jesse Wilson) */
41 public class BinderTestSuite extends TestCase {
42 
suite()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
configure(ScopedBindingBuilder sbb)273       void configure(ScopedBindingBuilder sbb) {}
274 
275       @Override
apply(Builder builder)276       void apply(Builder builder) {}
277     },
278 
279     EAGER_SINGLETON {
280       @Override
configure(ScopedBindingBuilder sbb)281       void configure(ScopedBindingBuilder sbb) {
282         sbb.asEagerSingleton();
283       }
284 
285       @Override
apply(Builder builder)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
configure(ScopedBindingBuilder sbb)294       void configure(ScopedBindingBuilder sbb) {
295         sbb.in(Scopes.SINGLETON);
296       }
297 
298       @Override
apply(Builder builder)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
configure(ScopedBindingBuilder sbb)306       void configure(ScopedBindingBuilder sbb) {
307         sbb.in(Singleton.class);
308       }
309 
310       @Override
apply(Builder builder)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
configure(ScopedBindingBuilder sbb)318       void configure(ScopedBindingBuilder sbb) {
319         sbb.in(TwoAtATimeScoped.class);
320       }
321 
322       @Override
apply(Builder builder)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
configure(ScopedBindingBuilder sbb)330       void configure(ScopedBindingBuilder sbb) {
331         sbb.in(new TwoAtATimeScope());
332       }
333 
334       @Override
apply(Builder builder)335       void apply(Builder builder) {
336         builder.expectedValues(new PlainA(201), new PlainA(201), new PlainA(202), new PlainA(202));
337       }
338     };
339 
configure(ScopedBindingBuilder sbb)340     abstract void configure(ScopedBindingBuilder sbb);
341 
apply(Builder builder)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 
module(Module module)370     public Builder module(Module module) {
371       this.modules.add(module);
372       return this;
373     }
374 
creationTime(CreationTime creationTime)375     public Builder creationTime(CreationTime creationTime) {
376       this.creationTime = creationTime;
377       return this;
378     }
379 
name(String name)380     public Builder name(String name) {
381       this.name = name;
382       return this;
383     }
384 
key(Key<?> key, Class<? extends Injectable> injectsKey)385     public Builder key(Key<?> key, Class<? extends Injectable> injectsKey) {
386       this.key = key;
387       this.injectsKey = injectsKey;
388       return this;
389     }
390 
creationException(String message, Object... args)391     private Builder creationException(String message, Object... args) {
392       this.creationException = String.format(message, args);
393       return this;
394     }
395 
configurationException(String message, Object... args)396     private Builder configurationException(String message, Object... args) {
397       configurationException = String.format(message, args);
398       return this;
399     }
400 
scoper(Scoper scoper)401     private Builder scoper(Scoper scoper) {
402       name(name + " in " + scoper);
403       scoper.apply(this);
404       return this;
405     }
406 
expectedValues(T... values)407     private <T> Builder expectedValues(T... values) {
408       this.expectedValues.clear();
409       Collections.addAll(this.expectedValues, values);
410       return this;
411     }
412 
addToSuite(TestSuite suite)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 
SuccessTest(Builder builder)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
getName()446     public String getName() {
447       return name;
448     }
449 
newInjector()450     Injector newInjector() {
451       nextId.set(101);
452       return Guice.createInjector(modules);
453     }
454 
test()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 
CreationExceptionTest(Builder builder)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
getName()515     public String getName() {
516       return "creation errors:" + name;
517     }
518 
test()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 
ConfigurationExceptionTest(Builder builder)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
getName()546     public String getName() {
547       return "provision errors:" + name;
548     }
549 
newInjector()550     Injector newInjector() {
551       return Guice.createInjector(modules);
552     }
553 
test()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 
UserExceptionsTest(Builder builder)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
getName()624     public String getName() {
625       return "provision errors:" + name;
626     }
627 
newInjector()628     Injector newInjector() {
629       return Guice.createInjector(modules);
630     }
631 
test()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
inject( AWithProvidedBy aWithProvidedBy, Provider<AWithProvidedBy> aWithProvidedByProvider)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
inject( @amed"apple") AWithProvidedBy aWithProvidedBy, @Named("apple") Provider<AWithProvidedBy> aWithProvidedByProvider)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
inject( AWithImplementedBy aWithImplementedBy, Provider<AWithImplementedBy> aWithImplementedByProvider)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
inject( @amed"apple") AWithImplementedBy aWithImplementedBy, @Named("apple") Provider<AWithImplementedBy> aWithImplementedByProvider)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
inject(A a, Provider<A> aProvider)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 
PlainA()767     PlainA() {
768       value = nextId.getAndIncrement();
769       if (value < 0) {
770         throw new RuntimeException("Illegal value: " + value);
771       }
772     }
773 
PlainA(int value)774     PlainA(int value) {
775       this.value = value;
776     }
777 
778     @Override
equals(Object obj)779     public boolean equals(Object obj) {
780       return obj instanceof PlainA && value == ((PlainA) obj).value;
781     }
782 
783     @Override
hashCode()784     public int hashCode() {
785       return value;
786     }
787 
788     @Override
toString()789     public String toString() {
790       return "PlainA#" + value;
791     }
792   }
793 
794   static class PlainAProvider implements Provider<A> {
795     @Override
get()796     public A get() {
797       return new PlainA();
798     }
799   }
800 
801   static class InjectsPlainA extends Injectable {
802     @Inject
inject(PlainA plainA, Provider<PlainA> plainAProvider)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
scope(Key<T> key, final Provider<T> unscoped)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
inject(ScopedA scopedA, Provider<ScopedA> scopedAProvider)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
inject( @amed"apple") ScopedA scopedA, @Named("apple") Provider<ScopedA> scopedAProvider)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