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