/* * Copyright (C) 2015 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.inject.spi; import static com.google.inject.Asserts.assertContains; import static com.google.inject.name.Names.named; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.inject.AbstractModule; import com.google.inject.Binder; import com.google.inject.Binding; import com.google.inject.CreationException; import com.google.inject.Exposed; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.PrivateModule; import com.google.inject.internal.util.StackTraceElements; import com.google.inject.name.Named; import com.google.inject.name.Names; import java.lang.annotation.Annotation; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.Set; import junit.framework.TestCase; /** Tests for {@link ModuleAnnotatedMethodScanner} usage. */ public class ModuleAnnotatedMethodScannerTest extends TestCase { public void testScanning() throws Exception { Module module = new AbstractModule() { @Override protected void configure() {} @TestProvides @Named("foo") String foo() { return "foo"; } @TestProvides @Named("foo2") String foo2() { return "foo2"; } }; Injector injector = Guice.createInjector(module, NamedMunger.module()); // assert no bindings named "foo" or "foo2" exist -- they were munged. assertMungedBinding(injector, String.class, "foo", "foo"); assertMungedBinding(injector, String.class, "foo2", "foo2"); Binding<String> fooBinding = injector.getBinding(Key.get(String.class, named("foo-munged"))); Binding<String> foo2Binding = injector.getBinding(Key.get(String.class, named("foo2-munged"))); // Validate the provider has a sane toString assertEquals( methodName(TestProvides.class, "foo", module), fooBinding.getProvider().toString()); assertEquals( methodName(TestProvides.class, "foo2", module), foo2Binding.getProvider().toString()); } public void testSkipSources() throws Exception { Module module = new AbstractModule() { @Override protected void configure() { binder() .skipSources(getClass()) .install( new AbstractModule() { @Override protected void configure() {} @TestProvides @Named("foo") String foo() { return "foo"; } }); } }; Injector injector = Guice.createInjector(module, NamedMunger.module()); assertMungedBinding(injector, String.class, "foo", "foo"); } public void testWithSource() throws Exception { Module module = new AbstractModule() { @Override protected void configure() { binder() .withSource("source") .install( new AbstractModule() { @Override protected void configure() {} @TestProvides @Named("foo") String foo() { return "foo"; } }); } }; Injector injector = Guice.createInjector(module, NamedMunger.module()); assertMungedBinding(injector, String.class, "foo", "foo"); } public void testMoreThanOneClaimedAnnotationFails() throws Exception { Module module = new AbstractModule() { @Override protected void configure() {} @TestProvides @TestProvides2 String foo() { return "foo"; } }; try { Guice.createInjector(module, NamedMunger.module()); fail(); } catch (CreationException expected) { assertEquals(1, expected.getErrorMessages().size()); assertContains( expected.getMessage(), "More than one annotation claimed by NamedMunger on method " + module.getClass().getName() + ".foo(). Methods can only have " + "one annotation claimed per scanner."); } } private String methodName(Class<? extends Annotation> annotation, String method, Object container) throws Exception { return "@" + annotation.getName() + " " + StackTraceElements.forMember(container.getClass().getDeclaredMethod(method)); } @Documented @Target(METHOD) @Retention(RUNTIME) private @interface TestProvides {} @Documented @Target(METHOD) @Retention(RUNTIME) private @interface TestProvides2 {} private static class NamedMunger extends ModuleAnnotatedMethodScanner { static Module module() { return new AbstractModule() { @Override protected void configure() { binder().scanModulesForAnnotatedMethods(new NamedMunger()); } }; } @Override public String toString() { return "NamedMunger"; } @Override public Set<? extends Class<? extends Annotation>> annotationClasses() { return ImmutableSet.of(TestProvides.class, TestProvides2.class); } @Override public <T> Key<T> prepareMethod( Binder binder, Annotation annotation, Key<T> key, InjectionPoint injectionPoint) { return Key.get( key.getTypeLiteral(), Names.named(((Named) key.getAnnotation()).value() + "-munged")); } } private void assertMungedBinding( Injector injector, Class<?> clazz, String originalName, Object expectedValue) { assertNull(injector.getExistingBinding(Key.get(clazz, named(originalName)))); Binding<?> fooBinding = injector.getBinding(Key.get(clazz, named(originalName + "-munged"))); assertEquals(expectedValue, fooBinding.getProvider().get()); } public void testFailingScanner() { try { Guice.createInjector(new SomeModule(), FailingScanner.module()); fail(); } catch (CreationException expected) { Message m = Iterables.getOnlyElement(expected.getErrorMessages()); assertEquals( "An exception was caught and reported. Message: Failing in the scanner.", m.getMessage()); assertEquals(IllegalStateException.class, m.getCause().getClass()); ElementSource source = (ElementSource) Iterables.getOnlyElement(m.getSources()); assertEquals( SomeModule.class.getName(), Iterables.getOnlyElement(source.getModuleClassNames())); assertEquals( String.class.getName() + " " + SomeModule.class.getName() + ".aString()", source.toString()); } } public static class FailingScanner extends ModuleAnnotatedMethodScanner { static Module module() { return new AbstractModule() { @Override protected void configure() { binder().scanModulesForAnnotatedMethods(new FailingScanner()); } }; } @Override public Set<? extends Class<? extends Annotation>> annotationClasses() { return ImmutableSet.of(TestProvides.class); } @Override public <T> Key<T> prepareMethod( Binder binder, Annotation rawAnnotation, Key<T> key, InjectionPoint injectionPoint) { throw new IllegalStateException("Failing in the scanner."); } } static class SomeModule extends AbstractModule { @TestProvides String aString() { return "Foo"; } @Override protected void configure() {} } public void testChildInjectorInheritsScanner() { Injector parent = Guice.createInjector(NamedMunger.module()); Injector child = parent.createChildInjector( new AbstractModule() { @Override protected void configure() {} @TestProvides @Named("foo") String foo() { return "foo"; } }); assertMungedBinding(child, String.class, "foo", "foo"); } public void testChildInjectorScannersDontImpactSiblings() { Module module = new AbstractModule() { @Override protected void configure() {} @TestProvides @Named("foo") String foo() { return "foo"; } }; Injector parent = Guice.createInjector(); Injector child = parent.createChildInjector(NamedMunger.module(), module); assertMungedBinding(child, String.class, "foo", "foo"); // no foo nor foo-munged in sibling, since scanner never saw it. Injector sibling = parent.createChildInjector(module); assertNull(sibling.getExistingBinding(Key.get(String.class, named("foo")))); assertNull(sibling.getExistingBinding(Key.get(String.class, named("foo-munged")))); } public void testPrivateModuleInheritScanner_usingPrivateModule() { Injector injector = Guice.createInjector( NamedMunger.module(), new PrivateModule() { @Override protected void configure() {} @Exposed @TestProvides @Named("foo") String foo() { return "foo"; } }); assertMungedBinding(injector, String.class, "foo", "foo"); } public void testPrivateModule_skipSourcesWithinPrivateModule() { Injector injector = Guice.createInjector( NamedMunger.module(), new PrivateModule() { @Override protected void configure() { binder() .skipSources(getClass()) .install( new AbstractModule() { @Override protected void configure() {} @Exposed @TestProvides @Named("foo") String foo() { return "foo"; } }); } }); assertMungedBinding(injector, String.class, "foo", "foo"); } public void testPrivateModule_skipSourcesForPrivateModule() { Injector injector = Guice.createInjector( NamedMunger.module(), new AbstractModule() { @Override protected void configure() { binder() .skipSources(getClass()) .install( new PrivateModule() { @Override protected void configure() {} @Exposed @TestProvides @Named("foo") String foo() { return "foo"; } }); } }); assertMungedBinding(injector, String.class, "foo", "foo"); } public void testPrivateModuleInheritScanner_usingPrivateBinder() { Injector injector = Guice.createInjector( NamedMunger.module(), new AbstractModule() { @Override protected void configure() { binder() .newPrivateBinder() .install( new AbstractModule() { @Override protected void configure() {} @Exposed @TestProvides @Named("foo") String foo() { return "foo"; } }); } }); assertMungedBinding(injector, String.class, "foo", "foo"); } public void testPrivateModuleInheritScanner_skipSourcesFromPrivateBinder() { Injector injector = Guice.createInjector( NamedMunger.module(), new AbstractModule() { @Override protected void configure() { binder() .newPrivateBinder() .skipSources(getClass()) .install( new AbstractModule() { @Override protected void configure() {} @Exposed @TestProvides @Named("foo") String foo() { return "foo"; } }); } }); assertMungedBinding(injector, String.class, "foo", "foo"); } public void testPrivateModuleInheritScanner_skipSourcesFromPrivateBinder2() { Injector injector = Guice.createInjector( NamedMunger.module(), new AbstractModule() { @Override protected void configure() { binder() .skipSources(getClass()) .newPrivateBinder() .install( new AbstractModule() { @Override protected void configure() {} @Exposed @TestProvides @Named("foo") String foo() { return "foo"; } }); } }); assertMungedBinding(injector, String.class, "foo", "foo"); } public void testPrivateModuleScannersDontImpactSiblings_usingPrivateModule() { Injector injector = Guice.createInjector( new PrivateModule() { @Override protected void configure() { install(NamedMunger.module()); } @Exposed @TestProvides @Named("foo") String foo() { return "foo"; } }, new PrivateModule() { @Override protected void configure() {} // ignored! (because the scanner doesn't run over this module) @Exposed @TestProvides @Named("foo") String foo() { return "foo"; } }); assertMungedBinding(injector, String.class, "foo", "foo"); } public void testPrivateModuleScannersDontImpactSiblings_usingPrivateBinder() { Injector injector = Guice.createInjector( new AbstractModule() { @Override protected void configure() { binder() .newPrivateBinder() .install( new AbstractModule() { @Override protected void configure() { install(NamedMunger.module()); } @Exposed @TestProvides @Named("foo") String foo() { return "foo"; } }); } }, new AbstractModule() { @Override protected void configure() { binder() .newPrivateBinder() .install( new AbstractModule() { @Override protected void configure() {} // ignored! (because the scanner doesn't run over this module) @Exposed @TestProvides @Named("foo") String foo() { return "foo"; } }); } }); assertMungedBinding(injector, String.class, "foo", "foo"); } public void testPrivateModuleWithinPrivateModule() { Injector injector = Guice.createInjector( NamedMunger.module(), new PrivateModule() { @Override protected void configure() { expose(Key.get(String.class, named("foo-munged"))); install( new PrivateModule() { @Override protected void configure() {} @Exposed @TestProvides @Named("foo") String foo() { return "foo"; } }); } }); assertMungedBinding(injector, String.class, "foo", "foo"); } }