aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Berlin <sberlin@gmail.com>2015-02-25 11:49:14 -0500
committerSam Berlin <sberlin@gmail.com>2015-02-25 11:49:14 -0500
commit156c8cc762fab971efb727c7ab107fa243be2fc9 (patch)
treeeb87961ff032167d5997c055e797e83e217136b8
parent7ddc816ddc30f328690193be6e70a476a71899e7 (diff)
parent85f14e03ee00b20e20fd0f018a00ef52fcf909b1 (diff)
downloadguice-156c8cc762fab971efb727c7ab107fa243be2fc9.tar.gz
Merge pull request #905 from google/moe_writing_branch_from_b605a34702d8d8112983aca891e3e2b6987ec45e
Merge internal changes
-rw-r--r--build.properties2
-rw-r--r--build.xml13
-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
-rw-r--r--extensions/assistedinject/.gitignore1
-rw-r--r--extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java183
-rw-r--r--extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java86
-rw-r--r--extensions/dagger-adapter/build.properties8
-rw-r--r--extensions/dagger-adapter/build.xml21
-rw-r--r--extensions/dagger-adapter/lib/dagger-2.0-20150205.014011-14.jarbin0 -> 16693 bytes
-rw-r--r--extensions/dagger-adapter/pom.xml24
-rw-r--r--extensions/dagger-adapter/src/com/google/inject/daggeradapter/DaggerAdapter.java90
-rw-r--r--extensions/dagger-adapter/src/com/google/inject/daggeradapter/DaggerMethodScanner.java75
-rw-r--r--extensions/dagger-adapter/test/com/google/inject/daggeradapter/DaggerAdapterTest.java99
-rw-r--r--extensions/jmx/.gitignore1
-rw-r--r--extensions/mini/.gitignore1
-rw-r--r--extensions/multibindings/.gitignore1
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/ClassMapKey.java35
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java38
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/MapKey.java58
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java45
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/MultibindingsScanner.java192
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/OptionalBinder.java32
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoMap.java59
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoOptional.java63
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoSet.java53
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/StringMapKey.java35
-rw-r--r--extensions/multibindings/test/com/google/inject/multibindings/AllTests.java1
-rw-r--r--extensions/multibindings/test/com/google/inject/multibindings/ProvidesIntoTest.java373
-rw-r--r--extensions/pom.xml2
-rw-r--r--extensions/spring/.gitignore1
-rw-r--r--extensions/struts2/.gitignore1
-rw-r--r--pom.xml11
48 files changed, 2180 insertions, 150 deletions
diff --git a/build.properties b/build.properties
index f1e04745..cca000c0 100644
--- a/build.properties
+++ b/build.properties
@@ -8,6 +8,7 @@ jmx.src.dir=extensions/jmx/src
jndi.src.dir=extensions/jndi/src
throwingproviders.src.dir=extensions/throwingproviders/src
multibindings.src.dir=extensions/multibindings/src
+daggeradapter.src.dir=extensions/dagger-adapter/src
privatemodules.src.dir=extensions/privatemodules/src
lifecycle.src.dir=extensions/lifecycle/src
persist.src.dir=extensions/persist/src
@@ -26,6 +27,7 @@ javadoc.packagenames=com.google.inject,com.google.inject.spi,\
com.google.inject.assistedinject,\
com.google.inject.throwingproviders,\
com.google.inject.multibindings,\
+ com.google.inject.daggeradapter,\
com.google.inject.privatemodules,\
com.google.inject.util,\
com.google.inject.persist,\
diff --git a/build.xml b/build.xml
index 1efac5f1..3d0c7722 100644
--- a/build.xml
+++ b/build.xml
@@ -10,9 +10,9 @@
</path>
<path id="javadoc.classpath">
- <path refid="compile.classpath"/>
+ <path refid="compile.classpath"/>
<fileset dir="extensions">
- <include name="*/lib/*.jar"/>
+ <include name="*/lib/*.jar"/>
</fileset>
<pathelement location="${build.dir}/classes"/>
</path>
@@ -35,6 +35,7 @@
<ant antfile="extensions/jndi/build.xml" target="distjars" inheritAll="false"/>
<ant antfile="extensions/throwingproviders/build.xml" target="distjars" inheritAll="false"/>
<ant antfile="extensions/multibindings/build.xml" target="distjars" inheritAll="false"/>
+ <ant antfile="extensions/dagger-adapter/build.xml" target="distjars" inheritAll="false"/>
<ant antfile="extensions/persist/build.xml" target="distjars" inheritAll="false"/>
<ant antfile="extensions/grapher/build.xml" target="distjars" inheritAll="false"/>
<ant antfile="extensions/testlib/build.xml" target="distjars" inheritAll="false"/>
@@ -64,6 +65,9 @@
<fileset dir="extensions/multibindings/build" includes="*.jar"/>
</copy>
<copy toDir="${build.dir}/dist">
+ <fileset dir="extensions/dagger-adapter/build" includes="*.jar"/>
+ </copy>
+ <copy toDir="${build.dir}/dist">
<fileset dir="extensions/persist/build" includes="*.jar"/>
</copy>
<copy toDir="${build.dir}/dist">
@@ -159,6 +163,7 @@
<fileset dir="${jndi.src.dir}"/>
<fileset dir="${throwingproviders.src.dir}"/>
<fileset dir="${multibindings.src.dir}"/>
+ <fileset dir="${daggeradapter.src.dir}"/>
<fileset dir="${persist.src.dir}"/>
<fileset dir="${struts2.src.dir}"/>
<fileset dir="${grapher.src.dir}"/>
@@ -214,6 +219,9 @@
<group title="Multibinder Extension" packages="com.google.inject.multibindings"/>
<fileset dir="${multibindings.src.dir}"/>
+ <group title="Dagger Adapter" packages="com.google.inject.daggeradapter"/>
+ <fileset dir="${daggeradapter.src.dir}"/>
+
<group title="ThrowingProviders Extension" packages="com.google.inject.throwingproviders"/>
<fileset dir="${throwingproviders.src.dir}"/>
@@ -302,6 +310,7 @@
<ant dir="extensions/jndi" antfile="build.xml" target="clean"/>
<ant dir="extensions/throwingproviders" antfile="build.xml" target="clean"/>
<ant dir="extensions/multibindings" antfile="build.xml" target="clean"/>
+ <ant dir="extensions/dagger-adapter" antfile="build.xml" target="clean"/>
<ant dir="extensions/persist" antfile="build.xml" target="clean"/>
<ant dir="extensions/grapher" antfile="build.xml" target="clean"/>
<ant dir="extensions/testlib" antfile="build.xml" target="clean"/>
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"));
+ }
+ }
}
diff --git a/extensions/assistedinject/.gitignore b/extensions/assistedinject/.gitignore
new file mode 100644
index 00000000..84c048a7
--- /dev/null
+++ b/extensions/assistedinject/.gitignore
@@ -0,0 +1 @@
+/build/
diff --git a/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java b/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java
index 6ec0cd4a..b40e19f3 100644
--- a/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java
+++ b/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java
@@ -20,11 +20,13 @@ import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.getOnlyElement;
import com.google.common.base.Objects;
+import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
@@ -57,6 +59,7 @@ import com.google.inject.util.Providers;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
@@ -183,6 +186,9 @@ final class FactoryProvider2 <F> implements InvocationHandler,
/** Mapping from method to the data about how the method will be assisted. */
private final ImmutableMap<Method, AssistData> assistDataByMethod;
+ /** Mapping from method to method handle, for generated default methods. */
+ private final ImmutableMap<Method, MethodHandleWrapper> methodHandleByMethod;
+
/** the hosting injector, or null if we haven't been initialized yet */
private Injector injector;
@@ -214,10 +220,22 @@ final class FactoryProvider2 <F> implements InvocationHandler,
if(!factoryRawType.isInterface()) {
throw errors.addMessage("%s must be an interface.", factoryRawType).toException();
}
-
+
+ Multimap<String, Method> defaultMethods = HashMultimap.create();
+ Multimap<String, Method> otherMethods = HashMultimap.create();
ImmutableMap.Builder<Method, AssistData> assistDataBuilder = ImmutableMap.builder();
// TODO: also grab methods from superinterfaces
for (Method method : factoryRawType.getMethods()) {
+ // Skip default methods that java8 may have created.
+ if (isDefault(method) && (method.isBridge() || method.isSynthetic())) {
+ // Even synthetic default methods need the return type validation...
+ // unavoidable consequence of javac8. :-(
+ validateFactoryReturnType(errors, method.getReturnType(), factoryRawType);
+ defaultMethods.put(method.getName(), method);
+ continue;
+ }
+ otherMethods.put(method.getName(), method);
+
TypeLiteral<?> returnTypeLiteral = factoryType.getReturnType(method);
Key<?> returnType;
try {
@@ -289,9 +307,51 @@ final class FactoryProvider2 <F> implements InvocationHandler,
providers = providerListBuilder.build();
optimized = true;
}
- assistDataBuilder.put(method,
- new AssistData(constructor, returnType, immutableParamList, implementation,
- method, removeAssistedDeps(deps), optimized, providers));
+
+ AssistData data = new AssistData(constructor,
+ returnType,
+ immutableParamList,
+ implementation,
+ method,
+ removeAssistedDeps(deps),
+ optimized,
+ providers);
+ assistDataBuilder.put(method, data);
+ }
+
+ factory = factoryRawType.cast(Proxy.newProxyInstance(
+ BytecodeGen.getClassLoader(factoryRawType), new Class<?>[] {factoryRawType}, this));
+
+ // Now go back through default methods. Try to use MethodHandles to make things
+ // work. If that doesn't work, fallback to trying to find compatible method
+ // signatures.
+ Map<Method, AssistData> dataSoFar = assistDataBuilder.build();
+ ImmutableMap.Builder<Method, MethodHandleWrapper> methodHandleBuilder = ImmutableMap.builder();
+ for (Map.Entry<String, Method> entry : defaultMethods.entries()) {
+ Method defaultMethod = entry.getValue();
+ MethodHandleWrapper handle = MethodHandleWrapper.create(defaultMethod, factory);
+ if (handle != null) {
+ methodHandleBuilder.put(defaultMethod, handle);
+ } else {
+ boolean foundMatch = false;
+ for (Method otherMethod : otherMethods.get(defaultMethod.getName())) {
+ if (dataSoFar.containsKey(otherMethod) && isCompatible(defaultMethod, otherMethod)) {
+ if (foundMatch) {
+ errors.addMessage("Generated default method %s with parameters %s is"
+ + " signature-compatible with more than one non-default method."
+ + " Unable to create factory. As a workaround, remove the override"
+ + " so javac stops generating a default method.",
+ defaultMethod, Arrays.asList(defaultMethod.getParameterTypes()));
+ } else {
+ assistDataBuilder.put(defaultMethod, dataSoFar.get(otherMethod));
+ foundMatch = true;
+ }
+ }
+ }
+ if (!foundMatch) {
+ throw new IllegalStateException("Can't find method compatible with: " + defaultMethod);
+ }
+ }
}
// If we generated any errors (from finding matching constructors, for instance), throw an exception.
@@ -300,12 +360,35 @@ final class FactoryProvider2 <F> implements InvocationHandler,
}
assistDataByMethod = assistDataBuilder.build();
+ methodHandleByMethod = methodHandleBuilder.build();
} catch (ErrorsException e) {
throw new ConfigurationException(e.getErrors().getMessages());
}
+ }
+
+ static boolean isDefault(Method method) {
+ // Per the javadoc, default methods are non-abstract, public, non-static.
+ // They're also in interfaces, but we can guarantee that already since we only act
+ // on interfaces.
+ return (method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC))
+ == Modifier.PUBLIC;
+ }
- factory = factoryRawType.cast(Proxy.newProxyInstance(BytecodeGen.getClassLoader(factoryRawType),
- new Class[] { factoryRawType }, this));
+ private boolean isCompatible(Method src, Method dst) {
+ if (!src.getReturnType().isAssignableFrom(dst.getReturnType())) {
+ return false;
+ }
+ Class<?>[] srcParams = src.getParameterTypes();
+ Class<?>[] dstParams = dst.getParameterTypes();
+ if (srcParams.length != dstParams.length) {
+ return false;
+ }
+ for (int i = 0; i < srcParams.length; i++) {
+ if (!srcParams[i].isAssignableFrom(dstParams[i])) {
+ return false;
+ }
+ }
+ return true;
}
public F get() {
@@ -654,6 +737,13 @@ final class FactoryProvider2 <F> implements InvocationHandler,
* use that to get an instance of the return type.
*/
public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
+ // If we setup a method handle earlier for this method, call it.
+ // This is necessary for default methods that java8 creates, so we
+ // can call the default method implementation (and not our proxied version of it).
+ if (methodHandleByMethod.containsKey(method)) {
+ return methodHandleByMethod.get(method).invokeWithArguments(args);
+ }
+
if (method.getDeclaringClass().equals(Object.class)) {
if ("equals".equals(method.getName())) {
return proxy == args[0];
@@ -665,6 +755,7 @@ final class FactoryProvider2 <F> implements InvocationHandler,
}
AssistData data = assistDataByMethod.get(method);
+ checkState(data != null, "No data for method: %s", method);
Provider<?> provider;
if(data.cachedBinding != null) { // Try to get optimized form...
provider = data.cachedBinding.getProvider();
@@ -735,4 +826,84 @@ final class FactoryProvider2 <F> implements InvocationHandler,
+ " (This should never happen. If it does, please report it.)");
}
}
+
+ /** Wrapper around MethodHandles/MethodHandle, so we can compile+run on java6. */
+ private static class MethodHandleWrapper {
+ static final int ALL_MODES = Modifier.PRIVATE
+ | Modifier.STATIC /* package */
+ | Modifier.PUBLIC
+ | Modifier.PROTECTED;
+
+ static final Method unreflectSpecial;
+ static final Method bindTo;
+ static final Method invokeWithArguments;
+ static final Constructor<?> lookupCxtor;
+ static final boolean valid;
+
+ static {
+ Method unreflectSpecialTmp = null;
+ Method bindToTmp = null;
+ Method invokeWithArgumentsTmp = null;
+ boolean validTmp = false;
+ Constructor<?> lookupCxtorTmp = null;
+ try {
+ Class<?> lookupClass = Class.forName("java.lang.invoke.MethodHandles$Lookup");
+ unreflectSpecialTmp = lookupClass.getMethod("unreflectSpecial", Method.class, Class.class);
+ Class<?> methodHandleClass = Class.forName("java.lang.invoke.MethodHandle");
+ bindToTmp = methodHandleClass.getMethod("bindTo", Object.class);
+ invokeWithArgumentsTmp = methodHandleClass.getMethod("invokeWithArguments", Object[].class);
+ lookupCxtorTmp = lookupClass.getDeclaredConstructor(Class.class, int.class);
+ lookupCxtorTmp.setAccessible(true);
+ validTmp = true;
+ } catch (Exception invalid) {
+ // Ignore the exception, store the values & exit early in create(..) if invalid.
+ }
+
+ // Store refs to later.
+ valid = validTmp;
+ unreflectSpecial = unreflectSpecialTmp;
+ bindTo = bindToTmp;
+ invokeWithArguments = invokeWithArgumentsTmp;
+ lookupCxtor = lookupCxtorTmp;
+ }
+
+ static MethodHandleWrapper create(Method method, Object proxy) {
+ if (!valid) {
+ return null;
+ }
+ try {
+ Class<?> declaringClass = method.getDeclaringClass();
+ // Note: this isn't a public API, but we need to use it in order to call default methods.
+ Object lookup = lookupCxtor.newInstance(declaringClass, ALL_MODES);
+ method.setAccessible(true);
+ // These are part of the public API, but we use reflection since we run on java6
+ // and they were introduced in java7.
+ lookup = unreflectSpecial.invoke(lookup, method, declaringClass);
+ Object handle = bindTo.invoke(lookup, proxy);
+ return new MethodHandleWrapper(handle);
+ } catch (InvocationTargetException ite) {
+ return null;
+ } catch (IllegalAccessException iae) {
+ return null;
+ } catch (InstantiationException ie) {
+ return null;
+ }
+ }
+
+ final Object handle;
+
+ MethodHandleWrapper(Object handle) {
+ this.handle = handle;
+ }
+
+ Object invokeWithArguments(Object[] args) throws Exception {
+ // We must cast the args to an object so the Object[] is the first param,
+ // as opposed to each individual varargs param.
+ return invokeWithArguments.invoke(handle, (Object) args);
+ }
+
+ @Override public String toString() {
+ return handle.toString();
+ }
+ }
}
diff --git a/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java b/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java
index 8903042d..c0e9bbd2 100644
--- a/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java
+++ b/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java
@@ -1131,4 +1131,90 @@ public class FactoryProvider2Test extends TestCase {
this.delegate = null;
}
}
+
+ public static abstract class AbstractAssisted {
+ interface Factory<O extends AbstractAssisted, I extends CharSequence> {
+ O create(I string);
+ }
+ }
+
+ static class ConcreteAssisted extends AbstractAssisted {
+ @Inject ConcreteAssisted(@SuppressWarnings("unused") @Assisted String string) {}
+ }
+
+ static class ConcreteAssistedWithOverride extends AbstractAssisted {
+ @AssistedInject
+ ConcreteAssistedWithOverride(@SuppressWarnings("unused") @Assisted String string) {}
+
+ @AssistedInject
+ ConcreteAssistedWithOverride(@SuppressWarnings("unused") @Assisted StringBuilder sb) {}
+
+ interface Factory extends AbstractAssisted.Factory<ConcreteAssistedWithOverride, String> {
+ @Override ConcreteAssistedWithOverride create(String string);
+ }
+
+ interface Factory2 extends AbstractAssisted.Factory<ConcreteAssistedWithOverride, String> {
+ @Override ConcreteAssistedWithOverride create(String string);
+ ConcreteAssistedWithOverride create(StringBuilder sb);
+ }
+ }
+
+ static class ConcreteAssistedWithoutOverride extends AbstractAssisted {
+ @Inject ConcreteAssistedWithoutOverride(@SuppressWarnings("unused") @Assisted String string) {}
+ interface Factory extends AbstractAssisted.Factory<ConcreteAssistedWithoutOverride, String> {}
+ }
+
+ public static class Public extends AbstractAssisted {
+ @AssistedInject Public(@SuppressWarnings("unused") @Assisted String string) {}
+ @AssistedInject Public(@SuppressWarnings("unused") @Assisted StringBuilder sb) {}
+
+ public interface Factory extends AbstractAssisted.Factory<Public, String> {
+ @Override Public create(String string);
+ Public create(StringBuilder sb);
+ }
+ }
+
+ // See https://github.com/google/guice/issues/904
+ public void testGeneratedDefaultMethodsForwardCorrectly() {
+ final Key<AbstractAssisted.Factory<ConcreteAssisted, String>> concreteKey =
+ new Key<AbstractAssisted.Factory<ConcreteAssisted, String>>() {};
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override protected void configure() {
+ install(new FactoryModuleBuilder().build(ConcreteAssistedWithOverride.Factory.class));
+ install(new FactoryModuleBuilder().build(ConcreteAssistedWithOverride.Factory2.class));
+ install(new FactoryModuleBuilder().build(ConcreteAssistedWithoutOverride.Factory.class));
+ install(new FactoryModuleBuilder().build(Public.Factory.class));
+ install(new FactoryModuleBuilder().build(concreteKey));
+ }
+ });
+
+ ConcreteAssistedWithOverride.Factory factory1 =
+ injector.getInstance(ConcreteAssistedWithOverride.Factory.class);
+ factory1.create("foo");
+ AbstractAssisted.Factory<ConcreteAssistedWithOverride, String> factory1Abstract = factory1;
+ factory1Abstract.create("foo");
+
+ ConcreteAssistedWithOverride.Factory2 factory2 =
+ injector.getInstance(ConcreteAssistedWithOverride.Factory2.class);
+ factory2.create("foo");
+ factory2.create(new StringBuilder("foo"));
+ AbstractAssisted.Factory<ConcreteAssistedWithOverride, String> factory2Abstract = factory2;
+ factory2Abstract.create("foo");
+
+ ConcreteAssistedWithoutOverride.Factory factory3 =
+ injector.getInstance(ConcreteAssistedWithoutOverride.Factory.class);
+ factory3.create("foo");
+ AbstractAssisted.Factory<ConcreteAssistedWithoutOverride, String> factory3Abstract = factory3;
+ factory3Abstract.create("foo");
+
+ Public.Factory factory4 = injector.getInstance(Public.Factory.class);
+ factory4.create("foo");
+ factory4.create(new StringBuilder("foo"));
+ AbstractAssisted.Factory<Public, String> factory4Abstract = factory4;
+ factory4Abstract.create("foo");
+
+ AbstractAssisted.Factory<ConcreteAssisted, String> factory5 =
+ injector.getInstance(concreteKey);
+ factory5.create("foo");
+ }
}
diff --git a/extensions/dagger-adapter/build.properties b/extensions/dagger-adapter/build.properties
new file mode 100644
index 00000000..97e53c23
--- /dev/null
+++ b/extensions/dagger-adapter/build.properties
@@ -0,0 +1,8 @@
+lib.dir=../../lib
+ext.lib.dir=lib
+src.dir=src
+test.dir=test
+build.dir=build
+test.class=com.google.inject.daggeradapter.DaggerAdapterTest
+module=com.google.inject.daggeradapter
+fragment=true
diff --git a/extensions/dagger-adapter/build.xml b/extensions/dagger-adapter/build.xml
new file mode 100644
index 00000000..27eb4290
--- /dev/null
+++ b/extensions/dagger-adapter/build.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+
+<project name="guice-dagger-adapter" basedir="." default="jar">
+
+ <import file="../../common.xml"/>
+
+ <path id="compile.classpath">
+ <fileset dir="${lib.dir}" includes="*.jar"/>
+ <fileset dir="${ext.lib.dir}" includes="*.jar"/>
+ <pathelement path="../../build/classes"/>
+ <fileset dir="../multibindings/build" includes="*.jar"/>
+ </path>
+
+ <target name="jar" depends="compile, manifest" description="Build jar.">
+ <jar destfile="${build.dir}/${ant.project.name}-${version}.jar"
+ manifest="${build.dir}/META-INF/MANIFEST.MF">
+ <fileset dir="${build.dir}/classes" />
+ </jar>
+ </target>
+
+</project>
diff --git a/extensions/dagger-adapter/lib/dagger-2.0-20150205.014011-14.jar b/extensions/dagger-adapter/lib/dagger-2.0-20150205.014011-14.jar
new file mode 100644
index 00000000..3690cdc7
--- /dev/null
+++ b/extensions/dagger-adapter/lib/dagger-2.0-20150205.014011-14.jar
Binary files differ
diff --git a/extensions/dagger-adapter/pom.xml b/extensions/dagger-adapter/pom.xml
new file mode 100644
index 00000000..10af044f
--- /dev/null
+++ b/extensions/dagger-adapter/pom.xml
@@ -0,0 +1,24 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.google.inject.extensions</groupId>
+ <artifactId>extensions-parent</artifactId>
+ <version>4.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>dagger-interop</artifactId>
+ <name>Google Guice - Extensions - Dagger Interop</name>
+ <dependencies>
+ <dependency>
+ <groupId>com.google.inject.extensions</groupId>
+ <artifactId>guice-multibindings</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.dagger</groupId>
+ <artifactId>dagger</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/extensions/dagger-adapter/src/com/google/inject/daggeradapter/DaggerAdapter.java b/extensions/dagger-adapter/src/com/google/inject/daggeradapter/DaggerAdapter.java
new file mode 100644
index 00000000..eb7aac5f
--- /dev/null
+++ b/extensions/dagger-adapter/src/com/google/inject/daggeradapter/DaggerAdapter.java
@@ -0,0 +1,90 @@
+/**
+ * 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.daggeradapter;
+
+import com.google.common.base.Objects;
+import com.google.inject.Binder;
+import com.google.inject.Module;
+import com.google.inject.internal.ProviderMethodsModule;
+import com.google.inject.spi.ModuleAnnotatedMethodScanner;
+
+import java.util.Arrays;
+
+/**
+ * A utility to adapt classes annotated with {@link @dagger.Module} such that their
+ * {@link @dagger.Provides} methods can be properly invoked by Guice to perform their
+ * provision operations.
+ *
+ * <p>Simple example: <pre>{@code
+ * Guice.createInjector(...other modules..., DaggerAdapter.from(new SomeDaggerAdapter()));
+ * }</pre>
+ *
+ * <p>Some notes on usage and compatibility.
+ * <ul>
+ * <li>Dagger provider methods have a "SET_VALUES" provision mode not supported by Guice.
+ * <li>MapBindings are not yet implemented (pending).
+ * <li>Be careful about stateful modules. In contrast to Dagger (where components are
+ * expected to be recreated on-demand with new Module instances), Guice typically
+ * has a single injector with a long lifetime, so your module instance will be used
+ * throughout the lifetime of the entire app.
+ * <li>Dagger 1.x uses {@link @Singleton} for all scopes, including shorter-lived scopes
+ * like per-request or per-activity. Using modules written with Dagger 1.x usage
+ * in mind may result in mis-scoped objects.
+ * <li>Dagger 2.x supports custom scope annotations, but for use in Guice, a custom scope
+ * implementation must be registered in order to support the custom lifetime of that
+ * annotation.
+ * </ul>
+ *
+ * @author cgruber@google.com (Christian Gruber)
+ */
+public final class DaggerAdapter {
+ /**
+ * Returns a guice module from a dagger module.
+ *
+ * <p>Note: At present, it does not honor {@code @Module(includes=...)} directives.
+ */
+ public static Module from(Object... daggerModuleObjects) {
+ // TODO(cgruber): Gather injects=, dedupe, factor out instances, instantiate the rest, and go.
+ return new DaggerCompatibilityModule(daggerModuleObjects);
+ }
+
+ /**
+ * A Module that adapts Dagger {@code @Module}-annotated types to contribute configuration
+ * to an {@link com.google.inject.Injector} using a dagger-specific
+ * {@link ModuleAnnotatedMethodScanner}.
+ */
+ private static final class DaggerCompatibilityModule implements Module {
+ private final Object[] daggerModuleObjects;
+
+ private DaggerCompatibilityModule(Object... daggerModuleObjects) {
+ this.daggerModuleObjects = daggerModuleObjects;
+ }
+
+ @Override public void configure(Binder binder) {
+ for (Object module : daggerModuleObjects) {
+ binder.install(ProviderMethodsModule.forModule(module, DaggerMethodScanner.INSTANCE));
+ }
+ }
+
+ @Override public String toString() {
+ return Objects.toStringHelper(this)
+ .add("modules", Arrays.asList(daggerModuleObjects))
+ .toString();
+ }
+ }
+
+ private DaggerAdapter() {}
+}
diff --git a/extensions/dagger-adapter/src/com/google/inject/daggeradapter/DaggerMethodScanner.java b/extensions/dagger-adapter/src/com/google/inject/daggeradapter/DaggerMethodScanner.java
new file mode 100644
index 00000000..0dbdda21
--- /dev/null
+++ b/extensions/dagger-adapter/src/com/google/inject/daggeradapter/DaggerMethodScanner.java
@@ -0,0 +1,75 @@
+/**
+ * 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.daggeradapter;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Binder;
+import com.google.inject.Key;
+import com.google.inject.internal.UniqueAnnotations;
+import com.google.inject.multibindings.Multibinder;
+import com.google.inject.spi.InjectionPoint;
+import com.google.inject.spi.ModuleAnnotatedMethodScanner;
+
+import dagger.Provides;
+import dagger.Provides.Type;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Set;
+
+/**
+ * A scanner to process provider methods on Dagger modules.
+ *
+ * @author cgruber@google.com (Christian Gruber)
+ */
+final class DaggerMethodScanner extends ModuleAnnotatedMethodScanner {
+ static DaggerMethodScanner INSTANCE = new DaggerMethodScanner();
+
+ @Override public Set<? extends Class<? extends Annotation>> annotationClasses() {
+ return ImmutableSet.of(dagger.Provides.class);
+ }
+
+ @Override public <T> Key<T> prepareMethod(
+ Binder binder, Annotation rawAnnotation, Key<T> key, InjectionPoint injectionPoint) {
+ Method providesMethod = (Method) injectionPoint.getMember();
+ Provides annotation = (Provides) rawAnnotation;
+ switch (annotation.type()) {
+ case UNIQUE:
+ return key;
+ case MAP:
+ /* TODO(cgruber) implement map bindings */
+ binder.addError("Map bindings are not yet supported.");
+ case SET:
+ return processSetBinding(binder, key);
+ case SET_VALUES:
+ binder.addError(Type.SET_VALUES.name() + " contributions are not supported by Guice.",
+ providesMethod);
+ return key;
+ default:
+ binder.addError("Unknown @Provides type " + annotation.type() + ".", providesMethod);
+ return key;
+ }
+ }
+
+ private static <T> Key<T> processSetBinding(Binder binder, Key<T> key) {
+ Multibinder<T> setBinder = Multibinder.newSetBinder(binder, key.getTypeLiteral());
+ Key<T> newKey = Key.get(key.getTypeLiteral(), UniqueAnnotations.create());
+ setBinder.addBinding().to(newKey);
+ return newKey;
+ }
+
+ private DaggerMethodScanner() {}
+} \ No newline at end of file
diff --git a/extensions/dagger-adapter/test/com/google/inject/daggeradapter/DaggerAdapterTest.java b/extensions/dagger-adapter/test/com/google/inject/daggeradapter/DaggerAdapterTest.java
new file mode 100644
index 00000000..30e1d186
--- /dev/null
+++ b/extensions/dagger-adapter/test/com/google/inject/daggeradapter/DaggerAdapterTest.java
@@ -0,0 +1,99 @@
+/**
+ * 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.daggeradapter;
+
+import static dagger.Provides.Type.SET;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Binder;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.Provides;
+import com.google.inject.multibindings.Multibinder;
+import com.google.inject.util.Providers;
+
+import junit.framework.TestCase;
+
+import java.util.Set;
+
+/**
+ * Tests for {@link DaggerAdapter}.
+ *
+ * @author cgruber@google.com (Christian Gruber)
+ */
+
+public class DaggerAdapterTest extends TestCase {
+ @dagger.Module static class SimpleDaggerModule {
+ @dagger.Provides Integer anInteger() {
+ return 1;
+ }
+ }
+
+ public void testSimpleModule() {
+ Injector i = Guice.createInjector(DaggerAdapter.from(new SimpleDaggerModule()));
+ assertEquals((Integer) 1, i.getInstance(Integer.class));
+ }
+
+ static class SimpleGuiceModule extends AbstractModule {
+ @Provides String aString(Integer i) {
+ return i.toString();
+ }
+ @Override protected void configure() {}
+ }
+
+ public void testInteractionWithGuiceModules() {
+ Injector i = Guice.createInjector(
+ new SimpleGuiceModule(),
+ DaggerAdapter.from(new SimpleDaggerModule()));
+ assertEquals("1", i.getInstance(String.class));
+ }
+
+ @dagger.Module static class SetBindingDaggerModule1 {
+ @dagger.Provides(type=SET) Integer anInteger() {
+ return 5;
+ }
+ }
+
+ @dagger.Module static class SetBindingDaggerModule2 {
+ @dagger.Provides(type=SET) Integer anInteger() {
+ return 3;
+ }
+ }
+
+ public void testSetBindings() {
+ Injector i = Guice.createInjector(
+ DaggerAdapter.from(new SetBindingDaggerModule1(), new SetBindingDaggerModule2()));
+ assertEquals(ImmutableSet.of(3, 5), i.getInstance(new Key<Set<Integer>>() {}));
+ }
+
+ static class MultibindingGuiceModule implements Module {
+ @Override public void configure(Binder binder) {
+ Multibinder<Integer> mb = Multibinder.newSetBinder(binder, Integer.class);
+ mb.addBinding().toInstance(13);
+ mb.addBinding().toProvider(Providers.of(8)); // mix'n'match.
+ }
+ }
+
+ public void testSetBindingsWithGuiceModule() {
+ Injector i = Guice.createInjector(
+ new MultibindingGuiceModule(),
+ DaggerAdapter.from(new SetBindingDaggerModule1(), new SetBindingDaggerModule2()));
+ assertEquals(ImmutableSet.of(13, 3, 5, 8), i.getInstance(new Key<Set<Integer>>() {}));
+ }
+}
diff --git a/extensions/jmx/.gitignore b/extensions/jmx/.gitignore
new file mode 100644
index 00000000..84c048a7
--- /dev/null
+++ b/extensions/jmx/.gitignore
@@ -0,0 +1 @@
+/build/
diff --git a/extensions/mini/.gitignore b/extensions/mini/.gitignore
new file mode 100644
index 00000000..84c048a7
--- /dev/null
+++ b/extensions/mini/.gitignore
@@ -0,0 +1 @@
+/build/
diff --git a/extensions/multibindings/.gitignore b/extensions/multibindings/.gitignore
new file mode 100644
index 00000000..84c048a7
--- /dev/null
+++ b/extensions/multibindings/.gitignore
@@ -0,0 +1 @@
+/build/
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/ClassMapKey.java b/extensions/multibindings/src/com/google/inject/multibindings/ClassMapKey.java
new file mode 100644
index 00000000..47c8c17d
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/ClassMapKey.java
@@ -0,0 +1,35 @@
+/**
+ * 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.multibindings;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Allows {@literal @}{@link ProvidesIntoMap} to specify a class map key.
+ */
+@MapKey(unwrapValue = true)
+@Documented
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface ClassMapKey {
+ Class<?> value();
+}
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java b/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
index 89df7713..fc3d74f5 100644
--- a/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
+++ b/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
@@ -143,7 +143,7 @@ public abstract class MapBinder<K, V> {
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder,
TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
- return newMapBinder(binder, keyType, valueType, Key.get(mapOf(keyType, valueType)),
+ return newRealMapBinder(binder, keyType, valueType, Key.get(mapOf(keyType, valueType)),
Multibinder.newSetBinder(binder, entryOfProviderOf(keyType, valueType)));
}
@@ -163,7 +163,7 @@ public abstract class MapBinder<K, V> {
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder,
TypeLiteral<K> keyType, TypeLiteral<V> valueType, Annotation annotation) {
binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
- return newMapBinder(binder, keyType, valueType,
+ return newRealMapBinder(binder, keyType, valueType,
Key.get(mapOf(keyType, valueType), annotation),
Multibinder.newSetBinder(binder, entryOfProviderOf(keyType, valueType), annotation));
}
@@ -184,7 +184,7 @@ public abstract class MapBinder<K, V> {
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder, TypeLiteral<K> keyType,
TypeLiteral<V> valueType, Class<? extends Annotation> annotationType) {
binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
- return newMapBinder(binder, keyType, valueType,
+ return newRealMapBinder(binder, keyType, valueType,
Key.get(mapOf(keyType, valueType), annotationType),
Multibinder.newSetBinder(binder, entryOfProviderOf(keyType, valueType), annotationType));
}
@@ -236,7 +236,19 @@ public abstract class MapBinder<K, V> {
Map.class, Entry.class, keyType.getType(), Types.providerOf(valueType.getType())));
}
- private static <K, V> MapBinder<K, V> newMapBinder(Binder binder,
+ // Note: We use valueTypeAndAnnotation effectively as a Pair<TypeLiteral, Annotation|Class>
+ // since it's an easy way to group a type and an optional annotation type or instance.
+ static <K, V> RealMapBinder<K, V> newRealMapBinder(Binder binder, TypeLiteral<K> keyType,
+ Key<V> valueTypeAndAnnotation) {
+ binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
+ TypeLiteral<V> valueType = valueTypeAndAnnotation.getTypeLiteral();
+ return newRealMapBinder(binder, keyType, valueType,
+ valueTypeAndAnnotation.ofType(mapOf(keyType, valueType)),
+ Multibinder.newSetBinder(binder,
+ valueTypeAndAnnotation.ofType(entryOfProviderOf(keyType, valueType))));
+ }
+
+ private static <K, V> RealMapBinder<K, V> newRealMapBinder(Binder binder,
TypeLiteral<K> keyType, TypeLiteral<V> valueType, Key<Map<K, V>> mapKey,
Multibinder<Entry<K, Provider<V>>> entrySetBinder) {
RealMapBinder<K, V> mapBinder =
@@ -342,12 +354,8 @@ public abstract class MapBinder<K, V> {
multimapKey, providerMultimapKey, entrySetBinder.getSetKey()));
return this;
}
-
- /**
- * This creates two bindings. One for the {@code Map.Entry<K, Provider<V>>}
- * and another for {@code V}.
- */
- @Override public LinkedBindingBuilder<V> addBinding(K key) {
+
+ Key<V> getKeyForNewValue(K key) {
checkNotNull(key, "key");
checkConfiguration(!isInitialized(), "MapBinder was already initialized");
@@ -355,7 +363,15 @@ public abstract class MapBinder<K, V> {
new RealElement(entrySetBinder.getSetName(), MAPBINDER, keyType.toString()));
entrySetBinder.addBinding().toProvider(new ProviderMapEntry<K, V>(
key, binder.getProvider(valueKey), valueKey));
- return binder.bind(valueKey);
+ return valueKey;
+ }
+
+ /**
+ * This creates two bindings. One for the {@code Map.Entry<K, Provider<V>>}
+ * and another for {@code V}.
+ */
+ @Override public LinkedBindingBuilder<V> addBinding(K key) {
+ return binder.bind(getKeyForNewValue(key));
}
@Override public void configure(Binder binder) {
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/MapKey.java b/extensions/multibindings/src/com/google/inject/multibindings/MapKey.java
new file mode 100644
index 00000000..bcb6a3b8
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/MapKey.java
@@ -0,0 +1,58 @@
+/**
+ * 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.multibindings;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Allows users define customized key type annotations for map bindings by annotating an annotation
+ * of a {@code Map}'s key type. The custom key annotation can be applied to methods also annotated
+ * with {@literal @}{@link ProvidesIntoMap}.
+ *
+ * <p>A {@link StringMapKey} and {@link ClassMapKey} are provided for convenience with maps whose
+ * keys are strings or classes. For maps with enums or primitive types as keys, you must provide
+ * your own MapKey annotation, such as this one for an enum:
+ *
+ * <pre>
+ * {@literal @}MapKey(unwrapValue = true)
+ * {@literal @}Retention(RUNTIME)
+ * public {@literal @}interface MyCustomEnumKey {
+ * MyCustomEnum value();
+ * }
+ * </pre>
+ *
+ * You can also use the whole annotation as the key, if {@code unwrapValue=false}.
+ * When unwrapValue is false, the annotation type will be the key type for the injected map and
+ * the annotation instances will be the key values. If {@code unwrapValue=true}, the value() type
+ * will be the key type for injected map and the value() instances will be the keys values.
+ */
+@Documented
+@Target(ANNOTATION_TYPE)
+@Retention(RUNTIME)
+public @interface MapKey {
+ /**
+ * if {@code unwrapValue} is false, then the whole annotation will be the type and annotation
+ * instances will be the keys. If {@code unwrapValue} is true, the value() type of key type
+ * annotation will be the key type for injected map and the value instances will be the keys.
+ */
+ boolean unwrapValue();
+}
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java b/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
index 66a4951f..56433f78 100644
--- a/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
+++ b/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
@@ -121,10 +121,7 @@ public abstract class Multibinder<T> {
* itself bound with no binding annotation.
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, TypeLiteral<T> type) {
- binder = binder.skipSources(RealMultibinder.class, Multibinder.class);
- RealMultibinder<T> result = new RealMultibinder<T>(binder, type, Key.get(setOf(type)));
- binder.install(result);
- return result;
+ return newRealSetBinder(binder, Key.get(type));
}
/**
@@ -132,7 +129,7 @@ public abstract class Multibinder<T> {
* itself bound with no binding annotation.
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, Class<T> type) {
- return newSetBinder(binder, TypeLiteral.get(type));
+ return newRealSetBinder(binder, Key.get(type));
}
/**
@@ -141,11 +138,7 @@ public abstract class Multibinder<T> {
*/
public static <T> Multibinder<T> newSetBinder(
Binder binder, TypeLiteral<T> type, Annotation annotation) {
- binder = binder.skipSources(RealMultibinder.class, Multibinder.class);
- RealMultibinder<T> result =
- new RealMultibinder<T>(binder, type, Key.get(setOf(type), annotation));
- binder.install(result);
- return result;
+ return newRealSetBinder(binder, Key.get(type, annotation));
}
/**
@@ -154,7 +147,7 @@ public abstract class Multibinder<T> {
*/
public static <T> Multibinder<T> newSetBinder(
Binder binder, Class<T> type, Annotation annotation) {
- return newSetBinder(binder, TypeLiteral.get(type), annotation);
+ return newRealSetBinder(binder, Key.get(type, annotation));
}
/**
@@ -163,9 +156,24 @@ public abstract class Multibinder<T> {
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, TypeLiteral<T> type,
Class<? extends Annotation> annotationType) {
+ return newRealSetBinder(binder, Key.get(type, annotationType));
+ }
+
+ /**
+ * Returns a new multibinder that collects instances of the key's type in a {@link Set} that is
+ * itself bound with the annotation (if any) of the key.
+ */
+ public static <T> Multibinder<T> newSetBinder(Binder binder, Key<T> key) {
+ return newRealSetBinder(binder, key);
+ }
+
+ /**
+ * Implementation of newSetBinder.
+ */
+ static <T> RealMultibinder<T> newRealSetBinder(Binder binder, Key<T> key) {
binder = binder.skipSources(RealMultibinder.class, Multibinder.class);
- RealMultibinder<T> result =
- new RealMultibinder<T>(binder, type, Key.get(setOf(type), annotationType));
+ RealMultibinder<T> result = new RealMultibinder<T>(binder, key.getTypeLiteral(),
+ key.ofType(setOf(key.getTypeLiteral())));
binder.install(result);
return result;
}
@@ -176,7 +184,7 @@ public abstract class Multibinder<T> {
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, Class<T> type,
Class<? extends Annotation> annotationType) {
- return newSetBinder(binder, TypeLiteral.get(type), annotationType);
+ return newSetBinder(binder, Key.get(type, annotationType));
}
@SuppressWarnings("unchecked") // wrapping a T in a Set safely returns a Set<T>
@@ -295,11 +303,14 @@ public abstract class Multibinder<T> {
binder.install(new PermitDuplicatesModule(permitDuplicatesKey));
return this;
}
-
- @Override public LinkedBindingBuilder<T> addBinding() {
+
+ Key<T> getKeyForNewItem() {
checkConfiguration(!isInitialized(), "Multibinder was already initialized");
+ return Key.get(elementType, new RealElement(setName, MULTIBINDER, ""));
+ }
- return binder.bind(Key.get(elementType, new RealElement(setName, MULTIBINDER, "")));
+ @Override public LinkedBindingBuilder<T> addBinding() {
+ return binder.bind(getKeyForNewItem());
}
/**
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/MultibindingsScanner.java b/extensions/multibindings/src/com/google/inject/multibindings/MultibindingsScanner.java
new file mode 100644
index 00000000..02fce320
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/MultibindingsScanner.java
@@ -0,0 +1,192 @@
+/**
+ * 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.multibindings;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Binder;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
+import com.google.inject.spi.InjectionPoint;
+import com.google.inject.spi.ModuleAnnotatedMethodScanner;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Set;
+
+/**
+ * Scans a module for annotations that signal multibindings, mapbindings, and optional bindings.
+ */
+public class MultibindingsScanner {
+
+ private MultibindingsScanner() {}
+
+ /**
+ * Returns a module that, when installed, will scan all modules for methods with the annotations
+ * {@literal @}{@link ProvidesIntoMap}, {@literal @}{@link ProvidesIntoSet}, and
+ * {@literal @}{@link ProvidesIntoOptional}.
+ *
+ * <p>This is a convenience method, equivalent to doing
+ * {@code binder().scanModulesForAnnotatedMethods(MultibindingsScanner.scanner())}.
+ */
+ public static Module asModule() {
+ return new AbstractModule() {
+ @Override protected void configure() {
+ binder().scanModulesForAnnotatedMethods(Scanner.INSTANCE);
+ }
+ };
+ }
+
+ /**
+ * Returns a {@link ModuleAnnotatedMethodScanner} that, when bound, will scan all modules for
+ * methods with the annotations {@literal @}{@link ProvidesIntoMap},
+ * {@literal @}{@link ProvidesIntoSet}, and {@literal @}{@link ProvidesIntoOptional}.
+ */
+ public static ModuleAnnotatedMethodScanner scanner() {
+ return Scanner.INSTANCE;
+ }
+
+ private static class Scanner extends ModuleAnnotatedMethodScanner {
+ private static final Scanner INSTANCE = new Scanner();
+
+ @Override
+ public Set<? extends Class<? extends Annotation>> annotationClasses() {
+ return ImmutableSet.of(
+ ProvidesIntoSet.class, ProvidesIntoMap.class, ProvidesIntoOptional.class);
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"}) // mapKey doesn't know its key type
+ @Override
+ public <T> Key<T> prepareMethod(Binder binder, Annotation annotation, Key<T> key,
+ InjectionPoint injectionPoint) {
+ Method method = (Method) injectionPoint.getMember();
+ AnnotationOrError mapKey = findMapKeyAnnotation(binder, method);
+ if (annotation instanceof ProvidesIntoSet) {
+ if (mapKey.annotation != null) {
+ binder.addError("Found a MapKey annotation on non map binding at %s.", method);
+ }
+ return Multibinder.newRealSetBinder(binder, key).getKeyForNewItem();
+ } else if (annotation instanceof ProvidesIntoMap) {
+ if (mapKey.error) {
+ // Already failed on the MapKey, don't bother doing more work.
+ return key;
+ }
+ if (mapKey.annotation == null) {
+ // If no MapKey, make an error and abort.
+ binder.addError("No MapKey found for map binding at %s.", method);
+ return key;
+ }
+ TypeAndValue typeAndValue = typeAndValueOfMapKey(mapKey.annotation);
+ return MapBinder.newRealMapBinder(binder, typeAndValue.type, key)
+ .getKeyForNewValue(typeAndValue.value);
+ } else if (annotation instanceof ProvidesIntoOptional) {
+ if (mapKey.annotation != null) {
+ binder.addError("Found a MapKey annotation on non map binding at %s.", method);
+ }
+ switch (((ProvidesIntoOptional)annotation).value()) {
+ case DEFAULT:
+ return OptionalBinder.newRealOptionalBinder(binder, key).getKeyForDefaultBinding();
+ case ACTUAL:
+ return OptionalBinder.newRealOptionalBinder(binder, key).getKeyForActualBinding();
+ }
+ }
+ throw new IllegalStateException("Invalid annotation: " + annotation);
+ }
+ }
+
+ private static class AnnotationOrError {
+ final Annotation annotation;
+ final boolean error;
+ AnnotationOrError(Annotation annotation, boolean error) {
+ this.annotation = annotation;
+ this.error = error;
+ }
+
+ static AnnotationOrError forPossiblyNullAnnotation(Annotation annotation) {
+ return new AnnotationOrError(annotation, false);
+ }
+
+ static AnnotationOrError forError() {
+ return new AnnotationOrError(null, true);
+ }
+ }
+
+ private static AnnotationOrError findMapKeyAnnotation(Binder binder, Method method) {
+ Annotation foundAnnotation = null;
+ for (Annotation annotation : method.getAnnotations()) {
+ MapKey mapKey = annotation.annotationType().getAnnotation(MapKey.class);
+ if (mapKey != null) {
+ if (foundAnnotation != null) {
+ binder.addError("Found more than one MapKey annotations on %s.", method);
+ return AnnotationOrError.forError();
+ }
+ if (mapKey.unwrapValue()) {
+ try {
+ // validate there's a declared method called "value"
+ Method valueMethod = annotation.annotationType().getDeclaredMethod("value");
+ if (valueMethod.getReturnType().isArray()) {
+ binder.addError("Array types are not allowed in a MapKey with unwrapValue=true: %s",
+ annotation.annotationType());
+ return AnnotationOrError.forError();
+ }
+ } catch (NoSuchMethodException invalid) {
+ binder.addError("No 'value' method in MapKey with unwrapValue=true: %s",
+ annotation.annotationType());
+ return AnnotationOrError.forError();
+ }
+ }
+ foundAnnotation = annotation;
+ }
+ }
+ return AnnotationOrError.forPossiblyNullAnnotation(foundAnnotation);
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ static TypeAndValue<?> typeAndValueOfMapKey(Annotation mapKeyAnnotation) {
+ if (!mapKeyAnnotation.annotationType().getAnnotation(MapKey.class).unwrapValue()) {
+ return new TypeAndValue(TypeLiteral.get(mapKeyAnnotation.annotationType()), mapKeyAnnotation);
+ } else {
+ try {
+ Method valueMethod = mapKeyAnnotation.annotationType().getDeclaredMethod("value");
+ valueMethod.setAccessible(true);
+ TypeLiteral<?> returnType =
+ TypeLiteral.get(mapKeyAnnotation.annotationType()).getReturnType(valueMethod);
+ return new TypeAndValue(returnType, valueMethod.invoke(mapKeyAnnotation));
+ } catch (NoSuchMethodException e) {
+ throw new IllegalStateException(e);
+ } catch (SecurityException e) {
+ throw new IllegalStateException(e);
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(e);
+ } catch (InvocationTargetException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+ private static class TypeAndValue<T> {
+ final TypeLiteral<T> type;
+ final T value;
+
+ TypeAndValue(TypeLiteral<T> type, T value) {
+ this.type = type;
+ this.value = value;
+ }
+ }
+}
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/OptionalBinder.java b/extensions/multibindings/src/com/google/inject/multibindings/OptionalBinder.java
index 60e72d9f..d18f974d 100644
--- a/extensions/multibindings/src/com/google/inject/multibindings/OptionalBinder.java
+++ b/extensions/multibindings/src/com/google/inject/multibindings/OptionalBinder.java
@@ -129,7 +129,7 @@ import javax.inject.Qualifier;
* public class FrameworkModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
- * .setDefault().to(DEFAULT_LOOKUP_URL);
+ * .setDefault().toInstance(DEFAULT_LOOKUP_URL);
* }
* }</code></pre>
* With the above module, code can inject an {@code @LookupUrl String} and it
@@ -138,7 +138,7 @@ import javax.inject.Qualifier;
* public class UserLookupModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
- * .setBinding().to(CUSTOM_LOOKUP_URL);
+ * .setBinding().toInstance(CUSTOM_LOOKUP_URL);
* }
* }</code></pre>
* ... which will override the default value.
@@ -150,12 +150,12 @@ import javax.inject.Qualifier;
* public class FrameworkModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
- * .setDefault().to(DEFAULT_LOOKUP_URL);
+ * .setDefault().toInstance(DEFAULT_LOOKUP_URL);
* }
* }
* public class UserLookupModule extends AbstractModule {
* protected void configure() {
- * bind(Key.get(String.class, LookupUrl.class)).to(CUSTOM_LOOKUP_URL);
+ * bind(Key.get(String.class, LookupUrl.class)).toInstance(CUSTOM_LOOKUP_URL);
* }
* }</code></pre>
* ... would generate an error, because both the framework and the user are trying to bind
@@ -191,14 +191,18 @@ public abstract class OptionalBinder<T> {
private OptionalBinder() {}
public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, Class<T> type) {
- return newOptionalBinder(binder, Key.get(type));
+ return newRealOptionalBinder(binder, Key.get(type));
}
public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, TypeLiteral<T> type) {
- return newOptionalBinder(binder, Key.get(type));
+ return newRealOptionalBinder(binder, Key.get(type));
}
public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, Key<T> type) {
+ return newRealOptionalBinder(binder, type);
+ }
+
+ static <T> RealOptionalBinder<T> newRealOptionalBinder(Binder binder, Key<T> type) {
binder = binder.skipSources(OptionalBinder.class, RealOptionalBinder.class);
RealOptionalBinder<T> optionalBinder = new RealOptionalBinder<T>(binder, type);
binder.install(optionalBinder);
@@ -349,16 +353,24 @@ public abstract class OptionalBinder<T> {
binder.bind(typeKey).toProvider(new RealDirectTypeProvider());
}
- @Override public LinkedBindingBuilder<T> setDefault() {
+ Key<T> getKeyForDefaultBinding() {
checkConfiguration(!isInitialized(), "already initialized");
addDirectTypeBinding(binder);
- return binder.bind(defaultKey);
+ return defaultKey;
}
- @Override public LinkedBindingBuilder<T> setBinding() {
+ @Override public LinkedBindingBuilder<T> setDefault() {
+ return binder.bind(getKeyForDefaultBinding());
+ }
+
+ Key<T> getKeyForActualBinding() {
checkConfiguration(!isInitialized(), "already initialized");
addDirectTypeBinding(binder);
- return binder.bind(actualKey);
+ return actualKey;
+ }
+
+ @Override public LinkedBindingBuilder<T> setBinding() {
+ return binder.bind(getKeyForActualBinding());
}
@Override public void configure(Binder binder) {
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoMap.java b/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoMap.java
new file mode 100644
index 00000000..886a0fd1
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoMap.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.multibindings;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.Module;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates methods of a {@link Module} to add items to a {@link MapBinder}.
+ * The method's return type, binding annotation and additional key annotation determines
+ * what Map this will contribute to. For example,
+ *
+ * <pre>
+ * {@literal @}ProvidesIntoMap
+ * {@literal @}StringMapKey("Foo")
+ * {@literal @}Named("plugins")
+ * Plugin provideFooUrl(FooManager fm) { returm fm.getPlugin(); }
+ *
+ * {@literal @}ProvidesIntoMap
+ * {@literal @}StringMapKey("Bar")
+ * {@literal @}Named("urls")
+ * Plugin provideBarUrl(BarManager bm) { return bm.getPlugin(); }
+ * </pre>
+ *
+ * will add two items to the {@code @Named("urls") Map<String, Plugin>} map. The key 'Foo'
+ * will map to the provideFooUrl method, and the key 'Bar' will map to the provideBarUrl method.
+ * The values are bound as providers and will be evaluated at injection time.
+ *
+ * <p>Because the key is specified as an annotation, only Strings, Classes, enums, primitive
+ * types and annotation instances are supported as keys.
+ *
+ * @author sameb@google.com (Sam Berlin)
+ * @since 4.0
+ */
+@Documented
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface ProvidesIntoMap {
+}
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoOptional.java b/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoOptional.java
new file mode 100644
index 00000000..6f021700
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoOptional.java
@@ -0,0 +1,63 @@
+/**
+ * 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.multibindings;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.Module;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates methods of a {@link Module} to add items to a {@link Multibinder}.
+ * The method's return type and binding annotation determines what Optional this will
+ * contribute to. For example,
+ *
+ * <pre>
+ * {@literal @}ProvidesIntoOptional(DEFAULT)
+ * {@literal @}Named("url")
+ * String provideFooUrl(FooManager fm) { returm fm.getUrl(); }
+ *
+ * {@literal @}ProvidesIntoOptional(ACTUAL)
+ * {@literal @}Named("url")
+ * String provideBarUrl(BarManager bm) { return bm.getUrl(); }
+ * </pre>
+ *
+ * will set the default value of {@code @Named("url") Optional<String>} to foo's URL,
+ * and then override it to bar's URL.
+ *
+ * @author sameb@google.com (Sam Berlin)
+ * @since 4.0
+ */
+@Documented
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface ProvidesIntoOptional {
+ enum Type {
+ /** Corresponds to {@link OptionalBinder#setBinding}. */
+ ACTUAL,
+
+ /** Corresponds to {@link OptionalBinder#setDefault}. */
+ DEFAULT
+ }
+
+ /** Specifies if the binding is for the actual or default value. */
+ Type value();
+}
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoSet.java b/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoSet.java
new file mode 100644
index 00000000..b26df683
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoSet.java
@@ -0,0 +1,53 @@
+/**
+ * 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.multibindings;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.Module;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates methods of a {@link Module} to add items to a {@link Multibinder}.
+ * The method's return type and binding annotation determines what Set this will
+ * contribute to. For example,
+ *
+ * <pre>
+ * {@literal @}ProvidesIntoSet
+ * {@literal @}Named("urls")
+ * String provideFooUrl(FooManager fm) { returm fm.getUrl(); }
+ *
+ * {@literal @}ProvidesIntoSet
+ * {@literal @}Named("urls")
+ * String provideBarUrl(BarManager bm) { return bm.getUrl(); }
+ * </pre>
+ *
+ * will add two items to the {@code @Named("urls") Set<String>} set. The items are bound as
+ * providers and will be evaluated at injection time.
+ *
+ * @author sameb@google.com (Sam Berlin)
+ * @since 4.0
+ */
+@Documented
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface ProvidesIntoSet {
+}
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/StringMapKey.java b/extensions/multibindings/src/com/google/inject/multibindings/StringMapKey.java
new file mode 100644
index 00000000..dd4d99bc
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/StringMapKey.java
@@ -0,0 +1,35 @@
+/**
+ * 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.multibindings;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Allows {@literal @}{@link ProvidesIntoMap} to specify a string map key.
+ */
+@MapKey(unwrapValue = true)
+@Documented
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface StringMapKey {
+ String value();
+}
diff --git a/extensions/multibindings/test/com/google/inject/multibindings/AllTests.java b/extensions/multibindings/test/com/google/inject/multibindings/AllTests.java
index 9ea52db3..6806edde 100644
--- a/extensions/multibindings/test/com/google/inject/multibindings/AllTests.java
+++ b/extensions/multibindings/test/com/google/inject/multibindings/AllTests.java
@@ -30,6 +30,7 @@ public class AllTests {
suite.addTestSuite(MultibinderTest.class);
suite.addTestSuite(OptionalBinderTest.class);
suite.addTestSuite(RealElementTest.class);
+ suite.addTestSuite(ProvidesIntoTest.class);
return suite;
}
}
diff --git a/extensions/multibindings/test/com/google/inject/multibindings/ProvidesIntoTest.java b/extensions/multibindings/test/com/google/inject/multibindings/ProvidesIntoTest.java
new file mode 100644
index 00000000..62c7a58b
--- /dev/null
+++ b/extensions/multibindings/test/com/google/inject/multibindings/ProvidesIntoTest.java
@@ -0,0 +1,373 @@
+/**
+ * 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.multibindings;
+
+import static com.google.inject.Asserts.assertContains;
+import static com.google.inject.name.Names.named;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.CreationException;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.multibindings.ProvidesIntoOptional.Type;
+import com.google.inject.name.Named;
+
+import junit.framework.TestCase;
+
+import java.lang.annotation.Retention;
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tests the various @ProvidesInto annotations.
+ *
+ * @author sameb@google.com (Sam Berlin)
+ */
+public class ProvidesIntoTest extends TestCase {
+
+ public void testAnnotation() throws Exception {
+ Injector injector = Guice.createInjector(MultibindingsScanner.asModule(), new AbstractModule() {
+ @Override protected void configure() {}
+
+ @ProvidesIntoSet
+ @Named("foo")
+ String setFoo() { return "foo"; }
+
+ @ProvidesIntoSet
+ @Named("foo")
+ String setFoo2() { return "foo2"; }
+
+ @ProvidesIntoSet
+ @Named("bar")
+ String setBar() { return "bar"; }
+
+ @ProvidesIntoSet
+ @Named("bar")
+ String setBar2() { return "bar2"; }
+
+ @ProvidesIntoSet
+ String setNoAnnotation() { return "na"; }
+
+ @ProvidesIntoSet
+ String setNoAnnotation2() { return "na2"; }
+
+ @ProvidesIntoMap
+ @StringMapKey("fooKey")
+ @Named("foo")
+ String mapFoo() { return "foo"; }
+
+ @ProvidesIntoMap
+ @StringMapKey("foo2Key")
+ @Named("foo")
+ String mapFoo2() { return "foo2"; }
+
+ @ProvidesIntoMap
+ @ClassMapKey(String.class)
+ @Named("bar")
+ String mapBar() { return "bar"; }
+
+ @ProvidesIntoMap
+ @ClassMapKey(Number.class)
+ @Named("bar")
+ String mapBar2() { return "bar2"; }
+
+ @ProvidesIntoMap
+ @TestEnumKey(TestEnum.A)
+ String mapNoAnnotation() { return "na"; }
+
+ @ProvidesIntoMap
+ @TestEnumKey(TestEnum.B)
+ String mapNoAnnotation2() { return "na2"; }
+
+ @ProvidesIntoMap
+ @WrappedKey(number = 1)
+ Number wrapped1() { return 11; }
+
+ @ProvidesIntoMap
+ @WrappedKey(number = 2)
+ Number wrapped2() { return 22; }
+
+ @ProvidesIntoOptional(ProvidesIntoOptional.Type.DEFAULT)
+ @Named("foo")
+ String optionalDefaultFoo() { return "foo"; }
+
+ @ProvidesIntoOptional(ProvidesIntoOptional.Type.ACTUAL)
+ @Named("foo")
+ String optionalActualFoo() { return "foo2"; }
+
+ @ProvidesIntoOptional(ProvidesIntoOptional.Type.DEFAULT)
+ @Named("bar")
+ String optionalDefaultBar() { return "bar"; }
+
+ @ProvidesIntoOptional(ProvidesIntoOptional.Type.ACTUAL)
+ String optionalActualBar() { return "na2"; }
+ });
+
+ Set<String> fooSet = injector.getInstance(new Key<Set<String>>(named("foo")) {});
+ assertEquals(ImmutableSet.of("foo", "foo2"), fooSet);
+
+ Set<String> barSet = injector.getInstance(new Key<Set<String>>(named("bar")) {});
+ assertEquals(ImmutableSet.of("bar", "bar2"), barSet);
+
+ Set<String> noAnnotationSet = injector.getInstance(new Key<Set<String>>() {});
+ assertEquals(ImmutableSet.of("na", "na2"), noAnnotationSet);
+
+ Map<String, String> fooMap =
+ injector.getInstance(new Key<Map<String, String>>(named("foo")) {});
+ assertEquals(ImmutableMap.of("fooKey", "foo", "foo2Key", "foo2"), fooMap);
+
+ Map<Class<?>, String> barMap =
+ injector.getInstance(new Key<Map<Class<?>, String>>(named("bar")) {});
+ assertEquals(ImmutableMap.of(String.class, "bar", Number.class, "bar2"), barMap);
+
+ Map<TestEnum, String> noAnnotationMap =
+ injector.getInstance(new Key<Map<TestEnum, String>>() {});
+ assertEquals(ImmutableMap.of(TestEnum.A, "na", TestEnum.B, "na2"), noAnnotationMap);
+
+ Map<WrappedKey, Number> wrappedMap =
+ injector.getInstance(new Key<Map<WrappedKey, Number>>() {});
+ assertEquals(ImmutableMap.of(wrappedKeyFor(1), 11, wrappedKeyFor(2), 22), wrappedMap);
+
+ Optional<String> fooOptional =
+ injector.getInstance(new Key<Optional<String>>(named("foo")) {});
+ assertEquals("foo2", fooOptional.get());
+
+ Optional<String> barOptional =
+ injector.getInstance(new Key<Optional<String>>(named("bar")) {});
+ assertEquals("bar", barOptional.get());
+
+ Optional<String> noAnnotationOptional =
+ injector.getInstance(new Key<Optional<String>>() {});
+ assertEquals("na2", noAnnotationOptional.get());
+ }
+
+ enum TestEnum {
+ A, B
+ }
+
+ @MapKey(unwrapValue = true)
+ @Retention(RUNTIME)
+ @interface TestEnumKey {
+ TestEnum value();
+ }
+
+ @MapKey(unwrapValue = false)
+ @Retention(RUNTIME)
+ @interface WrappedKey {
+ int number();
+ }
+
+ @SuppressWarnings("unused") @WrappedKey(number=1) private static Object wrappedKey1Holder;
+ @SuppressWarnings("unused") @WrappedKey(number=2) private static Object wrappedKey2Holder;
+ WrappedKey wrappedKeyFor(int number) throws Exception {
+ Field field;
+ switch (number) {
+ case 1:
+ field = ProvidesIntoTest.class.getDeclaredField("wrappedKey1Holder");
+ break;
+ case 2:
+ field = ProvidesIntoTest.class.getDeclaredField("wrappedKey2Holder");
+ break;
+ default:
+ throw new IllegalArgumentException("only 1 or 2 supported");
+ }
+ return field.getAnnotation(WrappedKey.class);
+ }
+
+ public void testDoubleScannerIsIgnored() {
+ Injector injector = Guice.createInjector(
+ MultibindingsScanner.asModule(),
+ MultibindingsScanner.asModule(),
+ new AbstractModule() {
+ @Override protected void configure() {}
+ @ProvidesIntoSet String provideFoo() { return "foo"; }
+ }
+ );
+ assertEquals(ImmutableSet.of("foo"), injector.getInstance(new Key<Set<String>>() {}));
+ }
+
+ @MapKey(unwrapValue = true)
+ @Retention(RUNTIME)
+ @interface ArrayUnwrappedKey {
+ int[] value();
+ }
+
+ public void testArrayKeys_unwrapValuesTrue() {
+ Module m = new AbstractModule() {
+ @Override protected void configure() {}
+ @ProvidesIntoMap @ArrayUnwrappedKey({1, 2}) String provideFoo() { return "foo"; }
+ };
+ try {
+ Guice.createInjector(MultibindingsScanner.asModule(), m);
+ fail();
+ } catch (CreationException ce) {
+ assertEquals(1, ce.getErrorMessages().size());
+ assertContains(ce.getMessage(),
+ "Array types are not allowed in a MapKey with unwrapValue=true: "
+ + ArrayUnwrappedKey.class.getName(),
+ "at " + m.getClass().getName() + ".provideFoo(");
+ }
+ }
+
+ @MapKey(unwrapValue = false)
+ @Retention(RUNTIME)
+ @interface ArrayWrappedKey {
+ int[] number();
+ }
+
+ @SuppressWarnings("unused") @ArrayWrappedKey(number={1, 2}) private static Object arrayWrappedKeyHolder12;
+ @SuppressWarnings("unused") @ArrayWrappedKey(number={3, 4}) private static Object arrayWrappedKeyHolder34;
+ ArrayWrappedKey arrayWrappedKeyFor(int number) throws Exception {
+ Field field;
+ switch (number) {
+ case 12:
+ field = ProvidesIntoTest.class.getDeclaredField("arrayWrappedKeyHolder12");
+ break;
+ case 34:
+ field = ProvidesIntoTest.class.getDeclaredField("arrayWrappedKeyHolder34");
+ break;
+ default:
+ throw new IllegalArgumentException("only 1 or 2 supported");
+ }
+ return field.getAnnotation(ArrayWrappedKey.class);
+ }
+
+ public void testArrayKeys_unwrapValuesFalse() throws Exception {
+ Module m = new AbstractModule() {
+ @Override protected void configure() {}
+ @ProvidesIntoMap @ArrayWrappedKey(number = {1, 2}) String provideFoo() { return "foo"; }
+ @ProvidesIntoMap @ArrayWrappedKey(number = {3, 4}) String provideBar() { return "bar"; }
+ };
+ Injector injector = Guice.createInjector(MultibindingsScanner.asModule(), m);
+ Map<ArrayWrappedKey, String> map =
+ injector.getInstance(new Key<Map<ArrayWrappedKey, String>>() {});
+ ArrayWrappedKey key12 = arrayWrappedKeyFor(12);
+ ArrayWrappedKey key34 = arrayWrappedKeyFor(34);
+ assertEquals("foo", map.get(key12));
+ assertEquals("bar", map.get(key34));
+ assertEquals(2, map.size());
+ }
+
+ public void testProvidesIntoSetWithMapKey() {
+ Module m = new AbstractModule() {
+ @Override protected void configure() {}
+ @ProvidesIntoSet @TestEnumKey(TestEnum.A) String provideFoo() { return "foo"; }
+ };
+ try {
+ Guice.createInjector(MultibindingsScanner.asModule(), m);
+ fail();
+ } catch (CreationException ce) {
+ assertEquals(1, ce.getErrorMessages().size());
+ assertContains(ce.getMessage(), "Found a MapKey annotation on non map binding at "
+ + m.getClass().getName() + ".provideFoo");
+ }
+ }
+
+ public void testProvidesIntoOptionalWithMapKey() {
+ Module m = new AbstractModule() {
+ @Override protected void configure() {}
+
+ @ProvidesIntoOptional(Type.ACTUAL)
+ @TestEnumKey(TestEnum.A)
+ String provideFoo() {
+ return "foo";
+ }
+ };
+ try {
+ Guice.createInjector(MultibindingsScanner.asModule(), m);
+ fail();
+ } catch (CreationException ce) {
+ assertEquals(1, ce.getErrorMessages().size());
+ assertContains(ce.getMessage(), "Found a MapKey annotation on non map binding at "
+ + m.getClass().getName() + ".provideFoo");
+ }
+ }
+
+ public void testProvidesIntoMapWithoutMapKey() {
+ Module m = new AbstractModule() {
+ @Override protected void configure() {}
+ @ProvidesIntoMap String provideFoo() { return "foo"; }
+ };
+ try {
+ Guice.createInjector(MultibindingsScanner.asModule(), m);
+ fail();
+ } catch (CreationException ce) {
+ assertEquals(1, ce.getErrorMessages().size());
+ assertContains(ce.getMessage(), "No MapKey found for map binding at "
+ + m.getClass().getName() + ".provideFoo");
+ }
+ }
+
+ @MapKey(unwrapValue = true)
+ @Retention(RUNTIME)
+ @interface TestEnumKey2 {
+ TestEnum value();
+ }
+
+ public void testMoreThanOneMapKeyAnnotation() {
+ Module m = new AbstractModule() {
+ @Override protected void configure() {}
+
+ @ProvidesIntoMap
+ @TestEnumKey(TestEnum.A)
+ @TestEnumKey2(TestEnum.B)
+ String provideFoo() {
+ return "foo";
+ }
+ };
+ try {
+ Guice.createInjector(MultibindingsScanner.asModule(), m);
+ fail();
+ } catch (CreationException ce) {
+ assertEquals(1, ce.getErrorMessages().size());
+ assertContains(ce.getMessage(), "Found more than one MapKey annotations on "
+ + m.getClass().getName() + ".provideFoo");
+ }
+ }
+
+ @MapKey(unwrapValue = true)
+ @Retention(RUNTIME)
+ @interface MissingValueMethod {
+ }
+
+ public void testMapKeyMissingValueMethod() {
+ Module m = new AbstractModule() {
+ @Override protected void configure() {}
+
+ @ProvidesIntoMap
+ @MissingValueMethod
+ String provideFoo() {
+ return "foo";
+ }
+ };
+ try {
+ Guice.createInjector(MultibindingsScanner.asModule(), m);
+ fail();
+ } catch (CreationException ce) {
+ assertEquals(1, ce.getErrorMessages().size());
+ assertContains(ce.getMessage(), "No 'value' method in MapKey with unwrapValue=true: "
+ + MissingValueMethod.class.getName());
+ }
+ }
+}
diff --git a/extensions/pom.xml b/extensions/pom.xml
index 084967cf..80824241 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -18,6 +18,8 @@
<modules>
<module>assistedinject</module>
+ <!-- TODO(cgruber): Comment out dagger-adapter module if guice releases before dagger 2.x -->
+ <module>dagger-adapter</module>
<module>grapher</module>
<module>jmx</module>
<module>jndi</module>
diff --git a/extensions/spring/.gitignore b/extensions/spring/.gitignore
new file mode 100644
index 00000000..84c048a7
--- /dev/null
+++ b/extensions/spring/.gitignore
@@ -0,0 +1 @@
+/build/
diff --git a/extensions/struts2/.gitignore b/extensions/struts2/.gitignore
new file mode 100644
index 00000000..84c048a7
--- /dev/null
+++ b/extensions/struts2/.gitignore
@@ -0,0 +1 @@
+/build/
diff --git a/pom.xml b/pom.xml
index b5c94aff..70d9fa45 100644
--- a/pom.xml
+++ b/pom.xml
@@ -467,4 +467,15 @@ See the Apache License Version 2.0 for the specific language governing permissio
</profile>
</profiles>
+ <!-- TODO(cgruber): Update the google parent pom or migrate to sonatype's -->
+ <!-- TODO(cgruber): Comment out dagger-adapter from extensions/pom.xml if v2 is not released. -->
+ <repositories>
+ <repository>
+ <id>sonatype-nexus-snapshots</id>
+ <name>Sonatype Nexus Snapshots</name>
+ <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+ <releases><enabled>false</enabled></releases>
+ <snapshots><enabled>true</enabled></snapshots>
+ </repository>
+ </repositories>
</project>