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 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