package com.google.inject.internal;

import static com.google.inject.internal.Element.Type.MAPBINDER;
import static com.google.inject.internal.Errors.checkConfiguration;
import static com.google.inject.internal.Errors.checkNotNull;
import static com.google.inject.internal.RealMultibinder.setOf;
import static com.google.inject.util.Types.newParameterizedType;
import static com.google.inject.util.Types.newParameterizedTypeWithOwner;

import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.internal.InternalProviderInstanceBindingImpl.InitializationTiming;
import com.google.inject.multibindings.MapBinderBinding;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.multibindings.MultibindingsTargetVisitor;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.Element;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.util.Types;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * The actual mapbinder plays several roles:
 *
 *
As a MapBinder, it acts as a factory for LinkedBindingBuilders for each of the map's values.
 * It delegates to a {@link Multibinder} of entries (keys to value providers).
 *
 *
As a Module, it installs the binding to the map itself, as well as to a corresponding map
 * whose values are providers.
 *
 *
As a module, this implements equals() and hashcode() in order to trick Guice into executing
 * its configure() method only once. That makes it so that multiple mapbinders can be created for
 * the same target map, but only one is bound. Since the list of bindings is retrieved from the
 * injector itself (and not the mapbinder), each mapbinder has access to all contributions from all
 * equivalent mapbinders.
 *
 *
Rather than binding a single Map.Entry<K, V>, the map binder binds keys and values
 * independently. This allows the values to be properly scoped.
 */
public final class RealMapBinder<K, V> implements Module {

  /**
   * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a {@link
   * Map} that is itself bound with no binding annotation.
   */
  public static <K, V> RealMapBinder<K, V> newMapRealBinder(
      Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
    binder = binder.skipSources(RealMapBinder.class);
    return newRealMapBinder(
        binder,
        keyType,
        valueType,
        Key.get(mapOf(keyType, valueType)),
        RealMultibinder.newRealSetBinder(binder, Key.get(entryOfProviderOf(keyType, valueType))));
  }

  /**
   * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a {@link
   * Map} that is itself bound with {@code annotation}.
   */
  public static <K, V> RealMapBinder<K, V> newRealMapBinder(
      Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType, Annotation annotation) {
    binder = binder.skipSources(RealMapBinder.class);
    return newRealMapBinder(
        binder,
        keyType,
        valueType,
        Key.get(mapOf(keyType, valueType), annotation),
        RealMultibinder.newRealSetBinder(
            binder, Key.get(entryOfProviderOf(keyType, valueType), annotation)));
  }

  /**
   * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a {@link
   * Map} that is itself bound with {@code annotationType}.
   */
  public static <K, V> RealMapBinder<K, V> newRealMapBinder(
      Binder binder,
      TypeLiteral<K> keyType,
      TypeLiteral<V> valueType,
      Class<? extends Annotation> annotationType) {
    binder = binder.skipSources(RealMapBinder.class);
    return newRealMapBinder(
        binder,
        keyType,
        valueType,
        Key.get(mapOf(keyType, valueType), annotationType),
        RealMultibinder.newRealSetBinder(
            binder, Key.get(entryOfProviderOf(keyType, valueType), annotationType)));
  }

  @SuppressWarnings("unchecked") // a map of <K, V> is safely a Map<K, V>
  static <K, V> TypeLiteral<Map<K, V>> mapOf(TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
    return (TypeLiteral<Map<K, V>>)
        TypeLiteral.get(Types.mapOf(keyType.getType(), valueType.getType()));
  }

  @SuppressWarnings("unchecked") // a provider map <K, V> is safely a Map<K, Provider<V>>
  static <K, V> TypeLiteral<Map<K, Provider<V>>> mapOfProviderOf(
      TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
    return (TypeLiteral<Map<K, Provider<V>>>)
        TypeLiteral.get(Types.mapOf(keyType.getType(), Types.providerOf(valueType.getType())));
  }

  // provider map <K, V> is safely a Map<K, javax.inject.Provider<V>>>
  @SuppressWarnings("unchecked")
  static <K, V> TypeLiteral<Map<K, javax.inject.Provider<V>>> mapOfJavaxProviderOf(
      TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
    return (TypeLiteral<Map<K, javax.inject.Provider<V>>>)
        TypeLiteral.get(
            Types.mapOf(
                keyType.getType(),
                newParameterizedType(javax.inject.Provider.class, valueType.getType())));
  }

  @SuppressWarnings("unchecked") // a provider map <K, Set<V>> is safely a Map<K, Set<Provider<V>>>
  static <K, V> TypeLiteral<Map<K, Set<Provider<V>>>> mapOfSetOfProviderOf(
      TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
    return (TypeLiteral<Map<K, Set<Provider<V>>>>)
        TypeLiteral.get(
            Types.mapOf(keyType.getType(), Types.setOf(Types.providerOf(valueType.getType()))));
  }

  @SuppressWarnings("unchecked") // a provider map <K, Set<V>> is safely a Map<K, Set<Provider<V>>>
  static <K, V> TypeLiteral<Map<K, Set<javax.inject.Provider<V>>>> mapOfSetOfJavaxProviderOf(
      TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
    return (TypeLiteral<Map<K, Set<javax.inject.Provider<V>>>>)
        TypeLiteral.get(
            Types.mapOf(
                keyType.getType(), Types.setOf(Types.javaxProviderOf(valueType.getType()))));
  }

  @SuppressWarnings("unchecked") // a provider map <K, Set<V>> is safely a Map<K, Set<Provider<V>>>
  static <K, V> TypeLiteral<Map<K, Collection<Provider<V>>>> mapOfCollectionOfProviderOf(
      TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
    return (TypeLiteral<Map<K, Collection<Provider<V>>>>)
        TypeLiteral.get(
            Types.mapOf(
                keyType.getType(), Types.collectionOf(Types.providerOf(valueType.getType()))));
  }

  @SuppressWarnings("unchecked") // a provider map <K, Set<V>> is safely a Map<K, Set<Provider<V>>>
  static <K, V>
      TypeLiteral<Map<K, Collection<javax.inject.Provider<V>>>> mapOfCollectionOfJavaxProviderOf(
          TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
    return (TypeLiteral<Map<K, Collection<javax.inject.Provider<V>>>>)
        TypeLiteral.get(
            Types.mapOf(
                keyType.getType(), Types.collectionOf(Types.javaxProviderOf(valueType.getType()))));
  }

  @SuppressWarnings("unchecked") // a provider entry <K, V> is safely a Map.Entry<K, Provider<V>>
  static <K, V> TypeLiteral<Map.Entry<K, Provider<V>>> entryOfProviderOf(
      TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
    return (TypeLiteral<Map.Entry<K, Provider<V>>>)
        TypeLiteral.get(
            newParameterizedTypeWithOwner(
                Map.class,
                Map.Entry.class,
                keyType.getType(),
                Types.providerOf(valueType.getType())));
  }

  @SuppressWarnings("unchecked") // a provider entry <K, V> is safely a Map.Entry<K, Provider<V>>
  static <K, V> TypeLiteral<Map.Entry<K, Provider<V>>> entryOfJavaxProviderOf(
      TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
    return (TypeLiteral<Map.Entry<K, Provider<V>>>)
        TypeLiteral.get(
            newParameterizedTypeWithOwner(
                Map.class,
                Map.Entry.class,
                keyType.getType(),
                Types.javaxProviderOf(valueType.getType())));
  }

  @SuppressWarnings("unchecked") // a provider entry <K, V> is safely a Map.Entry<K, Provider<V>>
  static <K, V>
      TypeLiteral<Set<Map.Entry<K, javax.inject.Provider<V>>>> setOfEntryOfJavaxProviderOf(
          TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
    return (TypeLiteral<Set<Map.Entry<K, javax.inject.Provider<V>>>>)
        TypeLiteral.get(Types.setOf(entryOfJavaxProviderOf(keyType, valueType).getType()));
  }

  /** Given a Key<T> will return a Key<Provider<T>> */
  @SuppressWarnings("unchecked")
  private static <T> Key<Provider<T>> getKeyOfProvider(Key<T> valueKey) {
    return (Key<Provider<T>>)
        valueKey.ofType(Types.providerOf(valueKey.getTypeLiteral().getType()));
  }

  // 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(RealMapBinder.class);
    TypeLiteral<V> valueType = valueTypeAndAnnotation.getTypeLiteral();
    return newRealMapBinder(
        binder,
        keyType,
        valueType,
        valueTypeAndAnnotation.ofType(mapOf(keyType, valueType)),
        RealMultibinder.newRealSetBinder(
            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,
      RealMultibinder<Map.Entry<K, Provider<V>>> entrySetBinder) {
    RealMapBinder<K, V> mapBinder =
        new RealMapBinder<K, V>(binder, keyType, valueType, mapKey, entrySetBinder);
    binder.install(mapBinder);
    return mapBinder;
  }

  // Until the injector initializes us, we don't know what our dependencies are,
  // so initialize to the whole Injector.
  private static final ImmutableSet<Dependency<?>> MODULE_DEPENDENCIES =
      ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Injector.class)));

  private final BindingSelection<K, V> bindingSelection;
  private final Binder binder;

  private final RealMultibinder<Map.Entry<K, Provider<V>>> entrySetBinder;

  private RealMapBinder(
      Binder binder,
      TypeLiteral<K> keyType,
      TypeLiteral<V> valueType,
      Key<Map<K, V>> mapKey,
      RealMultibinder<Map.Entry<K, Provider<V>>> entrySetBinder) {
    this.bindingSelection = new BindingSelection<>(keyType, valueType, mapKey, entrySetBinder);
    this.binder = binder;
    this.entrySetBinder = entrySetBinder;
  }

  public void permitDuplicates() {
    checkConfiguration(!bindingSelection.isInitialized(), "MapBinder was already initialized");
    entrySetBinder.permitDuplicates();
    binder.install(new MultimapBinder<K, V>(bindingSelection));
  }

  /** Adds a binding to the map for the given key. */
  Key<V> getKeyForNewValue(K key) {
    checkNotNull(key, "key");
    checkConfiguration(!bindingSelection.isInitialized(), "MapBinder was already initialized");
    RealMultibinder<Map.Entry<K, Provider<V>>> entrySetBinder =
        bindingSelection.getEntrySetBinder();

    Key<V> valueKey =
        Key.get(
            bindingSelection.getValueType(),
            new RealElement(
                entrySetBinder.getSetName(), MAPBINDER, bindingSelection.getKeyType().toString()));
    entrySetBinder.addBinding().toProvider(new ProviderMapEntry<K, V>(key, valueKey));
    return valueKey;
  }

  /**
   * This creates two bindings. One for the {@code Map.Entry<K, Provider<V>>} and another for {@code
   * V}.
   */
  public LinkedBindingBuilder<V> addBinding(K key) {
    return binder.bind(getKeyForNewValue(key));
  }

  @Override
  public void configure(Binder binder) {
    checkConfiguration(!bindingSelection.isInitialized(), "MapBinder was already initialized");

    // Binds a Map<K, Provider<V>>
    RealProviderMapProvider<K, V> providerMapProvider =
        new RealProviderMapProvider<K, V>(bindingSelection);
    binder.bind(bindingSelection.getProviderMapKey()).toProvider(providerMapProvider);

    // The map this exposes is internally an ImmutableMap, so it's OK to massage
    // the guice Provider to javax Provider in the value (since Guice provider
    // implements javax Provider).
    @SuppressWarnings({"unchecked", "rawtypes"})
    Provider<Map<K, javax.inject.Provider<V>>> javaxProviderMapProvider =
        (Provider) providerMapProvider;
    binder.bind(bindingSelection.getJavaxProviderMapKey()).toProvider(javaxProviderMapProvider);

    RealMapProvider<K, V> mapProvider = new RealMapProvider<>(bindingSelection);
    binder.bind(bindingSelection.getMapKey()).toProvider(mapProvider);

    // The Map.Entries are all ProviderMapEntry instances which do not allow setValue, so it is
    // safe to massage the return type like this
    @SuppressWarnings({"unchecked", "rawtypes"})
    Key<Set<Map.Entry<K, javax.inject.Provider<V>>>> massagedEntrySetProviderKey =
        (Key) bindingSelection.getEntrySetBinder().getSetKey();
    binder.bind(bindingSelection.getEntrySetJavaxProviderKey()).to(massagedEntrySetProviderKey);
  }

  @Override
  public boolean equals(Object o) {
    return o instanceof RealMapBinder
        && ((RealMapBinder<?, ?>) o).bindingSelection.equals(bindingSelection);
  }

  @Override
  public int hashCode() {
    return bindingSelection.hashCode();
  }

  /**
   * The BindingSelection contains some of the core state and logic for the MapBinder.
   *
   *
It lazily computes the value for keys for various permutations of Maps that are provided by
   * this module. It also builds up maps from {@code K} to {@code Binding<V>}, which is used by all
   * of the internal factories to actually provide the desired maps.
   *
   *
During initialization time there is only one BindingSelection. It is possible that multiple
   * different BindingSelections are constructed. Specifically, in the case of two different modules
   * each adding bindings to the same MapBinder. If that happens, we define the BindingSelection
   * held by the {@link RealMapProvider} to be the authoritative one. The logic for this exists in
   * {@link RealMultimapBinderProviderWithDependencies}. This is done to avoid confusion because the
   * BindingSelection contains mutable state.
   */
  private static final class BindingSelection<K, V> {
    private enum InitializationState {
      UNINITIALIZED,
      INITIALIZED,
      HAS_ERRORS;
    }

    private final TypeLiteral<K> keyType;
    private final TypeLiteral<V> valueType;
    private final Key<Map<K, V>> mapKey;

    // Lazily computed
    private Key<Map<K, javax.inject.Provider<V>>> javaxProviderMapKey;
    private Key<Map<K, Provider<V>>> providerMapKey;
    private Key<Map<K, Set<V>>> multimapKey;
    private Key<Map<K, Set<Provider<V>>>> providerSetMultimapKey;
    private Key<Map<K, Set<javax.inject.Provider<V>>>> javaxProviderSetMultimapKey;
    private Key<Map<K, Collection<Provider<V>>>> providerCollectionMultimapKey;
    private Key<Map<K, Collection<javax.inject.Provider<V>>>> javaxProviderCollectionMultimapKey;
    private Key<Set<Map.Entry<K, javax.inject.Provider<V>>>> entrySetJavaxProviderKey;

    private final RealMultibinder<Map.Entry<K, Provider<V>>> entrySetBinder;

    private InitializationState initializationState;

    /**
     * These are built during initialization and used by all factories to actually provide the
     * relevant maps. These contain all of the necessary information about the map binder.
     */
    private ImmutableMap<K, Binding<V>> mapBindings;

    private ImmutableMap<K, Set<Binding<V>>> multimapBindings;
    private ImmutableList<Map.Entry<K, Binding<V>>> entries;

    /**
     * Indicates if this Map permits duplicates. It is initialized during initialization by querying
     * the injector. This is done because multiple different modules can contribute to a MapBinder,
     * and any one could set permitDuplicates.
     */
    private boolean permitsDuplicates;

    private BindingSelection(
        TypeLiteral<K> keyType,
        TypeLiteral<V> valueType,
        Key<Map<K, V>> mapKey,
        RealMultibinder<Map.Entry<K, Provider<V>>> entrySetBinder) {
      this.keyType = keyType;
      this.valueType = valueType;
      this.mapKey = mapKey;
      this.entrySetBinder = entrySetBinder;
      this.initializationState = InitializationState.UNINITIALIZED;
    }

    /**
     * Will initialize internal data structures.
     *
     * @return {@code true} if initialization was successful, {@code false} if there were errors
     */
    private boolean tryInitialize(InjectorImpl injector, Errors errors) {
      // Every one of our providers will call this method, so only execute the logic once.
      if (initializationState != InitializationState.UNINITIALIZED) {
        return initializationState != InitializationState.HAS_ERRORS;
      }

      // Multiple different modules can all contribute to the same MapBinder, and if any
      // one of them permits duplicates, then the map binder as a whole will permit duplicates.
      // Since permitDuplicates() may not have been called on this instance, we need to go
      // to the injector to see if permitDuplicates was set.
      permitsDuplicates = entrySetBinder.permitsDuplicates(injector);

      // We now build the Map<K, Set<Binding<V>>> from the entrySetBinder.
      // The entrySetBinder contains all of the ProviderMapEntrys, and once
      // we have those, it's easy to iterate through them to organize them by K.
      Map<K, ImmutableSet.Builder<Binding<V>>> bindingMultimapMutable =
          new LinkedHashMap<K, ImmutableSet.Builder<Binding<V>>>();
      Map<K, Binding<V>> bindingMapMutable = new LinkedHashMap<>();
      Multimap<K, Indexer.IndexedBinding> index = HashMultimap.create();
      Indexer indexer = new Indexer(injector);
      Multimap<K, Binding<V>> duplicates = null;

      ImmutableList.Builder<Map.Entry<K, Binding<V>>> entriesBuilder = ImmutableList.builder();

      // We get all of the Bindings that were put into the entrySetBinder
      for (Binding<Map.Entry<K, Provider<V>>> binding :
          injector.findBindingsByType(entrySetBinder.getElementTypeLiteral())) {
        if (entrySetBinder.containsElement(binding)) {

          // Protected by findBindingByType() and the fact that all providers are added by us
          // in addBinding(). It would theoretically be possible for someone to directly
          // add their own binding to the entrySetBinder, but they shouldn't do that.
          @SuppressWarnings({"unchecked", "rawtypes"})
          ProviderInstanceBinding<ProviderMapEntry<K, V>> entryBinding =
              (ProviderInstanceBinding) binding;

          // We added all these bindings initially, so we know they are ProviderMapEntrys
          @SuppressWarnings({"unchecked", "rawtypes"})
          ProviderMapEntry<K, V> entry = (ProviderMapEntry) entryBinding.getUserSuppliedProvider();
          K key = entry.getKey();

          Key<V> valueKey = entry.getValueKey();
          Binding<V> valueBinding = injector.getExistingBinding(valueKey);

          // Use the indexer to de-dupe user bindings. This is needed because of the
          // uniqueId in RealElement. The uniqueId intentionally circumvents the regular
          // Guice deduplication, so we need to re-implement our own here, ignoring
          // uniqueId.
          if (index.put(key, valueBinding.acceptTargetVisitor(indexer))) {

            entriesBuilder.add(Maps.immutableEntry(key, valueBinding));

            Binding<V> previous = bindingMapMutable.put(key, valueBinding);
            // Check if this is a duplicate binding
            if (previous != null && !permitsDuplicates) {
              if (duplicates == null) {
                // This is linked for both keys and values to maintain order
                duplicates = LinkedHashMultimap.create();
              }

              // We add both the previous and the current value to the duplicates map.
              // This is because if there are three duplicates, we will only execute this code
              // for the second and third, but we want all three values to display a helpful
              // error message. We rely on the multimap to dedupe repeated values.
              duplicates.put(key, previous);
              duplicates.put(key, valueBinding);
            }

            // Don't do extra work unless we need to
            if (permitsDuplicates) {
              // Create a set builder for this key if it's the first time we've seen it
              bindingMultimapMutable.computeIfAbsent(key, k -> ImmutableSet.builder());

              // Add the Binding<V>
              bindingMultimapMutable.get(key).add(valueBinding);
            }
          }
        }
      }

      // It is safe to check if duplicates is non-null because if duplicates are allowed,
      // we don't build up this data structure
      if (duplicates != null) {
        initializationState = InitializationState.HAS_ERRORS;
        reportDuplicateKeysError(duplicates, errors);

        return false;
      }

      // Build all of the ImmutableSet.Builders,
      // transforming from Map<K, ImmutableSet.Builder<Binding<V>>> to
      // ImmutableMap<K, Set<Binding<V>>>
      ImmutableMap.Builder<K, Set<Binding<V>>> bindingsMultimapBuilder = ImmutableMap.builder();
      for (Map.Entry<K, ImmutableSet.Builder<Binding<V>>> entry :
          bindingMultimapMutable.entrySet()) {
        bindingsMultimapBuilder.put(entry.getKey(), entry.getValue().build());
      }
      mapBindings = ImmutableMap.copyOf(bindingMapMutable);
      multimapBindings = bindingsMultimapBuilder.build();

      entries = entriesBuilder.build();

      initializationState = InitializationState.INITIALIZED;

      return true;
    }

    private static <K, V> void reportDuplicateKeysError(
        Multimap<K, Binding<V>> duplicates, Errors errors) {
      StringBuilder sb = new StringBuilder("Map injection failed due to duplicated key ");
      boolean first = true;
      for (Map.Entry<K, Collection<Binding<V>>> entry : duplicates.asMap().entrySet()) {
        K dupKey = entry.getKey();

        if (first) {
          first = false;
          sb.append("\"" + dupKey + "\", from bindings:\n");
        } else {
          sb.append("\n and key: \"" + dupKey + "\", from bindings:\n");
        }

        for (Binding<V> dup : entry.getValue()) {
          sb.append("\t at " + Errors.convert(dup.getSource()) + "\n");
        }
      }

      // TODO(user): Add a different error for every duplicated key
      errors.addMessage(sb.toString());
    }

    private boolean containsElement(Element element) {
      if (entrySetBinder.containsElement(element)) {
        return true;
      }

      Key<?> key;
      if (element instanceof Binding) {
        key = ((Binding<?>) element).getKey();
      } else {
        return false; // cannot match;
      }

      return key.equals(getMapKey())
          || key.equals(getProviderMapKey())
          || key.equals(getJavaxProviderMapKey())
          || key.equals(getMultimapKey())
          || key.equals(getProviderSetMultimapKey())
          || key.equals(getJavaxProviderSetMultimapKey())
          || key.equals(getProviderCollectionMultimapKey())
          || key.equals(getJavaxProviderCollectionMultimapKey())
          || key.equals(entrySetBinder.getSetKey())
          || key.equals(getEntrySetJavaxProviderKey())
          || matchesValueKey(key);
    }

    /** Returns true if the key indicates this is a value in the map. */
    private boolean matchesValueKey(Key<?> key) {
      return key.getAnnotation() instanceof RealElement
          && ((RealElement) key.getAnnotation()).setName().equals(entrySetBinder.getSetName())
          && ((RealElement) key.getAnnotation()).type() == MAPBINDER
          && ((RealElement) key.getAnnotation()).keyType().equals(keyType.toString())
          && key.getTypeLiteral().equals(valueType);
    }

    private Key<Map<K, Provider<V>>> getProviderMapKey() {
      Key<Map<K, Provider<V>>> local = providerMapKey;
      if (local == null) {
        local = providerMapKey = mapKey.ofType(mapOfProviderOf(keyType, valueType));
      }
      return local;
    }

    private Key<Map<K, javax.inject.Provider<V>>> getJavaxProviderMapKey() {
      Key<Map<K, javax.inject.Provider<V>>> local = javaxProviderMapKey;
      if (local == null) {
        local = javaxProviderMapKey = mapKey.ofType(mapOfJavaxProviderOf(keyType, valueType));
      }
      return local;
    }

    private Key<Map<K, Set<V>>> getMultimapKey() {
      Key<Map<K, Set<V>>> local = multimapKey;
      if (local == null) {
        local = multimapKey = mapKey.ofType(mapOf(keyType, setOf(valueType)));
      }
      return local;
    }

    private Key<Map<K, Set<Provider<V>>>> getProviderSetMultimapKey() {
      Key<Map<K, Set<Provider<V>>>> local = providerSetMultimapKey;
      if (local == null) {
        local = providerSetMultimapKey = mapKey.ofType(mapOfSetOfProviderOf(keyType, valueType));
      }
      return local;
    }

    private Key<Map<K, Set<javax.inject.Provider<V>>>> getJavaxProviderSetMultimapKey() {
      Key<Map<K, Set<javax.inject.Provider<V>>>> local = javaxProviderSetMultimapKey;
      if (local == null) {
        local =
            javaxProviderSetMultimapKey =
                mapKey.ofType(mapOfSetOfJavaxProviderOf(keyType, valueType));
      }
      return local;
    }

    private Key<Map<K, Collection<Provider<V>>>> getProviderCollectionMultimapKey() {
      Key<Map<K, Collection<Provider<V>>>> local = providerCollectionMultimapKey;
      if (local == null) {
        local =
            providerCollectionMultimapKey =
                mapKey.ofType(mapOfCollectionOfProviderOf(keyType, valueType));
      }
      return local;
    }

    private Key<Map<K, Collection<javax.inject.Provider<V>>>>
        getJavaxProviderCollectionMultimapKey() {
      Key<Map<K, Collection<javax.inject.Provider<V>>>> local = javaxProviderCollectionMultimapKey;
      if (local == null) {
        local =
            javaxProviderCollectionMultimapKey =
                mapKey.ofType(mapOfCollectionOfJavaxProviderOf(keyType, valueType));
      }
      return local;
    }

    private Key<Set<Map.Entry<K, javax.inject.Provider<V>>>> getEntrySetJavaxProviderKey() {
      Key<Set<Map.Entry<K, javax.inject.Provider<V>>>> local = entrySetJavaxProviderKey;
      if (local == null) {
        local =
            entrySetJavaxProviderKey =
                mapKey.ofType(setOfEntryOfJavaxProviderOf(keyType, valueType));
      }
      return local;
    }

    private ImmutableMap<K, Binding<V>> getMapBindings() {
      checkConfiguration(isInitialized(), "MapBinder has not yet been initialized");
      return mapBindings;
    }

    private ImmutableMap<K, Set<Binding<V>>> getMultimapBindings() {
      checkConfiguration(isInitialized(), "MapBinder has not yet been initialized");
      return multimapBindings;
    }

    private ImmutableList<Map.Entry<K, Binding<V>>> getEntries() {
      checkConfiguration(isInitialized(), "MapBinder has not yet been initialized");
      return entries;
    }

    private boolean isInitialized() {
      return initializationState == InitializationState.INITIALIZED;
    }

    private TypeLiteral<K> getKeyType() {
      return keyType;
    }

    private TypeLiteral<V> getValueType() {
      return valueType;
    }

    private Key<Map<K, V>> getMapKey() {
      return mapKey;
    }

    private RealMultibinder<Map.Entry<K, Provider<V>>> getEntrySetBinder() {
      return entrySetBinder;
    }

    private boolean permitsDuplicates() {
      if (isInitialized()) {
        return permitsDuplicates;
      } else {
        throw new UnsupportedOperationException(
            "permitsDuplicates() not supported for module bindings");
      }
    }

    @Override
    public boolean equals(Object o) {
      return o instanceof BindingSelection && ((BindingSelection<?, ?>) o).mapKey.equals(mapKey);
    }

    @Override
    public int hashCode() {
      return mapKey.hashCode();
    }
  }

  private static final class RealProviderMapProvider<K, V>
      extends RealMapBinderProviderWithDependencies<K, V, Map<K, Provider<V>>> {
    private Map<K, Provider<V>> mapOfProviders;
    private Set<Dependency<?>> dependencies = RealMapBinder.MODULE_DEPENDENCIES;

    private RealProviderMapProvider(BindingSelection<K, V> bindingSelection) {
      super(bindingSelection);
    }

    @Override
    public Set<Dependency<?>> getDependencies() {
      return dependencies;
    }

    @Override
    protected void doInitialize(InjectorImpl injector, Errors errors) {
      ImmutableMap.Builder<K, Provider<V>> mapOfProvidersBuilder = ImmutableMap.builder();
      ImmutableSet.Builder<Dependency<?>> dependenciesBuilder = ImmutableSet.builder();
      for (Map.Entry<K, Binding<V>> entry : bindingSelection.getMapBindings().entrySet()) {
        mapOfProvidersBuilder.put(entry.getKey(), entry.getValue().getProvider());
        dependenciesBuilder.add(Dependency.get(getKeyOfProvider(entry.getValue().getKey())));
      }

      mapOfProviders = mapOfProvidersBuilder.build();
      dependencies = dependenciesBuilder.build();
    }

    @Override
    protected Map<K, Provider<V>> doProvision(InternalContext context, Dependency<?> dependency) {
      return mapOfProviders;
    }
  }

  private static final class RealMapProvider<K, V>
      extends RealMapBinderProviderWithDependencies<K, V, Map<K, V>>
      implements ProviderWithExtensionVisitor<Map<K, V>>, MapBinderBinding<Map<K, V>> {
    private Set<Dependency<?>> dependencies = RealMapBinder.MODULE_DEPENDENCIES;

    /**
     * An array of all the injectors.
     *
     *
This is parallel to array of keys below
     */
    private SingleParameterInjector<V>[] injectors;

    private K[] keys;

    private RealMapProvider(BindingSelection<K, V> bindingSelection) {
      super(bindingSelection);
    }

    private BindingSelection<K, V> getBindingSelection() {
      return bindingSelection;
    }

    @Override
    protected void doInitialize(InjectorImpl injector, Errors errors) throws ErrorsException {
      @SuppressWarnings("unchecked")
      K[] keysArray = (K[]) new Object[bindingSelection.getMapBindings().size()];
      keys = keysArray;
      ImmutableSet.Builder<Dependency<?>> dependenciesBuilder = ImmutableSet.builder();
      int i = 0;
      for (Map.Entry<K, Binding<V>> entry : bindingSelection.getMapBindings().entrySet()) {
        dependenciesBuilder.add(Dependency.get(entry.getValue().getKey()));
        keys[i] = entry.getKey();
        i++;
      }

      ImmutableSet<Dependency<?>> localDependencies = dependenciesBuilder.build();
      dependencies = localDependencies;

      List<Dependency<?>> dependenciesList = localDependencies.asList();

      // We know the type because we built up our own sets of dependencies, it's just
      // that the interface uses a "?" generic
      @SuppressWarnings("unchecked")
      SingleParameterInjector<V>[] typedInjectors =
          (SingleParameterInjector<V>[]) injector.getParametersInjectors(dependenciesList, errors);
      injectors = typedInjectors;
    }

    @Override
    protected Map<K, V> doProvision(InternalContext context, Dependency<?> dependency)
        throws InternalProvisionException {
      SingleParameterInjector<V>[] localInjectors = injectors;
      if (localInjectors == null) {
        // if injectors == null, then we have no bindings so return the empty map.
        return ImmutableMap.of();
      }

      ImmutableMap.Builder<K, V> resultBuilder = ImmutableMap.builder();
      K[] localKeys = keys;
      for (int i = 0; i < localInjectors.length; i++) {
        SingleParameterInjector<V> injector = localInjectors[i];
        K key = localKeys[i];

        V value = injector.inject(context);

        if (value == null) {
          throw createNullValueException(key, bindingSelection.getMapBindings().get(key));
        }

        resultBuilder.put(key, value);
      }

      return resultBuilder.build();
    }

    @Override
    public Set<Dependency<?>> getDependencies() {
      return dependencies;
    }

    @Override
    @SuppressWarnings("unchecked")
    public  W acceptExtensionVisitor(
        BindingTargetVisitor visitor, ProviderInstanceBinding<? extends B> binding) {
      if (visitor instanceof MultibindingsTargetVisitor) {
        return ((MultibindingsTargetVisitor<Map<K, V>, W>) visitor).visit(this);
      } else {
        return visitor.visit(binding);
      }
    }

    @Override
    public Key<Map<K, V>> getMapKey() {
      return bindingSelection.getMapKey();
    }

    @Override
    public Set<Key<?>> getAlternateMapKeys() {
      return ImmutableSet.of(
          (Key<?>) bindingSelection.getJavaxProviderMapKey(),
          (Key<?>) bindingSelection.getProviderMapKey(),
          (Key<?>) bindingSelection.getProviderSetMultimapKey(),
          (Key<?>) bindingSelection.getJavaxProviderSetMultimapKey(),
          (Key<?>) bindingSelection.getProviderCollectionMultimapKey(),
          (Key<?>) bindingSelection.getJavaxProviderCollectionMultimapKey(),
          (Key<?>) bindingSelection.getMultimapKey());
    }

    @Override
    public TypeLiteral<K> getKeyTypeLiteral() {
      return bindingSelection.getKeyType();
    }

    @Override
    public TypeLiteral<V> getValueTypeLiteral() {
      return bindingSelection.getValueType();
    }

    @Override
    @SuppressWarnings("unchecked")
    public List<Map.Entry<?, Binding<?>>> getEntries() {
      if (bindingSelection.isInitialized()) {
        return (List<Map.Entry<?, Binding<?>>>) (List<?>) bindingSelection.getEntries();
      } else {
        throw new UnsupportedOperationException("getEntries() not supported for module bindings");
      }
    }

    @Override
    public List<Map.Entry<?, Binding<?>>> getEntries(Iterable<? extends Element> elements) {
      // Iterate over the elements, building up the below maps
      // This is a preprocessing step allowing us to only iterate over elements
      // once and have O(n) runtime
      ImmutableMultimap.Builder<K, Key<V>> keyToValueKeyBuilder = ImmutableMultimap.builder();
      ImmutableMap.Builder<Key<V>, Binding<V>> valueKeyToBindingBuilder = ImmutableMap.builder();
      ImmutableMap.Builder<Key<V>, K> valueKeyToKeyBuilder = ImmutableMap.builder();
      ImmutableMap.Builder<Key<V>, Binding<Map.Entry<K, Provider<V>>>>
          valueKeyToEntryBindingBuilder = ImmutableMap.builder();
      for (Element element : elements) {
        if (element instanceof Binding) {
          Binding<?> binding = (Binding<?>) element;
          if (bindingSelection.matchesValueKey(binding.getKey())
              && binding.getKey().getTypeLiteral().equals(bindingSelection.valueType)) {
            // Safe because of the check on the type literal above
            @SuppressWarnings("unchecked")
            Binding<V> typedBinding = (Binding<V>) binding;
            Key<V> typedKey = typedBinding.getKey();
            valueKeyToBindingBuilder.put(typedKey, typedBinding);
          }
        }

        if (element instanceof ProviderInstanceBinding
            && bindingSelection.getEntrySetBinder().containsElement(element)) {
          // Safe because of the instanceof check, and containsElement() check
          @SuppressWarnings({"unchecked", "rawtypes"})
          ProviderInstanceBinding<Map.Entry<K, Provider<V>>> entryBinding =
              (ProviderInstanceBinding) element;

          // Safe because of the check for containsElement() above
          @SuppressWarnings("unchecked")
          Provider<Map.Entry<K, Provider<V>>> typedProvider =
              (Provider<Map.Entry<K, Provider<V>>>) entryBinding.getUserSuppliedProvider();
          Provider<Map.Entry<K, Provider<V>>> userSuppliedProvider = typedProvider;

          if (userSuppliedProvider instanceof ProviderMapEntry) {
            // Safe because of the instanceof check
            @SuppressWarnings("unchecked")
            ProviderMapEntry<K, V> typedUserSuppliedProvider =
                (ProviderMapEntry<K, V>) userSuppliedProvider;
            ProviderMapEntry<K, V> entry = typedUserSuppliedProvider;

            keyToValueKeyBuilder.put(entry.getKey(), entry.getValueKey());
            valueKeyToEntryBindingBuilder.put(entry.getValueKey(), entryBinding);
            valueKeyToKeyBuilder.put(entry.getValueKey(), entry.getKey());
          }
        }
      }

      ImmutableMultimap<K, Key<V>> keyToValueKey = keyToValueKeyBuilder.build();
      ImmutableMap<Key<V>, K> valueKeyToKey = valueKeyToKeyBuilder.build();
      ImmutableMap<Key<V>, Binding<V>> valueKeyToBinding = valueKeyToBindingBuilder.build();
      ImmutableMap<Key<V>, Binding<Map.Entry<K, Provider<V>>>> valueKeyToEntryBinding =
          valueKeyToEntryBindingBuilder.build();

      // Check that there is a 1:1 mapping from keys from the ProviderMapEntrys to the
      // keys from the Bindings.
      Set<Key<V>> keysFromProviderMapEntrys = Sets.newHashSet(keyToValueKey.values());
      Set<Key<V>> keysFromBindings = valueKeyToBinding.keySet();

      if (!keysFromProviderMapEntrys.equals(keysFromBindings)) {
        Set<Key<V>> keysOnlyFromProviderMapEntrys =
            Sets.difference(keysFromProviderMapEntrys, keysFromBindings);
        Set<Key<V>> keysOnlyFromBindings =
            Sets.difference(keysFromBindings, keysFromProviderMapEntrys);

        StringBuilder sb = new StringBuilder("Expected a 1:1 mapping from map keys to values.");

        if (!keysOnlyFromBindings.isEmpty()) {
          sb.append(
              Errors.format("%nFound these Bindings that were missing an associated entry:%n"));
          for (Key<V> key : keysOnlyFromBindings) {
            sb.append(
                Errors.format("  %s bound at: %s%n", key, valueKeyToBinding.get(key).getSource()));
          }
        }

        if (!keysOnlyFromProviderMapEntrys.isEmpty()) {
          sb.append(Errors.format("%nFound these map keys without a corresponding value:%n"));
          for (Key<V> key : keysOnlyFromProviderMapEntrys) {
            sb.append(
                Errors.format(
                    "  '%s' bound at: %s%n",
                    valueKeyToKey.get(key), valueKeyToEntryBinding.get(key).getSource()));
          }
        }

        throw new IllegalArgumentException(sb.toString());
      }

      // Now that we have the two maps, generate the result map
      ImmutableList.Builder<Map.Entry<?, Binding<?>>> resultBuilder = ImmutableList.builder();
      for (Map.Entry<K, Key<V>> entry : keyToValueKey.entries()) {
        Binding<?> binding = valueKeyToBinding.get(entry.getValue());
        // No null check for binding needed because of the above check to make sure all the
        // values in keyToValueKey are present as keys in valueKeyToBinding

        @SuppressWarnings({"unchecked", "rawtypes"})
        Map.Entry<?, Binding<?>> newEntry =
            (Map.Entry) Maps.immutableEntry(entry.getKey(), binding);
        resultBuilder.add(newEntry);
      }
      return resultBuilder.build();
    }

    @Override
    public boolean permitsDuplicates() {
      if (bindingSelection.isInitialized()) {
        return bindingSelection.permitsDuplicates();
      } else {
        throw new UnsupportedOperationException(
            "permitsDuplicates() not supported for module bindings");
      }
    }

    @Override
    public boolean containsElement(Element element) {
      return bindingSelection.containsElement(element);
    }
  }

  /**
   * Binds {@code Map<K, Set<V>>} and {{@code Map<K, Set<Provider<V>>>}.
   *
   *
This will only exist if permitDuplicates() is called.
   */
  private static final class MultimapBinder<K, V> implements Module {
    private final BindingSelection<K, V> bindingSelection;

    private MultimapBinder(BindingSelection<K, V> bindingSelection) {
      this.bindingSelection = bindingSelection;
    }

    @Override
    public void configure(Binder binder) {
      // Binds a Map<K, Set<Provider<V>>>
      Provider<Map<K, Set<Provider<V>>>> multimapProvider =
          new RealProviderMultimapProvider<K, V>(bindingSelection.getMapKey());
      binder.bind(bindingSelection.getProviderSetMultimapKey()).toProvider(multimapProvider);

      // Provide links from a few different public keys to the providerMultimapKey.
      // The collection this exposes is internally an ImmutableMap, so it's OK to massage
      // the guice Provider to javax Provider in the value (since the guice Provider implements
      // javax Provider).
      @SuppressWarnings({"unchecked", "rawtypes"})
      Provider<Map<K, Set<javax.inject.Provider<V>>>> javaxProvider = (Provider) multimapProvider;
      binder.bind(bindingSelection.getJavaxProviderSetMultimapKey()).toProvider(javaxProvider);

      @SuppressWarnings({"unchecked", "rawtypes"})
      Provider<Map<K, Collection<Provider<V>>>> collectionProvider = (Provider) multimapProvider;
      binder
          .bind(bindingSelection.getProviderCollectionMultimapKey())
          .toProvider(collectionProvider);

      @SuppressWarnings({"unchecked", "rawtypes"})
      Provider<Map<K, Collection<javax.inject.Provider<V>>>> collectionJavaxProvider =
          (Provider) multimapProvider;
      binder
          .bind(bindingSelection.getJavaxProviderCollectionMultimapKey())
          .toProvider(collectionJavaxProvider);

      // Binds a Map<K, Set<V>>
      @SuppressWarnings({"unchecked", "rawtypes"})
      Provider<Map<K, Set<V>>> realMultimapProvider =
          new RealMultimapProvider(bindingSelection.getMapKey());
      binder.bind(bindingSelection.getMultimapKey()).toProvider(realMultimapProvider);
    }

    @Override
    public int hashCode() {
      return bindingSelection.hashCode();
    }

    @Override
    public boolean equals(Object o) {
      return o instanceof MultimapBinder
          && ((MultimapBinder<?, ?>) o).bindingSelection.equals(bindingSelection);
    }

    private static final class RealProviderMultimapProvider<K, V>
        extends RealMultimapBinderProviderWithDependencies<K, V, Map<K, Set<Provider<V>>>> {
      private Map<K, Set<Provider<V>>> multimapOfProviders;
      private Set<Dependency<?>> dependencies = RealMapBinder.MODULE_DEPENDENCIES;

      private RealProviderMultimapProvider(Key<Map<K, V>> mapKey) {
        super(mapKey);
      }

      @Override
      public Set<Dependency<?>> getDependencies() {
        return dependencies;
      }

      @Override
      protected void doInitialize(InjectorImpl injector, Errors errors) {
        ImmutableMap.Builder<K, Set<Provider<V>>> multimapOfProvidersBuilder =
            ImmutableMap.builder();
        ImmutableSet.Builder<Dependency<?>> dependenciesBuilder = ImmutableSet.builder();
        for (Map.Entry<K, Set<Binding<V>>> entry :
            bindingSelection.getMultimapBindings().entrySet()) {
          ImmutableSet.Builder<Provider<V>> providersBuilder = ImmutableSet.builder();
          for (Binding<V> binding : entry.getValue()) {
            providersBuilder.add(binding.getProvider());
            dependenciesBuilder.add(Dependency.get(getKeyOfProvider(binding.getKey())));
          }

          multimapOfProvidersBuilder.put(entry.getKey(), providersBuilder.build());
        }
        multimapOfProviders = multimapOfProvidersBuilder.build();
        dependencies = dependenciesBuilder.build();
      }

      @Override
      protected Map<K, Set<Provider<V>>> doProvision(
          InternalContext context, Dependency<?> dependency) {
        return multimapOfProviders;
      }
    }

    private static final class RealMultimapProvider<K, V>
        extends RealMultimapBinderProviderWithDependencies<K, V, Map<K, Set<V>>> {

      /**
       * A simple class to hold a key and the associated bindings as an array.
       *
       *
Arrays are used for performance.
       */
      private static final class PerKeyData<K, V> {
        private final K key;
        private final Binding<V>[] bindings;
        private final SingleParameterInjector<V>[] injectors;

        private PerKeyData(K key, Binding<V>[] bindings, SingleParameterInjector<V>[] injectors) {
          Preconditions.checkArgument(bindings.length == injectors.length);

          this.key = key;
          this.bindings = bindings;
          this.injectors = injectors;
        }
      }

      private Set<Dependency<?>> dependencies = RealMapBinder.MODULE_DEPENDENCIES;

      private PerKeyData<K, V>[] perKeyDatas;

      private RealMultimapProvider(Key<Map<K, V>> mapKey) {
        super(mapKey);
      }

      @Override
      public Set<Dependency<?>> getDependencies() {
        return dependencies;
      }

      @Override
      protected void doInitialize(InjectorImpl injector, Errors errors) throws ErrorsException {
        @SuppressWarnings({"unchecked", "rawtypes"})
        PerKeyData<K, V>[] typedPerKeyData =
            new PerKeyData[bindingSelection.getMapBindings().size()];
        perKeyDatas = typedPerKeyData;
        ImmutableSet.Builder<Dependency<?>> dependenciesBuilder = ImmutableSet.builder();
        List<Dependency<?>> dependenciesForKey = Lists.newArrayList();
        int i = 0;
        for (Map.Entry<K, Set<Binding<V>>> entry :
            bindingSelection.getMultimapBindings().entrySet()) {
          // Clear the list of dependencies because we're reusing it for each different key
          dependenciesForKey.clear();

          Set<Binding<V>> bindings = entry.getValue();
          @SuppressWarnings({"unchecked", "rawtypes"})
          Binding<V>[] typedBindings = new Binding[bindings.size()];
          Binding<V>[] bindingsArray = typedBindings;
          int j = 0;
          for (Binding<V> binding : bindings) {
            Dependency<V> dependency = Dependency.get(binding.getKey());
            dependenciesBuilder.add(dependency);
            dependenciesForKey.add(dependency);
            bindingsArray[j] = binding;
            j++;
          }

          @SuppressWarnings("unchecked")
          SingleParameterInjector<V>[] injectors =
              (SingleParameterInjector<V>[])
                  injector.getParametersInjectors(dependenciesForKey, errors);

          perKeyDatas[i] = new PerKeyData<>(entry.getKey(), bindingsArray, injectors);
          i++;
        }

        dependencies = dependenciesBuilder.build();
      }

      @Override
      protected Map<K, Set<V>> doProvision(InternalContext context, Dependency<?> dependency)
          throws InternalProvisionException {
        ImmutableMap.Builder<K, Set<V>> resultBuilder = ImmutableMap.builder();

        for (PerKeyData<K, V> perKeyData : perKeyDatas) {
          ImmutableSet.Builder<V> bindingsBuilder = ImmutableSet.builder();
          SingleParameterInjector<V>[] injectors = perKeyData.injectors;
          for (int i = 0; i < injectors.length; i++) {
            SingleParameterInjector<V> injector = injectors[i];
            V value = injector.inject(context);

            if (value == null) {
              throw createNullValueException(perKeyData.key, perKeyData.bindings[i]);
            }

            bindingsBuilder.add(value);
          }

          resultBuilder.put(perKeyData.key, bindingsBuilder.build());
        }

        return resultBuilder.build();
      }
    }
  }

  /** A factory for a {@code Map.Entry<K, Provider<V>>}. */
  // VisibleForTesting
  static final class ProviderMapEntry<K, V>
      extends InternalProviderInstanceBindingImpl.Factory<Map.Entry<K, Provider<V>>> {
    private final K key;
    private final Key<V> valueKey;
    private Map.Entry<K, Provider<V>> entry;

    ProviderMapEntry(K key, Key<V> valueKey) {
      super(InitializationTiming.EAGER);
      this.key = key;
      this.valueKey = valueKey;
    }

    @Override
    public Set<Dependency<?>> getDependencies() {
      // The dependencies are Key<Provider<V>>
      return ImmutableSet.<Dependency<?>>of(Dependency.get(getKeyOfProvider(valueKey)));
    }

    @Override
    void initialize(InjectorImpl injector, Errors errors) {
      Binding<V> valueBinding = injector.getExistingBinding(valueKey);
      entry = Maps.immutableEntry(key, valueBinding.getProvider());
    }

    @Override
    protected Map.Entry<K, Provider<V>> doProvision(
        InternalContext context, Dependency<?> dependency) {
      return entry;
    }

    K getKey() {
      return key;
    }

    Key<V> getValueKey() {
      return valueKey;
    }

    @Override
    public boolean equals(Object obj) {
      if (obj instanceof ProviderMapEntry) {
        ProviderMapEntry<?, ?> o = (ProviderMapEntry<?, ?>) obj;
        return key.equals(o.key) && valueKey.equals(o.valueKey);
      }
      return false;
    }

    @Override
    public int hashCode() {
      return Objects.hashCode(key, valueKey);
    }

    @Override
    public String toString() {
      return "ProviderMapEntry(" + key + ", " + valueKey + ")";
    }
  }

  /** A base class for ProviderWithDependencies that need equality based on a specific object. */
  private abstract static class RealMapBinderProviderWithDependencies<K, V, P>
      extends InternalProviderInstanceBindingImpl.Factory
 {
    final BindingSelection<K, V> bindingSelection;

    private RealMapBinderProviderWithDependencies(BindingSelection<K, V> bindingSelection) {
      // While MapBinders only depend on bindings created in modules so we could theoretically
      // initialize eagerly, they also depend on
      // 1. findBindingsByType returning results
      // 2. being able to call BindingImpl.acceptTargetVisitor
      // neither of those is available during eager initialization, so we use DELAYED
      super(InitializationTiming.DELAYED);

      this.bindingSelection = bindingSelection;
    }

    @Override
    final void initialize(InjectorImpl injector, Errors errors) throws ErrorsException {
      if (bindingSelection.tryInitialize(injector, errors)) {
        doInitialize(injector, errors);
      }
    }

    /**
     * Initialize the factory. BindingSelection is guaranteed to be initialized at this point and
     * this will be called prior to any provisioning.
     */
    protected abstract void doInitialize(InjectorImpl injector, Errors errors)
        throws ErrorsException;

    @Override
    public boolean equals(Object obj) {
      return obj != null
          && this.getClass() == obj.getClass()
          && bindingSelection.equals(
              ((RealMapBinderProviderWithDependencies<?, ?, ?>) obj).bindingSelection);
    }

    @Override
    public int hashCode() {
      return bindingSelection.hashCode();
    }
  }

  /**
   * A base class for ProviderWithDependencies that need equality based on a specific object.
   *
   *
This differs from {@link RealMapBinderProviderWithDependencies} in that it gets the {@code
   * bindingSelection} from the injector at initialization time, rather than in the constructor.
   * This is done to allow all the providers to operate on the same instance of the {@link
   * BindingSelection}.
   */
  private abstract static class RealMultimapBinderProviderWithDependencies<K, V, P>
      extends InternalProviderInstanceBindingImpl.Factory
 {
    final Key<Map<K, V>> mapKey;
    BindingSelection<K, V> bindingSelection;

    private RealMultimapBinderProviderWithDependencies(Key<Map<K, V>> mapKey) {
      // While MapBinders only depend on bindings created in modules so we could theoretically
      // initialize eagerly, they also depend on
      // 1. findBindingsByType returning results
      // 2. being able to call BindingImpl.acceptTargetVisitor
      // neither of those is available during eager initialization, so we use DELAYED
      super(InitializationTiming.DELAYED);

      this.mapKey = mapKey;
    }

    /**
     * This will get the authoritative {@link BindingSelection} from the map provider. This
     * guarantees that everyone has the same instance of the bindingSelection and sees consistent
     * state.
     */
    @Override
    final void initialize(InjectorImpl injector, Errors errors) throws ErrorsException {
      Binding<Map<K, V>> mapBinding = injector.getExistingBinding(mapKey);
      ProviderInstanceBinding<Map<K, V>> providerInstanceBinding =
          (ProviderInstanceBinding<Map<K, V>>) mapBinding;
      @SuppressWarnings("unchecked")
      RealMapProvider<K, V> mapProvider =
          (RealMapProvider<K, V>) providerInstanceBinding.getUserSuppliedProvider();

      this.bindingSelection = mapProvider.getBindingSelection();

      if (bindingSelection.tryInitialize(injector, errors)) {
        doInitialize(injector, errors);
      }
    }

    /**
     * Initialize the factory. BindingSelection is guaranteed to be initialized at this point and
     * this will be called prior to any provisioning.
     */
    abstract void doInitialize(InjectorImpl injector, Errors errors) throws ErrorsException;

    @Override
    public boolean equals(Object obj) {
      return obj != null
          && this.getClass() == obj.getClass()
          && mapKey.equals(((RealMultimapBinderProviderWithDependencies<?, ?, ?>) obj).mapKey);
    }

    @Override
    public int hashCode() {
      return mapKey.hashCode();
    }
  }

  private static <K, V> InternalProvisionException createNullValueException(
      K key, Binding<V> binding) {
    return InternalProvisionException.create(
        "Map injection failed due to null value for key \"%s\", bound at: %s",
        key, binding.getSource());
  }
}


/*
 * Copyright (C) 2010 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.persist.finder;

import java.lang.reflect.Method;

/**
 * Utility that helps you introspect dynamic finder methods.
 *
 * @author dhanji@gmail.com (Dhanji R. Prasanna)
 */
public final class DynamicFinder {
  private final Method method;
  private final Finder finder;

  public DynamicFinder(Method method) {
    this.method = method;
    this.finder = method.getAnnotation(Finder.class);
  }

  /**
   * Returns some metadata if the method is annotated {@code @Finder} or null.
   *
   * @param method a method you want to test as a dynamic finder
   */
  public static DynamicFinder from(Method method) {
    return method.isAnnotationPresent(Finder.class) ? new DynamicFinder(method) : null;
  }

  public Finder metadata() {
    return finder;
  }
}


/*
 * Copyright (C) 2017 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 static com.google.common.base.Preconditions.checkArgument;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.inject.Guice;
import com.google.inject.Key;
import com.google.inject.MembersInjector;
import com.google.inject.Provides;
import com.google.inject.ProvisionException;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.util.SourceProvider;
import com.google.inject.internal.util.StackTraceElements;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.InjectionListener;
import com.google.inject.spi.Message;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A checked exception for provisioning errors.
 *
 *
This is the internal dual of {@link ProvisionException}, similar to the relationship between
 * {@link com.google.inject.ConfigurationException} and {@link ErrorsException}. This is useful for
 * several reasons:
 *
 *

 *
Since it is a checked exception, we get some assistance from the java compiler in ensuring
 *       that we correctly handle it everywhere. ProvisionException is unchecked.
 *
Since this is an internal package, we can add useful construction and mutation APIs that
 *       would be undesirable in a public supported API.
 *

 *
 *
This exception will be thrown when errors are encountered during provisioning, ErrorsException
 * will continue to be used for errors that are encountered during provisioning and both make use of
 * the {@link Message} as the core model.
 *
 *
NOTE: this object stores a list of messages but in the most common case the cardinality will
 * be 1. The only time that multiple errors might be reported via this mechanism is when {@link
 * #errorInUserCode} is called with an exception that holds multiple errors (like
 * ProvisionException).
 */
public final class InternalProvisionException extends Exception {
  private static final Logger logger = Logger.getLogger(Guice.class.getName());
  private static final Set<Dependency<?>> warnedDependencies =
      Collections.newSetFromMap(new ConcurrentHashMap<Dependency<?>, Boolean>());

  public static InternalProvisionException circularDependenciesDisabled(Class<?> expectedType) {
    return create(
        "Found a circular dependency involving %s, and circular dependencies are disabled.",
        expectedType);
  }

  public static InternalProvisionException cannotProxyClass(Class<?> expectedType) {
    return create(
        "Tried proxying %s to support a circular dependency, but it is not an interface.",
        expectedType);
  }

  public static InternalProvisionException create(String format, Object... arguments) {
    return new InternalProvisionException(Messages.create(format, arguments));
  }

  public static InternalProvisionException errorInUserCode(
      Throwable cause, String messageFormat, Object... arguments) {
    Collection<Message> messages = Errors.getMessagesFromThrowable(cause);
    if (!messages.isEmpty()) {
      // TODO(lukes): it seems like we are dropping some valuable context here..
      // consider eliminating this special case
      return new InternalProvisionException(messages);
    } else {
      return new InternalProvisionException(Messages.create(cause, messageFormat, arguments));
    }
  }

  public static InternalProvisionException subtypeNotProvided(
      Class<? extends javax.inject.Provider<?>> providerType, Class<?> type) {
    return create("%s doesn't provide instances of %s.", providerType, type);
  }

  public static InternalProvisionException errorInProvider(Throwable cause) {
    return errorInUserCode(cause, "Error in custom provider, %s", cause);
  }

  public static InternalProvisionException errorInjectingMethod(Throwable cause) {
    return errorInUserCode(cause, "Error injecting method, %s", cause);
  }

  public static InternalProvisionException errorInjectingConstructor(Throwable cause) {
    return errorInUserCode(cause, "Error injecting constructor, %s", cause);
  }

  public static InternalProvisionException errorInUserInjector(
      MembersInjector<?> listener, TypeLiteral<?> type, RuntimeException cause) {
    return errorInUserCode(
        cause, "Error injecting %s using %s.%n Reason: %s", type, listener, cause);
  }

  public static InternalProvisionException jitDisabled(Key<?> key) {
    return create("Explicit bindings are required and %s is not explicitly bound.", key);
  }

  public static InternalProvisionException errorNotifyingInjectionListener(
      InjectionListener<?> listener, TypeLiteral<?> type, RuntimeException cause) {
    return errorInUserCode(
        cause, "Error notifying InjectionListener %s of %s.%n Reason: %s", listener, type, cause);
  }

  /**
   * Returns {@code value} if it is non-null or allowed to be null. Otherwise a message is added and
   * an {@code InternalProvisionException} is thrown.
   */
  static void onNullInjectedIntoNonNullableDependency(Object source, Dependency<?> dependency)
      throws InternalProvisionException {
    // Hack to allow null parameters to @Provides methods, for backwards compatibility.
    if (dependency.getInjectionPoint().getMember() instanceof Method) {
      Method annotated = (Method) dependency.getInjectionPoint().getMember();
      if (annotated.isAnnotationPresent(Provides.class)) {
        switch (InternalFlags.getNullableProvidesOption()) {
          case ERROR:
            break; // break out & let the below exception happen
          case IGNORE:
            return; // user doesn't care about injecting nulls to non-@Nullables.
          case WARN:
            // Warn only once, otherwise we spam logs too much.
            if (warnedDependencies.add(dependency)) {
              logger.log(
                  Level.WARNING,
                  "Guice injected null into {0} (a {1}), please mark it @Nullable."
                      + " Use -Dguice_check_nullable_provides_params=ERROR to turn this into an"
                      + " error.",
                  new Object[] {
                    Messages.formatParameter(dependency), Messages.convert(dependency.getKey())
                  });
            }
            return;
        }
      }
    }

    Object formattedDependency =
        (dependency.getParameterIndex() != -1)
            ? Messages.formatParameter(dependency)
            : StackTraceElements.forMember(dependency.getInjectionPoint().getMember());

    throw InternalProvisionException.create(
            "null returned by binding at %s%n but %s is not @Nullable", source, formattedDependency)
        .addSource(source);
  }

  private final List<Object> sourcesToPrepend = new ArrayList<>();
  private final ImmutableList<Message> errors;

  private InternalProvisionException(Message error) {
    this(ImmutableList.of(error));
  }

  private InternalProvisionException(Iterable<Message> errors) {
    this.errors = ImmutableList.copyOf(errors);
    checkArgument(!this.errors.isEmpty(), "Can't create a provision exception with no errors");
  }

  /**
   * Prepends the given {@code source} to the stack of binding sources for the errors reported in
   * this exception.
   *
   *
See {@link Errors#withSource(Object)}
   *
   *
It is expected that this method is called as the exception propagates up the stack.
   *
   * @param source
   * @return {@code this}
   */
  InternalProvisionException addSource(Object source) {
    if (source == SourceProvider.UNKNOWN_SOURCE) {
      return this;
    }
    int sz = sourcesToPrepend.size();
    if (sz > 0 && sourcesToPrepend.get(sz - 1) == source) {
      // This is for when there are two identical sources added in a row.  This behavior is copied
      // from Errors.withSource where it can happen when an constructor/provider method throws an
      // exception
      return this;
    }
    sourcesToPrepend.add(source);
    return this;
  }

  ImmutableList<Message> getErrors() {
    ImmutableList.Builder<Message> builder = ImmutableList.builder();
    // reverse them since sources are added as the exception propagates (so the first source is the
    // last one added)
    List<Object> newSources = Lists.reverse(sourcesToPrepend);
    for (Message error : errors) {
      builder.add(Messages.mergeSources(newSources, error));
    }
    return builder.build();
  }

  /** Returns this exception convered to a ProvisionException. */
  public ProvisionException toProvisionException() {
    return new ProvisionException(getErrors());
  }
}


/*
 * Copyright (C) 2006 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.common.collect.ImmutableSet;
import com.google.inject.internal.ProvisionListenerStackCallback.ProvisionCallback;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.InjectionPoint;
import java.lang.reflect.InvocationTargetException;
import java.util.Set;

/**
 * Creates instances using an injectable constructor. After construction, all injectable fields and
 * methods are injected.
 *
 * @author crazybob@google.com (Bob Lee)
 */
final class ConstructorInjector<T> {

  private final ImmutableSet<InjectionPoint> injectableMembers;
  private final SingleParameterInjector<?>[] parameterInjectors;
  private final ConstructionProxy<T> constructionProxy;
  private final MembersInjectorImpl<T> membersInjector;

  ConstructorInjector(
      Set<InjectionPoint> injectableMembers,
      ConstructionProxy<T> constructionProxy,
      SingleParameterInjector<?>[] parameterInjectors,
      MembersInjectorImpl<T> membersInjector) {
    this.injectableMembers = ImmutableSet.copyOf(injectableMembers);
    this.constructionProxy = constructionProxy;
    this.parameterInjectors = parameterInjectors;
    this.membersInjector = membersInjector;
  }

  public ImmutableSet<InjectionPoint> getInjectableMembers() {
    return injectableMembers;
  }

  ConstructionProxy<T> getConstructionProxy() {
    return constructionProxy;
  }

  /**
   * Construct an instance. Returns {@code Object} instead of {@code T} because it may return a
   * proxy.
   */
  Object construct(
      final InternalContext context,
      Dependency<?> dependency,
      /* @Nullable */ ProvisionListenerStackCallback<T> provisionCallback)
      throws InternalProvisionException {
    final ConstructionContext<T> constructionContext = context.getConstructionContext(this);
    // We have a circular reference between constructors. Return a proxy.
    if (constructionContext.isConstructing()) {
      // TODO (crazybob): if we can't proxy this object, can we proxy the other object?
      return constructionContext.createProxy(
          context.getInjectorOptions(), dependency.getKey().getTypeLiteral().getRawType());
    }

    // If we're re-entering this factory while injecting fields or methods,
    // return the same instance. This prevents infinite loops.
    T t = constructionContext.getCurrentReference();
    if (t != null) {
      if (context.getInjectorOptions().disableCircularProxies) {
        throw InternalProvisionException.circularDependenciesDisabled(
            dependency.getKey().getTypeLiteral().getRawType());
      } else {
        return t;
      }
    }

    constructionContext.startConstruction();
    try {
      // Optimization: Don't go through the callback stack if we have no listeners.
      if (provisionCallback == null) {
        return provision(context, constructionContext);
      } else {
        return provisionCallback.provision(
            context,
            new ProvisionCallback<T>() {
              @Override
              public T call() throws InternalProvisionException {
                return provision(context, constructionContext);
              }
            });
      }
    } finally {
      constructionContext.finishConstruction();
    }
  }

  /** Provisions a new T. */
  private T provision(InternalContext context, ConstructionContext<T> constructionContext)
      throws InternalProvisionException {
    try {
      T t;
      try {
        Object[] parameters = SingleParameterInjector.getAll(context, parameterInjectors);
        t = constructionProxy.newInstance(parameters);
        constructionContext.setProxyDelegates(t);
      } finally {
        constructionContext.finishConstruction();
      }

      // Store reference. If an injector re-enters this factory, they'll get the same reference.
      constructionContext.setCurrentReference(t);

      MembersInjectorImpl<T> localMembersInjector = membersInjector;
      localMembersInjector.injectMembers(t, context, false);
      localMembersInjector.notifyListeners(t);

      return t;
    } catch (InvocationTargetException userException) {
      Throwable cause = userException.getCause() != null ? userException.getCause() : userException;
      throw InternalProvisionException.errorInjectingConstructor(cause)
          .addSource(constructionProxy.getInjectionPoint());
    } finally {
      constructionContext.removeCurrentReference();
    }
  }
}


/*
 * Copyright (C) 2008 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 static com.google.common.base.Preconditions.checkNotNull;

import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.Scope;
import com.google.inject.spi.Element;
import com.google.inject.spi.InstanceBinding;
import java.lang.annotation.Annotation;
import java.util.List;

/**
 * Bind a value or constant.
 *
 * @author jessewilson@google.com (Jesse Wilson)
 */
public abstract class AbstractBindingBuilder<T> {

  public static final String IMPLEMENTATION_ALREADY_SET = "Implementation is set more than once.";
  public static final String SINGLE_INSTANCE_AND_SCOPE =
      "Setting the scope is not permitted when binding to a single instance.";
  public static final String SCOPE_ALREADY_SET = "Scope is set more than once.";
  public static final String BINDING_TO_NULL =
      "Binding to null instances is not allowed. "
          + "Use toProvider(Providers.of(null)) if this is your intended behaviour.";
  public static final String CONSTANT_VALUE_ALREADY_SET = "Constant value is set more than once.";
  public static final String ANNOTATION_ALREADY_SPECIFIED =
      "More than one annotation is specified for this binding.";

  protected static final Key<?> NULL_KEY = Key.get(Void.class);

  protected List<Element> elements;
  protected int position;
  protected final Binder binder;
  private BindingImpl<T> binding;

  public AbstractBindingBuilder(Binder binder, List<Element> elements, Object source, Key<T> key) {
    this.binder = binder;
    this.elements = elements;
    this.position = elements.size();
    this.binding = new UntargettedBindingImpl<>(source, key, Scoping.UNSCOPED);
    elements.add(position, this.binding);
  }

  protected BindingImpl<T> getBinding() {
    return binding;
  }

  protected BindingImpl<T> setBinding(BindingImpl<T> binding) {
    this.binding = binding;
    elements.set(position, binding);
    return binding;
  }

  /** Sets the binding to a copy with the specified annotation on the bound key */
  protected BindingImpl<T> annotatedWithInternal(Class<? extends Annotation> annotationType) {
    checkNotNull(annotationType, "annotationType");
    checkNotAnnotated();
    return setBinding(
        binding.withKey(Key.get(this.binding.getKey().getTypeLiteral(), annotationType)));
  }

  /** Sets the binding to a copy with the specified annotation on the bound key */
  protected BindingImpl<T> annotatedWithInternal(Annotation annotation) {
    checkNotNull(annotation, "annotation");
    checkNotAnnotated();
    return setBinding(binding.withKey(Key.get(this.binding.getKey().getTypeLiteral(), annotation)));
  }

  public void in(final Class<? extends Annotation> scopeAnnotation) {
    checkNotNull(scopeAnnotation, "scopeAnnotation");
    checkNotScoped();
    setBinding(getBinding().withScoping(Scoping.forAnnotation(scopeAnnotation)));
  }

  public void in(final Scope scope) {
    checkNotNull(scope, "scope");
    checkNotScoped();
    setBinding(getBinding().withScoping(Scoping.forInstance(scope)));
  }

  public void asEagerSingleton() {
    checkNotScoped();
    setBinding(getBinding().withScoping(Scoping.EAGER_SINGLETON));
  }

  protected boolean keyTypeIsSet() {
    return !Void.class.equals(binding.getKey().getTypeLiteral().getType());
  }

  protected void checkNotTargetted() {
    if (!(binding instanceof UntargettedBindingImpl)) {
      binder.addError(IMPLEMENTATION_ALREADY_SET);
    }
  }

  protected void checkNotAnnotated() {
    if (binding.getKey().getAnnotationType() != null) {
      binder.addError(ANNOTATION_ALREADY_SPECIFIED);
    }
  }

  protected void checkNotScoped() {
    // Scoping isn't allowed when we have only one instance.
    if (binding instanceof InstanceBinding) {
      binder.addError(SINGLE_INSTANCE_AND_SCOPE);
      return;
    }

    if (binding.getScoping().isExplicitlyScoped()) {
      binder.addError(SCOPE_ALREADY_SET);
    }
  }
}


/*
 * Copyright (C) 2006 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.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.inject.Binder;
import com.google.inject.internal.Errors;
import com.google.inject.internal.util.SourceProvider;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.List;

/**
 * An error message and the context in which it occured. Messages are usually created internally by
 * Guice and its extensions. Messages can be created explicitly in a module using {@link
 * com.google.inject.Binder#addError(Throwable) addError()} statements:
 *
 *

 *     try {
 *       bindPropertiesFromFile();
 *     } catch (IOException e) {
 *       addError(e);
 *     }

 *
 * @author crazybob@google.com (Bob Lee)
 */
public final class Message implements Serializable, Element {
  private final String message;
  private final Throwable cause;
  private final List<Object> sources;

  /** @since 2.0 */
  public Message(List<Object> sources, String message, Throwable cause) {
    this.sources = ImmutableList.copyOf(sources);
    this.message = checkNotNull(message, "message");
    this.cause = cause;
  }

  /** @since 4.0 */
  public Message(String message, Throwable cause) {
    this(ImmutableList.of(), message, cause);
  }

  public Message(Object source, String message) {
    this(ImmutableList.of(source), message, null);
  }

  public Message(String message) {
    this(ImmutableList.of(), message, null);
  }

  @Override
  public String getSource() {
    return sources.isEmpty()
        ? SourceProvider.UNKNOWN_SOURCE.toString()
        : Errors.convert(sources.get(sources.size() - 1)).toString();
  }

  /** @since 2.0 */
  public List<Object> getSources() {
    return sources;
  }

  /** Gets the error message text. */
  public String getMessage() {
    return message;
  }

  /** @since 2.0 */
  @Override
  public <T> T acceptVisitor(ElementVisitor<T> visitor) {
    return visitor.visit(this);
  }

  /**
   * Returns the throwable that caused this message, or {@code null} if this message was not caused
   * by a throwable.
   *
   * @since 2.0
   */
  public Throwable getCause() {
    return cause;
  }

  @Override
  public String toString() {
    return message;
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(message, cause, sources);
  }

  @Override
  public boolean equals(Object o) {
    if (!(o instanceof Message)) {
      return false;
    }
    Message e = (Message) o;
    return message.equals(e.message) && Objects.equal(cause, e.cause) && sources.equals(e.sources);
  }

  /** @since 2.0 */
  @Override
  public void applyTo(Binder binder) {
    binder.withSource(getSource()).addError(this);
  }

  /**
   * When serialized, we eagerly convert sources to strings. This hurts our formatting, but it
   * guarantees that the receiving end will be able to read the message.
   */
  private Object writeReplace() throws ObjectStreamException {
    Object[] sourcesAsStrings = sources.toArray();
    for (int i = 0; i < sourcesAsStrings.length; i++) {
      sourcesAsStrings[i] = Errors.convert(sourcesAsStrings[i]).toString();
    }
    return new Message(ImmutableList.copyOf(sourcesAsStrings), message, cause);
  }

  private static final long serialVersionUID = 0;
}


/*
 * Copyright (C) 2010 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.throwingproviders;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.Annotations;
import com.google.inject.internal.Errors;
import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.Message;
import com.google.inject.util.Modules;
import java.lang.annotation.Annotation;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.List;
import java.util.logging.Logger;

/**
 * Creates bindings to methods annotated with {@literal @}{@link CheckedProvides}. Use the scope and
 * binding annotations on the provider method to configure the binding.
 *
 * @author sameb@google.com (Sam Berlin)
 */
final class CheckedProviderMethodsModule implements Module {
  private static final Key<Logger> LOGGER_KEY = Key.get(Logger.class);

  private final Object delegate;
  private final TypeLiteral<?> typeLiteral;

  private CheckedProviderMethodsModule(Object delegate) {
    this.delegate = checkNotNull(delegate, "delegate");
    this.typeLiteral = TypeLiteral.get(this.delegate.getClass());
  }

  /** Returns a module which creates bindings for provider methods from the given module. */
  static Module forModule(Module module) {
    // avoid infinite recursion, since installing a module always installs itself
    if (module instanceof CheckedProviderMethodsModule) {
      return Modules.EMPTY_MODULE;
    }

    return new CheckedProviderMethodsModule(module);
  }

  @Override
  public synchronized void configure(Binder binder) {
    for (CheckedProviderMethod<?> throwingProviderMethod : getProviderMethods(binder)) {
      throwingProviderMethod.configure(binder);
    }
  }

  List<CheckedProviderMethod<?>> getProviderMethods(Binder binder) {
    List<CheckedProviderMethod<?>> result = Lists.newArrayList();
    for (Class<?> c = delegate.getClass(); c != Object.class; c = c.getSuperclass()) {
      for (Method method : c.getDeclaredMethods()) {
        CheckedProvides checkedProvides = method.getAnnotation(CheckedProvides.class);
        if (checkedProvides != null) {
          result.add(createProviderMethod(binder, method, checkedProvides));
        }
      }
    }
    return result;
  }

  <T> CheckedProviderMethod<T> createProviderMethod(
      Binder binder, final Method method, CheckedProvides checkedProvides) {
    @SuppressWarnings("rawtypes")
    Class<? extends CheckedProvider> throwingProvider = checkedProvides.value();
    binder = binder.withSource(method);
    Errors errors = new Errors(method);

    // prepare the parameter providers
    List<Dependency<?>> dependencies = Lists.newArrayList();
    List<Provider<?>> parameterProviders = Lists.newArrayList();
    List<TypeLiteral<?>> parameterTypes = typeLiteral.getParameterTypes(method);
    Annotation[][] parameterAnnotations = method.getParameterAnnotations();
    for (int i = 0; i < parameterTypes.size(); i++) {
      Key<?> key = getKey(errors, parameterTypes.get(i), method, parameterAnnotations[i]);
      if (key.equals(LOGGER_KEY)) {
        // If it was a Logger, change the key to be unique & bind it to a
        // provider that provides a logger with a proper name.
        // This solves issue 482 (returning a new anonymous logger on every call exhausts memory)
        Key<Logger> loggerKey = Key.get(Logger.class, UniqueAnnotations.create());
        binder.bind(loggerKey).toProvider(new LogProvider(method));
        key = loggerKey;
      }
      dependencies.add(Dependency.get(key));
      parameterProviders.add(binder.getProvider(key));
    }

    @SuppressWarnings("unchecked") // Define T as the method's return type.
    TypeLiteral<T> returnType = (TypeLiteral<T>) typeLiteral.getReturnType(method);
    List<TypeLiteral<?>> exceptionTypes = typeLiteral.getExceptionTypes(method);

    Key<T> key = getKey(errors, returnType, method, method.getAnnotations());
    Class<? extends Annotation> scopeAnnotation =
        Annotations.findScopeAnnotation(errors, method.getAnnotations());

    for (Message message : errors.getMessages()) {
      binder.addError(message);
    }

    return new CheckedProviderMethod<T>(
        key,
        method,
        delegate,
        ImmutableSet.copyOf(dependencies),
        parameterProviders,
        scopeAnnotation,
        throwingProvider,
        exceptionTypes,
        checkedProvides.scopeExceptions());
  }

  <T> Key<T> getKey(Errors errors, TypeLiteral<T> type, Member member, Annotation[] annotations) {
    Annotation bindingAnnotation = Annotations.findBindingAnnotation(errors, member, annotations);
    return bindingAnnotation == null ? Key.get(type) : Key.get(type, bindingAnnotation);
  }

  @Override
  public boolean equals(Object o) {
    return o instanceof CheckedProviderMethodsModule
        && ((CheckedProviderMethodsModule) o).delegate == delegate;
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  /** A provider that returns a logger based on the method name. */
  private static final class LogProvider implements Provider<Logger> {
    private final String name;

    public LogProvider(Method method) {
      this.name = method.getDeclaringClass().getName() + "." + method.getName();
    }

    @Override
    public Logger get() {
      return Logger.getLogger(name);
    }
  }
}