Merge pull request #905 from google/moe_writing_branch_from_b605a34702d8d8112983aca891e3e2b6987ec45e

Merge internal changes
diff --git a/build.properties b/build.properties
index f1e0474..cca000c 100644
--- a/build.properties
+++ b/build.properties
@@ -8,6 +8,7 @@
 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 @@
   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 1efac5f..3d0c772 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 e895759..e930c3b 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.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 @@
    * @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 7d1eb66..db6a7a6 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.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 @@
   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 @@
 
   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 @@
 
   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 6100879..5982bb3 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.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 @@
       // 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 @@
       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 @@
       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 71d953c..6a6f7ef 100644
--- a/core/src/com/google/inject/internal/InternalContext.java
+++ b/core/src/com/google/inject/internal/InternalContext.java
@@ -43,11 +43,11 @@
   /**
    * 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 @@
   
   /** 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 0000000..85c721d
--- /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 98eb45d..7682dab 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.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 @@
   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 @@
   /**
    * 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 @@
     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 @@
     @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 @@
 
   @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 d0ee4fb..8a828f2 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.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 @@
       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 @@
   
   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 0780bf8..1bbea0d 100644
--- a/core/src/com/google/inject/spi/DefaultElementVisitor.java
+++ b/core/src/com/google/inject/spi/DefaultElementVisitor.java
@@ -102,4 +102,8 @@
   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 5e99086..f0d9d13 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 @@
    * @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 986582e..46072e3 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.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.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -107,11 +108,15 @@
     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 @@
       this.elements = elements;
     }
 
+    @Override
     public void configure(Binder binder) {
       for (Element element : elements) {
         element.applyTo(binder);
@@ -138,22 +144,37 @@
     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 @@
           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 @@
       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 @@
     }
     /*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 @@
       }
     }
 
+    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 @@
         }
         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 @@
       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 @@
         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 @@
       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 @@
       }
       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 @@
     }
 
     /**
-     * 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 @@
       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 99b6d91..36adc85 100644
--- a/core/src/com/google/inject/spi/ModuleAnnotatedMethodScanner.java
+++ b/core/src/com/google/inject/spi/ModuleAnnotatedMethodScanner.java
@@ -18,8 +18,6 @@
 
 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 @@
  * 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 0000000..d632420
--- /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 19add7e..1e07de2 100644
--- a/core/src/com/google/inject/spi/ModuleSource.java
+++ b/core/src/com/google/inject/spi/ModuleSource.java
@@ -27,7 +27,7 @@
 /**
  * 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 @@
    * 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 @@
   /**
    * 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 @@
   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 @@
 
   /**
    * 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 @@
   }
 
   /**
-   * 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 @@
     }
     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 @@
     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 c166b8e..08ec92c 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.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 @@
       
       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 @@
       }
     }
   }
+
+  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 b003fb5..3797d88 100644
--- a/core/test/com/google/inject/internal/WeakKeySetTest.java
+++ b/core/test/com/google/inject/internal/WeakKeySetTest.java
@@ -41,6 +41,7 @@
 /*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 @@
       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 62f8220..e73a9af 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.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 @@
 
 /** 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 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 @@
           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 3b8e05b..16d7a2c 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.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.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 @@
     });
     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 0000000..84c048a
--- /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 6ec0cd4..b40e19f 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.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 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 @@
   /** 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 @@
       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 @@
           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 @@
       }
 
       assistDataByMethod = assistDataBuilder.build();
+      methodHandleByMethod = methodHandleBuilder.build();
     } catch (ErrorsException e) {
       throw new ConfigurationException(e.getErrors().getMessages());
     }
+  }
 
-    factory = factoryRawType.cast(Proxy.newProxyInstance(BytecodeGen.getClassLoader(factoryRawType),
-        new Class[] { factoryRawType }, this));
+  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;
+  }
+
+  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 @@
    * 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 @@
     }
 
     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 @@
               + " (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 8903042..c0e9bbd 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 @@
       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 0000000..97e53c2
--- /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 0000000..27eb429
--- /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 0000000..3690cdc
--- /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 0000000..10af044
--- /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 0000000..eb7aac5
--- /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 0000000..0dbdda2
--- /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 0000000..30e1d18
--- /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 0000000..84c048a
--- /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 0000000..84c048a
--- /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 0000000..84c048a
--- /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 0000000..47c8c17
--- /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 89df771..fc3d74f 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 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 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 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 @@
         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 @@
           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 @@
           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 0000000..bcb6a3b
--- /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 66a4951..56433f7 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 @@
    * 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 @@
    * 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 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 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 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 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 @@
       binder.install(new PermitDuplicatesModule(permitDuplicatesKey));
       return this;
     }
+    
+    Key<T> getKeyForNewItem() {
+      checkConfiguration(!isInitialized(), "Multibinder was already initialized");
+      return Key.get(elementType, new RealElement(setName, MULTIBINDER, ""));
+    }
 
     @Override public LinkedBindingBuilder<T> addBinding() {
-      checkConfiguration(!isInitialized(), "Multibinder was already initialized");
-
-      return binder.bind(Key.get(elementType, new RealElement(setName, MULTIBINDER, "")));
+      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 0000000..02fce32
--- /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 60e72d9..d18f974 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 @@
  * 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 @@
  * 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 @@
  * 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 @@
   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 @@
       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> setDefault() {
+      return binder.bind(getKeyForDefaultBinding());
+    }
+    
+    Key<T> getKeyForActualBinding() {
+      checkConfiguration(!isInitialized(), "already initialized");      
+      addDirectTypeBinding(binder);
+      return actualKey;
     }
 
     @Override public LinkedBindingBuilder<T> setBinding() {
-      checkConfiguration(!isInitialized(), "already initialized");      
-      addDirectTypeBinding(binder);
-      return binder.bind(actualKey);
+      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 0000000..886a0fd
--- /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 0000000..6f02170
--- /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 0000000..b26df68
--- /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 0000000..dd4d99b
--- /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 9ea52db..6806edd 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 @@
     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 0000000..62c7a58
--- /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 084967c..8082424 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 0000000..84c048a
--- /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 0000000..84c048a
--- /dev/null
+++ b/extensions/struts2/.gitignore
@@ -0,0 +1 @@
+/build/
diff --git a/pom.xml b/pom.xml
index b5c94af..70d9fa4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -467,4 +467,15 @@
     </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>