• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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