1 /**
2  * Copyright (C) 2015 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.name.Names.named;
21 import static java.lang.annotation.ElementType.METHOD;
22 import static java.lang.annotation.RetentionPolicy.RUNTIME;
23 
24 import com.google.common.collect.ImmutableSet;
25 import com.google.common.collect.Iterables;
26 import com.google.inject.AbstractModule;
27 import com.google.inject.Binder;
28 import com.google.inject.Binding;
29 import com.google.inject.CreationException;
30 import com.google.inject.Exposed;
31 import com.google.inject.Guice;
32 import com.google.inject.Injector;
33 import com.google.inject.Key;
34 import com.google.inject.Module;
35 import com.google.inject.PrivateModule;
36 import com.google.inject.internal.util.StackTraceElements;
37 import com.google.inject.name.Named;
38 import com.google.inject.name.Names;
39 
40 import junit.framework.TestCase;
41 
42 import java.lang.annotation.Annotation;
43 import java.lang.annotation.Documented;
44 import java.lang.annotation.Retention;
45 import java.lang.annotation.Target;
46 import java.util.Set;
47 
48 /** Tests for {@link ModuleAnnotatedMethodScanner} usage. */
49 public class ModuleAnnotatedMethodScannerTest extends TestCase {
50 
testScanning()51   public void testScanning() throws Exception {
52     Module module = new AbstractModule() {
53       @Override protected void configure() {}
54 
55       @TestProvides @Named("foo") String foo() {
56         return "foo";
57       }
58 
59       @TestProvides @Named("foo2") String foo2() {
60         return "foo2";
61       }
62     };
63     Injector injector = Guice.createInjector(module, NamedMunger.module());
64 
65     // assert no bindings named "foo" or "foo2" exist -- they were munged.
66     assertMungedBinding(injector, String.class, "foo", "foo");
67     assertMungedBinding(injector, String.class, "foo2", "foo2");
68 
69     Binding<String> fooBinding = injector.getBinding(Key.get(String.class, named("foo-munged")));
70     Binding<String> foo2Binding = injector.getBinding(Key.get(String.class, named("foo2-munged")));
71     // Validate the provider has a sane toString
72     assertEquals(methodName(TestProvides.class, "foo", module),
73         fooBinding.getProvider().toString());
74     assertEquals(methodName(TestProvides.class, "foo2", module),
75         foo2Binding.getProvider().toString());
76   }
77 
testSkipSources()78   public void testSkipSources() throws Exception {
79     Module module = new AbstractModule() {
80       @Override protected void configure() {
81         binder().skipSources(getClass()).install(new AbstractModule() {
82           @Override protected void configure() {}
83 
84           @TestProvides @Named("foo") String foo() { return "foo"; }
85         });
86       }
87     };
88     Injector injector = Guice.createInjector(module, NamedMunger.module());
89     assertMungedBinding(injector, String.class, "foo", "foo");
90   }
91 
testWithSource()92   public void testWithSource() throws Exception {
93     Module module = new AbstractModule() {
94       @Override protected void configure() {
95         binder().withSource("source").install(new AbstractModule() {
96           @Override protected void configure() {}
97 
98           @TestProvides @Named("foo") String foo() { return "foo"; }
99         });
100       }
101     };
102     Injector injector = Guice.createInjector(module, NamedMunger.module());
103     assertMungedBinding(injector, String.class, "foo", "foo");
104   }
105 
testMoreThanOneClaimedAnnotationFails()106   public void testMoreThanOneClaimedAnnotationFails() throws Exception {
107     Module module = new AbstractModule() {
108       @Override protected void configure() {}
109 
110       @TestProvides @TestProvides2 String foo() {
111         return "foo";
112       }
113     };
114     try {
115       Guice.createInjector(module, NamedMunger.module());
116       fail();
117     } catch(CreationException expected) {
118       assertEquals(1, expected.getErrorMessages().size());
119       assertContains(expected.getMessage(),
120           "More than one annotation claimed by NamedMunger on method "
121               + module.getClass().getName() + ".foo(). Methods can only have "
122               + "one annotation claimed per scanner.");
123     }
124   }
125 
methodName(Class<? extends Annotation> annotation, String method, Object container)126   private String methodName(Class<? extends Annotation> annotation, String method, Object container)
127       throws Exception {
128     return "@" + annotation.getName() + " "
129         + StackTraceElements.forMember(container.getClass().getDeclaredMethod(method));
130   }
131 
132   @Documented @Target(METHOD) @Retention(RUNTIME)
133   private @interface TestProvides {}
134 
135   @Documented @Target(METHOD) @Retention(RUNTIME)
136   private @interface TestProvides2 {}
137 
138   private static class NamedMunger extends ModuleAnnotatedMethodScanner {
module()139     static Module module() {
140       return new AbstractModule() {
141         @Override protected void configure() {
142           binder().scanModulesForAnnotatedMethods(new NamedMunger());
143         }
144       };
145     }
146 
147     @Override
toString()148     public String toString() {
149       return "NamedMunger";
150     }
151 
152     @Override
annotationClasses()153     public Set<? extends Class<? extends Annotation>> annotationClasses() {
154       return ImmutableSet.of(TestProvides.class, TestProvides2.class);
155     }
156 
157     @Override
prepareMethod(Binder binder, Annotation annotation, Key<T> key, InjectionPoint injectionPoint)158     public <T> Key<T> prepareMethod(Binder binder, Annotation annotation, Key<T> key,
159         InjectionPoint injectionPoint) {
160       return Key.get(key.getTypeLiteral(),
161           Names.named(((Named) key.getAnnotation()).value() + "-munged"));
162     }
163   }
164 
165   private void assertMungedBinding(Injector injector, Class<?> clazz, String originalName,
166       Object expectedValue) {
167     assertNull(injector.getExistingBinding(Key.get(clazz, named(originalName))));
168     Binding<?> fooBinding = injector.getBinding(Key.get(clazz, named(originalName + "-munged")));
169     assertEquals(expectedValue, fooBinding.getProvider().get());
170   }
171 
172   public void testFailingScanner() {
173     try {
174       Guice.createInjector(new SomeModule(), FailingScanner.module());
175       fail();
176     } catch (CreationException expected) {
177       Message m = Iterables.getOnlyElement(expected.getErrorMessages());
178       assertEquals(
179           "An exception was caught and reported. Message: Failing in the scanner.",
180           m.getMessage());
181       assertEquals(IllegalStateException.class, m.getCause().getClass());
182       ElementSource source = (ElementSource) Iterables.getOnlyElement(m.getSources());
183       assertEquals(SomeModule.class.getName(),
184           Iterables.getOnlyElement(source.getModuleClassNames()));
185       assertEquals(String.class.getName() + " " + SomeModule.class.getName() + ".aString()",
186           source.toString());
187     }
188   }
189 
190   public static class FailingScanner extends ModuleAnnotatedMethodScanner {
191     static Module module() {
192       return new AbstractModule() {
193         @Override protected void configure() {
194           binder().scanModulesForAnnotatedMethods(new FailingScanner());
195         }
196       };
197     }
198 
199     @Override public Set<? extends Class<? extends Annotation>> annotationClasses() {
200       return ImmutableSet.of(TestProvides.class);
201     }
202 
203     @Override public <T> Key<T> prepareMethod(
204         Binder binder, Annotation rawAnnotation, Key<T> key, InjectionPoint injectionPoint) {
205       throw new IllegalStateException("Failing in the scanner.");
206     }
207   }
208 
209   static class SomeModule extends AbstractModule {
210     @TestProvides String aString() {
211       return "Foo";
212     }
213 
214     @Override protected void configure() {}
215   }
216 
217   public void testChildInjectorInheritsScanner() {
218     Injector parent = Guice.createInjector(NamedMunger.module());
219     Injector child = parent.createChildInjector(new AbstractModule() {
220       @Override protected void configure() {}
221 
222       @TestProvides @Named("foo") String foo() {
223         return "foo";
224       }
225     });
226     assertMungedBinding(child, String.class, "foo", "foo");
227   }
228 
229   public void testChildInjectorScannersDontImpactSiblings() {
230     Module module = new AbstractModule() {
231       @Override
232       protected void configure() {}
233 
234       @TestProvides @Named("foo") String foo() {
235         return "foo";
236       }
237     };
238     Injector parent = Guice.createInjector();
239     Injector child = parent.createChildInjector(NamedMunger.module(), module);
240     assertMungedBinding(child, String.class, "foo", "foo");
241 
242     // no foo nor foo-munged in sibling, since scanner never saw it.
243     Injector sibling = parent.createChildInjector(module);
244     assertNull(sibling.getExistingBinding(Key.get(String.class, named("foo"))));
245     assertNull(sibling.getExistingBinding(Key.get(String.class, named("foo-munged"))));
246   }
247 
248   public void testPrivateModuleInheritScanner_usingPrivateModule() {
249     Injector injector = Guice.createInjector(NamedMunger.module(), new PrivateModule() {
250       @Override protected void configure() {}
251 
252       @Exposed @TestProvides @Named("foo") String foo() {
253         return "foo";
254       }
255     });
256     assertMungedBinding(injector, String.class, "foo", "foo");
257   }
258 
259   public void testPrivateModule_skipSourcesWithinPrivateModule() {
260     Injector injector = Guice.createInjector(NamedMunger.module(), new PrivateModule() {
261       @Override protected void configure() {
262         binder().skipSources(getClass()).install(new AbstractModule() {
263           @Override protected void configure() {}
264           @Exposed @TestProvides @Named("foo") String foo() {
265             return "foo";
266           }
267         });
268       }
269     });
270     assertMungedBinding(injector, String.class, "foo", "foo");
271   }
272 
273   public void testPrivateModule_skipSourcesForPrivateModule() {
274     Injector injector = Guice.createInjector(NamedMunger.module(), new AbstractModule() {
275       @Override protected void configure() {
276         binder().skipSources(getClass()).install(new PrivateModule() {
277           @Override protected void configure() {}
278 
279           @Exposed @TestProvides @Named("foo") String foo() {
280             return "foo";
281           }
282         });
283       }});
284     assertMungedBinding(injector, String.class, "foo", "foo");
285   }
286 
287   public void testPrivateModuleInheritScanner_usingPrivateBinder() {
288     Injector injector = Guice.createInjector(NamedMunger.module(), new AbstractModule() {
289       @Override protected void configure() {
290         binder().newPrivateBinder().install(new AbstractModule() {
291           @Override protected void configure() {}
292 
293           @Exposed @TestProvides @Named("foo") String foo() {
294             return "foo";
295           }
296         });
297       }
298     });
299     assertMungedBinding(injector, String.class, "foo", "foo");
300   }
301 
302   public void testPrivateModuleInheritScanner_skipSourcesFromPrivateBinder() {
303     Injector injector = Guice.createInjector(NamedMunger.module(), new AbstractModule() {
304       @Override protected void configure() {
305         binder().newPrivateBinder().skipSources(getClass()).install(new AbstractModule() {
306           @Override protected void configure() {}
307 
308           @Exposed @TestProvides @Named("foo") String foo() {
309             return "foo";
310           }
311         });
312       }
313     });
314     assertMungedBinding(injector, String.class, "foo", "foo");
315   }
316 
317   public void testPrivateModuleInheritScanner_skipSourcesFromPrivateBinder2() {
318     Injector injector = Guice.createInjector(NamedMunger.module(), new AbstractModule() {
319       @Override protected void configure() {
320         binder().skipSources(getClass()).newPrivateBinder().install(new AbstractModule() {
321           @Override protected void configure() {}
322 
323           @Exposed @TestProvides @Named("foo") String foo() {
324             return "foo";
325           }
326         });
327       }
328     });
329     assertMungedBinding(injector, String.class, "foo", "foo");
330   }
331 
332   public void testPrivateModuleScannersDontImpactSiblings_usingPrivateModule() {
333     Injector injector = Guice.createInjector(new PrivateModule() {
334       @Override protected void configure() {
335         install(NamedMunger.module());
336       }
337 
338       @Exposed @TestProvides @Named("foo") String foo() {
339         return "foo";
340       }
341     }, new PrivateModule() {
342       @Override protected void configure() {}
343 
344       // ignored! (because the scanner doesn't run over this module)
345       @Exposed @TestProvides @Named("foo") String foo() {
346         return "foo";
347       }
348     });
349     assertMungedBinding(injector, String.class, "foo", "foo");
350   }
351 
352   public void testPrivateModuleScannersDontImpactSiblings_usingPrivateBinder() {
353     Injector injector = Guice.createInjector(new AbstractModule() {
354       @Override protected void configure() {
355         binder().newPrivateBinder().install(new AbstractModule() {
356           @Override protected void configure() {
357             install(NamedMunger.module());
358           }
359 
360           @Exposed @TestProvides @Named("foo") String foo() {
361             return "foo";
362           }
363         });
364       }
365     }, new AbstractModule() {
366       @Override protected void configure() {
367         binder().newPrivateBinder().install(new AbstractModule() {
368           @Override protected void configure() {}
369 
370           // ignored! (because the scanner doesn't run over this module)
371           @Exposed @TestProvides @Named("foo") String foo() {
372             return "foo";
373           }
374         });
375       }});
376     assertMungedBinding(injector, String.class, "foo", "foo");
377   }
378 
379   public void testPrivateModuleWithinPrivateModule() {
380     Injector injector = Guice.createInjector(NamedMunger.module(), new PrivateModule() {
381       @Override protected void configure() {
382         expose(Key.get(String.class, named("foo-munged")));
383         install(new PrivateModule() {
384           @Override protected void configure() {}
385 
386           @Exposed @TestProvides @Named("foo") String foo() {
387             return "foo";
388           }
389         });
390       }
391     });
392     assertMungedBinding(injector, String.class, "foo", "foo");
393   }
394 }
395