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
17package com.google.inject.spi;
18
19import static com.google.inject.Asserts.assertContains;
20import static com.google.inject.name.Names.named;
21import static java.lang.annotation.ElementType.METHOD;
22import static java.lang.annotation.RetentionPolicy.RUNTIME;
23
24import com.google.common.collect.ImmutableSet;
25import com.google.common.collect.Iterables;
26import com.google.inject.AbstractModule;
27import com.google.inject.Binder;
28import com.google.inject.Binding;
29import com.google.inject.CreationException;
30import com.google.inject.Exposed;
31import com.google.inject.Guice;
32import com.google.inject.Injector;
33import com.google.inject.Key;
34import com.google.inject.Module;
35import com.google.inject.PrivateModule;
36import com.google.inject.internal.util.StackTraceElements;
37import com.google.inject.name.Named;
38import com.google.inject.name.Names;
39
40import junit.framework.TestCase;
41
42import java.lang.annotation.Annotation;
43import java.lang.annotation.Documented;
44import java.lang.annotation.Retention;
45import java.lang.annotation.Target;
46import java.util.Set;
47
48/** Tests for {@link ModuleAnnotatedMethodScanner} usage. */
49public class ModuleAnnotatedMethodScannerTest extends TestCase {
50
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
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
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
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
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 {
139    static Module module() {
140      return new AbstractModule() {
141        @Override protected void configure() {
142          binder().scanModulesForAnnotatedMethods(new NamedMunger());
143        }
144      };
145    }
146
147    @Override
148    public String toString() {
149      return "NamedMunger";
150    }
151
152    @Override
153    public Set<? extends Class<? extends Annotation>> annotationClasses() {
154      return ImmutableSet.of(TestProvides.class, TestProvides2.class);
155    }
156
157    @Override
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