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