aboutsummaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/src/com/google/inject/Binder.java12
-rw-r--r--core/src/com/google/inject/internal/InheritingState.java25
-rw-r--r--core/src/com/google/inject/internal/InjectorShell.java20
-rw-r--r--core/src/com/google/inject/internal/InternalContext.java30
-rw-r--r--core/src/com/google/inject/internal/ModuleAnnotatedMethodScannerProcessor.java36
-rw-r--r--core/src/com/google/inject/internal/ProviderMethodsModule.java21
-rw-r--r--core/src/com/google/inject/internal/State.java14
-rw-r--r--core/src/com/google/inject/spi/DefaultElementVisitor.java4
-rw-r--r--core/src/com/google/inject/spi/ElementVisitor.java8
-rw-r--r--core/src/com/google/inject/spi/Elements.java131
-rw-r--r--core/src/com/google/inject/spi/ModuleAnnotatedMethodScanner.java10
-rw-r--r--core/src/com/google/inject/spi/ModuleAnnotatedMethodScannerBinding.java59
-rw-r--r--core/src/com/google/inject/spi/ModuleSource.java60
-rw-r--r--core/src/com/google/inject/util/Modules.java27
-rw-r--r--core/test/com/google/inject/internal/WeakKeySetTest.java9
-rw-r--r--core/test/com/google/inject/spi/ModuleAnnotatedMethodScannerTest.java208
-rw-r--r--core/test/com/google/inject/util/OverrideModuleTest.java52
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"));
+ }
+ }
}