1 /* 2 * Copyright (C) 2008 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.asModuleChain; 20 import static com.google.inject.Asserts.assertContains; 21 import static com.google.inject.Asserts.getDeclaringSourcePart; 22 import static com.google.inject.name.Names.named; 23 24 import com.google.common.collect.ImmutableSet; 25 import com.google.inject.internal.Annotations; 26 import com.google.inject.name.Named; 27 import com.google.inject.name.Names; 28 import com.google.inject.spi.Dependency; 29 import com.google.inject.spi.ExposedBinding; 30 import com.google.inject.spi.PrivateElements; 31 import com.google.inject.util.Types; 32 import java.util.ArrayList; 33 import java.util.Collection; 34 import java.util.List; 35 import junit.framework.TestCase; 36 37 /** @author jessewilson@google.com (Jesse Wilson) */ 38 public class PrivateModuleTest extends TestCase { 39 testBasicUsage()40 public void testBasicUsage() { 41 Injector injector = 42 Guice.createInjector( 43 new AbstractModule() { 44 @Override 45 protected void configure() { 46 bind(String.class).annotatedWith(named("a")).toInstance("public"); 47 48 install( 49 new PrivateModule() { 50 @Override 51 public void configure() { 52 bind(String.class).annotatedWith(named("b")).toInstance("i"); 53 54 bind(AB.class).annotatedWith(named("one")).to(AB.class); 55 expose(AB.class).annotatedWith(named("one")); 56 } 57 }); 58 59 install( 60 new PrivateModule() { 61 @Override 62 public void configure() { 63 bind(String.class).annotatedWith(named("b")).toInstance("ii"); 64 65 bind(AB.class).annotatedWith(named("two")).to(AB.class); 66 expose(AB.class).annotatedWith(named("two")); 67 } 68 }); 69 } 70 }); 71 72 AB ab1 = injector.getInstance(Key.get(AB.class, named("one"))); 73 assertEquals("public", ab1.a); 74 assertEquals("i", ab1.b); 75 76 AB ab2 = injector.getInstance(Key.get(AB.class, named("two"))); 77 assertEquals("public", ab2.a); 78 assertEquals("ii", ab2.b); 79 } 80 testWithoutPrivateModules()81 public void testWithoutPrivateModules() { 82 Injector injector = 83 Guice.createInjector( 84 new AbstractModule() { 85 @Override 86 protected void configure() { 87 PrivateBinder bindA = binder().newPrivateBinder(); 88 bindA.bind(String.class).annotatedWith(named("a")).toInstance("i"); 89 bindA.expose(String.class).annotatedWith(named("a")); 90 bindA.bind(String.class).annotatedWith(named("c")).toInstance("private to A"); 91 92 PrivateBinder bindB = binder().newPrivateBinder(); 93 bindB.bind(String.class).annotatedWith(named("b")).toInstance("ii"); 94 bindB.expose(String.class).annotatedWith(named("b")); 95 bindB.bind(String.class).annotatedWith(named("c")).toInstance("private to B"); 96 } 97 }); 98 99 assertEquals("i", injector.getInstance(Key.get(String.class, named("a")))); 100 assertEquals("ii", injector.getInstance(Key.get(String.class, named("b")))); 101 } 102 testMisplacedExposedAnnotation()103 public void testMisplacedExposedAnnotation() { 104 try { 105 Guice.createInjector( 106 new AbstractModule() { 107 108 @Provides 109 @Exposed 110 String provideString() { 111 return "i"; 112 } 113 }); 114 fail(); 115 } catch (CreationException expected) { 116 assertContains( 117 expected.getMessage(), 118 "Cannot expose java.lang.String on a standard binder. ", 119 "Exposed bindings are only applicable to private binders.", 120 " at " + PrivateModuleTest.class.getName(), 121 "provideString(PrivateModuleTest.java:"); 122 } 123 } 124 testMisplacedExposeStatement()125 public void testMisplacedExposeStatement() { 126 try { 127 Guice.createInjector( 128 new AbstractModule() { 129 @Override 130 protected void configure() { 131 ((PrivateBinder) binder()).expose(String.class).annotatedWith(named("a")); 132 } 133 }); 134 fail(); 135 } catch (CreationException expected) { 136 assertContains( 137 expected.getMessage(), 138 "Cannot expose java.lang.String on a standard binder. ", 139 "Exposed bindings are only applicable to private binders.", 140 " at " + PrivateModuleTest.class.getName(), 141 getDeclaringSourcePart(getClass())); 142 } 143 } 144 testPrivateModulesAndProvidesMethods()145 public void testPrivateModulesAndProvidesMethods() { 146 Injector injector = 147 Guice.createInjector( 148 new AbstractModule() { 149 @Override 150 protected void configure() { 151 install( 152 new PrivateModule() { 153 @Override 154 public void configure() { 155 expose(String.class).annotatedWith(named("a")); 156 } 157 158 @Provides 159 @Named("a") 160 String providePublicA() { 161 return "i"; 162 } 163 164 @Provides 165 @Named("b") 166 String providePrivateB() { 167 return "private"; 168 } 169 }); 170 171 install( 172 new PrivateModule() { 173 @Override 174 public void configure() {} 175 176 @Provides 177 @Named("c") 178 String providePrivateC() { 179 return "private"; 180 } 181 182 @Provides 183 @Exposed 184 @Named("d") 185 String providePublicD() { 186 return "ii"; 187 } 188 }); 189 } 190 }); 191 192 assertEquals("i", injector.getInstance(Key.get(String.class, named("a")))); 193 194 try { 195 injector.getInstance(Key.get(String.class, named("b"))); 196 fail(); 197 } catch (ConfigurationException expected) { 198 } 199 200 try { 201 injector.getInstance(Key.get(String.class, named("c"))); 202 fail(); 203 } catch (ConfigurationException expected) { 204 } 205 206 assertEquals("ii", injector.getInstance(Key.get(String.class, named("d")))); 207 } 208 testCannotBindAKeyExportedByASibling()209 public void testCannotBindAKeyExportedByASibling() { 210 try { 211 Guice.createInjector( 212 new AbstractModule() { 213 @Override 214 protected void configure() { 215 install( 216 new PrivateModule() { 217 @Override 218 public void configure() { 219 bind(String.class).toInstance("public"); 220 expose(String.class); 221 } 222 }); 223 224 install( 225 new PrivateModule() { 226 @Override 227 public void configure() { 228 bind(String.class).toInstance("private"); 229 } 230 }); 231 } 232 }); 233 fail(); 234 } catch (CreationException expected) { 235 assertContains( 236 expected.getMessage(), 237 "A binding to java.lang.String was already configured at ", 238 getClass().getName(), 239 getDeclaringSourcePart(getClass()), 240 " at " + getClass().getName(), 241 getDeclaringSourcePart(getClass())); 242 } 243 } 244 testExposeButNoBind()245 public void testExposeButNoBind() { 246 try { 247 Guice.createInjector( 248 new AbstractModule() { 249 @Override 250 protected void configure() { 251 bind(String.class).annotatedWith(named("a")).toInstance("a"); 252 bind(String.class).annotatedWith(named("b")).toInstance("b"); 253 254 install( 255 new PrivateModule() { 256 @Override 257 public void configure() { 258 expose(AB.class); 259 } 260 }); 261 } 262 }); 263 fail("AB was exposed but not bound"); 264 } catch (CreationException expected) { 265 assertContains( 266 expected.getMessage(), 267 "Could not expose() " + AB.class.getName() + ", it must be explicitly bound", 268 getDeclaringSourcePart(getClass())); 269 } 270 } 271 272 /** 273 * Ensure that when we've got errors in different private modules, Guice presents all errors in a 274 * unified message. 275 */ testMessagesFromPrivateModulesAreNicelyIntegrated()276 public void testMessagesFromPrivateModulesAreNicelyIntegrated() { 277 try { 278 Guice.createInjector( 279 new PrivateModule() { 280 @Override 281 public void configure() { 282 bind(C.class); 283 } 284 }, 285 new PrivateModule() { 286 @Override 287 public void configure() { 288 bind(AB.class); 289 } 290 }); 291 fail(); 292 } catch (CreationException expected) { 293 assertContains( 294 expected.getMessage(), 295 "1) No implementation for " + C.class.getName() + " was bound.", 296 "at " + getClass().getName(), 297 getDeclaringSourcePart(getClass()), 298 "2) No implementation for " + String.class.getName(), 299 "Named(value=" + Annotations.memberValueString("a") + ") was bound.", 300 "for field at " + AB.class.getName() + ".a(PrivateModuleTest.java:", 301 "3) No implementation for " + String.class.getName(), 302 "Named(value=" + Annotations.memberValueString("b") + ") was bound.", 303 "for field at " + AB.class.getName() + ".b(PrivateModuleTest.java:", 304 "3 errors"); 305 } 306 } 307 testNestedPrivateInjectors()308 public void testNestedPrivateInjectors() { 309 Injector injector = 310 Guice.createInjector( 311 new PrivateModule() { 312 @Override 313 public void configure() { 314 expose(String.class); 315 316 install( 317 new PrivateModule() { 318 @Override 319 public void configure() { 320 bind(String.class).toInstance("nested"); 321 expose(String.class); 322 } 323 }); 324 } 325 }); 326 327 assertEquals("nested", injector.getInstance(String.class)); 328 } 329 testInstallingRegularModulesFromPrivateModules()330 public void testInstallingRegularModulesFromPrivateModules() { 331 Injector injector = 332 Guice.createInjector( 333 new PrivateModule() { 334 @Override 335 public void configure() { 336 expose(String.class); 337 338 install( 339 new AbstractModule() { 340 @Override 341 protected void configure() { 342 bind(String.class).toInstance("nested"); 343 } 344 }); 345 } 346 }); 347 348 assertEquals("nested", injector.getInstance(String.class)); 349 } 350 testNestedPrivateModulesWithSomeKeysUnexposed()351 public void testNestedPrivateModulesWithSomeKeysUnexposed() { 352 Injector injector = 353 Guice.createInjector( 354 new PrivateModule() { 355 @Override 356 public void configure() { 357 bind(String.class) 358 .annotatedWith(named("bound outer, exposed outer")) 359 .toInstance("boeo"); 360 expose(String.class).annotatedWith(named("bound outer, exposed outer")); 361 bind(String.class) 362 .annotatedWith(named("bound outer, exposed none")) 363 .toInstance("boen"); 364 expose(String.class).annotatedWith(named("bound inner, exposed both")); 365 366 install( 367 new PrivateModule() { 368 @Override 369 public void configure() { 370 bind(String.class) 371 .annotatedWith(named("bound inner, exposed both")) 372 .toInstance("bieb"); 373 expose(String.class).annotatedWith(named("bound inner, exposed both")); 374 bind(String.class) 375 .annotatedWith(named("bound inner, exposed none")) 376 .toInstance("bien"); 377 } 378 }); 379 } 380 }); 381 382 assertEquals( 383 "boeo", injector.getInstance(Key.get(String.class, named("bound outer, exposed outer")))); 384 assertEquals( 385 "bieb", injector.getInstance(Key.get(String.class, named("bound inner, exposed both")))); 386 387 try { 388 injector.getInstance(Key.get(String.class, named("bound outer, exposed none"))); 389 fail(); 390 } catch (ConfigurationException expected) { 391 } 392 393 try { 394 injector.getInstance(Key.get(String.class, named("bound inner, exposed none"))); 395 fail(); 396 } catch (ConfigurationException expected) { 397 } 398 } 399 testDependenciesBetweenPrivateAndPublic()400 public void testDependenciesBetweenPrivateAndPublic() { 401 Injector injector = 402 Guice.createInjector( 403 new PrivateModule() { 404 @Override 405 protected void configure() {} 406 407 @Provides 408 @Exposed 409 @Named("a") 410 String provideA() { 411 return "A"; 412 } 413 414 @Provides 415 @Exposed 416 @Named("abc") 417 String provideAbc(@Named("ab") String ab) { 418 return ab + "C"; 419 } 420 }, 421 new AbstractModule() { 422 423 @Provides 424 @Named("ab") 425 String provideAb(@Named("a") String a) { 426 return a + "B"; 427 } 428 429 @Provides 430 @Named("abcd") 431 String provideAbcd(@Named("abc") String abc) { 432 return abc + "D"; 433 } 434 }); 435 436 assertEquals("ABCD", injector.getInstance(Key.get(String.class, named("abcd")))); 437 } 438 testDependenciesBetweenPrivateAndPublicWithPublicEagerSingleton()439 public void testDependenciesBetweenPrivateAndPublicWithPublicEagerSingleton() { 440 Injector injector = 441 Guice.createInjector( 442 new PrivateModule() { 443 @Override 444 protected void configure() {} 445 446 @Provides 447 @Exposed 448 @Named("a") 449 String provideA() { 450 return "A"; 451 } 452 453 @Provides 454 @Exposed 455 @Named("abc") 456 String provideAbc(@Named("ab") String ab) { 457 return ab + "C"; 458 } 459 }, 460 new AbstractModule() { 461 @Override 462 protected void configure() { 463 bind(String.class) 464 .annotatedWith(named("abcde")) 465 .toProvider( 466 new Provider<String>() { 467 @Inject 468 @Named("abcd") 469 String abcd; 470 471 @Override 472 public String get() { 473 return abcd + "E"; 474 } 475 }) 476 .asEagerSingleton(); 477 } 478 479 @Provides 480 @Named("ab") 481 String provideAb(@Named("a") String a) { 482 return a + "B"; 483 } 484 485 @Provides 486 @Named("abcd") 487 String provideAbcd(@Named("abc") String abc) { 488 return abc + "D"; 489 } 490 }); 491 492 assertEquals("ABCDE", injector.getInstance(Key.get(String.class, named("abcde")))); 493 } 494 testDependenciesBetweenPrivateAndPublicWithPrivateEagerSingleton()495 public void testDependenciesBetweenPrivateAndPublicWithPrivateEagerSingleton() { 496 Injector injector = 497 Guice.createInjector( 498 new AbstractModule() { 499 500 @Provides 501 @Named("ab") 502 String provideAb(@Named("a") String a) { 503 return a + "B"; 504 } 505 506 @Provides 507 @Named("abcd") 508 String provideAbcd(@Named("abc") String abc) { 509 return abc + "D"; 510 } 511 }, 512 new PrivateModule() { 513 @Override 514 protected void configure() { 515 bind(String.class) 516 .annotatedWith(named("abcde")) 517 .toProvider( 518 new Provider<String>() { 519 @Inject 520 @Named("abcd") 521 String abcd; 522 523 @Override 524 public String get() { 525 return abcd + "E"; 526 } 527 }) 528 .asEagerSingleton(); 529 expose(String.class).annotatedWith(named("abcde")); 530 } 531 532 @Provides 533 @Exposed 534 @Named("a") 535 String provideA() { 536 return "A"; 537 } 538 539 @Provides 540 @Exposed 541 @Named("abc") 542 String provideAbc(@Named("ab") String ab) { 543 return ab + "C"; 544 } 545 }); 546 547 assertEquals("ABCDE", injector.getInstance(Key.get(String.class, named("abcde")))); 548 } 549 550 static class AB { 551 @Inject 552 @Named("a") 553 String a; 554 555 @Inject 556 @Named("b") 557 String b; 558 } 559 560 interface C {} 561 testSpiAccess()562 public void testSpiAccess() { 563 Injector injector = 564 Guice.createInjector( 565 new PrivateModule() { 566 @Override 567 public void configure() { 568 bind(String.class).annotatedWith(named("a")).toInstance("private"); 569 bind(String.class).annotatedWith(named("b")).toInstance("exposed"); 570 expose(String.class).annotatedWith(named("b")); 571 } 572 }); 573 574 ExposedBinding<?> binding = 575 (ExposedBinding<?>) injector.getBinding(Key.get(String.class, Names.named("b"))); 576 assertEquals( 577 ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Injector.class))), 578 binding.getDependencies()); 579 PrivateElements privateElements = binding.getPrivateElements(); 580 assertEquals( 581 ImmutableSet.<Key<?>>of(Key.get(String.class, named("b"))), 582 privateElements.getExposedKeys()); 583 assertContains( 584 privateElements.getExposedSource(Key.get(String.class, named("b"))).toString(), 585 PrivateModuleTest.class.getName(), 586 getDeclaringSourcePart(getClass())); 587 Injector privateInjector = privateElements.getInjector(); 588 assertEquals("private", privateInjector.getInstance(Key.get(String.class, Names.named("a")))); 589 } 590 testParentBindsSomethingInPrivate()591 public void testParentBindsSomethingInPrivate() { 592 try { 593 Guice.createInjector(new FailingModule()); 594 fail(); 595 } catch (CreationException expected) { 596 assertEquals(1, expected.getErrorMessages().size()); 597 assertContains( 598 expected.toString(), 599 "Unable to create binding for java.util.List.", 600 "It was already configured on one or more child injectors or private modules", 601 "bound at " + FailingPrivateModule.class.getName() + ".configure(", 602 asModuleChain(FailingModule.class, ManyPrivateModules.class, FailingPrivateModule.class), 603 "bound at " + SecondFailingPrivateModule.class.getName() + ".configure(", 604 asModuleChain( 605 FailingModule.class, ManyPrivateModules.class, SecondFailingPrivateModule.class), 606 "If it was in a PrivateModule, did you forget to expose the binding?", 607 "at " + FailingModule.class.getName() + ".configure("); 608 } 609 } 610 testParentBindingToPrivateLinkedJitBinding()611 public void testParentBindingToPrivateLinkedJitBinding() { 612 Injector injector = Guice.createInjector(new ManyPrivateModules()); 613 try { 614 injector.getBinding(Key.get(Types.providerOf(List.class))); 615 fail(); 616 } catch (ConfigurationException expected) { 617 assertEquals(1, expected.getErrorMessages().size()); 618 assertContains( 619 expected.toString(), 620 "Unable to create binding for com.google.inject.Provider<java.util.List>.", 621 "It was already configured on one or more child injectors or private modules", 622 "bound at " + FailingPrivateModule.class.getName() + ".configure(", 623 asModuleChain(ManyPrivateModules.class, FailingPrivateModule.class), 624 "bound at " + SecondFailingPrivateModule.class.getName() + ".configure(", 625 asModuleChain(ManyPrivateModules.class, SecondFailingPrivateModule.class), 626 "If it was in a PrivateModule, did you forget to expose the binding?", 627 "while locating com.google.inject.Provider<java.util.List>"); 628 } 629 } 630 testParentBindingToPrivateJitBinding()631 public void testParentBindingToPrivateJitBinding() { 632 Injector injector = Guice.createInjector(new ManyPrivateModules()); 633 try { 634 injector.getBinding(PrivateFoo.class); 635 fail(); 636 } catch (ConfigurationException expected) { 637 assertEquals(1, expected.getErrorMessages().size()); 638 assertContains( 639 expected.toString(), 640 "Unable to create binding for " + PrivateFoo.class.getName(), 641 "It was already configured on one or more child injectors or private modules", 642 "(bound by a just-in-time binding)", 643 "If it was in a PrivateModule, did you forget to expose the binding?", 644 "while locating " + PrivateFoo.class.getName()); 645 } 646 } 647 648 private static class FailingModule extends AbstractModule { 649 @Override configure()650 protected void configure() { 651 bind(Collection.class).to(List.class); 652 install(new ManyPrivateModules()); 653 } 654 } 655 656 private static class ManyPrivateModules extends AbstractModule { 657 @Override configure()658 protected void configure() { 659 // make sure duplicate sources are collapsed 660 install(new FailingPrivateModule()); 661 install(new FailingPrivateModule()); 662 // but additional sources are listed 663 install(new SecondFailingPrivateModule()); 664 } 665 } 666 667 private static class FailingPrivateModule extends PrivateModule { 668 @Override configure()669 protected void configure() { 670 bind(List.class).toInstance(new ArrayList()); 671 672 // Add the Provider<List> binding, created just-in-time, 673 // to make sure our linked JIT bindings have the correct source. 674 getProvider(Key.get(Types.providerOf(List.class))); 675 676 // Request a JIT binding for PrivateFoo, which can only 677 // be created in the private module because it depends 678 // on List. 679 getProvider(PrivateFoo.class); 680 } 681 } 682 683 /** A second class, so we can see another name in the source list. */ 684 private static class SecondFailingPrivateModule extends PrivateModule { 685 @Override configure()686 protected void configure() { 687 bind(List.class).toInstance(new ArrayList()); 688 689 // Add the Provider<List> binding, created just-in-time, 690 // to make sure our linked JIT bindings have the correct source. 691 getProvider(Key.get(Types.providerOf(List.class))); 692 693 // Request a JIT binding for PrivateFoo, which can only 694 // be created in the private module because it depends 695 // on List. 696 getProvider(PrivateFoo.class); 697 } 698 } 699 700 private static class PrivateFoo { 701 @Inject List list; 702 } 703 } 704