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
17package com.google.inject;
18
19import static com.google.inject.Asserts.assertContains;
20import static com.google.inject.name.Names.named;
21
22import com.google.common.collect.ImmutableSet;
23import com.google.common.collect.Sets;
24import com.google.common.util.concurrent.Runnables;
25import com.google.inject.matcher.Matchers;
26import com.google.inject.spi.InjectionPoint;
27import com.google.inject.spi.TypeEncounter;
28import com.google.inject.spi.TypeListener;
29
30import junit.framework.TestCase;
31
32/*if[AOP]*/
33import org.aopalliance.intercept.MethodInterceptor;
34import org.aopalliance.intercept.MethodInvocation;
35/*end[AOP]*/
36
37import java.lang.reflect.Constructor;
38import java.util.Collection;
39import java.util.List;
40import java.util.Map;
41import java.util.Set;
42import java.util.concurrent.atomic.AtomicInteger;
43import java.util.logging.Logger;
44
45/**
46 * @author crazybob@google.com (Bob Lee)
47 */
48public class BindingTest extends TestCase {
49
50  static class Dependent {
51    @Inject A a;
52    @Inject Dependent(A a, B b) {}
53    @Inject void injectBob(Bob bob) {}
54  }
55
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
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> {
102    public Foo get() {
103      throw new UnsupportedOperationException();
104    }
105  }
106
107  public static class Bar {}
108
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   */
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);
139    public Integer get() {
140      return next.getAndIncrement();
141    }
142  }
143
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 {
152    @Inject public PublicNoArgAnnotated() { }
153  }
154
155  static class ProtectedNoArgAnnotated {
156    @Inject protected ProtectedNoArgAnnotated() { }
157  }
158
159  static class PackagePrivateNoArgAnnotated {
160    @Inject PackagePrivateNoArgAnnotated() { }
161  }
162
163  static class PrivateNoArgAnnotated {
164    @Inject private PrivateNoArgAnnotated() { }
165  }
166
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 {
176    public PublicNoArg() { }
177  }
178
179  static class ProtectedNoArg {
180    protected ProtectedNoArg() { }
181  }
182
183  static class PackagePrivateNoArg {
184    PackagePrivateNoArg() { }
185  }
186
187  private static class PrivateNoArgInPrivateClass {
188    PrivateNoArgInPrivateClass() { }
189  }
190
191  static class PrivateNoArg {
192    private PrivateNoArg() { }
193  }
194
195  private void assertBindingSucceeds(final Class<?> clazz) {
196    assertNotNull(Guice.createInjector().getInstance(clazz));
197  }
198
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
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 {
223    @Inject TooManyConstructors(Injector i) {}
224    @Inject TooManyConstructors() {}
225  }
226
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
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
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]*/
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
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
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
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
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
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
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
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
435    public C(Stage stage, T t) {
436      this.stage = stage;
437      this.t = t;
438    }
439
440    @Inject C() {}
441  }
442
443  public static class D {
444    Stage stage;
445    public D(Stage stage) {
446      this.stage = stage;
447    }
448  }
449
450  private static class E {
451    Stage stage;
452    private E(Stage stage) {
453      this.stage = stage;
454    }
455  }
456
457  @Singleton
458  public static class F {
459    Stage stage;
460    @Inject public F(Stage stage) {
461      this.stage = stage;
462    }
463  }
464
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 {
492    public Food getMaterial() { return Food.PORK; }
493    public boolean isCooked() { return true; }
494  }
495
496  private static class TurkeyBacon extends Bacon {
497    public Food getMaterial() { return Food.TURKEY; }
498  }
499
500  private static class UncookedBacon extends Bacon {
501    public boolean isCooked() { return false; }
502  }
503}
504