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.name.Named;
26 import com.google.inject.name.Names;
27 import com.google.inject.spi.Dependency;
28 import com.google.inject.spi.ExposedBinding;
29 import com.google.inject.spi.PrivateElements;
30 import com.google.inject.util.Types;
31 
32 import junit.framework.TestCase;
33 
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.List;
37 
38 /**
39  * @author jessewilson@google.com (Jesse Wilson)
40  */
41 public class PrivateModuleTest extends TestCase {
42 
testBasicUsage()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 
testWithoutPrivateModules()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 
testMisplacedExposedAnnotation()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 
testMisplacedExposeStatement()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 
testPrivateModulesAndProvidesMethods()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 
testCannotBindAKeyExportedByASibling()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 
testExposeButNoBind()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    */
testMessagesFromPrivateModulesAreNicelyIntegrated()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 
testNestedPrivateInjectors()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 
testInstallingRegularModulesFromPrivateModules()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 
testNestedPrivateModulesWithSomeKeysUnexposed()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 
testDependenciesBetweenPrivateAndPublic()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 
testDependenciesBetweenPrivateAndPublicWithPublicEagerSingleton()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 
testDependenciesBetweenPrivateAndPublicWithPrivateEagerSingleton()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 
testSpiAccess()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 
testParentBindsSomethingInPrivate()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 
testParentBindingToPrivateLinkedJitBinding()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 
testParentBindingToPrivateJitBinding()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 {
configure()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 {
configure()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 {
configure()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 {
configure()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