Inline requests for map bindings

Under the hood, remove ContributionBinding.Kind.SYNTHETIC_MAP. Map<K, V> and Map<K, Produced<V>> bindings now act just like Map<K, FrameworkType<V>>, and have dependencies on their contributions directly instead of wrapping a another FrameworkType<Map<K, FrameworkType<V>>>

This is a rollforward of 1a5123d0. The original commit changed the semantics of dependency requests for Map<K, V> and Set<T> bindings.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=167024142
diff --git a/java/dagger/internal/MapBuilder.java b/java/dagger/internal/MapBuilder.java
new file mode 100644
index 0000000..1560491
--- /dev/null
+++ b/java/dagger/internal/MapBuilder.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Dagger Authors.
+ *
+ * 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 dagger.internal;
+
+import static dagger.internal.DaggerCollections.newLinkedHashMapWithExpectedSize;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * A fluent builder class that returns a {@link Map}. Used in component implementations where a map
+ * must be created in one fluent statement for inlined request fulfillments.
+ */
+public final class MapBuilder<K, V> {
+  private final Map<K, V> contributions;
+
+  private MapBuilder(int size) {
+    contributions = newLinkedHashMapWithExpectedSize(size);
+  }
+
+  /**
+   * Creates a new {@link MapBuilder} with {@code size} elements.
+   */
+  public static <K, V> MapBuilder<K, V> newMapBuilder(int size) {
+    return new MapBuilder<>(size);
+  }
+
+  public MapBuilder<K, V> put(K key, V value) {
+    contributions.put(key, value);
+    return this;
+  }
+
+  public Map<K, V> build() {
+    switch (contributions.size()) {
+      case 0:
+        return Collections.emptyMap();
+      default:
+        return Collections.unmodifiableMap(contributions);
+    }
+  }
+}
diff --git a/java/dagger/internal/MapFactory.java b/java/dagger/internal/MapFactory.java
index d4b9189..b6f925a 100644
--- a/java/dagger/internal/MapFactory.java
+++ b/java/dagger/internal/MapFactory.java
@@ -17,8 +17,11 @@
 package dagger.internal;
 
 import static dagger.internal.DaggerCollections.newLinkedHashMapWithExpectedSize;
+import static dagger.internal.Preconditions.checkNotNull;
 import static java.util.Collections.unmodifiableMap;
 
+import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 import javax.inject.Provider;
@@ -32,18 +35,28 @@
  *
  */
 public final class MapFactory<K, V> implements Factory<Map<K, V>> {
+  private static final Provider<Map<Object, Object>> EMPTY =
+      InstanceFactory.create(Collections.emptyMap());
+
   private final Map<K, Provider<V>> contributingMap;
 
-  private MapFactory(Map<K, Provider<V>> map) {
-    this.contributingMap = unmodifiableMap(map);
+  /**
+   * Returns a new {@link Builder}
+   */
+  public static <K, V> Builder<K, V> builder(int size) {
+    return new Builder<>(size);
   }
 
   /**
-   * Returns a new MapFactory.
+   * Returns a factory of an empty map.
    */
-  public static <K, V> MapFactory<K, V> create(Provider<Map<K, Provider<V>>> mapProviderFactory) {
-    Map<K, Provider<V>> map = mapProviderFactory.get();
-    return new MapFactory<K, V>(map);
+  @SuppressWarnings("unchecked") // safe contravariant cast
+  public static <K, V> Provider<Map<K, V>> emptyMapProvider() {
+    return (Provider<Map<K, V>>) (Provider) EMPTY;
+  }
+
+  private MapFactory(Map<K, Provider<V>> map) {
+    this.contributingMap = unmodifiableMap(map);
   }
 
   /**
@@ -58,4 +71,25 @@
     }
     return unmodifiableMap(result);
   }
+
+  // TODO(ronshapiro): can we merge the builders? Or maybe just use a (Immutable)MapBuilder?
+  /** A builder for {@link MapFactory}. */
+  public static final class Builder<K, V> {
+    private final LinkedHashMap<K, Provider<V>> map;
+
+    private Builder(int size) {
+      this.map = newLinkedHashMapWithExpectedSize(size);
+    }
+
+    /** Associates {@code key} with {@code providerOfValue}. */
+    public Builder<K, V> put(K key, Provider<V> providerOfValue) {
+      map.put(checkNotNull(key, "key"), checkNotNull(providerOfValue, "provider"));
+      return this;
+    }
+
+    /** Returns a new {@link MapProviderFactory}. */
+    public MapFactory<K, V> build() {
+      return new MapFactory<>(map);
+    }
+  }
 }
diff --git a/java/dagger/internal/MapProviderFactory.java b/java/dagger/internal/MapProviderFactory.java
index c27beb2..2458a3c 100644
--- a/java/dagger/internal/MapProviderFactory.java
+++ b/java/dagger/internal/MapProviderFactory.java
@@ -17,10 +17,10 @@
 package dagger.internal;
 
 import static dagger.internal.DaggerCollections.newLinkedHashMapWithExpectedSize;
+import static dagger.internal.Preconditions.checkNotNull;
 import static java.util.Collections.unmodifiableMap;
 
 import dagger.Lazy;
-import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import javax.inject.Provider;
@@ -34,24 +34,13 @@
  */
 public final class MapProviderFactory<K, V>
     implements Factory<Map<K, Provider<V>>>, Lazy<Map<K, Provider<V>>> {
-  private static final MapProviderFactory<Object, Object> EMPTY =
-      new MapProviderFactory<Object, Object>(Collections.<Object, Provider<Object>>emptyMap());
-
   private final Map<K, Provider<V>> contributingMap;
 
   /**
    * Returns a new {@link Builder}
    */
   public static <K, V> Builder<K, V> builder(int size) {
-    return new Builder<K, V>(size);
-  }
-
-  /**
-   * Returns a factory of an empty map.
-   */
-  @SuppressWarnings("unchecked") // safe contravariant cast
-  public static <K, V> MapProviderFactory<K, V> empty() {
-    return (MapProviderFactory<K, V>) EMPTY;
+    return new Builder<>(size);
   }
 
   private MapProviderFactory(Map<K, Provider<V>> contributingMap) {
@@ -68,37 +57,23 @@
     return this.contributingMap;
   }
 
-  /**
-   * A builder to help build the {@link MapProviderFactory}
-   */
+  /** A builder for {@link MapProviderFactory}. */
   public static final class Builder<K, V> {
-    private final LinkedHashMap<K, Provider<V>> mapBuilder;
+    private final LinkedHashMap<K, Provider<V>> map;
 
     private Builder(int size) {
-      // TODO(user): consider which way to initialize mapBuilder is better
-      this.mapBuilder = newLinkedHashMapWithExpectedSize(size);
+      this.map = newLinkedHashMapWithExpectedSize(size);
     }
 
-    /**
-     * Returns a new {@link MapProviderFactory}
-     */
-    public MapProviderFactory<K, V> build() {
-      return new MapProviderFactory<K, V>(this.mapBuilder);
-    }
-
-    /**
-     * Associate k with providerOfValue in {@code Builder}
-     */
+    /** Associates {@code key} with {@code providerOfValue}. */
     public Builder<K, V> put(K key, Provider<V> providerOfValue) {
-      if (key == null) {
-        throw new NullPointerException("The key is null");
-      }
-      if (providerOfValue == null) {
-        throw new NullPointerException("The provider of the value is null");
-      }
-
-      this.mapBuilder.put(key, providerOfValue);
+      map.put(checkNotNull(key, "key"), checkNotNull(providerOfValue, "provider"));
       return this;
     }
+
+    /** Returns a new {@link MapProviderFactory}. */
+    public MapProviderFactory<K, V> build() {
+      return new MapProviderFactory<>(map);
+    }
   }
 }
diff --git a/java/dagger/internal/codegen/BindingExpression.java b/java/dagger/internal/codegen/BindingExpression.java
index 093e5c5..06c8e64 100644
--- a/java/dagger/internal/codegen/BindingExpression.java
+++ b/java/dagger/internal/codegen/BindingExpression.java
@@ -198,6 +198,9 @@
         case SYNTHETIC_MULTIBOUND_SET:
           return new SetBindingExpression(
               provisionBinding, graph, componentBindingExpressions, bindingExpression, elements);
+        case SYNTHETIC_MULTIBOUND_MAP:
+          return new MapBindingExpression(
+              provisionBinding, graph, componentBindingExpressions, bindingExpression, elements);
         case SYNTHETIC_OPTIONAL_BINDING:
           return new OptionalBindingExpression(
               provisionBinding, bindingExpression, componentBindingExpressions);
diff --git a/java/dagger/internal/codegen/BindingGraph.java b/java/dagger/internal/codegen/BindingGraph.java
index 110d08e..14b9c7d 100644
--- a/java/dagger/internal/codegen/BindingGraph.java
+++ b/java/dagger/internal/codegen/BindingGraph.java
@@ -35,7 +35,6 @@
 import com.google.auto.common.MoreTypes;
 import com.google.auto.value.AutoValue;
 import com.google.auto.value.extension.memoized.Memoized;
-import com.google.common.base.VerifyException;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -519,7 +518,6 @@
 
             ImmutableSet.Builder<Optional<ContributionBinding>> maybeContributionBindings =
                 ImmutableSet.builder();
-            maybeContributionBindings.add(syntheticMapOfValuesBinding(requestKey));
             maybeContributionBindings.add(
                 syntheticMultibinding(
                     requestKey, multibindingContributions, multibindingDeclarations));
@@ -585,71 +583,17 @@
             owningResolver.componentDescriptor.subcomponentsByBuilderType().get(builderType));
       }
 
-      private Iterable<Key> keysMatchingRequest(Key requestKey) {
+      private ImmutableSet<Key> keysMatchingRequest(Key requestKey) {
         ImmutableSet.Builder<Key> keys = ImmutableSet.builder();
         keys.add(requestKey);
         keyFactory.unwrapSetKey(requestKey, Produced.class).ifPresent(keys::add);
         keyFactory.rewrapMapKey(requestKey, Producer.class, Provider.class).ifPresent(keys::add);
         keyFactory.rewrapMapKey(requestKey, Provider.class, Producer.class).ifPresent(keys::add);
+        keys.addAll(keyFactory.implicitFrameworkMapKeys(requestKey));
         return keys.build();
       }
 
       /**
-       * If {@code key} is a {@code Map<K, V>} or {@code Map<K, Produced<V>>}, and there are any
-       * multibinding contributions or declarations that apply to that map, returns a synthetic
-       * binding for the {@code key} that depends on an {@linkplain #syntheticMultibinding(Key,
-       * Iterable, Iterable) underlying synthetic multibinding}.
-       *
-       * <p>The returned binding has the same {@link BindingType} as the underlying synthetic
-       * multibinding.
-       */
-      private Optional<ContributionBinding> syntheticMapOfValuesBinding(final Key key) {
-        return syntheticMultibinding(
-                key,
-                multibindingContributionsForValueMap(key),
-                multibindingDeclarationsForValueMap(key))
-            .map(
-                syntheticMultibinding -> {
-                  switch (syntheticMultibinding.bindingType()) {
-                    case PROVISION:
-                      return provisionBindingFactory.syntheticMapOfValuesBinding(key);
-
-                    case PRODUCTION:
-                      return productionBindingFactory.syntheticMapOfValuesOrProducedBinding(key);
-
-                    default:
-                      throw new VerifyException(syntheticMultibinding.toString());
-                  }
-                });
-      }
-
-      /**
-       * If {@code key} is for {@code Map<K, V>} or {@code Map<K, Produced<V>>}, returns all
-       * multibinding contributions whose key is for {@code Map<K, Provider<V>>} or {@code Map<K,
-       * Producer<V>>} with the same qualifier and {@code K} and {@code V}.
-       */
-      private ImmutableSet<ContributionBinding> multibindingContributionsForValueMap(Key key) {
-        return keyFactory
-            .implicitFrameworkMapKeys(key)
-            .stream()
-            .flatMap(mapKey -> getExplicitMultibindings(mapKey).stream())
-            .collect(toImmutableSet());
-      }
-
-      /**
-       * If {@code key} is for {@code Map<K, V>} or {@code Map<K, Produced<V>>}, returns all
-       * multibinding declarations whose key is for {@code Map<K, Provider<V>>} or {@code Map<K,
-       * Producer<V>>} with the same qualifier and {@code K} and {@code V}.
-       */
-      private ImmutableSet<MultibindingDeclaration> multibindingDeclarationsForValueMap(Key key) {
-        return keyFactory
-            .implicitFrameworkMapKeys(key)
-            .stream()
-            .flatMap(mapKey -> getMultibindingDeclarations(mapKey).stream())
-            .collect(toImmutableSet());
-      }
-
-      /**
        * Returns a synthetic binding that depends on individual multibinding contributions.
        *
        * <p>If there are no {@code multibindingContributions} or {@code multibindingDeclarations},
@@ -1165,7 +1109,9 @@
                   .stream()
                   .map(ContributionBinding::bindingKind)
                   .anyMatch(SYNTHETIC_MULTIBOUND_KINDS::contains)
-              && !getLocalExplicitMultibindings(resolvedBindings.key()).isEmpty();
+              && keysMatchingRequest(resolvedBindings.key())
+                  .stream()
+                  .anyMatch(key -> !getLocalExplicitMultibindings(key).isEmpty());
         }
 
         /**
diff --git a/java/dagger/internal/codegen/BindingGraphValidator.java b/java/dagger/internal/codegen/BindingGraphValidator.java
index 1bbcbdc..32143d0 100644
--- a/java/dagger/internal/codegen/BindingGraphValidator.java
+++ b/java/dagger/internal/codegen/BindingGraphValidator.java
@@ -30,7 +30,6 @@
 import static dagger.internal.codegen.ConfigurationAnnotations.getComponentAnnotation;
 import static dagger.internal.codegen.ConfigurationAnnotations.getComponentDependencies;
 import static dagger.internal.codegen.ContributionBinding.Kind.INJECTION;
-import static dagger.internal.codegen.ContributionBinding.Kind.SYNTHETIC_MAP;
 import static dagger.internal.codegen.ContributionBinding.Kind.SYNTHETIC_MULTIBOUND_KINDS;
 import static dagger.internal.codegen.ContributionBinding.Kind.SYNTHETIC_MULTIBOUND_MAP;
 import static dagger.internal.codegen.ContributionBinding.indexMapBindingsByAnnotationType;
@@ -994,8 +993,7 @@
             .stream()
             .map(ContributionBinding::bindingKind)
             // TODO(dpb): Kill with fire.
-            .anyMatch(
-                kind -> SYNTHETIC_MULTIBOUND_KINDS.contains(kind) || SYNTHETIC_MAP.equals(kind))) {
+            .anyMatch(SYNTHETIC_MULTIBOUND_KINDS::contains)) {
           reportMultipleContributionTypes();
           return;
         }
diff --git a/java/dagger/internal/codegen/ContributionBinding.java b/java/dagger/internal/codegen/ContributionBinding.java
index 8891e67..6706757 100644
--- a/java/dagger/internal/codegen/ContributionBinding.java
+++ b/java/dagger/internal/codegen/ContributionBinding.java
@@ -73,12 +73,6 @@
    */
   enum Kind {
     /**
-     * The synthetic binding for {@code Map<K, V>} that depends on either
-     * {@code Map<K, Provider<V>>} or {@code Map<K, Producer<V>>}.
-     */
-    SYNTHETIC_MAP,
-
-    /**
      * A synthetic binding for a multibound set that depends on the individual multibinding
      * {@link Provides @Provides} or {@link Produces @Produces} methods.
      */
diff --git a/java/dagger/internal/codegen/DependencyRequest.java b/java/dagger/internal/codegen/DependencyRequest.java
index bbd899a..06ab062 100644
--- a/java/dagger/internal/codegen/DependencyRequest.java
+++ b/java/dagger/internal/codegen/DependencyRequest.java
@@ -341,26 +341,48 @@
     }
 
     /**
+     * Creates synthetic dependency requests for each individual multibinding contribution in {@code
+     * multibindingContributions}.
+     */
+    ImmutableSet<DependencyRequest> forMultibindingContributions(
+        Key multibindingKey, Iterable<ContributionBinding> multibindingContributions) {
+      ImmutableSet.Builder<DependencyRequest> requests = ImmutableSet.builder();
+      for (ContributionBinding multibindingContribution : multibindingContributions) {
+        requests.add(forMultibindingContribution(multibindingKey, multibindingContribution));
+      }
+      return requests.build();
+    }
+
+    /**
      * Creates a synthetic dependency request for one individual {@code multibindingContribution}.
      */
     private DependencyRequest forMultibindingContribution(
-        ContributionBinding multibindingContribution) {
+        Key multibindingKey, ContributionBinding multibindingContribution) {
       checkArgument(
           multibindingContribution.key().multibindingContributionIdentifier().isPresent(),
           "multibindingContribution's key must have a multibinding contribution identifier: %s",
           multibindingContribution);
       return DependencyRequest.builder()
-          .kind(multibindingContributionRequestKind(multibindingContribution))
+          .kind(multibindingContributionRequestKind(multibindingKey, multibindingContribution))
           .key(multibindingContribution.key())
           .build();
     }
 
-    private Kind multibindingContributionRequestKind(ContributionBinding multibindingContribution) {
+    // TODO(b/28555349): support PROVIDER_OF_LAZY here too
+    private static final ImmutableSet<Kind> WRAPPING_MAP_VALUE_FRAMEWORK_TYPES =
+        ImmutableSet.of(Kind.PROVIDER, Kind.PRODUCER);
+
+    private Kind multibindingContributionRequestKind(
+        Key multibindingKey, ContributionBinding multibindingContribution) {
       switch (multibindingContribution.contributionType()) {
         case MAP:
-          return multibindingContribution.bindingType().equals(BindingType.PRODUCTION)
-              ? Kind.PRODUCER
-              : Kind.PROVIDER;
+          MapType mapType = MapType.from(multibindingKey);
+          for (Kind kind : WRAPPING_MAP_VALUE_FRAMEWORK_TYPES) {
+            if (mapType.valuesAreTypeOf(kind.frameworkClass.get())) {
+              return kind;
+            }
+          }
+          // fall through
         case SET:
         case SET_VALUES:
           return Kind.INSTANCE;
@@ -372,19 +394,6 @@
       }
     }
 
-    /**
-     * Creates synthetic dependency requests for each individual multibinding contribution in {@code
-     * multibindingContributions}.
-     */
-    ImmutableSet<DependencyRequest> forMultibindingContributions(
-        Iterable<ContributionBinding> multibindingContributions) {
-      ImmutableSet.Builder<DependencyRequest> requests = ImmutableSet.builder();
-      for (ContributionBinding multibindingContribution : multibindingContributions) {
-        requests.add(forMultibindingContribution(multibindingContribution));
-      }
-      return requests.build();
-    }
-
     DependencyRequest forRequiredVariable(VariableElement variableElement) {
       return forRequiredVariable(variableElement, Optional.empty());
     }
diff --git a/java/dagger/internal/codegen/FrameworkFieldInitializer.java b/java/dagger/internal/codegen/FrameworkFieldInitializer.java
index a8b5b16..0b22230 100644
--- a/java/dagger/internal/codegen/FrameworkFieldInitializer.java
+++ b/java/dagger/internal/codegen/FrameworkFieldInitializer.java
@@ -29,7 +29,6 @@
 import static dagger.internal.codegen.ContributionBinding.Kind.INJECTION;
 import static dagger.internal.codegen.MapKeys.getMapKeyExpression;
 import static dagger.internal.codegen.MoreAnnotationMirrors.getTypeValue;
-import static dagger.internal.codegen.SourceFiles.frameworkMapFactoryClassName;
 import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding;
 import static dagger.internal.codegen.SourceFiles.mapFactoryClassName;
 import static dagger.internal.codegen.SourceFiles.membersInjectorNameForType;
@@ -48,6 +47,7 @@
 import com.google.auto.common.MoreElements;
 import com.google.auto.common.MoreTypes;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.CodeBlock;
@@ -328,14 +328,6 @@
               makeParametersCodeBlock(arguments));
         }
 
-      case SYNTHETIC_MAP:
-        FrameworkDependency frameworkDependency = getOnlyElement(binding.frameworkDependencies());
-        return CodeBlock.of(
-            "$T.create($L)",
-            mapFactoryClassName(binding),
-            componentBindingExpressions.getDependencyExpression(
-                frameworkDependency, componentName));
-
       case SYNTHETIC_MULTIBOUND_SET:
         return factoryForSetMultibindingInitialization(binding);
 
@@ -410,16 +402,27 @@
 
     ImmutableList.Builder<CodeBlock> codeBlocks = ImmutableList.builder();
     MapType mapType = MapType.from(binding.key().type());
-    CodeBlock.Builder builderCall =
-        CodeBlock.builder().add("$T.", frameworkMapFactoryClassName(binding.bindingType()));
+    CodeBlock.Builder builderCall = CodeBlock.builder().add("$T.", mapFactoryClassName(binding));
     boolean useRawTypes = useRawType();
     if (!useRawTypes) {
-      builderCall.add(
-          "<$T, $T>",
-          mapType.keyType(),
-          mapType.unwrappedValueType(binding.bindingType().frameworkClass()));
+      // TODO(ronshapiro): either inline this into mapFactoryClassName, or add a
+      // mapType.unwrappedValueType() method that doesn't require a framework type
+      TypeMirror valueType = mapType.valueType();
+      for (Class<?> frameworkClass :
+          ImmutableSet.of(Provider.class, Producer.class, Produced.class)) {
+        if (mapType.valuesAreTypeOf(frameworkClass)) {
+          valueType = mapType.unwrappedValueType(frameworkClass);
+          break;
+        }
+      }
+      builderCall.add("<$T, $T>", mapType.keyType(), valueType);
     }
-    builderCall.add("builder($L)", frameworkDependencies.size());
+
+    if (binding.bindingType().equals(BindingType.PROVISION)) {
+      builderCall.add("builder($L)", frameworkDependencies.size());
+    } else {
+      builderCall.add("builder()");
+    }
     codeBlocks.add(builderCall.build());
 
     for (FrameworkDependency frameworkDependency : frameworkDependencies) {
diff --git a/java/dagger/internal/codegen/Key.java b/java/dagger/internal/codegen/Key.java
index 3c4cecc..4871e4b 100644
--- a/java/dagger/internal/codegen/Key.java
+++ b/java/dagger/internal/codegen/Key.java
@@ -546,12 +546,15 @@
     }
 
     /**
-     * Optionally extract a {@link Key} for the underlying provision binding(s) if such a
-     * valid key can be inferred from the given key.  Specifically, if the key represents a
-     * {@link Map}{@code <K, V>}, a key of {@code Map<K, Provider<V>>} will be returned.
+     * Optionally extract a {@link Key} for the underlying provision binding(s) if such a valid key
+     * can be inferred from the given key. Specifically, if the key represents a {@link Map}{@code
+     * <K, V>} or {@code Map<K, Producer<V>>}, a key of {@code Map<K, Provider<V>>} will be
+     * returned.
      */
     Optional<Key> implicitMapProviderKeyFrom(Key possibleMapKey) {
-      return wrapMapKey(possibleMapKey, Provider.class);
+      return firstPresent(
+          rewrapMapKey(possibleMapKey, Produced.class, Provider.class),
+          wrapMapKey(possibleMapKey, Provider.class));
     }
 
     /**
diff --git a/java/dagger/internal/codegen/MapBindingExpression.java b/java/dagger/internal/codegen/MapBindingExpression.java
new file mode 100644
index 0000000..8e5746c
--- /dev/null
+++ b/java/dagger/internal/codegen/MapBindingExpression.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2017 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static dagger.internal.codegen.Accessibility.isTypeAccessibleFrom;
+import static dagger.internal.codegen.CodeBlocks.toParametersCodeBlock;
+import static dagger.internal.codegen.ContributionBinding.Kind.SYNTHETIC_MULTIBOUND_MAP;
+import static dagger.internal.codegen.MapKeys.getMapKeyExpression;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import dagger.internal.MapBuilder;
+import java.util.Collections;
+import java.util.Map;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+
+/** A {@link BindingExpression} for multibound maps. */
+final class MapBindingExpression extends SimpleInvocationBindingExpression {
+  /** Maximum number of key-value pairs that can be passed to ImmutableMap.of(K, V, K, V, ...). */
+  private static final int MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS = 5;
+
+  private final ProvisionBinding binding;
+  private final ImmutableMap<DependencyRequest, ContributionBinding> dependencies;
+  private final ComponentBindingExpressions componentBindingExpressions;
+  private final Elements elements;
+
+  MapBindingExpression(
+      ProvisionBinding binding,
+      BindingGraph graph,
+      ComponentBindingExpressions componentBindingExpressions,
+      BindingExpression delegate,
+      Elements elements) {
+    super(delegate);
+    ContributionBinding.Kind bindingKind = binding.bindingKind();
+    checkArgument(bindingKind.equals(SYNTHETIC_MULTIBOUND_MAP), bindingKind);
+    this.binding = binding;
+    this.componentBindingExpressions = componentBindingExpressions;
+    this.elements = elements;
+    this.dependencies =
+        Maps.toMap(
+            binding.dependencies(),
+            dep -> graph.resolvedBindings().get(dep.bindingKey()).contributionBinding());
+  }
+
+  @Override
+  CodeBlock getInstanceDependencyExpression(
+      DependencyRequest.Kind requestKind, ClassName requestingClass) {
+    // TODO(ronshapiro): We should also make an ImmutableMap version of MapFactory
+    boolean isImmutableMapAvailable = isImmutableMapAvailable();
+    // TODO(ronshapiro, gak): Use Maps.immutableEnumMap() if it's available?
+    if (isImmutableMapAvailable && dependencies.size() <= MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS) {
+      return CodeBlock.builder()
+          .add("$T.", ImmutableMap.class)
+          .add(maybeTypeParameters(requestingClass))
+          .add(
+              "of($L)",
+              dependencies
+                  .keySet()
+                  .stream()
+                  .map(dependency -> keyAndValueExpression(dependency, requestingClass))
+                  .collect(toParametersCodeBlock()))
+          .build();
+    }
+    switch (dependencies.size()) {
+      case 0:
+        return collectionsStaticFactoryInvocation(requestingClass, CodeBlock.of("emptyMap()"));
+      case 1:
+        return collectionsStaticFactoryInvocation(
+            requestingClass,
+            CodeBlock.of(
+                "singletonMap($L)",
+                keyAndValueExpression(getOnlyElement(dependencies.keySet()), requestingClass)));
+      default:
+        CodeBlock.Builder instantiation = CodeBlock.builder();
+        instantiation
+            .add("$T.", isImmutableMapAvailable ? ImmutableMap.class : MapBuilder.class)
+            .add(maybeTypeParameters(requestingClass));
+        if (isImmutableMapAvailable) {
+          // TODO(ronshapiro): builderWithExpectedSize
+          instantiation.add("builder()");
+        } else {
+          instantiation.add("newMapBuilder($L)", dependencies.size());
+        }
+        for (DependencyRequest dependency : dependencies.keySet()) {
+          instantiation.add(".put($L)", keyAndValueExpression(dependency, requestingClass));
+        }
+        return instantiation.add(".build()").build();
+    }
+  }
+
+  private CodeBlock keyAndValueExpression(DependencyRequest dependency, ClassName requestingClass) {
+    return CodeBlock.of(
+        "$L, $L",
+        getMapKeyExpression(dependencies.get(dependency).mapKey().get()),
+        componentBindingExpressions.getDependencyExpression(dependency, requestingClass));
+  }
+
+  private CodeBlock collectionsStaticFactoryInvocation(
+      ClassName requestingClass, CodeBlock methodInvocation) {
+    return CodeBlock.builder()
+        .add("$T.", Collections.class)
+        .add(maybeTypeParameters(requestingClass))
+        .add(methodInvocation)
+        .build();
+  }
+
+  private CodeBlock maybeTypeParameters(ClassName requestingClass) {
+    TypeMirror bindingKeyType = binding.key().type();
+    MapType mapType = MapType.from(binding.key());
+    return isTypeAccessibleFrom(bindingKeyType, requestingClass.packageName())
+        ? CodeBlock.of("<$T, $T>", mapType.keyType(), mapType.valueType())
+        : CodeBlock.of("");
+  }
+
+  private boolean isImmutableMapAvailable() {
+    return elements.getTypeElement(ImmutableMap.class.getCanonicalName()) != null;
+  }
+
+  @Override
+  protected CodeBlock explicitTypeParameter(ClassName requestingClass) {
+    if (isImmutableMapAvailable()) {
+      TypeMirror keyType = binding.key().type();
+      return CodeBlock.of(
+          "<$T>",
+          isTypeAccessibleFrom(keyType, requestingClass.packageName()) ? keyType : Map.class);
+    }
+    return CodeBlock.of("");
+  }
+}
diff --git a/java/dagger/internal/codegen/MemberSelect.java b/java/dagger/internal/codegen/MemberSelect.java
index 5466292..fc0217a 100644
--- a/java/dagger/internal/codegen/MemberSelect.java
+++ b/java/dagger/internal/codegen/MemberSelect.java
@@ -20,17 +20,19 @@
 import static dagger.internal.codegen.Accessibility.isTypeAccessibleFrom;
 import static dagger.internal.codegen.CodeBlocks.toTypeNamesCodeBlock;
 import static dagger.internal.codegen.ContributionBinding.FactoryCreationStrategy.SINGLETON_INSTANCE;
-import static dagger.internal.codegen.ContributionBinding.Kind.INJECTION;
-import static dagger.internal.codegen.ContributionBinding.Kind.PROVISION;
 import static dagger.internal.codegen.SourceFiles.bindingTypeElementTypeVariableNames;
-import static dagger.internal.codegen.SourceFiles.frameworkMapFactoryClassName;
 import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding;
 import static dagger.internal.codegen.SourceFiles.setFactoryClassName;
 import static dagger.internal.codegen.TypeNames.FACTORY;
+import static dagger.internal.codegen.TypeNames.MAP_FACTORY;
 import static dagger.internal.codegen.TypeNames.MEMBERS_INJECTOR;
 import static dagger.internal.codegen.TypeNames.MEMBERS_INJECTORS;
+import static dagger.internal.codegen.TypeNames.PRODUCER;
+import static dagger.internal.codegen.TypeNames.PRODUCERS;
+import static dagger.internal.codegen.TypeNames.PROVIDER;
 import static javax.lang.model.type.TypeKind.DECLARED;
 
+import com.google.auto.common.MoreTypes;
 import com.google.common.collect.ImmutableList;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.CodeBlock;
@@ -88,7 +90,7 @@
             && !contributionBinding.scope().isPresent()) {
           switch (contributionBinding.bindingKind()) {
             case SYNTHETIC_MULTIBOUND_MAP:
-              return Optional.of(emptyFrameworkMapFactory(contributionBinding));
+              return Optional.of(emptyMapFactory(contributionBinding));
 
             case SYNTHETIC_MULTIBOUND_SET:
               return Optional.of(emptySetFactory(contributionBinding));
@@ -166,20 +168,19 @@
         MEMBERS_INJECTOR);
   }
 
-  /**
-   * A {@link MemberSelect} for a factory of an empty map of factory types, where a factory can be
-   * either a {@link javax.inject.Provider} or {@link dagger.producers.Producer}.
-   */
-  private static MemberSelect emptyFrameworkMapFactory(ContributionBinding contributionBinding) {
+  /** A {@link MemberSelect} for a factory of an empty map. */
+  private static MemberSelect emptyMapFactory(ContributionBinding contributionBinding) {
     BindingType bindingType = contributionBinding.bindingType();
-    MapType mapType = MapType.from(contributionBinding.key());
-
-    return new ParameterizedStaticMethod(
-        frameworkMapFactoryClassName(bindingType),
-        ImmutableList.of(
-            mapType.keyType(), mapType.unwrappedValueType(bindingType.frameworkClass())),
-        CodeBlock.of("empty()"),
-        ClassName.get(bindingType.frameworkClass()));
+    ImmutableList<TypeMirror> typeParameters =
+        ImmutableList.copyOf(
+            MoreTypes.asDeclared(contributionBinding.key().type()).getTypeArguments());
+    if (bindingType.equals(BindingType.PRODUCTION)) {
+      return new ParameterizedStaticMethod(
+          PRODUCERS, typeParameters, CodeBlock.of("emptyMapProducer()"), PRODUCER);
+    } else {
+      return new ParameterizedStaticMethod(
+          MAP_FACTORY, typeParameters, CodeBlock.of("emptyMapProvider()"), PROVIDER);
+    }
   }
 
   /**
diff --git a/java/dagger/internal/codegen/ProductionBinding.java b/java/dagger/internal/codegen/ProductionBinding.java
index 4a71dea..441fb5c 100644
--- a/java/dagger/internal/codegen/ProductionBinding.java
+++ b/java/dagger/internal/codegen/ProductionBinding.java
@@ -179,28 +179,6 @@
     }
 
     /**
-     * A synthetic binding of {@code Map<K, V>} or {@code Map<K, Produced<V>>} that depends on
-     * {@code Map<K, Producer<V>>}.
-     */
-    ProductionBinding syntheticMapOfValuesOrProducedBinding(Key mapOfValuesOrProducedKey) {
-      checkNotNull(mapOfValuesOrProducedKey);
-      Optional<Key> mapOfProducersKey =
-          keyFactory.implicitMapProducerKeyFrom(mapOfValuesOrProducedKey);
-      checkArgument(
-          mapOfProducersKey.isPresent(),
-          "%s is not a key for of Map<K, V> or Map<K, Produced<V>>",
-          mapOfValuesOrProducedKey);
-      DependencyRequest requestForMapOfProducers =
-          dependencyRequestFactory.producerForImplicitMapBinding(mapOfProducersKey.get());
-      return ProductionBinding.builder()
-          .contributionType(ContributionType.UNIQUE)
-          .key(mapOfValuesOrProducedKey)
-          .explicitDependencies(requestForMapOfProducers)
-          .bindingKind(Kind.SYNTHETIC_MAP)
-          .build();
-    }
-
-    /**
      * A synthetic binding that depends explicitly on a set of individual provision or production
      * multibinding contribution methods.
      *
@@ -212,7 +190,7 @@
           .contributionType(ContributionType.UNIQUE)
           .key(key)
           .explicitDependencies(
-              dependencyRequestFactory.forMultibindingContributions(multibindingContributions))
+              dependencyRequestFactory.forMultibindingContributions(key, multibindingContributions))
           .bindingKind(Kind.forMultibindingKey(key))
           .build();
     }
diff --git a/java/dagger/internal/codegen/ProvisionBinding.java b/java/dagger/internal/codegen/ProvisionBinding.java
index 6d40e97..67168ce 100644
--- a/java/dagger/internal/codegen/ProvisionBinding.java
+++ b/java/dagger/internal/codegen/ProvisionBinding.java
@@ -235,21 +235,6 @@
           .build();
     }
 
-    /** A synthetic binding of {@code Map<K, V>} that depends on {@code Map<K, Provider<V>>}. */
-    ProvisionBinding syntheticMapOfValuesBinding(Key mapOfValuesKey) {
-      checkNotNull(mapOfValuesKey);
-      Optional<Key> mapOfProvidersKey = keyFactory.implicitMapProviderKeyFrom(mapOfValuesKey);
-      checkArgument(mapOfProvidersKey.isPresent(), "%s is not a key for Map<K, V>", mapOfValuesKey);
-      DependencyRequest requestForMapOfProviders =
-          dependencyRequestFactory.providerForImplicitMapBinding(mapOfProvidersKey.get());
-      return ProvisionBinding.builder()
-          .contributionType(ContributionType.UNIQUE)
-          .key(mapOfValuesKey)
-          .provisionDependencies(requestForMapOfProviders)
-          .bindingKind(Kind.SYNTHETIC_MAP)
-          .build();
-    }
-
     /**
      * A synthetic binding that depends explicitly on a set of individual provision multibinding
      * contribution methods.
@@ -262,7 +247,7 @@
           .contributionType(ContributionType.UNIQUE)
           .key(key)
           .provisionDependencies(
-              dependencyRequestFactory.forMultibindingContributions(multibindingContributions))
+              dependencyRequestFactory.forMultibindingContributions(key, multibindingContributions))
           .bindingKind(Kind.forMultibindingKey(key))
           .build();
     }
diff --git a/java/dagger/internal/codegen/SourceFiles.java b/java/dagger/internal/codegen/SourceFiles.java
index ded5f6f..858f184 100644
--- a/java/dagger/internal/codegen/SourceFiles.java
+++ b/java/dagger/internal/codegen/SourceFiles.java
@@ -19,8 +19,10 @@
 import static com.google.common.base.CaseFormat.LOWER_CAMEL;
 import static com.google.common.base.CaseFormat.UPPER_CAMEL;
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.base.Verify.verify;
 import static dagger.internal.codegen.ContributionBinding.Kind.INJECTION;
+import static dagger.internal.codegen.ContributionBinding.Kind.SYNTHETIC_MULTIBOUND_MAP;
 import static dagger.internal.codegen.ContributionBinding.Kind.SYNTHETIC_MULTIBOUND_SET;
 import static dagger.internal.codegen.Optionals.optionalComparator;
 import static dagger.internal.codegen.TypeNames.DOUBLE_CHECK;
@@ -51,17 +53,15 @@
 import com.squareup.javapoet.ParameterizedTypeName;
 import com.squareup.javapoet.TypeName;
 import com.squareup.javapoet.TypeVariableName;
-import dagger.internal.MapFactory;
-import dagger.internal.MapProviderFactory;
 import dagger.internal.SetFactory;
 import dagger.producers.Produced;
-import dagger.producers.internal.MapOfProducerProducer;
-import dagger.producers.internal.MapProducer;
+import dagger.producers.Producer;
 import dagger.producers.internal.SetOfProducedProducer;
 import dagger.producers.internal.SetProducer;
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
+import javax.inject.Provider;
 import javax.lang.model.SourceVersion;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.TypeElement;
@@ -264,41 +264,24 @@
     }
   }
 
-  /**
-   * The {@link java.util.Map}-of-value factory class name appropriate for map bindings.
-   *
-   * <ul>
-   * <li>{@link MapFactory} for provision bindings.
-   * <li>{@link MapProducer} for production bindings.
-   * </ul>
-   */
+  /** The {@link java.util.Map} factory class name appropriate for map bindings. */
   static ClassName mapFactoryClassName(ContributionBinding binding) {
+    checkState(binding.bindingKind().equals(SYNTHETIC_MULTIBOUND_MAP), binding.bindingKind());
+    MapType mapType = MapType.from(binding.key());
     switch (binding.bindingType()) {
-      case PRODUCTION:
-        return MapType.from(binding.key()).valuesAreTypeOf(Produced.class)
-            ? MAP_OF_PRODUCED_PRODUCER : MAP_PRODUCER;
-
       case PROVISION:
-        return MAP_FACTORY;
-
+        return mapType.valuesAreTypeOf(Provider.class) ? MAP_PROVIDER_FACTORY : MAP_FACTORY;
+      case PRODUCTION:
+        return mapType.valuesAreFrameworkType()
+            ? mapType.valuesAreTypeOf(Producer.class)
+                ? MAP_OF_PRODUCER_PRODUCER
+                : MAP_OF_PRODUCED_PRODUCER
+            : MAP_PRODUCER;
       default:
-        throw new AssertionError(binding.toString());
+        throw new IllegalArgumentException(binding.bindingType().toString());
     }
   }
 
-  /**
-   * The {@link java.util.Map}-of-framework factory class name appropriate for map bindings.
-   *
-   * <ul>
-   * <li>{@link MapProviderFactory} for provision bindings.
-   * <li>{@link MapOfProducerProducer} for production bindings.
-   * </ul>
-   */
-  static ClassName frameworkMapFactoryClassName(BindingType bindingType) {
-    return bindingType.equals(BindingType.PRODUCTION)
-        ? MAP_OF_PRODUCER_PRODUCER : MAP_PROVIDER_FACTORY;
-  }
-
   private static String factoryPrefix(ContributionBinding binding) {
     switch (binding.bindingKind()) {
       case INJECTION: