diff options
Diffstat (limited to 'core')
17 files changed, 622 insertions, 104 deletions
diff --git a/core/src/com/google/inject/Binder.java b/core/src/com/google/inject/Binder.java index e8957593..e930c3be 100644 --- a/core/src/com/google/inject/Binder.java +++ b/core/src/com/google/inject/Binder.java @@ -22,6 +22,7 @@ import com.google.inject.binder.LinkedBindingBuilder; import com.google.inject.matcher.Matcher; import com.google.inject.spi.Dependency; import com.google.inject.spi.Message; +import com.google.inject.spi.ModuleAnnotatedMethodScanner; import com.google.inject.spi.ProvisionListener; import com.google.inject.spi.TypeConverter; import com.google.inject.spi.TypeListener; @@ -507,4 +508,15 @@ public interface Binder { * @since 4.0 */ void requireExactBindingAnnotations(); + + /** + * Adds a scanner that will look in all installed modules for annotations the scanner can parse, + * and binds them like {@literal @}Provides methods. Scanners apply to all modules installed in + * the injector. Scanners installed in child injectors or private modules do not impact modules in + * siblings or parents, however scanners installed in parents do apply to all child injectors and + * private modules. + * + * @since 4.0 + */ + void scanModulesForAnnotatedMethods(ModuleAnnotatedMethodScanner scanner); } diff --git a/core/src/com/google/inject/internal/InheritingState.java b/core/src/com/google/inject/internal/InheritingState.java index 7d1eb66e..db6a7a67 100644 --- a/core/src/com/google/inject/internal/InheritingState.java +++ b/core/src/com/google/inject/internal/InheritingState.java @@ -26,6 +26,7 @@ import com.google.inject.Binding; import com.google.inject.Key; import com.google.inject.Scope; import com.google.inject.TypeLiteral; +import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding; import com.google.inject.spi.ProvisionListenerBinding; import com.google.inject.spi.ScopeBinding; import com.google.inject.spi.TypeConverterBinding; @@ -55,7 +56,8 @@ final class InheritingState implements State { private final List<MethodAspect> methodAspects = Lists.newArrayList(); /*end[AOP]*/ private final List<TypeListenerBinding> typeListenerBindings = Lists.newArrayList(); - private final List<ProvisionListenerBinding> provisionListenerBindings = Lists.newArrayList(); + private final List<ProvisionListenerBinding> provisionListenerBindings = Lists.newArrayList(); + private final List<ModuleAnnotatedMethodScannerBinding> scannerBindings = Lists.newArrayList(); private final WeakKeySet blacklistedKeys; private final Object lock; private final Object singletonCreationLock; @@ -138,8 +140,8 @@ final class InheritingState implements State { public List<TypeListenerBinding> getTypeListenerBindings() { List<TypeListenerBinding> parentBindings = parent.getTypeListenerBindings(); - List<TypeListenerBinding> result - = new ArrayList<TypeListenerBinding>(parentBindings.size() + 1); + List<TypeListenerBinding> result = + Lists.newArrayListWithCapacity(parentBindings.size() + typeListenerBindings.size()); result.addAll(parentBindings); result.addAll(typeListenerBindings); return result; @@ -151,13 +153,26 @@ final class InheritingState implements State { public List<ProvisionListenerBinding> getProvisionListenerBindings() { List<ProvisionListenerBinding> parentBindings = parent.getProvisionListenerBindings(); - List<ProvisionListenerBinding> result - = new ArrayList<ProvisionListenerBinding>(parentBindings.size() + 1); + List<ProvisionListenerBinding> result = + Lists.newArrayListWithCapacity(parentBindings.size() + provisionListenerBindings.size()); result.addAll(parentBindings); result.addAll(provisionListenerBindings); return result; } + public void addScanner(ModuleAnnotatedMethodScannerBinding scanner) { + scannerBindings.add(scanner); + } + + public List<ModuleAnnotatedMethodScannerBinding> getScannerBindings() { + List<ModuleAnnotatedMethodScannerBinding> parentBindings = parent.getScannerBindings(); + List<ModuleAnnotatedMethodScannerBinding> result = + Lists.newArrayListWithCapacity(parentBindings.size() + scannerBindings.size()); + result.addAll(parentBindings); + result.addAll(scannerBindings); + return result; + } + public void blacklist(Key<?> key, State state, Object source) { parent.blacklist(key, state, source); blacklistedKeys.add(key, state, source); diff --git a/core/src/com/google/inject/internal/InjectorShell.java b/core/src/com/google/inject/internal/InjectorShell.java index 61008798..5982bb39 100644 --- a/core/src/com/google/inject/internal/InjectorShell.java +++ b/core/src/com/google/inject/internal/InjectorShell.java @@ -35,6 +35,7 @@ import com.google.inject.spi.Dependency; import com.google.inject.spi.Element; import com.google.inject.spi.Elements; import com.google.inject.spi.InjectionPoint; +import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding; import com.google.inject.spi.PrivateElements; import com.google.inject.spi.ProvisionListenerBinding; import com.google.inject.spi.TypeListenerBinding; @@ -131,6 +132,8 @@ final class InjectorShell { // bind Singleton if this is a top-level injector if (parent == null) { modules.add(0, new RootModule()); + } else { + modules.add(0, new InheritedScannersModule(parent.state)); } elements.addAll(Elements.getElements(stage, modules)); @@ -184,6 +187,9 @@ final class InjectorShell { new UntargettedBindingProcessor(errors, bindingData).process(injector, elements); stopwatch.resetAndLog("Binding creation"); + new ModuleAnnotatedMethodScannerProcessor(errors).process(injector, elements); + stopwatch.resetAndLog("Module annotated method scanners creation"); + List<InjectorShell> injectorShells = Lists.newArrayList(); injectorShells.add(new InjectorShell(this, elements, injector)); @@ -289,4 +295,18 @@ final class InjectorShell { binder.bindScope(javax.inject.Singleton.class, SINGLETON); } } + + private static class InheritedScannersModule implements Module { + private final State state; + + InheritedScannersModule(State state) { + this.state = state; + } + + public void configure(Binder binder) { + for (ModuleAnnotatedMethodScannerBinding binding : state.getScannerBindings()) { + binding.applyTo(binder); + } + } + } } diff --git a/core/src/com/google/inject/internal/InternalContext.java b/core/src/com/google/inject/internal/InternalContext.java index 71d953c5..6a6f7efc 100644 --- a/core/src/com/google/inject/internal/InternalContext.java +++ b/core/src/com/google/inject/internal/InternalContext.java @@ -43,11 +43,11 @@ final class InternalContext { /** * Keeps track of the hierarchy of types needed during injection. * - * <p>This is a pairwise combination of dependencies and sources, with dependencies on even - * indices, and sources on odd indices. This structure is to avoid the memory overhead of + * <p>This is a pairwise combination of dependencies and sources, with dependencies or keys on + * even indices, and sources on odd indices. This structure is to avoid the memory overhead of * DependencyAndSource objects, which can add to several tens of megabytes in large applications. */ - private final List<Object> state = Lists.newArrayList(); + private final DependencyStack state = new DependencyStack(); @SuppressWarnings("unchecked") public <T> ConstructionContext<T> getConstructionContext(Object key) { @@ -75,29 +75,41 @@ final class InternalContext { /** Pops the current state & sets the new dependency. */ public void popStateAndSetDependency(Dependency<?> newDependency) { - popState(); + state.pop(); this.dependency = newDependency; } /** Adds to the state without setting the dependency. */ public void pushState(Key<?> key, Object source) { - state.add(key == null ? null : Dependency.get(key)); + state.add(key); state.add(source); } /** Pops from the state without setting a dependency. */ public void popState() { - state.remove(state.size() - 1); - state.remove(state.size() - 1); + state.pop(); } /** Returns the current dependency chain (all the state). */ public List<DependencyAndSource> getDependencyChain() { ImmutableList.Builder<DependencyAndSource> builder = ImmutableList.builder(); for (int i = 0; i < state.size(); i += 2) { - builder.add(new DependencyAndSource( - (Dependency<?>) state.get(i), state.get(i + 1))); + Object evenEntry = state.get(i); + Dependency<?> dependency; + if (evenEntry instanceof Key) { + dependency = Dependency.get((Key<?>) evenEntry); + } else { + dependency = (Dependency<?>) evenEntry; + } + builder.add(new DependencyAndSource(dependency, state.get(i + 1))); } return builder.build(); } + + private static final class DependencyStack extends ArrayList<Object> { + void pop() { + int sz = size(); + removeRange(sz - 2, sz); + } + } } diff --git a/core/src/com/google/inject/internal/ModuleAnnotatedMethodScannerProcessor.java b/core/src/com/google/inject/internal/ModuleAnnotatedMethodScannerProcessor.java new file mode 100644 index 00000000..85c721d8 --- /dev/null +++ b/core/src/com/google/inject/internal/ModuleAnnotatedMethodScannerProcessor.java @@ -0,0 +1,36 @@ +/** + * 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.internal; + +import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding; + +/** + * Handles {@code Binder.scanModulesForAnnotatedMethods} commands. + * + * @author sameb@google.com (Sam Berlin) + */ +final class ModuleAnnotatedMethodScannerProcessor extends AbstractProcessor { + + ModuleAnnotatedMethodScannerProcessor(Errors errors) { + super(errors); + } + + @Override public Boolean visit(ModuleAnnotatedMethodScannerBinding command) { + injector.state.addScanner(command); + return true; + } +} diff --git a/core/src/com/google/inject/internal/ProviderMethodsModule.java b/core/src/com/google/inject/internal/ProviderMethodsModule.java index 98eb45d3..7682dab5 100644 --- a/core/src/com/google/inject/internal/ProviderMethodsModule.java +++ b/core/src/com/google/inject/internal/ProviderMethodsModule.java @@ -29,10 +29,10 @@ import com.google.inject.Module; import com.google.inject.Provider; import com.google.inject.Provides; import com.google.inject.TypeLiteral; -import com.google.inject.spi.ModuleAnnotatedMethodScanner; import com.google.inject.spi.Dependency; import com.google.inject.spi.InjectionPoint; import com.google.inject.spi.Message; +import com.google.inject.spi.ModuleAnnotatedMethodScanner; import com.google.inject.util.Modules; import java.lang.annotation.Annotation; @@ -55,8 +55,8 @@ public final class ProviderMethodsModule implements Module { private static ModuleAnnotatedMethodScanner PROVIDES_BUILDER = new ModuleAnnotatedMethodScanner() { @Override - public <T> Key<T> prepareMethod(Binder binder, Annotation annotation, Key<T> key, - InjectionPoint injectionPoint) { + public <T> Key<T> prepareMethod( + Binder binder, Annotation annotation, Key<T> key, InjectionPoint injectionPoint) { return key; } @@ -89,7 +89,7 @@ public final class ProviderMethodsModule implements Module { /** * Returns a module which creates bindings methods in the module that match the scanner. */ - public static Module forModule(Module module, ModuleAnnotatedMethodScanner scanner) { + public static Module forModule(Object module, ModuleAnnotatedMethodScanner scanner) { return forObject(module, false, scanner); } @@ -114,6 +114,10 @@ public final class ProviderMethodsModule implements Module { return new ProviderMethodsModule(object, skipFastClassGeneration, scanner); } + public Object getDelegateModule() { + return delegate; + } + @Override public synchronized void configure(Binder binder) { for (ProviderMethod<?> providerMethod : getProviderMethods(binder)) { @@ -258,7 +262,11 @@ public final class ProviderMethodsModule implements Module { @SuppressWarnings("unchecked") // Define T as the method's return type. TypeLiteral<T> returnType = (TypeLiteral<T>) typeLiteral.getReturnType(method); Key<T> key = getKey(errors, returnType, method, method.getAnnotations()); - key = scanner.prepareMethod(binder, annotation, key, point); + try { + key = scanner.prepareMethod(binder, annotation, key, point); + } catch(Throwable t) { + binder.addError(t); + } Class<? extends Annotation> scopeAnnotation = Annotations.findScopeAnnotation(errors, method.getAnnotations()); for (Message message : errors.getMessages()) { @@ -275,7 +283,8 @@ public final class ProviderMethodsModule implements Module { @Override public boolean equals(Object o) { return o instanceof ProviderMethodsModule - && ((ProviderMethodsModule) o).delegate == delegate; + && ((ProviderMethodsModule) o).delegate == delegate + && ((ProviderMethodsModule) o).scanner == scanner; } @Override public int hashCode() { diff --git a/core/src/com/google/inject/internal/State.java b/core/src/com/google/inject/internal/State.java index d0ee4fbc..8a828f2e 100644 --- a/core/src/com/google/inject/internal/State.java +++ b/core/src/com/google/inject/internal/State.java @@ -23,6 +23,8 @@ import com.google.inject.Binding; import com.google.inject.Key; import com.google.inject.Scope; import com.google.inject.TypeLiteral; +import com.google.inject.spi.ModuleAnnotatedMethodScanner; +import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding; import com.google.inject.spi.ProvisionListenerBinding; import com.google.inject.spi.ScopeBinding; import com.google.inject.spi.TypeConverterBinding; @@ -105,6 +107,14 @@ interface State { return ImmutableList.of(); } + public void addScanner(ModuleAnnotatedMethodScannerBinding scanner) { + throw new UnsupportedOperationException(); + } + + public List<ModuleAnnotatedMethodScannerBinding> getScannerBindings() { + return ImmutableList.of(); + } + public void blacklist(Key<?> key, State state, Object source) { } @@ -166,6 +176,10 @@ interface State { List<ProvisionListenerBinding> getProvisionListenerBindings(); + void addScanner(ModuleAnnotatedMethodScannerBinding scanner); + + List<ModuleAnnotatedMethodScannerBinding> getScannerBindings(); + /** * Forbids the corresponding injector from creating a binding to {@code key}. Child injectors * blacklist their bound keys on their parent injectors to prevent just-in-time bindings on the diff --git a/core/src/com/google/inject/spi/DefaultElementVisitor.java b/core/src/com/google/inject/spi/DefaultElementVisitor.java index 0780bf80..1bbea0d2 100644 --- a/core/src/com/google/inject/spi/DefaultElementVisitor.java +++ b/core/src/com/google/inject/spi/DefaultElementVisitor.java @@ -102,4 +102,8 @@ public abstract class DefaultElementVisitor<V> implements ElementVisitor<V> { public V visit(RequireExactBindingAnnotationsOption option) { return visitOther(option); } + + public V visit(ModuleAnnotatedMethodScannerBinding binding) { + return visitOther(binding); + } } diff --git a/core/src/com/google/inject/spi/ElementVisitor.java b/core/src/com/google/inject/spi/ElementVisitor.java index 5e990866..f0d9d138 100644 --- a/core/src/com/google/inject/spi/ElementVisitor.java +++ b/core/src/com/google/inject/spi/ElementVisitor.java @@ -16,6 +16,7 @@ package com.google.inject.spi; +import com.google.inject.Binder; import com.google.inject.Binding; import com.google.inject.Inject; @@ -120,4 +121,11 @@ public interface ElementVisitor<V> { * @since 4.0 */ V visit(RequireExactBindingAnnotationsOption option); + + /** + * Visits a {@link Binder#scanModulesForAnnotatedMethods} command. + * + * @since 4.0 + */ + V visit(ModuleAnnotatedMethodScannerBinding binding); } diff --git a/core/src/com/google/inject/spi/Elements.java b/core/src/com/google/inject/spi/Elements.java index 986582e2..46072e30 100644 --- a/core/src/com/google/inject/spi/Elements.java +++ b/core/src/com/google/inject/spi/Elements.java @@ -17,13 +17,12 @@ package com.google.inject.spi; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.inject.internal.InternalFlags.IncludeStackTraceOption; import static com.google.inject.internal.InternalFlags.getIncludeStackTraceOption; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.common.collect.Sets; - import com.google.inject.AbstractModule; import com.google.inject.Binder; import com.google.inject.Binding; @@ -44,6 +43,7 @@ import com.google.inject.internal.BindingBuilder; import com.google.inject.internal.ConstantBindingBuilderImpl; import com.google.inject.internal.Errors; import com.google.inject.internal.ExposureBuilder; +import com.google.inject.internal.InternalFlags.IncludeStackTraceOption; import com.google.inject.internal.PrivateElementsImpl; import com.google.inject.internal.ProviderMethodsModule; import com.google.inject.internal.util.SourceProvider; @@ -56,6 +56,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -107,11 +108,15 @@ public final class Elements { for (Module module : modules) { binder.install(module); } + binder.scanForAnnotatedMethods(); + for (RecordingBinder child : binder.privateBinders) { + child.scanForAnnotatedMethods(); + } // Free the memory consumed by the stack trace elements cache StackTraceElements.clearCache(); return Collections.unmodifiableList(binder.elements); } - + private static class ElementsAsModule implements Module { private final Iterable<? extends Element> elements; @@ -119,6 +124,7 @@ public final class Elements { this.elements = elements; } + @Override public void configure(Binder binder) { for (Element element : elements) { element.applyTo(binder); @@ -138,22 +144,37 @@ public final class Elements { return (BindingTargetVisitor<T, T>) GET_INSTANCE_VISITOR; } + private static class ModuleInfo { + private final Binder binder; + private final ModuleSource moduleSource; + + private ModuleInfo(Binder binder, ModuleSource moduleSource) { + this.binder = binder; + this.moduleSource = moduleSource; + } + } + private static class RecordingBinder implements Binder, PrivateBinder { private final Stage stage; - private final Set<Module> modules; + private final Map<Module, ModuleInfo> modules; private final List<Element> elements; private final Object source; /** The current modules stack */ private ModuleSource moduleSource = null; private final SourceProvider sourceProvider; + private final Set<ModuleAnnotatedMethodScanner> scanners; /** The binder where exposed bindings will be created */ private final RecordingBinder parent; private final PrivateElementsImpl privateElements; + /** All children private binders, so we can scan through them. */ + private final List<RecordingBinder> privateBinders; + private RecordingBinder(Stage stage) { this.stage = stage; - this.modules = Sets.newHashSet(); + this.modules = Maps.newLinkedHashMap(); + this.scanners = Sets.newLinkedHashSet(); this.elements = Lists.newArrayList(); this.source = null; this.sourceProvider = SourceProvider.DEFAULT_INSTANCE.plusSkippedClasses( @@ -161,6 +182,7 @@ public final class Elements { ConstantBindingBuilderImpl.class, AbstractBindingBuilder.class, BindingBuilder.class); this.parent = null; this.privateElements = null; + this.privateBinders = Lists.newArrayList(); } /** Creates a recording binder that's backed by {@code prototype}. */ @@ -171,26 +193,31 @@ public final class Elements { this.stage = prototype.stage; this.modules = prototype.modules; this.elements = prototype.elements; + this.scanners = prototype.scanners; this.source = source; this.moduleSource = prototype.moduleSource; this.sourceProvider = sourceProvider; this.parent = prototype.parent; this.privateElements = prototype.privateElements; + this.privateBinders = prototype.privateBinders; } /** Creates a private recording binder. */ private RecordingBinder(RecordingBinder parent, PrivateElementsImpl privateElements) { this.stage = parent.stage; - this.modules = Sets.newHashSet(); + this.modules = Maps.newLinkedHashMap(); + this.scanners = Sets.newLinkedHashSet(parent.scanners); this.elements = privateElements.getElementsMutable(); this.source = parent.source; this.moduleSource = parent.moduleSource; this.sourceProvider = parent.sourceProvider; this.parent = parent; this.privateElements = privateElements; + this.privateBinders = parent.privateBinders; } /*if[AOP]*/ + @Override public void bindInterceptor( Matcher<? super Class<?>> classMatcher, Matcher<? super Method> methodMatcher, @@ -200,19 +227,23 @@ public final class Elements { } /*end[AOP]*/ + @Override public void bindScope(Class<? extends Annotation> annotationType, Scope scope) { elements.add(new ScopeBinding(getElementSource(), annotationType, scope)); } + @Override @SuppressWarnings("unchecked") // it is safe to use the type literal for the raw type public void requestInjection(Object instance) { requestInjection((TypeLiteral<Object>) TypeLiteral.get(instance.getClass()), instance); } + @Override public <T> void requestInjection(TypeLiteral<T> type, T instance) { elements.add(new InjectionRequest<T>(getElementSource(), type, instance)); } + @Override public <T> MembersInjector<T> getMembersInjector(final TypeLiteral<T> typeLiteral) { final MembersInjectorLookup<T> element = new MembersInjectorLookup<T>(getElementSource(), typeLiteral); @@ -239,16 +270,60 @@ public final class Elements { } } + void scanForAnnotatedMethods() { + for (ModuleAnnotatedMethodScanner scanner : scanners) { + // Note: we must iterate over a copy of the modules because calling install(..) + // will mutate modules, otherwise causing a ConcurrentModificationException. + for (Map.Entry<Module, ModuleInfo> entry : Maps.newLinkedHashMap(modules).entrySet()) { + Module module = entry.getKey(); + // If this was from a child private binder, skip it... we'll process it later. + if (entry.getValue().binder != this) { + continue; + } + moduleSource = entry.getValue().moduleSource; + try { + install(ProviderMethodsModule.forModule(module, scanner)); + } catch(RuntimeException e) { + Collection<Message> messages = Errors.getMessagesFromThrowable(e); + if (!messages.isEmpty()) { + elements.addAll(messages); + } else { + addError(e); + } + } + } + } + moduleSource = null; + } + public void install(Module module) { - if (modules.add(module)) { + if (!modules.containsKey(module)) { Binder binder = this; + boolean unwrapModuleSource = false; // Update the module source for the new module - if (!(module instanceof ProviderMethodsModule)) { + if (module instanceof ProviderMethodsModule) { + // There are two reason's we'd want to get the module source in a ProviderMethodsModule. + // ModuleAnnotatedMethodScanner lets users scan their own modules for @Provides-like + // bindings. If they install the module at a top-level, then moduleSource can be null. + // Also, if they pass something other than 'this' to it, we'd have the wrong source. + Object delegate = ((ProviderMethodsModule) module).getDelegateModule(); + if (moduleSource == null + || !moduleSource.getModuleClassName().equals(delegate.getClass().getName())) { + moduleSource = getModuleSource(delegate); + unwrapModuleSource = true; + } + } else { moduleSource = getModuleSource(module); + unwrapModuleSource = true; } if (module instanceof PrivateModule) { binder = binder.newPrivateBinder(); - } + // Store the module in the private binder too. + ((RecordingBinder) binder).modules.put(module, new ModuleInfo(binder, moduleSource)); + } + // Always store this in the parent binder (even if it was a private module) + // so that we know not to process it again, and so that scanners inherit down. + modules.put(module, new ModuleInfo(binder, moduleSource)); try { module.configure(binder); } catch (RuntimeException e) { @@ -261,7 +336,7 @@ public final class Elements { } binder.install(ProviderMethodsModule.forModule(module)); // We are done with this module, so undo module source change - if (!(module instanceof ProviderMethodsModule)) { + if (unwrapModuleSource) { moduleSource = moduleSource.getParent(); } } @@ -334,37 +409,51 @@ public final class Elements { return new RecordingBinder(this, null, newSourceProvider); } + @Override public PrivateBinder newPrivateBinder() { PrivateElementsImpl privateElements = new PrivateElementsImpl(getElementSource()); RecordingBinder binder = new RecordingBinder(this, privateElements); + privateBinders.add(binder); elements.add(privateElements); return binder; } - + + @Override public void disableCircularProxies() { elements.add(new DisableCircularProxiesOption(getElementSource())); } - + + @Override public void requireExplicitBindings() { - elements.add(new RequireExplicitBindingsOption(getElementSource())); + elements.add(new RequireExplicitBindingsOption(getElementSource())); } - + + @Override public void requireAtInjectOnConstructors() { elements.add(new RequireAtInjectOnConstructorsOption(getElementSource())); } + @Override public void requireExactBindingAnnotations() { elements.add(new RequireExactBindingAnnotationsOption(getElementSource())); } + @Override + public void scanModulesForAnnotatedMethods(ModuleAnnotatedMethodScanner scanner) { + scanners.add(scanner); + elements.add(new ModuleAnnotatedMethodScannerBinding(getElementSource(), scanner)); + } + public void expose(Key<?> key) { exposeInternal(key); } + @Override public AnnotatedElementBuilder expose(Class<?> type) { return exposeInternal(Key.get(type)); } + @Override public AnnotatedElementBuilder expose(TypeLiteral<?> type) { return exposeInternal(Key.get(type)); } @@ -374,7 +463,9 @@ public final class Elements { addError("Cannot expose %s on a standard binder. " + "Exposed bindings are only applicable to private binders.", key); return new AnnotatedElementBuilder() { + @Override public void annotatedWith(Class<? extends Annotation> annotationType) {} + @Override public void annotatedWith(Annotation annotation) {} }; } @@ -384,7 +475,7 @@ public final class Elements { return builder; } - private ModuleSource getModuleSource(Module module) { + private ModuleSource getModuleSource(Object module) { StackTraceElement[] partialCallStack; if (getIncludeStackTraceOption() == IncludeStackTraceOption.COMPLETE) { partialCallStack = getPartialCallStack(new Throwable().getStackTrace()); @@ -412,7 +503,7 @@ public final class Elements { } IncludeStackTraceOption stackTraceOption = getIncludeStackTraceOption(); if (stackTraceOption == IncludeStackTraceOption.COMPLETE || - (stackTraceOption == IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE + (stackTraceOption == IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE && declaringSource == null)) { callStack = new Throwable().getStackTrace(); } @@ -436,9 +527,9 @@ public final class Elements { } /** - * Removes the {@link #moduleSource} call stack from the beginning of current call stack. It - * also removes the last two elements in order to make {@link #install(Module)} the last call - * in the call stack. + * Removes the {@link #moduleSource} call stack from the beginning of current call stack. It + * also removes the last two elements in order to make {@link #install(Module)} the last call + * in the call stack. */ private StackTraceElement[] getPartialCallStack(StackTraceElement[] callStack) { int toSkip = 0; @@ -452,7 +543,7 @@ public final class Elements { System.arraycopy(callStack, 1, partialCallStack, 0, chunkSize); return partialCallStack; } - + @Override public String toString() { return "Binder"; } diff --git a/core/src/com/google/inject/spi/ModuleAnnotatedMethodScanner.java b/core/src/com/google/inject/spi/ModuleAnnotatedMethodScanner.java index 99b6d91a..36adc852 100644 --- a/core/src/com/google/inject/spi/ModuleAnnotatedMethodScanner.java +++ b/core/src/com/google/inject/spi/ModuleAnnotatedMethodScanner.java @@ -18,8 +18,6 @@ package com.google.inject.spi; import com.google.inject.Binder; import com.google.inject.Key; -import com.google.inject.Module; -import com.google.inject.internal.ProviderMethodsModule; import java.lang.annotation.Annotation; import java.util.Set; @@ -29,14 +27,6 @@ import java.util.Set; * as providers, similar to {@code @Provides} methods. */ public abstract class ModuleAnnotatedMethodScanner { - - /** - * Scans the module for methods and returns a module that will bind the methods - * that match this scanner. - */ - public final Module forModule(Module module) { - return ProviderMethodsModule.forModule(module, this); - } /** * Returns the annotations this should scan for. Every method in the module that has one of these diff --git a/core/src/com/google/inject/spi/ModuleAnnotatedMethodScannerBinding.java b/core/src/com/google/inject/spi/ModuleAnnotatedMethodScannerBinding.java new file mode 100644 index 00000000..d6324206 --- /dev/null +++ b/core/src/com/google/inject/spi/ModuleAnnotatedMethodScannerBinding.java @@ -0,0 +1,59 @@ +/** + * 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.common.base.Preconditions.checkNotNull; + +import com.google.inject.Binder; +import com.google.inject.internal.Errors; + +/** + * Represents a call to {@link Binder#scanModulesForAnnotatedMethods} in a module. + * + * @author sameb@google.com (Sam Berlin) + * @since 4.0 + */ +public final class ModuleAnnotatedMethodScannerBinding implements Element { + private final Object source; + private final ModuleAnnotatedMethodScanner scanner; + + public ModuleAnnotatedMethodScannerBinding(Object source, ModuleAnnotatedMethodScanner scanner) { + this.source = checkNotNull(source, "source"); + this.scanner = checkNotNull(scanner, "scanner"); + } + + public Object getSource() { + return source; + } + + public ModuleAnnotatedMethodScanner getScanner() { + return scanner; + } + + public <T> T acceptVisitor(ElementVisitor<T> visitor) { + return visitor.visit(this); + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).scanModulesForAnnotatedMethods(scanner); + } + + @Override public String toString() { + return scanner + " which scans for " + scanner.annotationClasses() + + " (bound at " + Errors.convert(source) + ")"; + } +} diff --git a/core/src/com/google/inject/spi/ModuleSource.java b/core/src/com/google/inject/spi/ModuleSource.java index 19add7ee..1e07de2b 100644 --- a/core/src/com/google/inject/spi/ModuleSource.java +++ b/core/src/com/google/inject/spi/ModuleSource.java @@ -27,7 +27,7 @@ import java.util.List; /** * Associated to a {@link Module module}, provides the module class name, the parent module {@link * ModuleSource source}, and the call stack that ends just before the module {@link - * Module#configure(Binder) configure(Binder)} method invocation. + * Module#configure(Binder) configure(Binder)} method invocation. */ final class ModuleSource { @@ -35,16 +35,16 @@ final class ModuleSource { * The class name of module that this {@link ModuleSource} associated to. */ private final String moduleClassName; - + /** * The parent {@link ModuleSource module source}. */ private final ModuleSource parent; - - /** - * The chunk of call stack that starts from the parent module {@link Module#configure(Binder) - * configure(Binder)} call and ends just before the module {@link Module#configure(Binder) - * configure(Binder)} method invocation. For a module without a parent module the chunk starts + + /** + * The chunk of call stack that starts from the parent module {@link Module#configure(Binder) + * configure(Binder)} call and ends just before the module {@link Module#configure(Binder) + * configure(Binder)} method invocation. For a module without a parent module the chunk starts * from the bottom of call stack. The array is non-empty if stack trace collection is on. */ private final InMemoryStackTraceElement[] partialCallStack; @@ -52,32 +52,32 @@ final class ModuleSource { /** * Creates a new {@link ModuleSource} with a {@literal null} parent. * @param module the corresponding module - * @param partialCallStack the chunk of call stack that starts from the parent module {@link - * Module#configure(Binder) configure(Binder)} call and ends just before the module {@link + * @param partialCallStack the chunk of call stack that starts from the parent module {@link + * Module#configure(Binder) configure(Binder)} call and ends just before the module {@link * Module#configure(Binder) configure(Binder)} method invocation */ - ModuleSource(Module module, StackTraceElement[] partialCallStack) { + ModuleSource(Object module, StackTraceElement[] partialCallStack) { this(null, module, partialCallStack); - } - + } + /** * Creates a new {@link ModuleSource} Object. - * @param parent the parent module {@link ModuleSource source} + * @param parent the parent module {@link ModuleSource source} * @param module the corresponding module - * @param partialCallStack the chunk of call stack that starts from the parent module {@link - * Module#configure(Binder) configure(Binder)} call and ends just before the module {@link + * @param partialCallStack the chunk of call stack that starts from the parent module {@link + * Module#configure(Binder) configure(Binder)} call and ends just before the module {@link * Module#configure(Binder) configure(Binder)} method invocation */ private ModuleSource( - /* @Nullable */ ModuleSource parent, Module module, StackTraceElement[] partialCallStack) { + /* @Nullable */ ModuleSource parent, Object module, StackTraceElement[] partialCallStack) { Preconditions.checkNotNull(module, "module cannot be null."); Preconditions.checkNotNull(partialCallStack, "partialCallStack cannot be null."); this.parent = parent; this.moduleClassName = module.getClass().getName(); this.partialCallStack = StackTraceElements.convertToInMemoryStackTraceElement(partialCallStack); } - - /** + + /** * Returns the corresponding module class name. * * @see Class#getName() @@ -95,27 +95,27 @@ final class ModuleSource { StackTraceElement[] getPartialCallStack() { return StackTraceElements.convertToStackTraceElement(partialCallStack); } - + /** * Returns the size of partial call stack if stack trace collection is on otherwise zero. */ int getPartialCallStackSize() { return partialCallStack.length; } - - /** + + /** * Creates and returns a child {@link ModuleSource} corresponding to the {@link Module module}. * @param module the corresponding module - * @param partialCallStack the chunk of call stack that starts from the parent module {@link - * Module#configure(Binder) configure(Binder)} call and ends just before the module {@link + * @param partialCallStack the chunk of call stack that starts from the parent module {@link + * Module#configure(Binder) configure(Binder)} call and ends just before the module {@link * Module#configure(Binder) configure(Binder)} method invocation */ - ModuleSource createChild(Module module, StackTraceElement[] partialCallStack) { + ModuleSource createChild(Object module, StackTraceElement[] partialCallStack) { return new ModuleSource(this, module, partialCallStack); } - /** - * Returns the parent module {@link ModuleSource source}. + /** + * Returns the parent module {@link ModuleSource source}. */ ModuleSource getParent() { return parent; @@ -123,7 +123,7 @@ final class ModuleSource { /** * Returns the class names of modules in this module source. The first element (index 0) is filled - * by this object {@link #getModuleClassName()}. The second element is filled by the parent's + * by this object {@link #getModuleClassName()}. The second element is filled by the parent's * {@link #getModuleClassName()} and so on. */ List<String> getModuleClassNames() { @@ -138,7 +138,7 @@ final class ModuleSource { } /** - * Returns the size of {@link ModuleSource ModuleSources} chain (all parents) that ends at this + * Returns the size of {@link ModuleSource ModuleSources} chain (all parents) that ends at this * object. */ int size() { @@ -147,7 +147,7 @@ final class ModuleSource { } return parent.size() + 1; } - + /** * Returns the size of call stack that ends just before the module {@link Module#configure(Binder) * configure(Binder)} method invocation (see {@link #getStackTrace()}). @@ -170,7 +170,7 @@ final class ModuleSource { int cursor = 0; ModuleSource current = this; while (current != null) { - StackTraceElement[] chunk = + StackTraceElement[] chunk = StackTraceElements.convertToStackTraceElement(current.partialCallStack); int chunkSize = chunk.length; System.arraycopy(chunk, 0, callStack, cursor, chunkSize); diff --git a/core/src/com/google/inject/util/Modules.java b/core/src/com/google/inject/util/Modules.java index c166b8e2..08ec92c7 100644 --- a/core/src/com/google/inject/util/Modules.java +++ b/core/src/com/google/inject/util/Modules.java @@ -16,6 +16,7 @@ package com.google.inject.util; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -33,7 +34,9 @@ import com.google.inject.internal.Errors; import com.google.inject.spi.DefaultBindingScopingVisitor; import com.google.inject.spi.DefaultElementVisitor; import com.google.inject.spi.Element; +import com.google.inject.spi.ElementVisitor; import com.google.inject.spi.Elements; +import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding; import com.google.inject.spi.PrivateElements; import com.google.inject.spi.ScopeBinding; @@ -191,7 +194,9 @@ public final class Modules { final Binder binder = baseBinder.skipSources(this.getClass()); final LinkedHashSet<Element> elements = new LinkedHashSet<Element>(baseElements); - final List<Element> overrideElements = Elements.getElements(currentStage(), overrides); + final Module scannersModule = extractScanners(elements); + final List<Element> overrideElements = Elements.getElements(currentStage(), + ImmutableList.<Module>builder().addAll(overrides).add(scannersModule).build()); final Set<Key<?>> overriddenKeys = Sets.newHashSet(); final Map<Class<? extends Annotation>, ScopeBinding> overridesScopeAnnotations = @@ -331,4 +336,24 @@ public final class Modules { } } } + + private static Module extractScanners(Iterable<Element> elements) { + final List<ModuleAnnotatedMethodScannerBinding> scanners = Lists.newArrayList(); + ElementVisitor<Void> visitor = new DefaultElementVisitor<Void>() { + @Override public Void visit(ModuleAnnotatedMethodScannerBinding binding) { + scanners.add(binding); + return null; + } + }; + for (Element element : elements) { + element.acceptVisitor(visitor); + } + return new AbstractModule() { + @Override protected void configure() { + for (ModuleAnnotatedMethodScannerBinding scanner : scanners) { + scanner.applyTo(binder()); + } + } + }; + } } diff --git a/core/test/com/google/inject/internal/WeakKeySetTest.java b/core/test/com/google/inject/internal/WeakKeySetTest.java index b003fb5f..3797d889 100644 --- a/core/test/com/google/inject/internal/WeakKeySetTest.java +++ b/core/test/com/google/inject/internal/WeakKeySetTest.java @@ -41,6 +41,7 @@ import com.google.inject.internal.MethodAspect; /*end[AOP]*/ import com.google.inject.internal.State; import com.google.inject.internal.WeakKeySet; +import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding; import com.google.inject.spi.ProvisionListenerBinding; import com.google.inject.spi.ScopeBinding; import com.google.inject.spi.TypeConverterBinding; @@ -500,6 +501,14 @@ public class WeakKeySetTest extends TestCase { return ImmutableList.of(); } + public void addScanner(ModuleAnnotatedMethodScannerBinding scanner) { + throw new UnsupportedOperationException(); + } + + public List<ModuleAnnotatedMethodScannerBinding> getScannerBindings() { + return ImmutableList.of(); + } + public void blacklist(Key<?> key, State state, Object source) { } diff --git a/core/test/com/google/inject/spi/ModuleAnnotatedMethodScannerTest.java b/core/test/com/google/inject/spi/ModuleAnnotatedMethodScannerTest.java index 62f82203..e73a9afc 100644 --- a/core/test/com/google/inject/spi/ModuleAnnotatedMethodScannerTest.java +++ b/core/test/com/google/inject/spi/ModuleAnnotatedMethodScannerTest.java @@ -22,14 +22,17 @@ 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; @@ -44,32 +47,27 @@ import java.util.Set; /** Tests for {@link ModuleAnnotatedMethodScanner} usage. */ public class ModuleAnnotatedMethodScannerTest extends TestCase { - + public void testScanning() throws Exception { Module module = new AbstractModule() { - @Override protected void configure() { - install(new NamedMunger().forModule(this)); - } - + @Override protected void configure() {} + @TestProvides @Named("foo") String foo() { return "foo"; } - + @TestProvides @Named("foo2") String foo2() { return "foo2"; } }; - Injector injector = Guice.createInjector(module); + Injector injector = Guice.createInjector(module, NamedMunger.module()); // assert no bindings named "foo" or "foo2" exist -- they were munged. - assertNull(injector.getExistingBinding(Key.get(String.class, named("foo")))); - assertNull(injector.getExistingBinding(Key.get(String.class, named("foo2")))); + 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"))); - assertEquals("foo", fooBinding.getProvider().get()); - assertEquals("foo2", foo2Binding.getProvider().get()); - // Validate the provider has a sane toString assertEquals(methodName(TestProvides.class, "foo", module), fooBinding.getProvider().toString()); @@ -78,41 +76,51 @@ public class ModuleAnnotatedMethodScannerTest extends TestCase { } public void testMoreThanOneClaimedAnnotationFails() throws Exception { - final NamedMunger scanner = new NamedMunger(); Module module = new AbstractModule() { - @Override protected void configure() { - install(scanner.forModule(this)); - } - + @Override protected void configure() {} + @TestProvides @TestProvides2 String foo() { return "foo"; } }; try { - Guice.createInjector(module); + Guice.createInjector(module, NamedMunger.module()); fail(); } catch(CreationException expected) { assertEquals(1, expected.getErrorMessages().size()); assertContains(expected.getMessage(), - "More than one annotation claimed by " + scanner + " on method " + "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); @@ -125,4 +133,160 @@ public class ModuleAnnotatedMethodScannerTest extends TestCase { 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 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 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"); + } } diff --git a/core/test/com/google/inject/util/OverrideModuleTest.java b/core/test/com/google/inject/util/OverrideModuleTest.java index 3b8e05bc..16d7a2ce 100644 --- a/core/test/com/google/inject/util/OverrideModuleTest.java +++ b/core/test/com/google/inject/util/OverrideModuleTest.java @@ -20,12 +20,15 @@ import static com.google.inject.Asserts.asModuleChain; import static com.google.inject.Asserts.assertContains; import static com.google.inject.Guice.createInjector; import static com.google.inject.name.Names.named; +import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; 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; @@ -39,13 +42,18 @@ import com.google.inject.Scope; import com.google.inject.ScopeAnnotation; import com.google.inject.Stage; import com.google.inject.name.Named; -import com.google.inject.util.Modules; +import com.google.inject.name.Names; +import com.google.inject.spi.InjectionPoint; +import com.google.inject.spi.ModuleAnnotatedMethodScanner; import junit.framework.TestCase; +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.Date; +import java.util.Set; import java.util.concurrent.atomic.AtomicReference; /** @@ -678,4 +686,46 @@ public class OverrideModuleTest extends TestCase { }); Guice.createInjector(stage, module); } + + public void testOverridesApplyOriginalScanners() { + Injector injector = + Guice.createInjector(Modules.override(NamedMunger.module()).with(new AbstractModule() { + @Override protected void configure() {} + @TestProvides @Named("test") String provideString() { return "foo"; } + })); + + assertNull(injector.getExistingBinding(Key.get(String.class, named("test")))); + Binding<String> binding = injector.getBinding(Key.get(String.class, named("test-munged"))); + assertEquals("foo", binding.getProvider().get()); + } + + @Documented @Target(METHOD) @Retention(RUNTIME) + private @interface TestProvides {} + + 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); + } + + @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")); + } + } } |