• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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 
22 import com.google.common.collect.ImmutableSet;
23 import com.google.common.collect.Sets;
24 import com.google.common.util.concurrent.Runnables;
25 import com.google.inject.internal.Annotations;
26 import com.google.inject.matcher.Matchers;
27 import com.google.inject.name.Named;
28 import com.google.inject.spi.InjectionPoint;
29 import com.google.inject.spi.TypeEncounter;
30 import com.google.inject.spi.TypeListener;
31 
32 import junit.framework.TestCase;
33 
34 /*if[AOP]*/
35 import org.aopalliance.intercept.MethodInterceptor;
36 import org.aopalliance.intercept.MethodInvocation;
37 /*end[AOP]*/
38 
39 import java.lang.reflect.Constructor;
40 import java.util.ArrayList;
41 import java.util.Collection;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.concurrent.atomic.AtomicInteger;
46 import java.util.logging.Logger;
47 
48 /**
49  * @author crazybob@google.com (Bob Lee)
50  */
51 public class BindingTest extends TestCase {
52 
53   static class Dependent {
54     @Inject A a;
Dependent(A a, B b)55     @Inject Dependent(A a, B b) {}
injectBob(Bob bob)56     @Inject void injectBob(Bob bob) {}
57   }
58 
testExplicitCyclicDependency()59   public void testExplicitCyclicDependency() {
60     Guice.createInjector(
61             new AbstractModule() {
62               @Override
63               protected void configure() {
64                 bind(A.class);
65                 bind(B.class);
66               }
67             })
68         .getInstance(A.class);
69   }
70 
71   static class A { @Inject B b; }
72   static class B { @Inject A a; }
73 
74   static class Bob {}
75 
76   static class MyModule extends AbstractModule {
77 
78     @Override
configure()79     protected void configure() {
80       // Linked.
81       bind(Object.class).to(Runnable.class).in(Scopes.SINGLETON);
82 
83       // Instance.
84       bind(Runnable.class).toInstance(Runnables.doNothing());
85 
86       // Provider instance.
87       bind(Foo.class)
88           .toProvider(
89               new Provider<Foo>() {
90                 @Override
91                 public Foo get() {
92                   return new Foo();
93                 }
94               })
95           .in(Scopes.SINGLETON);
96 
97       // Provider.
98       bind(Foo.class)
99           .annotatedWith(named("provider"))
100           .toProvider(FooProvider.class);
101 
102       // Class.
103       bind(Bar.class).in(Scopes.SINGLETON);
104 
105       // Constant.
106       bindConstant().annotatedWith(named("name")).to("Bob");
107     }
108   }
109 
110   static class Foo {}
111 
112   public static class FooProvider implements Provider<Foo> {
113     @Override
get()114     public Foo get() {
115       throw new UnsupportedOperationException();
116     }
117   }
118 
119   public static class Bar {}
120 
testBindToUnboundLinkedBinding()121   public void testBindToUnboundLinkedBinding() {
122     try {
123       Guice.createInjector(
124           new AbstractModule() {
125             @Override
126             protected void configure() {
127               bind(Collection.class).to(List.class);
128             }
129           });
130       fail();
131     } catch (CreationException expected) {
132       assertContains(expected.getMessage(), "No implementation for java.util.List was bound.");
133     }
134   }
135 
136   /**
137    * This test ensures that the asEagerSingleton() scoping applies to the key,
138    * not to what the key is linked to.
139    */
testScopeIsAppliedToKeyNotTarget()140   public void testScopeIsAppliedToKeyNotTarget() {
141     Injector injector =
142         Guice.createInjector(
143             new AbstractModule() {
144               @Override
145               protected void configure() {
146                 bind(Integer.class).toProvider(Counter.class).asEagerSingleton();
147                 bind(Number.class).toProvider(Counter.class).asEagerSingleton();
148               }
149             });
150 
151     assertNotSame(injector.getInstance(Integer.class), injector.getInstance(Number.class));
152   }
153 
154   static class Counter implements Provider<Integer> {
155     static AtomicInteger next = new AtomicInteger(1);
156     @Override
get()157     public Integer get() {
158       return next.getAndIncrement();
159     }
160   }
161 
testAnnotatedNoArgConstructor()162   public void testAnnotatedNoArgConstructor() {
163     assertBindingSucceeds(PublicNoArgAnnotated.class);
164     assertBindingSucceeds(ProtectedNoArgAnnotated.class);
165     assertBindingSucceeds(PackagePrivateNoArgAnnotated.class);
166     assertBindingSucceeds(PrivateNoArgAnnotated.class);
167   }
168 
169   static class PublicNoArgAnnotated {
PublicNoArgAnnotated()170     @Inject public PublicNoArgAnnotated() { }
171   }
172 
173   static class ProtectedNoArgAnnotated {
ProtectedNoArgAnnotated()174     @Inject protected ProtectedNoArgAnnotated() { }
175   }
176 
177   static class PackagePrivateNoArgAnnotated {
PackagePrivateNoArgAnnotated()178     @Inject PackagePrivateNoArgAnnotated() { }
179   }
180 
181   static class PrivateNoArgAnnotated {
PrivateNoArgAnnotated()182     @Inject private PrivateNoArgAnnotated() { }
183   }
184 
testUnannotatedNoArgConstructor()185   public void testUnannotatedNoArgConstructor() throws Exception{
186     assertBindingSucceeds(PublicNoArg.class);
187     assertBindingSucceeds(ProtectedNoArg.class);
188     assertBindingSucceeds(PackagePrivateNoArg.class);
189     assertBindingSucceeds(PrivateNoArgInPrivateClass.class);
190     assertBindingFails(PrivateNoArg.class);
191   }
192 
193   static class PublicNoArg {
PublicNoArg()194     public PublicNoArg() { }
195   }
196 
197   static class ProtectedNoArg {
ProtectedNoArg()198     protected ProtectedNoArg() { }
199   }
200 
201   static class PackagePrivateNoArg {
PackagePrivateNoArg()202     PackagePrivateNoArg() { }
203   }
204 
205   private static class PrivateNoArgInPrivateClass {
PrivateNoArgInPrivateClass()206     PrivateNoArgInPrivateClass() { }
207   }
208 
209   static class PrivateNoArg {
PrivateNoArg()210     private PrivateNoArg() { }
211   }
212 
assertBindingSucceeds(final Class<?> clazz)213   private void assertBindingSucceeds(final Class<?> clazz) {
214     assertNotNull(Guice.createInjector().getInstance(clazz));
215   }
216 
assertBindingFails(final Class<?> clazz)217   private void assertBindingFails(final Class<?> clazz) throws NoSuchMethodException {
218     try {
219       Guice.createInjector().getInstance(clazz);
220       fail();
221     } catch (ConfigurationException expected) {
222       assertContains(expected.getMessage(),
223           "Could not find a suitable constructor in " + PrivateNoArg.class.getName(),
224           "at " + PrivateNoArg.class.getName() + ".class(BindingTest.java:");
225     }
226   }
227 
testTooManyConstructors()228   public void testTooManyConstructors() {
229     try {
230       Guice.createInjector().getInstance(TooManyConstructors.class);
231       fail();
232     } catch (ConfigurationException expected) {
233       assertContains(expected.getMessage(),
234           TooManyConstructors.class.getName() + " has more than one constructor annotated with "
235               + "@Inject. Classes must have either one (and only one) constructor",
236           "at " + TooManyConstructors.class.getName() + ".class(BindingTest.java:");
237     }
238   }
239 
240   @SuppressWarnings("InjectMultipleAtInjectConstructors")
241   static class TooManyConstructors {
242     @Inject
TooManyConstructors(Injector i)243     TooManyConstructors(Injector i) {}
244 
245     @Inject
TooManyConstructors()246     TooManyConstructors() {}
247   }
248 
testToConstructorBinding()249   public void testToConstructorBinding() throws NoSuchMethodException {
250     final Constructor<D> constructor = D.class.getConstructor(Stage.class);
251 
252     Injector injector =
253         Guice.createInjector(
254             new AbstractModule() {
255               @Override
256               protected void configure() {
257                 bind(Object.class).toConstructor(constructor);
258               }
259             });
260 
261     D d = (D) injector.getInstance(Object.class);
262     assertEquals(Stage.DEVELOPMENT, d.stage);
263   }
264 
testToConstructorBindingsOnParameterizedTypes()265   public void testToConstructorBindingsOnParameterizedTypes() throws NoSuchMethodException {
266     final Constructor<C> constructor = C.class.getConstructor(Stage.class, Object.class);
267     final Key<Object> s = new Key<Object>(named("s")) {};
268     final Key<Object> i = new Key<Object>(named("i")) {};
269 
270     Injector injector =
271         Guice.createInjector(
272             new AbstractModule() {
273               @Override
274               protected void configure() {
275                 bind(s).toConstructor(constructor, new TypeLiteral<C<Stage>>() {});
276                 bind(i).toConstructor(constructor, new TypeLiteral<C<Injector>>() {});
277               }
278             });
279 
280     C<Stage> one = (C<Stage>) injector.getInstance(s);
281     assertEquals(Stage.DEVELOPMENT, one.stage);
282     assertEquals(Stage.DEVELOPMENT, one.t);
283     assertEquals(Stage.DEVELOPMENT, one.anotherT);
284 
285     C<Injector> two = (C<Injector>) injector.getInstance(i);
286     assertEquals(Stage.DEVELOPMENT, two.stage);
287     assertEquals(injector, two.t);
288     assertEquals(injector, two.anotherT);
289   }
290 
testToConstructorBindingsFailsOnRawTypes()291   public void testToConstructorBindingsFailsOnRawTypes() throws NoSuchMethodException {
292     final Constructor constructor = C.class.getConstructor(Stage.class, Object.class);
293 
294     try {
295       Guice.createInjector(
296           new AbstractModule() {
297             @Override
298             protected void configure() {
299               bind(Object.class).toConstructor(constructor);
300             }
301           });
302       fail();
303     } catch (CreationException expected) {
304       assertContains(expected.getMessage(),
305           "1) T cannot be used as a key; It is not fully specified.",
306           "at " + C.class.getName() + ".<init>(BindingTest.java:",
307           "2) T cannot be used as a key; It is not fully specified.",
308           "at " + C.class.getName() + ".anotherT(BindingTest.java:");
309     }
310   }
311 
312 /*if[AOP]*/
testToConstructorAndMethodInterceptors()313   public void testToConstructorAndMethodInterceptors() throws NoSuchMethodException {
314     final Constructor<D> constructor = D.class.getConstructor(Stage.class);
315     final AtomicInteger count = new AtomicInteger();
316     final MethodInterceptor countingInterceptor =
317         new MethodInterceptor() {
318           @Override
319           public Object invoke(MethodInvocation methodInvocation) throws Throwable {
320             count.incrementAndGet();
321             return methodInvocation.proceed();
322           }
323         };
324 
325     Injector injector =
326         Guice.createInjector(
327             new AbstractModule() {
328               @Override
329               protected void configure() {
330                 bind(Object.class).toConstructor(constructor);
331                 bindInterceptor(Matchers.any(), Matchers.any(), countingInterceptor);
332               }
333             });
334 
335     D d = (D) injector.getInstance(Object.class);
336     d.hashCode();
337     d.hashCode();
338     assertEquals(2, count.get());
339   }
340 /*end[AOP]*/
341 
testInaccessibleConstructor()342   public void testInaccessibleConstructor() throws NoSuchMethodException {
343     final Constructor<E> constructor = E.class.getDeclaredConstructor(Stage.class);
344 
345     Injector injector =
346         Guice.createInjector(
347             new AbstractModule() {
348               @Override
349               protected void configure() {
350                 bind(E.class).toConstructor(constructor);
351               }
352             });
353 
354     E e = injector.getInstance(E.class);
355     assertEquals(Stage.DEVELOPMENT, e.stage);
356   }
357 
testToConstructorAndScopes()358   public void testToConstructorAndScopes() throws NoSuchMethodException {
359     final Constructor<F> constructor = F.class.getConstructor(Stage.class);
360 
361     final Key<Object> d = Key.get(Object.class, named("D")); // default scoping
362     final Key<Object> s = Key.get(Object.class, named("S")); // singleton
363     final Key<Object> n = Key.get(Object.class, named("N")); // "N" instances
364     final Key<Object> r = Key.get(Object.class, named("R")); // a regular binding
365 
366     Injector injector =
367         Guice.createInjector(
368             new AbstractModule() {
369               @Override
370               protected void configure() {
371                 bind(d).toConstructor(constructor);
372                 bind(s).toConstructor(constructor).in(Singleton.class);
373                 bind(n).toConstructor(constructor).in(Scopes.NO_SCOPE);
374                 bind(r).to(F.class);
375               }
376             });
377 
378     assertDistinct(injector, 1, d, d, d, d);
379     assertDistinct(injector, 1, s, s, s, s);
380     assertDistinct(injector, 4, n, n, n, n);
381     assertDistinct(injector, 1, r, r, r, r);
382     assertDistinct(injector, 4, d, d, r, r, s, s, n);
383   }
384 
assertDistinct(Injector injector, int expectedCount, Key<?>... keys)385   public void assertDistinct(Injector injector, int expectedCount, Key<?>... keys) {
386     ImmutableSet.Builder<Object> builder = ImmutableSet.builder();
387     for (Key<?> k : keys) {
388       builder.add(injector.getInstance(k));
389     }
390     assertEquals(expectedCount, builder.build().size());
391   }
392 
testToConstructorSpiData()393   public void testToConstructorSpiData() throws NoSuchMethodException {
394     final Set<TypeLiteral<?>> heardTypes = Sets.newHashSet();
395 
396     final Constructor<D> constructor = D.class.getConstructor(Stage.class);
397     final TypeListener listener =
398         new TypeListener() {
399           @Override
400           public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
401             if (!heardTypes.add(type)) {
402               fail("Heard " + type + " multiple times!");
403             }
404           }
405         };
406 
407     Guice.createInjector(
408         new AbstractModule() {
409           @Override
410           protected void configure() {
411             bind(Object.class).toConstructor(constructor);
412             bind(D.class).toConstructor(constructor);
413             bindListener(Matchers.any(), listener);
414           }
415         });
416 
417     assertEquals(ImmutableSet.of(TypeLiteral.get(D.class)), heardTypes);
418   }
419 
testInterfaceToImplementationConstructor()420   public void testInterfaceToImplementationConstructor() throws NoSuchMethodException {
421     final Constructor<CFoo> constructor = CFoo.class.getDeclaredConstructor();
422 
423     Injector injector =
424         Guice.createInjector(
425             new AbstractModule() {
426               @Override
427               protected void configure() {
428                 bind(IFoo.class).toConstructor(constructor);
429               }
430             });
431 
432     injector.getInstance(IFoo.class);
433   }
434 
435   public static interface IFoo {}
436   public static class CFoo implements IFoo {}
437 
testGetAllBindings()438   public void testGetAllBindings() {
439     Injector injector =
440         Guice.createInjector(
441             new AbstractModule() {
442               @Override
443               protected void configure() {
444                 bind(D.class).toInstance(new D(Stage.PRODUCTION));
445                 bind(Object.class).to(D.class);
446                 getProvider(new Key<C<Stage>>() {});
447               }
448             });
449 
450     Map<Key<?>,Binding<?>> bindings = injector.getAllBindings();
451     assertEquals(ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(D.class),
452         Key.get(Logger.class), Key.get(Object.class), new Key<C<Stage>>() {}),
453         bindings.keySet());
454 
455     // add a JIT binding
456     injector.getInstance(F.class);
457 
458     Map<Key<?>,Binding<?>> bindings2 = injector.getAllBindings();
459     assertEquals(ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(D.class),
460         Key.get(Logger.class), Key.get(Object.class), new Key<C<Stage>>() {}, Key.get(F.class)),
461         bindings2.keySet());
462 
463     // the original map shouldn't have changed
464     assertEquals(ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(D.class),
465         Key.get(Logger.class), Key.get(Object.class), new Key<C<Stage>>() {}),
466         bindings.keySet());
467 
468     // check the bindings' values
469     assertEquals(injector, bindings.get(Key.get(Injector.class)).getProvider().get());
470   }
471 
testGetAllServletBindings()472   public void testGetAllServletBindings() throws Exception {
473     Injector injector =
474         Guice.createInjector(
475             new AbstractModule() {
476               @Override
477               protected void configure() {
478                 bind(F.class); // an explicit binding that uses a JIT binding for a constructor
479               }
480             });
481     injector.getAllBindings();
482   }
483 
484   public static class C<T> {
485     private Stage stage;
486     private T t;
487     @Inject T anotherT;
488 
C(Stage stage, T t)489     public C(Stage stage, T t) {
490       this.stage = stage;
491       this.t = t;
492     }
493 
C()494     @Inject C() {}
495   }
496 
497   public static class D {
498     Stage stage;
D(Stage stage)499     public D(Stage stage) {
500       this.stage = stage;
501     }
502   }
503 
504   private static class E {
505     Stage stage;
E(Stage stage)506     private E(Stage stage) {
507       this.stage = stage;
508     }
509   }
510 
511   @Singleton
512   public static class F {
513     Stage stage;
F(Stage stage)514     @Inject public F(Stage stage) {
515       this.stage = stage;
516     }
517   }
518 
testTurkeyBaconProblemUsingToConstuctor()519   public void testTurkeyBaconProblemUsingToConstuctor() {
520     Injector injector = Guice.createInjector(new AbstractModule() {
521       @SuppressWarnings("unchecked")
522       @Override
523       public void configure() {
524         bind(Bacon.class).to(UncookedBacon.class);
525         bind(Bacon.class).annotatedWith(named("Turkey")).to(TurkeyBacon.class);
526         bind(Bacon.class).annotatedWith(named("Tofu")).to(TofuBacon.class);
527         bind(Bacon.class).annotatedWith(named("Cooked")).toConstructor(
528             (Constructor)InjectionPoint.forConstructorOf(Bacon.class).getMember());
529       }
530     });
531     Bacon bacon = injector.getInstance(Bacon.class);
532     assertEquals(Food.PORK, bacon.getMaterial());
533     assertFalse(bacon.isCooked());
534 
535     Bacon turkeyBacon = injector.getInstance(Key.get(Bacon.class, named("Turkey")));
536     assertEquals(Food.TURKEY, turkeyBacon.getMaterial());
537     assertTrue(turkeyBacon.isCooked());
538 
539     Bacon cookedBacon = injector.getInstance(Key.get(Bacon.class, named("Cooked")));
540     assertEquals(Food.PORK, cookedBacon.getMaterial());
541     assertTrue(cookedBacon.isCooked());
542 
543     try {
544       // Turkey typo, missing a letter...
545       injector.getInstance(Key.get(Bacon.class, named("Turky")));
546       fail();
547     } catch (ConfigurationException e) {
548       String msg = e.getMessage();
549       assertContains(
550           msg,
551           "Guice configuration errors:",
552           "1) No implementation for"
553               + " com.google.inject.BindingTest$Bacon annotated with"
554               + " @com.google.inject.name.Named(value="
555               + Annotations.memberValueString("Turky")
556               + ") was bound.",
557           "Did you mean?",
558           "* com.google.inject.BindingTest$Bacon annotated with"
559               + " @com.google.inject.name.Named(value="
560               + Annotations.memberValueString("Turkey")
561               + ")",
562           "* com.google.inject.BindingTest$Bacon annotated with"
563               + " @com.google.inject.name.Named(value="
564               + Annotations.memberValueString("Tofu")
565               + ")",
566           "1 more binding with other annotations.",
567           "while locating com.google.inject.BindingTest$Bacon annotated with"
568               + " @com.google.inject.name.Named(value="
569               + Annotations.memberValueString("Turky")
570               + ")");
571     }
572   }
573 
testMissingAnnotationOneChoice()574   public void testMissingAnnotationOneChoice() {
575     Injector injector = Guice.createInjector(new AbstractModule() {
576       @SuppressWarnings("unchecked")
577       @Override
578       public void configure() {
579         bind(Bacon.class).annotatedWith(named("Turkey")).to(TurkeyBacon.class);
580       }
581     });
582 
583     try {
584       // turkey typo (should be Upper case)...
585       injector.getInstance(Key.get(Bacon.class, named("turkey")));
586       fail();
587     } catch (ConfigurationException e) {
588       String msg = e.getMessage();
589       assertContains(msg, "Guice configuration errors:");
590       assertContains(
591           msg,
592           "1) No implementation for com.google.inject.BindingTest$Bacon"
593               + " annotated with"
594               + " @com.google.inject.name.Named(value="
595               + Annotations.memberValueString("turkey")
596               + ") was bound.",
597           "Did you mean?",
598           "* com.google.inject.BindingTest$Bacon annotated with"
599               + " @com.google.inject.name.Named(value="
600               + Annotations.memberValueString("Turkey")
601               + ")",
602           "while locating com.google.inject.BindingTest$Bacon annotated with"
603               + " @com.google.inject.name.Named(value="
604               + Annotations.memberValueString("turkey")
605               + ")");
606     }
607   }
608 
609   enum Food { TURKEY, PORK, TOFU }
610 
611   private static class Bacon {
getMaterial()612     public Food getMaterial() { return Food.PORK; }
isCooked()613     public boolean isCooked() { return true; }
614   }
615 
616   private static class TurkeyBacon extends Bacon {
617     @Override
getMaterial()618     public Food getMaterial() { return Food.TURKEY; }
619   }
620 
621   private static class TofuBacon extends Bacon {
622     @Override
getMaterial()623     public Food getMaterial() { return Food.TOFU; }
624   }
625 
626   private static class UncookedBacon extends Bacon {
627     @Override
isCooked()628     public boolean isCooked() { return false; }
629   }
630 
testMissingAnnotationRelated()631   public void testMissingAnnotationRelated() {
632     try {
633       final TypeLiteral<List<Butter>> list = new TypeLiteral<List<Butter>>() {};
634 
635       Guice.createInjector(new AbstractModule() {
636         @SuppressWarnings("unchecked")
637         @Override
638         public void configure() {
639           bind(list).toInstance(butters);
640           bind(Sandwitch.class).to(ButterSandwitch.class);
641         }
642       });
643 
644       fail();
645     } catch (CreationException e) {
646       final String msg = e.getMessage();
647       assertContains(msg, "Unable to create injector, see the following errors:",
648           "Did you mean?",
649           "java.util.List<com.google.inject.BindingTest$Butter> bound"
650           + "  at com.google.inject.BindingTest$24.configure");
651     }
652   }
653 
654   private static List<Butter> butters = new ArrayList<>();
655 
656   private static interface Sandwitch {};
657 
658   private static interface Butter {};
659 
660   private static class ButterSandwitch implements Sandwitch {
ButterSandwitch()661     private ButterSandwitch() {};
662 
663     @Inject
ButterSandwitch(@amed"unsalted") Butter butter)664     ButterSandwitch(@Named("unsalted") Butter butter) {};
665   }
666 }
667