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