1 /* 2 * Copyright (C) 2009 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.assistedinject; 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.Iterables; 24 import com.google.inject.AbstractModule; 25 import com.google.inject.Binding; 26 import com.google.inject.CreationException; 27 import com.google.inject.Guice; 28 import com.google.inject.Inject; 29 import com.google.inject.Injector; 30 import com.google.inject.Key; 31 import com.google.inject.Module; 32 import com.google.inject.Provides; 33 import com.google.inject.Singleton; 34 import com.google.inject.Stage; 35 import com.google.inject.TypeLiteral; 36 import com.google.inject.name.Named; 37 import com.google.inject.name.Names; 38 import com.google.inject.spi.Dependency; 39 import com.google.inject.spi.Element; 40 import com.google.inject.spi.Elements; 41 import com.google.inject.spi.HasDependencies; 42 import com.google.inject.spi.Message; 43 import java.util.Collection; 44 import java.util.HashSet; 45 import java.util.List; 46 import java.util.Set; 47 import junit.framework.TestCase; 48 49 public class FactoryModuleBuilderTest extends TestCase { 50 51 private enum Color { 52 BLUE, 53 GREEN, 54 RED, 55 GRAY, 56 BLACK 57 } 58 testImplicitForwardingAssistedBindingFailsWithInterface()59 public void testImplicitForwardingAssistedBindingFailsWithInterface() { 60 try { 61 Guice.createInjector( 62 new AbstractModule() { 63 @Override 64 protected void configure() { 65 bind(Car.class).to(Golf.class); 66 install(new FactoryModuleBuilder().build(ColoredCarFactory.class)); 67 } 68 }); 69 fail(); 70 } catch (CreationException ce) { 71 assertContains( 72 ce.getMessage(), 73 "1) " + Car.class.getName() + " is an interface, not a concrete class.", 74 "Unable to create AssistedInject factory.", 75 "while locating " + Car.class.getName(), 76 "at " + ColoredCarFactory.class.getName() + ".create("); 77 assertEquals(1, ce.getErrorMessages().size()); 78 } 79 } 80 testImplicitForwardingAssistedBindingFailsWithAbstractClass()81 public void testImplicitForwardingAssistedBindingFailsWithAbstractClass() { 82 try { 83 Guice.createInjector( 84 new AbstractModule() { 85 @Override 86 protected void configure() { 87 bind(AbstractCar.class).to(ArtCar.class); 88 install(new FactoryModuleBuilder().build(ColoredAbstractCarFactory.class)); 89 } 90 }); 91 fail(); 92 } catch (CreationException ce) { 93 assertContains( 94 ce.getMessage(), 95 "1) " + AbstractCar.class.getName() + " is abstract, not a concrete class.", 96 "Unable to create AssistedInject factory.", 97 "while locating " + AbstractCar.class.getName(), 98 "at " + ColoredAbstractCarFactory.class.getName() + ".create("); 99 assertEquals(1, ce.getErrorMessages().size()); 100 } 101 } 102 testImplicitForwardingAssistedBindingCreatesNewObjects()103 public void testImplicitForwardingAssistedBindingCreatesNewObjects() { 104 final Mustang providedMustang = new Mustang(Color.BLUE); 105 Injector injector = 106 Guice.createInjector( 107 new AbstractModule() { 108 @Override 109 protected void configure() { 110 install(new FactoryModuleBuilder().build(MustangFactory.class)); 111 } 112 113 @Provides 114 Mustang provide() { 115 return providedMustang; 116 } 117 }); 118 assertSame(providedMustang, injector.getInstance(Mustang.class)); 119 MustangFactory factory = injector.getInstance(MustangFactory.class); 120 Mustang created = factory.create(Color.GREEN); 121 assertNotSame(providedMustang, created); 122 assertEquals(Color.BLUE, providedMustang.color); 123 assertEquals(Color.GREEN, created.color); 124 } 125 testExplicitForwardingAssistedBindingFailsWithInterface()126 public void testExplicitForwardingAssistedBindingFailsWithInterface() { 127 try { 128 Guice.createInjector( 129 new AbstractModule() { 130 @Override 131 protected void configure() { 132 bind(Volkswagen.class).to(Golf.class); 133 install( 134 new FactoryModuleBuilder() 135 .implement(Car.class, Volkswagen.class) 136 .build(ColoredCarFactory.class)); 137 } 138 }); 139 fail(); 140 } catch (CreationException ce) { 141 assertContains( 142 ce.getMessage(), 143 "1) " + Volkswagen.class.getName() + " is an interface, not a concrete class.", 144 "Unable to create AssistedInject factory.", 145 "while locating " + Volkswagen.class.getName(), 146 "while locating " + Car.class.getName(), 147 "at " + ColoredCarFactory.class.getName() + ".create("); 148 assertEquals(1, ce.getErrorMessages().size()); 149 } 150 } 151 testExplicitForwardingAssistedBindingFailsWithAbstractClass()152 public void testExplicitForwardingAssistedBindingFailsWithAbstractClass() { 153 try { 154 Guice.createInjector( 155 new AbstractModule() { 156 @Override 157 protected void configure() { 158 bind(AbstractCar.class).to(ArtCar.class); 159 install( 160 new FactoryModuleBuilder() 161 .implement(Car.class, AbstractCar.class) 162 .build(ColoredCarFactory.class)); 163 } 164 }); 165 fail(); 166 } catch (CreationException ce) { 167 assertContains( 168 ce.getMessage(), 169 "1) " + AbstractCar.class.getName() + " is abstract, not a concrete class.", 170 "Unable to create AssistedInject factory.", 171 "while locating " + AbstractCar.class.getName(), 172 "while locating " + Car.class.getName(), 173 "at " + ColoredCarFactory.class.getName() + ".create("); 174 assertEquals(1, ce.getErrorMessages().size()); 175 } 176 } 177 testExplicitForwardingAssistedBindingCreatesNewObjects()178 public void testExplicitForwardingAssistedBindingCreatesNewObjects() { 179 final Mustang providedMustang = new Mustang(Color.BLUE); 180 Injector injector = 181 Guice.createInjector( 182 new AbstractModule() { 183 @Override 184 protected void configure() { 185 install( 186 new FactoryModuleBuilder() 187 .implement(Car.class, Mustang.class) 188 .build(ColoredCarFactory.class)); 189 } 190 191 @Provides 192 Mustang provide() { 193 return providedMustang; 194 } 195 }); 196 assertSame(providedMustang, injector.getInstance(Mustang.class)); 197 ColoredCarFactory factory = injector.getInstance(ColoredCarFactory.class); 198 Mustang created = (Mustang) factory.create(Color.GREEN); 199 assertNotSame(providedMustang, created); 200 assertEquals(Color.BLUE, providedMustang.color); 201 assertEquals(Color.GREEN, created.color); 202 } 203 testAnnotatedAndParentBoundReturnValue()204 public void testAnnotatedAndParentBoundReturnValue() { 205 Injector injector = 206 Guice.createInjector( 207 new AbstractModule() { 208 @Override 209 protected void configure() { 210 bind(Car.class).to(Golf.class); 211 212 bind(Integer.class).toInstance(911); 213 bind(Double.class).toInstance(5.0d); 214 install( 215 new FactoryModuleBuilder() 216 .implement(Car.class, Names.named("german"), Beetle.class) 217 .implement(Car.class, Names.named("american"), Mustang.class) 218 .build(AnnotatedVersatileCarFactory.class)); 219 } 220 }); 221 222 AnnotatedVersatileCarFactory factory = injector.getInstance(AnnotatedVersatileCarFactory.class); 223 assertTrue(factory.getGermanCar(Color.BLACK) instanceof Beetle); 224 assertTrue(injector.getInstance(Car.class) instanceof Golf); 225 } 226 testParentBoundReturnValue()227 public void testParentBoundReturnValue() { 228 Injector injector = 229 Guice.createInjector( 230 new AbstractModule() { 231 @Override 232 protected void configure() { 233 bind(Car.class).to(Golf.class); 234 bind(Double.class).toInstance(5.0d); 235 install( 236 new FactoryModuleBuilder() 237 .implement(Car.class, Mustang.class) 238 .build(ColoredCarFactory.class)); 239 } 240 }); 241 242 ColoredCarFactory factory = injector.getInstance(ColoredCarFactory.class); 243 assertTrue(factory.create(Color.RED) instanceof Mustang); 244 assertTrue(injector.getInstance(Car.class) instanceof Golf); 245 } 246 testConfigureAnnotatedReturnValue()247 public void testConfigureAnnotatedReturnValue() { 248 Injector injector = 249 Guice.createInjector( 250 new AbstractModule() { 251 @Override 252 protected void configure() { 253 install( 254 new FactoryModuleBuilder() 255 .implement(Car.class, Names.named("german"), Beetle.class) 256 .implement(Car.class, Names.named("american"), Mustang.class) 257 .build(AnnotatedVersatileCarFactory.class)); 258 } 259 }); 260 261 AnnotatedVersatileCarFactory factory = injector.getInstance(AnnotatedVersatileCarFactory.class); 262 assertTrue(factory.getGermanCar(Color.GRAY) instanceof Beetle); 263 assertTrue(factory.getAmericanCar(Color.BLACK) instanceof Mustang); 264 } 265 testNoBindingAssistedInject()266 public void testNoBindingAssistedInject() { 267 Injector injector = 268 Guice.createInjector( 269 new AbstractModule() { 270 @Override 271 protected void configure() { 272 install(new FactoryModuleBuilder().build(MustangFactory.class)); 273 } 274 }); 275 276 MustangFactory factory = injector.getInstance(MustangFactory.class); 277 278 Mustang mustang = factory.create(Color.BLUE); 279 assertEquals(Color.BLUE, mustang.color); 280 } 281 testBindingAssistedInject()282 public void testBindingAssistedInject() { 283 Injector injector = 284 Guice.createInjector( 285 new AbstractModule() { 286 @Override 287 protected void configure() { 288 install( 289 new FactoryModuleBuilder() 290 .implement(Car.class, Mustang.class) 291 .build(ColoredCarFactory.class)); 292 } 293 }); 294 295 ColoredCarFactory factory = injector.getInstance(ColoredCarFactory.class); 296 297 Mustang mustang = (Mustang) factory.create(Color.BLUE); 298 assertEquals(Color.BLUE, mustang.color); 299 } 300 testDuplicateBindings()301 public void testDuplicateBindings() { 302 Injector injector = 303 Guice.createInjector( 304 new AbstractModule() { 305 @Override 306 protected void configure() { 307 install( 308 new FactoryModuleBuilder() 309 .implement(Car.class, Mustang.class) 310 .build(ColoredCarFactory.class)); 311 install( 312 new FactoryModuleBuilder() 313 .implement(Car.class, Mustang.class) 314 .build(ColoredCarFactory.class)); 315 } 316 }); 317 318 ColoredCarFactory factory = injector.getInstance(ColoredCarFactory.class); 319 320 Mustang mustang = (Mustang) factory.create(Color.BLUE); 321 assertEquals(Color.BLUE, mustang.color); 322 } 323 testSimilarBindingsWithConflictingImplementations()324 public void testSimilarBindingsWithConflictingImplementations() { 325 try { 326 Injector injector = 327 Guice.createInjector( 328 new AbstractModule() { 329 @Override 330 protected void configure() { 331 install( 332 new FactoryModuleBuilder() 333 .implement(Car.class, Mustang.class) 334 .build(ColoredCarFactory.class)); 335 install( 336 new FactoryModuleBuilder() 337 .implement(Car.class, Golf.class) 338 .build(ColoredCarFactory.class)); 339 } 340 }); 341 injector.getInstance(ColoredCarFactory.class); 342 fail(); 343 } catch (CreationException ce) { 344 assertContains( 345 ce.getMessage(), 346 "A binding to " + ColoredCarFactory.class.getName() + " was already configured"); 347 assertEquals(1, ce.getErrorMessages().size()); 348 } 349 } 350 testMultipleReturnTypes()351 public void testMultipleReturnTypes() { 352 Injector injector = 353 Guice.createInjector( 354 new AbstractModule() { 355 @Override 356 protected void configure() { 357 bind(Double.class).toInstance(5.0d); 358 install(new FactoryModuleBuilder().build(VersatileCarFactory.class)); 359 } 360 }); 361 362 VersatileCarFactory factory = injector.getInstance(VersatileCarFactory.class); 363 364 Mustang mustang = factory.getMustang(Color.RED); 365 assertEquals(Color.RED, mustang.color); 366 367 Beetle beetle = factory.getBeetle(Color.GREEN); 368 assertEquals(Color.GREEN, beetle.color); 369 } 370 testParameterizedClassesWithNoImplements()371 public void testParameterizedClassesWithNoImplements() { 372 Injector injector = 373 Guice.createInjector( 374 new AbstractModule() { 375 @Override 376 protected void configure() { 377 install( 378 new FactoryModuleBuilder().build(new TypeLiteral<Foo.Factory<String>>() {})); 379 } 380 }); 381 382 Foo.Factory<String> factory = 383 injector.getInstance(Key.get(new TypeLiteral<Foo.Factory<String>>() {})); 384 @SuppressWarnings("unused") 385 Foo<String> foo = factory.create(new Bar()); 386 } 387 testGenericErrorMessageMakesSense()388 public void testGenericErrorMessageMakesSense() { 389 try { 390 Guice.createInjector( 391 new AbstractModule() { 392 @Override 393 protected void configure() { 394 install(new FactoryModuleBuilder().build(Key.get(Foo.Factory.class))); 395 } 396 }); 397 fail(); 398 } catch (CreationException ce) { 399 // Assert not only that it's the correct message, but also that it's the *only* message. 400 Collection<Message> messages = ce.getErrorMessages(); 401 assertEquals( 402 Foo.Factory.class.getName() + " cannot be used as a key; It is not fully specified.", 403 Iterables.getOnlyElement(messages).getMessage()); 404 } 405 } 406 407 interface Car {} 408 409 interface Volkswagen extends Car {} 410 411 interface ColoredCarFactory { create(Color color)412 Car create(Color color); 413 } 414 415 interface MustangFactory { create(Color color)416 Mustang create(Color color); 417 } 418 419 interface VersatileCarFactory { getMustang(Color color)420 Mustang getMustang(Color color); 421 getBeetle(Color color)422 Beetle getBeetle(Color color); 423 } 424 425 interface AnnotatedVersatileCarFactory { 426 @Named("german") getGermanCar(Color color)427 Car getGermanCar(Color color); 428 429 @Named("american") getAmericanCar(Color color)430 Car getAmericanCar(Color color); 431 } 432 433 public static class Golf implements Volkswagen {} 434 435 public static class Mustang implements Car { 436 private final Color color; 437 438 @Inject Mustang(@ssisted Color color)439 public Mustang(@Assisted Color color) { 440 this.color = color; 441 } 442 } 443 444 public static class Beetle implements Car { 445 private final Color color; 446 447 @Inject Beetle(@ssisted Color color)448 public Beetle(@Assisted Color color) { 449 this.color = color; 450 } 451 } 452 453 public static class Foo<E> { 454 static interface Factory<E> { create(Bar bar)455 Foo<E> create(Bar bar); 456 } 457 458 @SuppressWarnings("unused") 459 @Inject Foo(@ssisted Bar bar, Baz<E> baz)460 Foo(@Assisted Bar bar, Baz<E> baz) {} 461 } 462 463 public static class Bar {} 464 465 @SuppressWarnings("unused") 466 public static class Baz<E> {} 467 468 abstract static class AbstractCar implements Car {} 469 470 interface ColoredAbstractCarFactory { create(Color color)471 AbstractCar create(Color color); 472 } 473 474 public static class ArtCar extends AbstractCar {} 475 testFactoryBindingDependencies()476 public void testFactoryBindingDependencies() { 477 // validate dependencies work in all stages & as a raw element, 478 // and that dependencies work for methods, fields, constructors, 479 // and for @AssistedInject constructors too. 480 Module module = 481 new AbstractModule() { 482 @Override 483 protected void configure() { 484 bind(Integer.class).toInstance(42); 485 bind(Double.class).toInstance(4.2d); 486 bind(Float.class).toInstance(4.2f); 487 bind(String.class).annotatedWith(named("dog")).toInstance("dog"); 488 bind(String.class).annotatedWith(named("cat1")).toInstance("cat1"); 489 bind(String.class).annotatedWith(named("cat2")).toInstance("cat2"); 490 bind(String.class).annotatedWith(named("cat3")).toInstance("cat3"); 491 bind(String.class).annotatedWith(named("arbitrary")).toInstance("fail!"); 492 install( 493 new FactoryModuleBuilder() 494 .implement(Animal.class, Dog.class) 495 .build(AnimalHouse.class)); 496 } 497 }; 498 499 Set<Key<?>> expectedKeys = 500 ImmutableSet.<Key<?>>of( 501 Key.get(Integer.class), 502 Key.get(Double.class), 503 Key.get(Float.class), 504 Key.get(String.class, named("dog")), 505 Key.get(String.class, named("cat1")), 506 Key.get(String.class, named("cat2")), 507 Key.get(String.class, named("cat3"))); 508 509 Injector injector = Guice.createInjector(module); 510 validateDependencies(expectedKeys, injector.getBinding(AnimalHouse.class)); 511 512 injector = Guice.createInjector(Stage.TOOL, module); 513 validateDependencies(expectedKeys, injector.getBinding(AnimalHouse.class)); 514 515 List<Element> elements = Elements.getElements(module); 516 boolean found = false; 517 for (Element element : elements) { 518 if (element instanceof Binding) { 519 Binding<?> binding = (Binding<?>) element; 520 if (binding.getKey().equals(Key.get(AnimalHouse.class))) { 521 found = true; 522 validateDependencies(expectedKeys, binding); 523 break; 524 } 525 } 526 } 527 assertTrue(found); 528 } 529 validateDependencies(Set<Key<?>> expectedKeys, Binding<?> binding)530 private void validateDependencies(Set<Key<?>> expectedKeys, Binding<?> binding) { 531 Set<Dependency<?>> dependencies = ((HasDependencies) binding).getDependencies(); 532 Set<Key<?>> actualKeys = new HashSet<>(); 533 for (Dependency<?> dependency : dependencies) { 534 actualKeys.add(dependency.getKey()); 535 } 536 assertEquals(expectedKeys, actualKeys); 537 } 538 539 interface AnimalHouse { createAnimal(String name)540 Animal createAnimal(String name); 541 createCat(String name)542 Cat createCat(String name); 543 createCat(int age)544 Cat createCat(int age); 545 } 546 547 interface Animal {} 548 549 @SuppressWarnings("unused") 550 private static class Dog implements Animal { 551 @Inject int a; 552 553 @Inject Dog(@ssisted String a, double b)554 Dog(@Assisted String a, double b) {} 555 556 @Inject register(@amed"dog") String a)557 void register(@Named("dog") String a) {} 558 } 559 560 @SuppressWarnings("unused") 561 private static class Cat implements Animal { 562 @Inject float a; 563 564 @AssistedInject Cat(@ssisted String a, @Named("cat1") String b)565 Cat(@Assisted String a, @Named("cat1") String b) {} 566 567 @AssistedInject Cat(@ssisted int a, @Named("cat2") String b)568 Cat(@Assisted int a, @Named("cat2") String b) {} 569 570 @AssistedInject Cat(@ssisted byte a, @Named("catfail") String b)571 Cat(@Assisted byte a, @Named("catfail") String b) {} // not a dependency! 572 573 @Inject register(@amed"cat3") String a)574 void register(@Named("cat3") String a) {} 575 } 576 testFactoryPublicAndReturnTypeNotPublic()577 public void testFactoryPublicAndReturnTypeNotPublic() { 578 try { 579 Guice.createInjector( 580 new AbstractModule() { 581 @Override 582 protected void configure() { 583 install( 584 new FactoryModuleBuilder() 585 .implement(Hidden.class, HiddenImpl.class) 586 .build(NotHidden.class)); 587 } 588 }); 589 fail("Expected CreationException"); 590 } catch (CreationException ce) { 591 assertEquals( 592 NotHidden.class.getName() 593 + " is public, but has a method that returns a non-public type: " 594 + Hidden.class.getName() 595 + ". Due to limitations with java.lang.reflect.Proxy, this is not allowed. " 596 + "Please either make the factory non-public or the return type public.", 597 Iterables.getOnlyElement(ce.getErrorMessages()).getMessage()); 598 } 599 } 600 601 interface Hidden {} 602 603 public static class HiddenImpl implements Hidden {} 604 605 public interface NotHidden { create()606 Hidden create(); 607 } 608 testSingletonScopeOnAssistedClassIsIgnored()609 public void testSingletonScopeOnAssistedClassIsIgnored() { 610 try { 611 Guice.createInjector( 612 new AbstractModule() { 613 @Override 614 protected void configure() { 615 install(new FactoryModuleBuilder().build(SingletonFactory.class)); 616 } 617 }); 618 fail(); 619 } catch (CreationException ce) { 620 assertEquals(1, ce.getErrorMessages().size()); 621 assertEquals( 622 "Found scope annotation [" 623 + Singleton.class.getName() 624 + "]" 625 + " on implementation class [" 626 + AssistedSingleton.class.getName() 627 + "]" 628 + " of AssistedInject factory [" 629 + SingletonFactory.class.getName() 630 + "]." 631 + "\nThis is not allowed, please remove the scope annotation.", 632 Iterables.getOnlyElement(ce.getErrorMessages()).getMessage()); 633 } 634 } 635 636 interface SingletonFactory { create(String string)637 AssistedSingleton create(String string); 638 } 639 640 @SuppressWarnings("GuiceAssistedInjectScoping") 641 @Singleton 642 static class AssistedSingleton { 643 @Inject AssistedSingleton(@uppressWarnings"unused") @ssisted String string)644 public AssistedSingleton(@SuppressWarnings("unused") @Assisted String string) {} 645 } 646 } 647