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