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.spi;
18 
19 import static com.google.inject.Asserts.assertContains;
20 import static com.google.inject.Asserts.getDeclaringSourcePart;
21 import static com.google.inject.Asserts.isIncludeStackTraceComplete;
22 
23 import com.google.common.collect.ImmutableSet;
24 import com.google.common.collect.Lists;
25 import com.google.inject.AbstractModule;
26 import com.google.inject.Binding;
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.Provider;
33 import com.google.inject.Scope;
34 import com.google.inject.Scopes;
35 import com.google.inject.Singleton;
36 import com.google.inject.Stage;
37 import com.google.inject.name.Names;
38 import java.lang.reflect.Constructor;
39 import java.util.Collections;
40 import java.util.Comparator;
41 import java.util.Iterator;
42 import java.util.List;
43 import java.util.concurrent.atomic.AtomicBoolean;
44 import java.util.logging.Logger;
45 import junit.framework.AssertionFailedError;
46 import junit.framework.TestCase;
47 
48 /** @author jessewilson@google.com (Jesse Wilson) */
49 public class SpiBindingsTest extends TestCase {
50 
testBindConstant()51   public void testBindConstant() {
52     checkInjector(
53         new AbstractModule() {
54           @Override
55           protected void configure() {
56             bindConstant().annotatedWith(Names.named("one")).to(1);
57           }
58         },
59         new FailingElementVisitor() {
60           @Override
61           public <T> Void visit(Binding<T> binding) {
62             assertTrue(binding instanceof InstanceBinding);
63             assertEquals(Key.get(Integer.class, Names.named("one")), binding.getKey());
64             return null;
65           }
66         });
67   }
68 
testToInstanceBinding()69   public void testToInstanceBinding() {
70     checkInjector(
71         new AbstractModule() {
72           @Override
73           protected void configure() {
74             bind(String.class).toInstance("A");
75           }
76         },
77         new FailingElementVisitor() {
78           @Override
79           public <T> Void visit(Binding<T> binding) {
80             assertTrue(binding instanceof InstanceBinding);
81             checkBindingSource(binding);
82             assertEquals(Key.get(String.class), binding.getKey());
83             binding.acceptTargetVisitor(
84                 new FailingTargetVisitor<T>() {
85                   @Override
86                   public Void visit(InstanceBinding<? extends T> binding) {
87                     assertEquals("A", binding.getInstance());
88                     return null;
89                   }
90                 });
91             binding.acceptScopingVisitor(
92                 new FailingBindingScopingVisitor() {
93                   @Override
94                   public Void visitEagerSingleton() {
95                     return null;
96                   }
97                 });
98             return null;
99           }
100         });
101   }
102 
testToProviderBinding()103   public void testToProviderBinding() {
104     final Provider<String> stringProvider = new StringProvider();
105 
106     checkInjector(
107         new AbstractModule() {
108           @Override
109           protected void configure() {
110             bind(String.class).toProvider(stringProvider);
111           }
112         },
113         new FailingElementVisitor() {
114           @Override
115           public <T> Void visit(Binding<T> binding) {
116             assertTrue(binding instanceof ProviderInstanceBinding);
117             checkBindingSource(binding);
118             assertEquals(Key.get(String.class), binding.getKey());
119             binding.acceptTargetVisitor(
120                 new FailingTargetVisitor<T>() {
121                   @Override
122                   public Void visit(ProviderInstanceBinding<? extends T> binding) {
123                     assertSame(stringProvider, binding.getUserSuppliedProvider());
124                     return null;
125                   }
126                 });
127             return null;
128           }
129         });
130   }
131 
testToProviderKeyBinding()132   public void testToProviderKeyBinding() {
133     checkInjector(
134         new AbstractModule() {
135           @Override
136           protected void configure() {
137             bind(String.class).toProvider(StringProvider.class);
138           }
139         },
140         new FailingElementVisitor() {
141           @Override
142           public <T> Void visit(Binding<T> binding) {
143             assertTrue(binding instanceof ProviderKeyBinding);
144             checkBindingSource(binding);
145             assertEquals(Key.get(String.class), binding.getKey());
146             binding.acceptTargetVisitor(
147                 new FailingTargetVisitor<T>() {
148                   @Override
149                   public Void visit(ProviderKeyBinding<? extends T> binding) {
150                     assertEquals(Key.get(StringProvider.class), binding.getProviderKey());
151                     return null;
152                   }
153                 });
154             return null;
155           }
156         });
157   }
158 
testToKeyBinding()159   public void testToKeyBinding() {
160     final Key<String> aKey = Key.get(String.class, Names.named("a"));
161     final Key<String> bKey = Key.get(String.class, Names.named("b"));
162 
163     checkInjector(
164         new AbstractModule() {
165           @Override
166           protected void configure() {
167             bind(aKey).to(bKey);
168             bind(bKey).toInstance("B");
169           }
170         },
171         new FailingElementVisitor() {
172           @Override
173           public <T> Void visit(Binding<T> binding) {
174             assertTrue(binding instanceof LinkedKeyBinding);
175             checkBindingSource(binding);
176             assertEquals(aKey, binding.getKey());
177             binding.acceptTargetVisitor(
178                 new FailingTargetVisitor<T>() {
179                   @Override
180                   public Void visit(LinkedKeyBinding<? extends T> binding) {
181                     assertEquals(bKey, binding.getLinkedKey());
182                     return null;
183                   }
184                 });
185             return null;
186           }
187         },
188         new FailingElementVisitor() {
189           @Override
190           public <T> Void visit(Binding<T> binding) {
191             assertEquals(bKey, binding.getKey());
192             return null;
193           }
194         });
195   }
196 
testToConstructorBinding()197   public void testToConstructorBinding() {
198     checkInjector(
199         new AbstractModule() {
200           @Override
201           protected void configure() {
202             bind(D.class);
203           }
204         },
205         new FailingElementVisitor() {
206           @Override
207           public <T> Void visit(Binding<T> binding) {
208             assertTrue(binding instanceof ConstructorBinding);
209             checkBindingSource(binding);
210             assertEquals(Key.get(D.class), binding.getKey());
211             binding.acceptTargetVisitor(
212                 new FailingTargetVisitor<T>() {
213                   @Override
214                   public Void visit(ConstructorBinding<? extends T> binding) {
215                     Constructor<?> expected = D.class.getDeclaredConstructors()[0];
216                     assertEquals(expected, binding.getConstructor().getMember());
217                     assertEquals(ImmutableSet.<InjectionPoint>of(), binding.getInjectableMembers());
218                     return null;
219                   }
220                 });
221             return null;
222           }
223         });
224   }
225 
testConstantBinding()226   public void testConstantBinding() {
227     checkInjector(
228         new AbstractModule() {
229           @Override
230           protected void configure() {
231             bindConstant().annotatedWith(Names.named("one")).to(1);
232           }
233         },
234         new FailingElementVisitor() {
235           @Override
236           public <T> Void visit(Binding<T> binding) {
237             assertTrue(binding instanceof InstanceBinding);
238             checkBindingSource(binding);
239             assertEquals(Key.get(Integer.class, Names.named("one")), binding.getKey());
240             binding.acceptTargetVisitor(
241                 new FailingTargetVisitor<T>() {
242                   @Override
243                   public Void visit(InstanceBinding<? extends T> binding) {
244                     assertEquals(1, binding.getInstance());
245                     return null;
246                   }
247                 });
248             return null;
249           }
250         });
251   }
252 
testConvertedConstantBinding()253   public void testConvertedConstantBinding() {
254     Injector injector =
255         Guice.createInjector(
256             new AbstractModule() {
257               @Override
258               protected void configure() {
259                 bindConstant().annotatedWith(Names.named("one")).to("1");
260               }
261             });
262 
263     Binding<Integer> binding = injector.getBinding(Key.get(Integer.class, Names.named("one")));
264     assertEquals(Key.get(Integer.class, Names.named("one")), binding.getKey());
265     checkBindingSource(binding);
266     assertTrue(binding instanceof ConvertedConstantBinding);
267     binding.acceptTargetVisitor(
268         new FailingTargetVisitor<Integer>() {
269           @Override
270           public Void visit(ConvertedConstantBinding<? extends Integer> binding) {
271             assertEquals((Integer) 1, binding.getValue());
272             assertEquals(Key.get(String.class, Names.named("one")), binding.getSourceKey());
273             return null;
274           }
275         });
276   }
277 
testProviderBinding()278   public void testProviderBinding() {
279     Injector injector =
280         Guice.createInjector(
281             new AbstractModule() {
282               @Override
283               protected void configure() {
284                 bind(String.class).toInstance("A");
285               }
286             });
287 
288     Key<Provider<String>> providerOfStringKey = new Key<Provider<String>>() {};
289     Binding<Provider<String>> binding = injector.getBinding(providerOfStringKey);
290     assertEquals(providerOfStringKey, binding.getKey());
291     checkBindingSource(binding);
292     assertTrue(binding instanceof ProviderBinding);
293     binding.acceptTargetVisitor(
294         new FailingTargetVisitor<Provider<String>>() {
295           @Override
296           public Void visit(ProviderBinding<? extends Provider<String>> binding) {
297             assertEquals(Key.get(String.class), binding.getProvidedKey());
298             return null;
299           }
300         });
301   }
302 
testScopes()303   public void testScopes() {
304     checkInjector(
305         new AbstractModule() {
306           @Override
307           protected void configure() {
308             bind(String.class)
309                 .annotatedWith(Names.named("a"))
310                 .toProvider(StringProvider.class)
311                 .in(Singleton.class);
312             bind(String.class)
313                 .annotatedWith(Names.named("b"))
314                 .toProvider(StringProvider.class)
315                 .in(Scopes.SINGLETON);
316             bind(String.class)
317                 .annotatedWith(Names.named("c"))
318                 .toProvider(StringProvider.class)
319                 .asEagerSingleton();
320             bind(String.class).annotatedWith(Names.named("d")).toProvider(StringProvider.class);
321           }
322         },
323         new FailingElementVisitor() {
324           @Override
325           public <T> Void visit(Binding<T> command) {
326             assertEquals(Key.get(String.class, Names.named("a")), command.getKey());
327             command.acceptScopingVisitor(
328                 new FailingBindingScopingVisitor() {
329                   @Override
330                   public Void visitScope(Scope scope) {
331                     // even though we bound with an annotation, the injector always uses instances
332                     assertSame(Scopes.SINGLETON, scope);
333                     return null;
334                   }
335                 });
336             return null;
337           }
338         },
339         new FailingElementVisitor() {
340           @Override
341           public <T> Void visit(Binding<T> command) {
342             assertEquals(Key.get(String.class, Names.named("b")), command.getKey());
343             command.acceptScopingVisitor(
344                 new FailingBindingScopingVisitor() {
345                   @Override
346                   public Void visitScope(Scope scope) {
347                     assertSame(Scopes.SINGLETON, scope);
348                     return null;
349                   }
350                 });
351             return null;
352           }
353         },
354         new FailingElementVisitor() {
355           @Override
356           public <T> Void visit(Binding<T> command) {
357             assertEquals(Key.get(String.class, Names.named("c")), command.getKey());
358             command.acceptScopingVisitor(
359                 new FailingBindingScopingVisitor() {
360                   @Override
361                   public Void visitEagerSingleton() {
362                     return null;
363                   }
364                 });
365             return null;
366           }
367         },
368         new FailingElementVisitor() {
369           @Override
370           public <T> Void visit(Binding<T> command) {
371             assertEquals(Key.get(String.class, Names.named("d")), command.getKey());
372             command.acceptScopingVisitor(
373                 new FailingBindingScopingVisitor() {
374                   @Override
375                   public Void visitNoScoping() {
376                     return null;
377                   }
378                 });
379             return null;
380           }
381         });
382   }
383 
testExtensionSpi()384   public void testExtensionSpi() {
385     final AtomicBoolean visiting = new AtomicBoolean(false);
386 
387     final Injector injector =
388         Guice.createInjector(
389             new AbstractModule() {
390               @Override
391               protected void configure() {
392                 bind(String.class)
393                     .toProvider(
394                         new ProviderWithExtensionVisitor<String>() {
395                           @Override
396                           public <B, V> V acceptExtensionVisitor(
397                               BindingTargetVisitor<B, V> visitor,
398                               ProviderInstanceBinding<? extends B> binding) {
399                             assertSame(this, binding.getUserSuppliedProvider());
400                             // We can't always check for FailingSpiTargetVisitor,
401                             // because constructing the injector visits here, and we need
402                             // to process the binding as normal
403                             if (visiting.get()) {
404                               assertTrue(
405                                   "visitor: " + visitor,
406                                   visitor instanceof FailingSpiTargetVisitor);
407                               return (V) "visited";
408                             } else {
409                               return visitor.visit(binding);
410                             }
411                           }
412 
413                           @Override
414                           public String get() {
415                             return "FooBar";
416                           }
417                         });
418               }
419             });
420 
421     visiting.set(true);
422 
423     // Check for Provider<String> binding -- that is still a ProviderBinding.
424     Key<Provider<String>> providerOfStringKey = new Key<Provider<String>>() {};
425     Binding<Provider<String>> providerBinding = injector.getBinding(providerOfStringKey);
426     assertEquals(providerOfStringKey, providerBinding.getKey());
427     checkBindingSource(providerBinding);
428     assertTrue("binding: " + providerBinding, providerBinding instanceof ProviderBinding);
429     providerBinding.acceptTargetVisitor(
430         new FailingTargetVisitor<Provider<String>>() {
431           @Override
432           public Void visit(ProviderBinding<? extends Provider<String>> binding) {
433             assertEquals(Key.get(String.class), binding.getProvidedKey());
434             return null;
435           }
436         });
437 
438     // Check for String binding -- that one is ProviderInstanceBinding, and gets hooked
439     Binding<String> binding = injector.getBinding(String.class);
440     assertEquals(Key.get(String.class), binding.getKey());
441     checkBindingSource(binding);
442     assertTrue(binding instanceof ProviderInstanceBinding);
443     assertEquals("visited", binding.acceptTargetVisitor(new FailingSpiTargetVisitor<String>()));
444   }
445 
446   private static class FailingSpiTargetVisitor<T> extends DefaultBindingTargetVisitor<T, String> {
447     @Override
visitOther(Binding<? extends T> binding)448     protected String visitOther(Binding<? extends T> binding) {
449       throw new AssertionFailedError();
450     }
451   }
452 
checkBindingSource(Binding binding)453   public void checkBindingSource(Binding binding) {
454     assertContains(binding.getSource().toString(), getDeclaringSourcePart(getClass()));
455     ElementSource source = (ElementSource) binding.getSource();
456     assertFalse(source.getModuleClassNames().isEmpty());
457     if (isIncludeStackTraceComplete()) {
458       assertTrue(source.getStackTrace().length > 0);
459     } else {
460       assertEquals(0, source.getStackTrace().length);
461     }
462   }
463 
checkInjector(Module module, ElementVisitor<?>... visitors)464   public void checkInjector(Module module, ElementVisitor<?>... visitors) {
465     Injector injector = Guice.createInjector(module);
466 
467     List<Binding<?>> bindings = Lists.newArrayList(injector.getBindings().values());
468     for (Iterator<Binding<?>> i = bindings.iterator(); i.hasNext(); ) {
469       if (BUILT_IN_BINDINGS.contains(i.next().getKey())) {
470         i.remove();
471       }
472     }
473 
474     Collections.sort(bindings, orderByKey);
475 
476     assertEquals(bindings.size(), visitors.length);
477 
478     for (int i = 0; i < visitors.length; i++) {
479       ElementVisitor<?> visitor = visitors[i];
480       Binding<?> binding = bindings.get(i);
481       binding.acceptVisitor(visitor);
482     }
483   }
484 
485   private final ImmutableSet<Key<?>> BUILT_IN_BINDINGS =
486       ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(Logger.class));
487 
488   private final Comparator<Binding<?>> orderByKey =
489       new Comparator<Binding<?>>() {
490         @Override
491         public int compare(Binding<?> a, Binding<?> b) {
492           return a.getKey().toString().compareTo(b.getKey().toString());
493         }
494       };
495 
496   private static class StringProvider implements Provider<String> {
497     @Override
get()498     public String get() {
499       return "A";
500     }
501   }
502 
503   private static class C {}
504 
505   private static class D extends C {
506     @Inject
D(Injector unused)507     public D(Injector unused) {}
508   }
509 }
510