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.asModuleChain;
20import static com.google.inject.Asserts.assertContains;
21import static com.google.inject.Asserts.assertNotSerializable;
22import static com.google.inject.Asserts.getDeclaringSourcePart;
23import static com.google.inject.Asserts.isIncludeStackTraceOff;
24
25import com.google.common.collect.Iterables;
26import com.google.common.collect.Lists;
27import com.google.inject.name.Named;
28import com.google.inject.name.Names;
29import com.google.inject.spi.Message;
30import com.google.inject.util.Providers;
31
32import junit.framework.TestCase;
33
34import java.io.IOException;
35import java.util.Comparator;
36import java.util.Date;
37import java.util.List;
38import java.util.concurrent.Callable;
39import java.util.logging.Handler;
40import java.util.logging.LogRecord;
41import java.util.logging.Logger;
42
43/**
44 * @author crazybob@google.com (Bob Lee)
45 */
46public class BinderTest extends TestCase {
47
48  private final Logger loggerToWatch = Logger.getLogger(Guice.class.getName());
49
50  private final List<LogRecord> logRecords = Lists.newArrayList();
51  private final Handler fakeHandler = new Handler() {
52    @Override
53    public void publish(LogRecord logRecord) {
54      logRecords.add(logRecord);
55    }
56    @Override
57    public void flush() {}
58    @Override
59    public void close() throws SecurityException {}
60  };
61
62  Provider<Foo> fooProvider;
63
64  @Override protected void setUp() throws Exception {
65    super.setUp();
66    loggerToWatch.addHandler(fakeHandler);
67  }
68
69  @Override protected void tearDown() throws Exception {
70    loggerToWatch.removeHandler(fakeHandler);
71    super.tearDown();
72  }
73
74  public void testProviderFromBinder() {
75    Guice.createInjector(new Module() {
76      public void configure(Binder binder) {
77        fooProvider = binder.getProvider(Foo.class);
78
79        try {
80          fooProvider.get();
81        } catch (IllegalStateException e) { /* expected */ }
82      }
83    });
84
85    assertNotNull(fooProvider.get());
86  }
87
88  static class Foo {}
89
90  public void testMissingBindings() {
91    try {
92      Guice.createInjector(new AbstractModule() {
93        @Override
94        public void configure() {
95          getProvider(Runnable.class);
96          bind(Comparator.class);
97          requireBinding(Key.get(new TypeLiteral<Callable<String>>() {}));
98          bind(Date.class).annotatedWith(Names.named("date"));
99        }
100      });
101    } catch (CreationException e) {
102      assertEquals(4, e.getErrorMessages().size());
103      String segment1 = "No implementation for " + Comparator.class.getName() + " was bound.";
104      String segment2 = "No implementation for java.util.Date annotated with @"
105          + Named.class.getName() + "(value=date) was bound.";
106      String segment3 = "No implementation for java.lang.Runnable was bound.";
107      String segment4 = " No implementation for java.util.concurrent.Callable<java.lang.String> was"
108          + " bound.";
109      String atSegment = "at " + getClass().getName();
110      String sourceFileName = getDeclaringSourcePart(getClass());
111      if (isIncludeStackTraceOff()) {
112        assertContains(e.getMessage(),
113            segment1, atSegment, sourceFileName,
114            segment2, atSegment, sourceFileName,
115            segment3, atSegment, sourceFileName,
116            segment4, atSegment, sourceFileName);
117      } else {
118        assertContains(e.getMessage(),
119            segment3, atSegment, sourceFileName,
120            segment1, atSegment, sourceFileName,
121            segment4, atSegment, sourceFileName,
122            segment2, atSegment, sourceFileName);
123      }
124    }
125  }
126
127  public void testMissingDependency() {
128    try {
129      Guice.createInjector(new AbstractModule() {
130        @Override
131        public void configure() {
132          bind(NeedsRunnable.class);
133        }
134      });
135    } catch (CreationException e) {
136      assertEquals(1, e.getErrorMessages().size());
137      assertContains(e.getMessage(),
138          "No implementation for java.lang.Runnable was bound.",
139          "for field at " + NeedsRunnable.class.getName(), ".runnable(BinderTest.java:",
140          "at " + getClass().getName(), getDeclaringSourcePart(getClass()));
141    }
142  }
143
144  static class NeedsRunnable {
145    @Inject Runnable runnable;
146  }
147
148  public void testDanglingConstantBinding() {
149    try {
150      Guice.createInjector(new AbstractModule() {
151        @Override public void configure() {
152          bindConstant();
153        }
154      });
155      fail();
156    } catch (CreationException expected) {
157      assertContains(expected.getMessage(),
158          "1) Missing constant value. Please call to(...).",
159          "at " + getClass().getName());
160    }
161  }
162
163  public void testRecursiveBinding() {
164    try {
165      Guice.createInjector(new AbstractModule() {
166        @Override public void configure() {
167          bind(Runnable.class).to(Runnable.class);
168        }
169      });
170      fail();
171    } catch (CreationException expected) {
172      assertContains(expected.getMessage(),
173          "1) Binding points to itself.",
174          "at " + getClass().getName(), getDeclaringSourcePart(getClass()));
175    }
176  }
177
178  public void testBindingNullConstant() {
179    try {
180      Guice.createInjector(new AbstractModule() {
181        @Override public void configure() {
182          String none = null;
183          bindConstant().annotatedWith(Names.named("nullOne")).to(none);
184          bind(String.class).annotatedWith(Names.named("nullTwo")).toInstance(none);
185        }
186      });
187      fail();
188    } catch (CreationException expected) {
189      assertContains(expected.getMessage(),
190          "1) Binding to null instances is not allowed. Use toProvider(Providers.of(null))",
191          "2) Binding to null instances is not allowed. Use toProvider(Providers.of(null))");
192    }
193  }
194
195  public void testToStringOnBinderApi() {
196    try {
197      Guice.createInjector(new AbstractModule() {
198        @Override public void configure() {
199          assertEquals("Binder", binder().toString());
200          assertEquals("Provider<java.lang.Integer>", getProvider(Integer.class).toString());
201          assertEquals("Provider<java.util.List<java.lang.String>>",
202              getProvider(Key.get(new TypeLiteral<List<String>>() {})).toString());
203
204          assertEquals("BindingBuilder<java.lang.Integer>",
205              bind(Integer.class).toString());
206          assertEquals("BindingBuilder<java.lang.Integer>",
207              bind(Integer.class).annotatedWith(Names.named("a")).toString());
208          assertEquals("ConstantBindingBuilder", bindConstant().toString());
209          assertEquals("ConstantBindingBuilder",
210              bindConstant().annotatedWith(Names.named("b")).toString());
211          assertEquals("AnnotatedElementBuilder",
212              binder().newPrivateBinder().expose(Integer.class).toString());
213        }
214      });
215      fail();
216    } catch (CreationException ignored) {
217    }
218  }
219
220  public void testNothingIsSerializableInBinderApi() {
221    try {
222      Guice.createInjector(new AbstractModule() {
223        @Override public void configure() {
224          try {
225            assertNotSerializable(binder());
226            assertNotSerializable(getProvider(Integer.class));
227            assertNotSerializable(getProvider(Key.get(new TypeLiteral<List<String>>() {})));
228            assertNotSerializable(bind(Integer.class));
229            assertNotSerializable(bind(Integer.class).annotatedWith(Names.named("a")));
230            assertNotSerializable(bindConstant());
231            assertNotSerializable(bindConstant().annotatedWith(Names.named("b")));
232          } catch (IOException e) {
233            fail(e.getMessage());
234          }
235        }
236      });
237      fail();
238    } catch (CreationException ignored) {
239    }
240  }
241
242  /**
243   * Although {@code String[].class} isn't equal to {@code new
244   * GenericArrayTypeImpl(String.class)}, Guice should treat these two types
245   * interchangeably.
246   */
247  public void testArrayTypeCanonicalization() {
248    final String[] strings = new String[] { "A" };
249    final Integer[] integers = new Integer[] { 1 };
250
251    Injector injector = Guice.createInjector(new AbstractModule() {
252      @Override
253      protected void configure() {
254        bind(String[].class).toInstance(strings);
255        bind(new TypeLiteral<Integer[]>() {}).toInstance(integers);
256      }
257    });
258
259    assertSame(integers, injector.getInstance(Key.get(new TypeLiteral<Integer[]>() {})));
260    assertSame(integers, injector.getInstance(new Key<Integer[]>() {}));
261    assertSame(integers, injector.getInstance(Integer[].class));
262    assertSame(strings, injector.getInstance(Key.get(new TypeLiteral<String[]>() {})));
263    assertSame(strings, injector.getInstance(new Key<String[]>() {}));
264    assertSame(strings, injector.getInstance(String[].class));
265
266    try {
267      Guice.createInjector(new AbstractModule() {
268        @Override
269        protected void configure() {
270          bind(String[].class).toInstance(new String[] { "A" });
271          bind(new TypeLiteral<String[]>() {}).toInstance(new String[] { "B" });
272        }
273      });
274      fail();
275    } catch (CreationException expected) {
276      assertContains(expected.getMessage(),
277          "1) A binding to java.lang.String[] was already configured at " + getClass().getName(),
278          "at " + getClass().getName(), getDeclaringSourcePart(getClass()));
279      assertContains(expected.getMessage(), "1 error");
280    }
281
282    // passes because duplicates are ignored
283    injector = Guice.createInjector(new AbstractModule() {
284      @Override
285      protected void configure() {
286        bind(String[].class).toInstance(strings);
287        bind(new TypeLiteral<String[]>() {}).toInstance(strings);
288      }
289    });
290    assertSame(strings, injector.getInstance(Key.get(new TypeLiteral<String[]>() {})));
291    assertSame(strings, injector.getInstance(new Key<String[]>() {}));
292    assertSame(strings, injector.getInstance(String[].class));
293  }
294
295  static class ParentModule extends AbstractModule {
296    @Override protected void configure() {
297      install(new FooModule());
298      install(new BarModule());
299    }
300  }
301  static class FooModule extends AbstractModule {
302    @Override protected void configure() {
303      install(new ConstantModule("foo"));
304    }
305  }
306  static class BarModule extends AbstractModule {
307    @Override protected void configure() {
308      install(new ConstantModule("bar"));
309    }
310  }
311  static class ConstantModule extends AbstractModule {
312    private final String constant;
313    ConstantModule(String constant) {
314      this.constant = constant;
315    }
316    @Override protected void configure() {
317      bind(String.class).toInstance(constant);
318    }
319  }
320
321  /**
322   * Binding something to two different things should give an error.
323   */
324  public void testSettingBindingTwice() {
325    try {
326      Guice.createInjector(new ParentModule());
327      fail();
328    } catch(CreationException expected) {
329      assertContains(expected.getMessage(),
330        "1) A binding to java.lang.String was already configured at " + ConstantModule.class.getName(),
331        asModuleChain(ParentModule.class, FooModule.class, ConstantModule.class),
332        "at " + ConstantModule.class.getName(), getDeclaringSourcePart(getClass()),
333        asModuleChain(ParentModule.class, BarModule.class, ConstantModule.class));
334      assertContains(expected.getMessage(), "1 error");
335    }
336  }
337
338  /**
339   * Binding an @ImplementedBy thing to something else should also fail.
340   */
341  public void testSettingAtImplementedByTwice() {
342    try {
343      Guice.createInjector(new AbstractModule() {
344        @Override
345        protected void configure() {
346          bind(HasImplementedBy1.class);
347          bind(HasImplementedBy1.class).toInstance(new HasImplementedBy1() {});
348        }
349      });
350      fail();
351    } catch(CreationException expected) {
352      expected.printStackTrace();
353      assertContains(expected.getMessage(),
354        "1) A binding to " + HasImplementedBy1.class.getName()
355        + " was already configured at " + getClass().getName(),
356        "at " + getClass().getName(), getDeclaringSourcePart(getClass()));
357      assertContains(expected.getMessage(), "1 error");
358    }
359  }
360
361  /**
362   * See issue 614, Problem One
363   * https://github.com/google/guice/issues/614
364   */
365  public void testJitDependencyDoesntBlockOtherExplicitBindings() {
366    Injector injector = Guice.createInjector(new AbstractModule() {
367      @Override
368      protected void configure() {
369        bind(HasImplementedByThatNeedsAnotherImplementedBy.class);
370        bind(HasImplementedBy1.class).toInstance(new HasImplementedBy1() {});
371      }
372    });
373    injector.getAllBindings(); // just validate it doesn't throw.
374    // Also validate that we're using the explicit (and not @ImplementedBy) implementation
375    assertFalse(injector.getInstance(HasImplementedBy1.class) instanceof ImplementsHasImplementedBy1);
376  }
377
378  /**
379   * See issue 614, Problem Two
380   * https://github.com/google/guice/issues/id=614
381   */
382  public void testJitDependencyCanUseExplicitDependencies() {
383    Guice.createInjector(new AbstractModule() {
384      @Override
385      protected void configure() {
386        bind(HasImplementedByThatWantsExplicit.class);
387        bind(JustAnInterface.class).toInstance(new JustAnInterface() {});
388      }
389    });
390  }
391
392  /**
393   * Untargetted bindings should follow @ImplementedBy and @ProvidedBy
394   * annotations if they exist. Otherwise the class should be constructed
395   * directly.
396   */
397  public void testUntargettedBinding() {
398    Injector injector = Guice.createInjector(new AbstractModule() {
399      @Override
400      protected void configure() {
401        bind(HasProvidedBy1.class);
402        bind(HasImplementedBy1.class);
403        bind(HasProvidedBy2.class);
404        bind(HasImplementedBy2.class);
405        bind(JustAClass.class);
406      }
407    });
408
409    assertNotNull(injector.getInstance(HasProvidedBy1.class));
410    assertNotNull(injector.getInstance(HasImplementedBy1.class));
411    assertNotSame(HasProvidedBy2.class,
412        injector.getInstance(HasProvidedBy2.class).getClass());
413    assertSame(ExtendsHasImplementedBy2.class,
414        injector.getInstance(HasImplementedBy2.class).getClass());
415    assertSame(JustAClass.class, injector.getInstance(JustAClass.class).getClass());
416  }
417
418  public void testPartialInjectorGetInstance() {
419    Injector injector = Guice.createInjector();
420    try {
421      injector.getInstance(MissingParameter.class);
422      fail();
423    } catch (ConfigurationException expected) {
424      assertContains(expected.getMessage(),
425          "1) Could not find a suitable constructor in " + NoInjectConstructor.class.getName(),
426          "at " + MissingParameter.class.getName() + ".<init>(BinderTest.java:");
427    }
428  }
429
430  public void testUserReportedError() {
431    final Message message = new Message(getClass(), "Whoops!");
432    try {
433      Guice.createInjector(new AbstractModule() {
434        @Override
435        protected void configure() {
436          addError(message);
437        }
438      });
439      fail();
440    } catch (CreationException expected) {
441      assertSame(message, Iterables.getOnlyElement(expected.getErrorMessages()));
442    }
443  }
444
445  public void testUserReportedErrorsAreAlsoLogged() {
446    try {
447      Guice.createInjector(new AbstractModule() {
448        @Override
449        protected void configure() {
450          addError(new Message("Whoops!", new IllegalArgumentException()));
451        }
452      });
453      fail();
454    } catch (CreationException expected) {
455    }
456
457    LogRecord logRecord = Iterables.getOnlyElement(this.logRecords);
458    assertContains(logRecord.getMessage(),
459        "An exception was caught and reported. Message: java.lang.IllegalArgumentException");
460  }
461
462  public void testBindingToProvider() {
463    try {
464      Guice.createInjector(new AbstractModule() {
465        @Override
466        protected void configure() {
467          bind(new TypeLiteral<Provider<String>>() {}).toInstance(Providers.of("A"));
468        }
469      });
470      fail();
471    } catch (CreationException expected) {
472      assertContains(expected.getMessage(),
473          "1) Binding to Provider is not allowed.",
474          "at " + BinderTest.class.getName(), getDeclaringSourcePart(getClass()));
475    }
476  }
477
478  static class OuterCoreModule extends AbstractModule {
479    @Override protected void configure() {
480      install(new InnerCoreModule());
481    }
482  }
483  static class InnerCoreModule extends AbstractModule {
484    final Named red = Names.named("red");
485
486    @Override protected void configure() {
487      bind(AbstractModule.class).annotatedWith(red)
488      .toProvider(Providers.<AbstractModule>of(null));
489      bind(Binder.class).annotatedWith(red).toProvider(Providers.<Binder>of(null));
490      bind(Binding.class).annotatedWith(red).toProvider(Providers.<Binding>of(null));
491      bind(Injector.class).annotatedWith(red).toProvider(Providers.<Injector>of(null));
492      bind(Key.class).annotatedWith(red).toProvider(Providers.<Key>of(null));
493      bind(Module.class).annotatedWith(red).toProvider(Providers.<Module>of(null));
494      bind(Provider.class).annotatedWith(red).toProvider(Providers.<Provider>of(null));
495      bind(Scope.class).annotatedWith(red).toProvider(Providers.<Scope>of(null));
496      bind(Stage.class).annotatedWith(red).toProvider(Providers.<Stage>of(null));
497      bind(TypeLiteral.class).annotatedWith(red).toProvider(Providers.<TypeLiteral>of(null));
498      bind(new TypeLiteral<Key<String>>() {}).toProvider(Providers.<Key<String>>of(null));
499    }
500  }
501  public void testCannotBindToGuiceTypes() {
502    try {
503      Guice.createInjector(new OuterCoreModule());
504      fail();
505    } catch (CreationException expected) {
506      assertContains(expected.getMessage(),
507          "Binding to core guice framework type is not allowed: AbstractModule.",
508          "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
509          asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
510
511          "Binding to core guice framework type is not allowed: Binder.",
512          "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
513          asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
514
515          "Binding to core guice framework type is not allowed: Binding.",
516          "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
517          asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
518
519          "Binding to core guice framework type is not allowed: Injector.",
520          "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
521          asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
522
523          "Binding to core guice framework type is not allowed: Key.",
524          "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
525          asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
526
527          "Binding to core guice framework type is not allowed: Module.",
528          "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
529          asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
530
531          "Binding to Provider is not allowed.",
532          "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
533          asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
534
535          "Binding to core guice framework type is not allowed: Scope.",
536          "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
537          asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
538
539          "Binding to core guice framework type is not allowed: Stage.",
540          "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
541          asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
542
543          "Binding to core guice framework type is not allowed: TypeLiteral.",
544          "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
545          asModuleChain(OuterCoreModule.class, InnerCoreModule.class),
546
547          "Binding to core guice framework type is not allowed: Key.",
548          "at " + InnerCoreModule.class.getName() + getDeclaringSourcePart(getClass()),
549          asModuleChain(OuterCoreModule.class, InnerCoreModule.class));
550    }
551  }
552
553  static class MissingParameter {
554    @Inject MissingParameter(NoInjectConstructor noInjectConstructor) {}
555  }
556
557  static class NoInjectConstructor {
558    private NoInjectConstructor() {}
559  }
560
561  @ProvidedBy(HasProvidedBy1Provider.class)
562  interface HasProvidedBy1 {}
563
564  static class HasProvidedBy1Provider implements Provider<HasProvidedBy1> {
565    public HasProvidedBy1 get() {
566      return new HasProvidedBy1() {};
567    }
568  }
569
570  @ImplementedBy(ImplementsHasImplementedBy1.class)
571  interface HasImplementedBy1 {}
572
573  static class ImplementsHasImplementedBy1 implements HasImplementedBy1 {}
574
575  @ProvidedBy(HasProvidedBy2Provider.class)
576  static class HasProvidedBy2 {}
577
578  static class HasProvidedBy2Provider implements Provider<HasProvidedBy2> {
579    public HasProvidedBy2 get() {
580      return new HasProvidedBy2() {};
581    }
582  }
583
584  @ImplementedBy(ExtendsHasImplementedBy2.class)
585  static class HasImplementedBy2 {}
586
587  static class ExtendsHasImplementedBy2 extends HasImplementedBy2 {}
588
589  static class JustAClass {}
590
591  @ImplementedBy(ImplementsHasImplementedByThatNeedsAnotherImplementedBy.class)
592  static interface HasImplementedByThatNeedsAnotherImplementedBy {
593  }
594
595  static class ImplementsHasImplementedByThatNeedsAnotherImplementedBy
596    implements HasImplementedByThatNeedsAnotherImplementedBy {
597    @Inject
598    ImplementsHasImplementedByThatNeedsAnotherImplementedBy(
599        HasImplementedBy1 h1n1) {}
600  }
601
602  @ImplementedBy(ImplementsHasImplementedByThatWantsExplicit.class)
603  static interface HasImplementedByThatWantsExplicit {
604  }
605
606  static class ImplementsHasImplementedByThatWantsExplicit
607      implements HasImplementedByThatWantsExplicit {
608    @Inject ImplementsHasImplementedByThatWantsExplicit(JustAnInterface jai) {}
609  }
610
611  static interface JustAnInterface {}
612
613
614//  public void testBindInterfaceWithoutImplementation() {
615//    Guice.createInjector(new AbstractModule() {
616//      protected void configure() {
617//        bind(Runnable.class);
618//      }
619//    }).getInstance(Runnable.class);
620//  }
621
622  enum Roshambo { ROCK, SCISSORS, PAPER }
623
624  public void testInjectRawProvider() {
625    try {
626      Guice.createInjector().getInstance(Provider.class);
627      fail();
628    } catch (ConfigurationException expected) {
629      Asserts.assertContains(expected.getMessage(),
630          "1) Cannot inject a Provider that has no type parameter",
631          "while locating " + Provider.class.getName());
632    }
633  }
634}
635