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.matcher.Matchers;
26 import com.google.inject.spi.InjectionPoint;
27 import com.google.inject.spi.TypeEncounter;
28 import com.google.inject.spi.TypeListener;
29 
30 import junit.framework.TestCase;
31 
32 /*if[AOP]*/
33 import org.aopalliance.intercept.MethodInterceptor;
34 import org.aopalliance.intercept.MethodInvocation;
35 /*end[AOP]*/
36 
37 import java.lang.reflect.Constructor;
38 import java.util.Collection;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Set;
42 import java.util.concurrent.atomic.AtomicInteger;
43 import java.util.logging.Logger;
44 
45 /**
46  * @author crazybob@google.com (Bob Lee)
47  */
48 public class BindingTest extends TestCase {
49 
50   static class Dependent {
51     @Inject A a;
Dependent(A a, B b)52     @Inject Dependent(A a, B b) {}
injectBob(Bob bob)53     @Inject void injectBob(Bob bob) {}
54   }
55 
testExplicitCyclicDependency()56   public void testExplicitCyclicDependency() {
57     Guice.createInjector(new AbstractModule() {
58       protected void configure() {
59         bind(A.class);
60         bind(B.class);
61       }
62     }).getInstance(A.class);
63   }
64 
65   static class A { @Inject B b; }
66   static class B { @Inject A a; }
67 
68   static class Bob {}
69 
70   static class MyModule extends AbstractModule {
71 
configure()72     protected void configure() {
73       // Linked.
74       bind(Object.class).to(Runnable.class).in(Scopes.SINGLETON);
75 
76       // Instance.
77       bind(Runnable.class).toInstance(Runnables.doNothing());
78 
79       // Provider instance.
80       bind(Foo.class).toProvider(new Provider<Foo>() {
81         public Foo get() {
82           return new Foo();
83         }
84       }).in(Scopes.SINGLETON);
85 
86       // Provider.
87       bind(Foo.class)
88           .annotatedWith(named("provider"))
89           .toProvider(FooProvider.class);
90 
91       // Class.
92       bind(Bar.class).in(Scopes.SINGLETON);
93 
94       // Constant.
95       bindConstant().annotatedWith(named("name")).to("Bob");
96     }
97   }
98 
99   static class Foo {}
100 
101   public static class FooProvider implements Provider<Foo> {
get()102     public Foo get() {
103       throw new UnsupportedOperationException();
104     }
105   }
106 
107   public static class Bar {}
108 
testBindToUnboundLinkedBinding()109   public void testBindToUnboundLinkedBinding() {
110     try {
111       Guice.createInjector(new AbstractModule() {
112         protected void configure() {
113           bind(Collection.class).to(List.class);
114         }
115       });
116       fail();
117     } catch (CreationException expected) {
118       assertContains(expected.getMessage(), "No implementation for java.util.List was bound.");
119     }
120   }
121 
122   /**
123    * This test ensures that the asEagerSingleton() scoping applies to the key,
124    * not to what the key is linked to.
125    */
testScopeIsAppliedToKeyNotTarget()126   public void testScopeIsAppliedToKeyNotTarget() {
127     Injector injector = Guice.createInjector(new AbstractModule() {
128       protected void configure() {
129         bind(Integer.class).toProvider(Counter.class).asEagerSingleton();
130         bind(Number.class).toProvider(Counter.class).asEagerSingleton();
131       }
132     });
133 
134     assertNotSame(injector.getInstance(Integer.class), injector.getInstance(Number.class));
135   }
136 
137   static class Counter implements Provider<Integer> {
138     static AtomicInteger next = new AtomicInteger(1);
get()139     public Integer get() {
140       return next.getAndIncrement();
141     }
142   }
143 
testAnnotatedNoArgConstructor()144   public void testAnnotatedNoArgConstructor() {
145     assertBindingSucceeds(PublicNoArgAnnotated.class);
146     assertBindingSucceeds(ProtectedNoArgAnnotated.class);
147     assertBindingSucceeds(PackagePrivateNoArgAnnotated.class);
148     assertBindingSucceeds(PrivateNoArgAnnotated.class);
149   }
150 
151   static class PublicNoArgAnnotated {
PublicNoArgAnnotated()152     @Inject public PublicNoArgAnnotated() { }
153   }
154 
155   static class ProtectedNoArgAnnotated {
ProtectedNoArgAnnotated()156     @Inject protected ProtectedNoArgAnnotated() { }
157   }
158 
159   static class PackagePrivateNoArgAnnotated {
PackagePrivateNoArgAnnotated()160     @Inject PackagePrivateNoArgAnnotated() { }
161   }
162 
163   static class PrivateNoArgAnnotated {
PrivateNoArgAnnotated()164     @Inject private PrivateNoArgAnnotated() { }
165   }
166 
testUnannotatedNoArgConstructor()167   public void testUnannotatedNoArgConstructor() throws Exception{
168     assertBindingSucceeds(PublicNoArg.class);
169     assertBindingSucceeds(ProtectedNoArg.class);
170     assertBindingSucceeds(PackagePrivateNoArg.class);
171     assertBindingSucceeds(PrivateNoArgInPrivateClass.class);
172     assertBindingFails(PrivateNoArg.class);
173   }
174 
175   static class PublicNoArg {
PublicNoArg()176     public PublicNoArg() { }
177   }
178 
179   static class ProtectedNoArg {
ProtectedNoArg()180     protected ProtectedNoArg() { }
181   }
182 
183   static class PackagePrivateNoArg {
PackagePrivateNoArg()184     PackagePrivateNoArg() { }
185   }
186 
187   private static class PrivateNoArgInPrivateClass {
PrivateNoArgInPrivateClass()188     PrivateNoArgInPrivateClass() { }
189   }
190 
191   static class PrivateNoArg {
PrivateNoArg()192     private PrivateNoArg() { }
193   }
194 
assertBindingSucceeds(final Class<?> clazz)195   private void assertBindingSucceeds(final Class<?> clazz) {
196     assertNotNull(Guice.createInjector().getInstance(clazz));
197   }
198 
assertBindingFails(final Class<?> clazz)199   private void assertBindingFails(final Class<?> clazz) throws NoSuchMethodException {
200     try {
201       Guice.createInjector().getInstance(clazz);
202       fail();
203     } catch (ConfigurationException expected) {
204       assertContains(expected.getMessage(),
205           "Could not find a suitable constructor in " + PrivateNoArg.class.getName(),
206           "at " + PrivateNoArg.class.getName() + ".class(BindingTest.java:");
207     }
208   }
209 
testTooManyConstructors()210   public void testTooManyConstructors() {
211     try {
212       Guice.createInjector().getInstance(TooManyConstructors.class);
213       fail();
214     } catch (ConfigurationException expected) {
215       assertContains(expected.getMessage(),
216           TooManyConstructors.class.getName() + " has more than one constructor annotated with "
217               + "@Inject. Classes must have either one (and only one) constructor",
218           "at " + TooManyConstructors.class.getName() + ".class(BindingTest.java:");
219     }
220   }
221 
222   static class TooManyConstructors {
TooManyConstructors(Injector i)223     @Inject TooManyConstructors(Injector i) {}
TooManyConstructors()224     @Inject TooManyConstructors() {}
225   }
226 
testToConstructorBinding()227   public void testToConstructorBinding() throws NoSuchMethodException {
228     final Constructor<D> constructor = D.class.getConstructor(Stage.class);
229 
230     Injector injector = Guice.createInjector(new AbstractModule() {
231       protected void configure() {
232         bind(Object.class).toConstructor(constructor);
233       }
234     });
235 
236     D d = (D) injector.getInstance(Object.class);
237     assertEquals(Stage.DEVELOPMENT, d.stage);
238   }
239 
testToConstructorBindingsOnParameterizedTypes()240   public void testToConstructorBindingsOnParameterizedTypes() throws NoSuchMethodException {
241     final Constructor<C> constructor = C.class.getConstructor(Stage.class, Object.class);
242     final Key<Object> s = new Key<Object>(named("s")) {};
243     final Key<Object> i = new Key<Object>(named("i")) {};
244 
245     Injector injector = Guice.createInjector(new AbstractModule() {
246       protected void configure() {
247         bind(s).toConstructor(constructor, new TypeLiteral<C<Stage>>() {});
248         bind(i).toConstructor(constructor, new TypeLiteral<C<Injector>>() {});
249       }
250     });
251 
252     C<Stage> one = (C<Stage>) injector.getInstance(s);
253     assertEquals(Stage.DEVELOPMENT, one.stage);
254     assertEquals(Stage.DEVELOPMENT, one.t);
255     assertEquals(Stage.DEVELOPMENT, one.anotherT);
256 
257     C<Injector> two = (C<Injector>) injector.getInstance(i);
258     assertEquals(Stage.DEVELOPMENT, two.stage);
259     assertEquals(injector, two.t);
260     assertEquals(injector, two.anotherT);
261   }
262 
testToConstructorBindingsFailsOnRawTypes()263   public void testToConstructorBindingsFailsOnRawTypes() throws NoSuchMethodException {
264     final Constructor constructor = C.class.getConstructor(Stage.class, Object.class);
265 
266     try {
267       Guice.createInjector(new AbstractModule() {
268         protected void configure() {
269           bind(Object.class).toConstructor(constructor);
270         }
271       });
272       fail();
273     } catch (CreationException expected) {
274       assertContains(expected.getMessage(),
275           "1) T cannot be used as a key; It is not fully specified.",
276           "at " + C.class.getName() + ".<init>(BindingTest.java:",
277           "2) T cannot be used as a key; It is not fully specified.",
278           "at " + C.class.getName() + ".anotherT(BindingTest.java:");
279     }
280   }
281 
282 /*if[AOP]*/
testToConstructorAndMethodInterceptors()283   public void testToConstructorAndMethodInterceptors() throws NoSuchMethodException {
284     final Constructor<D> constructor = D.class.getConstructor(Stage.class);
285     final AtomicInteger count = new AtomicInteger();
286     final MethodInterceptor countingInterceptor = new MethodInterceptor() {
287       public Object invoke(MethodInvocation methodInvocation) throws Throwable {
288         count.incrementAndGet();
289         return methodInvocation.proceed();
290       }
291     };
292 
293     Injector injector = Guice.createInjector(new AbstractModule() {
294       protected void configure() {
295         bind(Object.class).toConstructor(constructor);
296         bindInterceptor(Matchers.any(), Matchers.any(), countingInterceptor);
297       }
298     });
299 
300     D d = (D) injector.getInstance(Object.class);
301     d.hashCode();
302     d.hashCode();
303     assertEquals(2, count.get());
304   }
305 /*end[AOP]*/
306 
testInaccessibleConstructor()307   public void testInaccessibleConstructor() throws NoSuchMethodException {
308     final Constructor<E> constructor = E.class.getDeclaredConstructor(Stage.class);
309 
310     Injector injector = Guice.createInjector(new AbstractModule() {
311       protected void configure() {
312         bind(E.class).toConstructor(constructor);
313       }
314     });
315 
316     E e = injector.getInstance(E.class);
317     assertEquals(Stage.DEVELOPMENT, e.stage);
318   }
319 
testToConstructorAndScopes()320   public void testToConstructorAndScopes() throws NoSuchMethodException {
321     final Constructor<F> constructor = F.class.getConstructor(Stage.class);
322 
323     final Key<Object> d = Key.get(Object.class, named("D")); // default scoping
324     final Key<Object> s = Key.get(Object.class, named("S")); // singleton
325     final Key<Object> n = Key.get(Object.class, named("N")); // "N" instances
326     final Key<Object> r = Key.get(Object.class, named("R")); // a regular binding
327 
328     Injector injector = Guice.createInjector(new AbstractModule() {
329       protected void configure() {
330         bind(d).toConstructor(constructor);
331         bind(s).toConstructor(constructor).in(Singleton.class);
332         bind(n).toConstructor(constructor).in(Scopes.NO_SCOPE);
333         bind(r).to(F.class);
334       }
335     });
336 
337     assertDistinct(injector, 1, d, d, d, d);
338     assertDistinct(injector, 1, s, s, s, s);
339     assertDistinct(injector, 4, n, n, n, n);
340     assertDistinct(injector, 1, r, r, r, r);
341     assertDistinct(injector, 4, d, d, r, r, s, s, n);
342   }
343 
assertDistinct(Injector injector, int expectedCount, Key<?>... keys)344   public void assertDistinct(Injector injector, int expectedCount, Key<?>... keys) {
345     ImmutableSet.Builder<Object> builder = ImmutableSet.builder();
346     for (Key<?> k : keys) {
347       builder.add(injector.getInstance(k));
348     }
349     assertEquals(expectedCount, builder.build().size());
350   }
351 
testToConstructorSpiData()352   public void testToConstructorSpiData() throws NoSuchMethodException {
353     final Set<TypeLiteral<?>> heardTypes = Sets.newHashSet();
354 
355     final Constructor<D> constructor = D.class.getConstructor(Stage.class);
356     final TypeListener listener = new TypeListener() {
357       public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
358         if (!heardTypes.add(type)) {
359           fail("Heard " + type + " multiple times!");
360         }
361       }
362     };
363 
364     Guice.createInjector(new AbstractModule() {
365       protected void configure() {
366         bind(Object.class).toConstructor(constructor);
367         bind(D.class).toConstructor(constructor);
368         bindListener(Matchers.any(), listener);
369       }
370     });
371 
372     assertEquals(ImmutableSet.of(TypeLiteral.get(D.class)), heardTypes);
373   }
374 
testInterfaceToImplementationConstructor()375   public void testInterfaceToImplementationConstructor() throws NoSuchMethodException {
376     final Constructor<CFoo> constructor = CFoo.class.getDeclaredConstructor();
377 
378     Injector injector = Guice.createInjector(new AbstractModule() {
379       protected void configure() {
380         bind(IFoo.class).toConstructor(constructor);
381       }
382     });
383 
384     injector.getInstance(IFoo.class);
385   }
386 
387   public static interface IFoo {}
388   public static class CFoo implements IFoo {}
389 
testGetAllBindings()390   public void testGetAllBindings() {
391     Injector injector = Guice.createInjector(new AbstractModule() {
392       protected void configure() {
393         bind(D.class).toInstance(new D(Stage.PRODUCTION));
394         bind(Object.class).to(D.class);
395         getProvider(new Key<C<Stage>>() {});
396       }
397     });
398 
399     Map<Key<?>,Binding<?>> bindings = injector.getAllBindings();
400     assertEquals(ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(D.class),
401         Key.get(Logger.class), Key.get(Object.class), new Key<C<Stage>>() {}),
402         bindings.keySet());
403 
404     // add a JIT binding
405     injector.getInstance(F.class);
406 
407     Map<Key<?>,Binding<?>> bindings2 = injector.getAllBindings();
408     assertEquals(ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(D.class),
409         Key.get(Logger.class), Key.get(Object.class), new Key<C<Stage>>() {}, Key.get(F.class)),
410         bindings2.keySet());
411 
412     // the original map shouldn't have changed
413     assertEquals(ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(D.class),
414         Key.get(Logger.class), Key.get(Object.class), new Key<C<Stage>>() {}),
415         bindings.keySet());
416 
417     // check the bindings' values
418     assertEquals(injector, bindings.get(Key.get(Injector.class)).getProvider().get());
419   }
420 
testGetAllServletBindings()421   public void testGetAllServletBindings() throws Exception {
422     Injector injector = Guice.createInjector(new AbstractModule() {
423       protected void configure() {
424         bind(F.class); // an explicit binding that uses a JIT binding for a constructor
425       }
426     });
427     injector.getAllBindings();
428   }
429 
430   public static class C<T> {
431     private Stage stage;
432     private T t;
433     @Inject T anotherT;
434 
C(Stage stage, T t)435     public C(Stage stage, T t) {
436       this.stage = stage;
437       this.t = t;
438     }
439 
C()440     @Inject C() {}
441   }
442 
443   public static class D {
444     Stage stage;
D(Stage stage)445     public D(Stage stage) {
446       this.stage = stage;
447     }
448   }
449 
450   private static class E {
451     Stage stage;
E(Stage stage)452     private E(Stage stage) {
453       this.stage = stage;
454     }
455   }
456 
457   @Singleton
458   public static class F {
459     Stage stage;
F(Stage stage)460     @Inject public F(Stage stage) {
461       this.stage = stage;
462     }
463   }
464 
testTurkeyBaconProblemUsingToConstuctor()465   public void testTurkeyBaconProblemUsingToConstuctor() {
466     Injector injector = Guice.createInjector(new AbstractModule() {
467       @SuppressWarnings("unchecked")
468       @Override
469       public void configure() {
470         bind(Bacon.class).to(UncookedBacon.class);
471         bind(Bacon.class).annotatedWith(named("Turkey")).to(TurkeyBacon.class);
472         bind(Bacon.class).annotatedWith(named("Cooked")).toConstructor(
473             (Constructor)InjectionPoint.forConstructorOf(Bacon.class).getMember());
474       }
475     });
476     Bacon bacon = injector.getInstance(Bacon.class);
477     assertEquals(Food.PORK, bacon.getMaterial());
478     assertFalse(bacon.isCooked());
479 
480     Bacon turkeyBacon = injector.getInstance(Key.get(Bacon.class, named("Turkey")));
481     assertEquals(Food.TURKEY, turkeyBacon.getMaterial());
482     assertTrue(turkeyBacon.isCooked());
483 
484     Bacon cookedBacon = injector.getInstance(Key.get(Bacon.class, named("Cooked")));
485     assertEquals(Food.PORK, cookedBacon.getMaterial());
486     assertTrue(cookedBacon.isCooked());
487   }
488 
489   enum Food { TURKEY, PORK }
490 
491   private static class Bacon {
getMaterial()492     public Food getMaterial() { return Food.PORK; }
isCooked()493     public boolean isCooked() { return true; }
494   }
495 
496   private static class TurkeyBacon extends Bacon {
getMaterial()497     public Food getMaterial() { return Food.TURKEY; }
498   }
499 
500   private static class UncookedBacon extends Bacon {
isCooked()501     public boolean isCooked() { return false; }
502   }
503 }
504