/**
 * 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.multibindings;

import static com.google.inject.Asserts.asModuleChain;
import static com.google.inject.Asserts.assertContains;
import static com.google.inject.multibindings.SpiUtils.assertMapVisitor;
import static com.google.inject.multibindings.SpiUtils.instance;
import static com.google.inject.multibindings.SpiUtils.providerInstance;
import static com.google.inject.multibindings.SpiUtils.VisitType.BOTH;
import static com.google.inject.multibindings.SpiUtils.VisitType.MODULE;
import static com.google.inject.name.Names.named;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.BindingAnnotation;
import com.google.inject.ConfigurationException;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Provides;
import com.google.inject.ProvisionException;
import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.WeakKeySetUtils;
import com.google.inject.name.Names;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.HasDependencies;
import com.google.inject.spi.InstanceBinding;
import com.google.inject.util.Modules;
import com.google.inject.util.Providers;

import junit.framework.TestCase;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author dpb@google.com (David P. Baker)
 */
public class MapBinderTest extends TestCase {

  final TypeLiteral<Map<String, javax.inject.Provider<String>>> mapOfStringJavaxProvider =
      new TypeLiteral<Map<String, javax.inject.Provider<String>>>() {};
  final TypeLiteral<Map<String, Provider<String>>> mapOfStringProvider =
      new TypeLiteral<Map<String, Provider<String>>>() {}; 
  final TypeLiteral<Map<String, String>> mapOfString = new TypeLiteral<Map<String, String>>() {};
  final TypeLiteral<Map<Integer, String>> mapOfIntString =
      new TypeLiteral<Map<Integer, String>>() {};
  final TypeLiteral<Map<String, Integer>> mapOfInteger = new TypeLiteral<Map<String, Integer>>() {};
  final TypeLiteral<Map<String, Set<String>>> mapOfSetOfString =
      new TypeLiteral<Map<String, Set<String>>>() {};
      
  private final TypeLiteral<String> stringType = TypeLiteral.get(String.class);
  private final TypeLiteral<Integer> intType = TypeLiteral.get(Integer.class);

  public void testMapBinderAggregatesMultipleModules() {
    Module abc = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(
            binder(), String.class, String.class);
        multibinder.addBinding("a").toInstance("A");
        multibinder.addBinding("b").toInstance("B");
        multibinder.addBinding("c").toInstance("C");
      }
    };
    Module de = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(
            binder(), String.class, String.class);
        multibinder.addBinding("d").toInstance("D");
        multibinder.addBinding("e").toInstance("E");
      }
    };

    Injector injector = Guice.createInjector(abc, de);
    Map<String, String> abcde = injector.getInstance(Key.get(mapOfString));

    assertEquals(mapOf("a", "A", "b", "B", "c", "C", "d", "D", "e", "E"), abcde);
    assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(abc, de), BOTH, false, 0,
        instance("a", "A"), instance("b", "B"), instance("c", "C"), instance("d", "D"), instance("e", "E"));

    // just make sure these succeed
    injector.getInstance(Key.get(mapOfStringProvider));
    injector.getInstance(Key.get(mapOfStringJavaxProvider));
  }

  public void testMapBinderAggregationForAnnotationInstance() {
    Module module = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(
            binder(), String.class, String.class, Names.named("abc"));
        multibinder.addBinding("a").toInstance("A");
        multibinder.addBinding("b").toInstance("B");

        multibinder = MapBinder.newMapBinder(
            binder(), String.class, String.class, Names.named("abc"));
        multibinder.addBinding("c").toInstance("C");
      }
    };
    Injector injector = Guice.createInjector(module);

    Key<Map<String, String>> key = Key.get(mapOfString, Names.named("abc"));
    Map<String, String> abc = injector.getInstance(key);
    assertEquals(mapOf("a", "A", "b", "B", "c", "C"), abc);
    assertMapVisitor(key, stringType, stringType, setOf(module), BOTH, false, 0,
        instance("a", "A"), instance("b", "B"), instance("c", "C"));
    
    // just make sure these succeed
    injector.getInstance(Key.get(mapOfStringProvider, Names.named("abc")));
    injector.getInstance(Key.get(mapOfStringJavaxProvider, Names.named("abc")));
  }

  public void testMapBinderAggregationForAnnotationType() {
    Module module = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(
            binder(), String.class, String.class, Abc.class);
        multibinder.addBinding("a").toInstance("A");
        multibinder.addBinding("b").toInstance("B");

        multibinder = MapBinder.newMapBinder(
            binder(), String.class, String.class, Abc.class);
        multibinder.addBinding("c").toInstance("C");
      }
    };
    Injector injector = Guice.createInjector(module);

    Key<Map<String, String>> key = Key.get(mapOfString, Abc.class);
    Map<String, String> abc = injector.getInstance(key);
    assertEquals(mapOf("a", "A", "b", "B", "c", "C"), abc);
    assertMapVisitor(key, stringType, stringType, setOf(module), BOTH, false, 0,
        instance("a", "A"), instance("b", "B"), instance("c", "C"));
    
    // just make sure these succeed
    injector.getInstance(Key.get(mapOfStringProvider, Abc.class));
    injector.getInstance(Key.get(mapOfStringJavaxProvider, Abc.class));
  }

  public void testMapBinderWithMultipleAnnotationValueSets() {
    Module module = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> abcMapBinder = MapBinder.newMapBinder(
            binder(), String.class, String.class, named("abc"));
        abcMapBinder.addBinding("a").toInstance("A");
        abcMapBinder.addBinding("b").toInstance("B");
        abcMapBinder.addBinding("c").toInstance("C");

        MapBinder<String, String> deMapBinder = MapBinder.newMapBinder(
            binder(), String.class, String.class, named("de"));
        deMapBinder.addBinding("d").toInstance("D");
        deMapBinder.addBinding("e").toInstance("E");
      }
    };
    Injector injector = Guice.createInjector(module);

    Key<Map<String, String>> abcKey = Key.get(mapOfString, named("abc"));
    Map<String, String> abc = injector.getInstance(abcKey);
    Key<Map<String, String>> deKey = Key.get(mapOfString, named("de"));
    Map<String, String> de = injector.getInstance(deKey);
    assertEquals(mapOf("a", "A", "b", "B", "c", "C"), abc);
    assertEquals(mapOf("d", "D", "e", "E"), de);
    assertMapVisitor(abcKey, stringType, stringType, setOf(module), BOTH, false, 1,
        instance("a", "A"), instance("b", "B"), instance("c", "C"));
    assertMapVisitor(deKey, stringType, stringType, setOf(module), BOTH, false, 1,
        instance("d", "D"), instance("e", "E"));     
    
    // just make sure these succeed
    injector.getInstance(Key.get(mapOfStringProvider, named("abc")));
    injector.getInstance(Key.get(mapOfStringJavaxProvider, named("abc")));
    injector.getInstance(Key.get(mapOfStringProvider, named("de")));
    injector.getInstance(Key.get(mapOfStringJavaxProvider, named("de")));
  }

  public void testMapBinderWithMultipleAnnotationTypeSets() {
    Module module = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> abcMapBinder = MapBinder.newMapBinder(
            binder(), String.class, String.class, Abc.class);
        abcMapBinder.addBinding("a").toInstance("A");
        abcMapBinder.addBinding("b").toInstance("B");
        abcMapBinder.addBinding("c").toInstance("C");

        MapBinder<String, String> deMapBinder = MapBinder.newMapBinder(
            binder(), String.class, String.class, De.class);
        deMapBinder.addBinding("d").toInstance("D");
        deMapBinder.addBinding("e").toInstance("E");
      }
    };
    Injector injector = Guice.createInjector(module);

    Key<Map<String, String>> abcKey = Key.get(mapOfString, Abc.class);
    Map<String, String> abc = injector.getInstance(abcKey);
    Key<Map<String, String>> deKey = Key.get(mapOfString, De.class);
    Map<String, String> de = injector.getInstance(deKey);
    assertEquals(mapOf("a", "A", "b", "B", "c", "C"), abc);
    assertEquals(mapOf("d", "D", "e", "E"), de);
    assertMapVisitor(abcKey, stringType, stringType, setOf(module), BOTH, false, 1,
        instance("a", "A"), instance("b", "B"), instance("c", "C"));
    assertMapVisitor(deKey, stringType, stringType, setOf(module), BOTH, false, 1,
        instance("d", "D"), instance("e", "E"));
    
    // just make sure these succeed
    injector.getInstance(Key.get(mapOfStringProvider, Abc.class));
    injector.getInstance(Key.get(mapOfStringJavaxProvider, Abc.class));
    injector.getInstance(Key.get(mapOfStringProvider, De.class));
    injector.getInstance(Key.get(mapOfStringJavaxProvider, De.class));
  }

  public void testMapBinderWithMultipleTypes() {
    Module module = new AbstractModule() {
      @Override protected void configure() {
        MapBinder.newMapBinder(binder(), String.class, String.class)
            .addBinding("a").toInstance("A");
        MapBinder.newMapBinder(binder(), String.class, Integer.class)
            .addBinding("1").toInstance(1);
      }
    };
    Injector injector = Guice.createInjector(module);

    assertEquals(mapOf("a", "A"), injector.getInstance(Key.get(mapOfString)));
    assertEquals(mapOf("1", 1), injector.getInstance(Key.get(mapOfInteger)));
    assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(module), BOTH, false, 1,
        instance("a", "A"));
    assertMapVisitor(Key.get(mapOfInteger), stringType, intType, setOf(module), BOTH, false, 1,
        instance("1", 1));
  }

  public void testMapBinderWithEmptyMap() {
    Module module = new AbstractModule() {
      @Override protected void configure() {
        MapBinder.newMapBinder(binder(), String.class, String.class);
      }
    };
    Injector injector = Guice.createInjector(module);

    Map<String, String> map = injector.getInstance(Key.get(mapOfString));
    assertEquals(Collections.emptyMap(), map);
    assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(module), BOTH, false, 0);
  }

  public void testMapBinderMapIsUnmodifiable() {
    Injector injector = Guice.createInjector(new AbstractModule() {
      @Override protected void configure() {
        MapBinder.newMapBinder(binder(), String.class, String.class)
            .addBinding("a").toInstance("A");
      }
    });

    Map<String, String> map = injector.getInstance(Key.get(mapOfString));
    try {
      map.clear();
      fail();
    } catch(UnsupportedOperationException expected) {
    }
  }

  public void testMapBinderMapIsLazy() {
    Module module = new AbstractModule() {
      @Override protected void configure() {
        MapBinder.newMapBinder(binder(), String.class, Integer.class)
            .addBinding("num").toProvider(new Provider<Integer>() {
          int nextValue = 1;
          @Override public Integer get() {
            return nextValue++;
          }
        });
      }
    };
    Injector injector = Guice.createInjector(module);

    assertEquals(mapOf("num", 1), injector.getInstance(Key.get(mapOfInteger)));
    assertEquals(mapOf("num", 2), injector.getInstance(Key.get(mapOfInteger)));
    assertEquals(mapOf("num", 3), injector.getInstance(Key.get(mapOfInteger)));
    assertMapVisitor(Key.get(mapOfInteger), stringType, intType, setOf(module), BOTH, false, 0,
        providerInstance("num", 1));
  }

  public void testMapBinderMapForbidsDuplicateKeys() {
    Module module = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(
            binder(), String.class, String.class);
        multibinder.addBinding("a").toInstance("A");
        multibinder.addBinding("a").toInstance("B");
      }
    };
    try {
      Guice.createInjector(module);
      fail();
    } catch(CreationException expected) {
      assertContains(expected.getMessage(),
          "Map injection failed due to duplicated key \"a\"");
    }
    
    assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(module), MODULE, false, 0,
        instance("a", "A"), instance("a", "B"));
  }

  public void testExhaustiveDuplicateErrorMessage() throws Exception {
    class Module1 extends AbstractModule {
      @Override protected void configure() {
        MapBinder<String, Object> mapbinder =
            MapBinder.newMapBinder(binder(), String.class, Object.class);
        mapbinder.addBinding("a").to(String.class);
      }
    }
    class Module2 extends AbstractModule {
      @Override protected void configure() {
        MapBinder<String, Object> mapbinder =
            MapBinder.newMapBinder(binder(), String.class, Object.class);
        mapbinder.addBinding("a").to(Integer.class);
        mapbinder.addBinding("b").to(String.class);
      }
    }
    class Module3 extends AbstractModule {
      @Override protected void configure() {
        MapBinder<String, Object> mapbinder =
            MapBinder.newMapBinder(binder(), String.class, Object.class);
        mapbinder.addBinding("b").to(Integer.class);
      }
    }
    class Main extends AbstractModule {
      @Override protected void configure() {
        MapBinder.newMapBinder(binder(), String.class, Object.class);
        install(new Module1());
        install(new Module2());
        install(new Module3());
      }
      @Provides String provideString() { return "foo"; }
      @Provides Integer provideInt() { return 42; }
    }
    try {
      Guice.createInjector(new Main());
      fail();
    } catch(CreationException ce) {
      assertContains(ce.getMessage(),
          "Map injection failed due to duplicated key \"a\", from bindings:",
          asModuleChain(Main.class, Module1.class),
          asModuleChain(Main.class, Module2.class),
          "and key: \"b\", from bindings:",
          asModuleChain(Main.class, Module2.class),
          asModuleChain(Main.class, Module3.class),
          "at " + Main.class.getName() + ".configure(",
          asModuleChain(Main.class, MapBinder.RealMapBinder.class));
      assertEquals(1, ce.getErrorMessages().size());
    }
  }

  public void testMapBinderMapPermitDuplicateElements() {
    Module ab = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(
            binder(), String.class, String.class);
        multibinder.addBinding("a").toInstance("A");
        multibinder.addBinding("b").toInstance("B");
        multibinder.permitDuplicates();
      }
    };
    Module bc = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(
            binder(), String.class, String.class);
        multibinder.addBinding("b").toInstance("B");
        multibinder.addBinding("c").toInstance("C");
        multibinder.permitDuplicates();
      }
    };
    Injector injector = Guice.createInjector(ab, bc);

    assertEquals(mapOf("a", "A", "b", "B", "c", "C"), injector.getInstance(Key.get(mapOfString)));
    assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(ab, bc), BOTH, true, 0,
        instance("a", "A"), instance("b", "B"), instance("c", "C"));
  }

  public void testMapBinderMapDoesNotDedupeDuplicateValues() {
    class ValueType {
      int keyPart;
      int dataPart;
      private ValueType(int keyPart, int dataPart) {
        this.keyPart = keyPart;
        this.dataPart = dataPart;
      }
      @Override
      public boolean equals(Object obj) {
        return (obj instanceof ValueType) && (keyPart == ((ValueType) obj).keyPart);
      }
      @Override
      public int hashCode() {
        return keyPart;
      }
    }
    Module m1 = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, ValueType> multibinder = MapBinder.newMapBinder(
            binder(), String.class, ValueType.class);
        multibinder.addBinding("a").toInstance(new ValueType(1, 2));
      }
    };
    Module m2 = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, ValueType> multibinder = MapBinder.newMapBinder(
            binder(), String.class, ValueType.class);
        multibinder.addBinding("b").toInstance(new ValueType(1, 3));
      }
    };
    
    Injector injector = Guice.createInjector(m1, m2);
    Map<String, ValueType> map = injector.getInstance(new Key<Map<String, ValueType>>() {});
    assertEquals(2, map.get("a").dataPart);
    assertEquals(3, map.get("b").dataPart);
  }

  public void testMapBinderMultimap() {
    AbstractModule ab1c = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(
            binder(), String.class, String.class);
        multibinder.addBinding("a").toInstance("A");
        multibinder.addBinding("b").toInstance("B1");
        multibinder.addBinding("c").toInstance("C");
      }
    };
    AbstractModule b2c = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(
            binder(), String.class, String.class);
        multibinder.addBinding("b").toInstance("B2");
        multibinder.addBinding("c").toInstance("C");
        multibinder.permitDuplicates();
      }
    };
    Injector injector = Guice.createInjector(ab1c, b2c);

    assertEquals(mapOf("a", setOf("A"), "b", setOf("B1", "B2"), "c", setOf("C")),
        injector.getInstance(Key.get(mapOfSetOfString)));
    assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(ab1c, b2c), BOTH, true, 0,
        instance("a", "A"), instance("b", "B1"), instance("b", "B2"), instance("c", "C"));
  }

  public void testMapBinderMultimapWithAnotation() {
    AbstractModule ab1 = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(
            binder(), String.class, String.class, Abc.class);
        multibinder.addBinding("a").toInstance("A");
        multibinder.addBinding("b").toInstance("B1");
      }
    };
    AbstractModule b2c = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(
            binder(), String.class, String.class, Abc.class);
        multibinder.addBinding("b").toInstance("B2");
        multibinder.addBinding("c").toInstance("C");
        multibinder.permitDuplicates();
      }
    };
    Injector injector = Guice.createInjector(ab1, b2c);

    assertEquals(mapOf("a", setOf("A"), "b", setOf("B1", "B2"), "c", setOf("C")),
        injector.getInstance(Key.get(mapOfSetOfString, Abc.class)));
    try {
      injector.getInstance(Key.get(mapOfSetOfString));
      fail();
    } catch (ConfigurationException expected) {}
    
    assertMapVisitor(Key.get(mapOfString, Abc.class), stringType, stringType, setOf(ab1, b2c), BOTH, true, 0,
        instance("a", "A"), instance("b", "B1"), instance("b", "B2"), instance("c", "C"));
  }

  public void testMapBinderMultimapIsUnmodifiable() {
    Injector injector = Guice.createInjector(new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> mapBinder = MapBinder.newMapBinder(
            binder(), String.class, String.class);
        mapBinder.addBinding("a").toInstance("A");
        mapBinder.permitDuplicates();
      }
    });

    Map<String, Set<String>> map = injector.getInstance(Key.get(mapOfSetOfString));
    try {
      map.clear();
      fail();
    } catch(UnsupportedOperationException expected) {
    }
    try {
      map.get("a").clear();
      fail();
    } catch(UnsupportedOperationException expected) {
    }
  }

  public void testMapBinderMapForbidsNullKeys() {
    try {
      Guice.createInjector(new AbstractModule() {
        @Override protected void configure() {
          MapBinder.newMapBinder(binder(), String.class, String.class).addBinding(null);
        }
      });
      fail();
    } catch (CreationException expected) {}
  }

  public void testMapBinderMapForbidsNullValues() {
    Injector injector = Guice.createInjector(new AbstractModule() {
      @Override protected void configure() {
        MapBinder.newMapBinder(binder(), String.class, String.class)
            .addBinding("null").toProvider(Providers.<String>of(null));
      }
    });

    try {
      injector.getInstance(Key.get(mapOfString));
      fail();
    } catch(ProvisionException expected) {
      assertContains(expected.getMessage(),
          "1) Map injection failed due to null value for key \"null\"");
    }
  }

  public void testMapBinderProviderIsScoped() {
    final Provider<Integer> counter = new Provider<Integer>() {
      int next = 1;
      @Override public Integer get() {
        return next++;
      }
    };

    Injector injector = Guice.createInjector(new AbstractModule() {
      @Override protected void configure() {
        MapBinder.newMapBinder(binder(), String.class, Integer.class)
            .addBinding("one").toProvider(counter).asEagerSingleton();
      }
    });

    assertEquals(1, (int) injector.getInstance(Key.get(mapOfInteger)).get("one"));
    assertEquals(1, (int) injector.getInstance(Key.get(mapOfInteger)).get("one"));
  }

  public void testSourceLinesInMapBindings() {
    try {
      Guice.createInjector(new AbstractModule() {
        @Override protected void configure() {
          MapBinder.newMapBinder(binder(), String.class, Integer.class)
              .addBinding("one");
        }
      });
      fail();
    } catch (CreationException expected) {
      assertContains(expected.getMessage(),
          "1) No implementation for java.lang.Integer",
          "at " + getClass().getName());
    }
  }

  /** We just want to make sure that mapbinder's binding depends on the underlying multibinder. */
  public void testMultibinderDependencies() {
    Injector injector = Guice.createInjector(new AbstractModule() {
      @Override protected void configure() {
        MapBinder<Integer, String> mapBinder
            = MapBinder.newMapBinder(binder(), Integer.class, String.class);
        mapBinder.addBinding(1).toInstance("A");
        mapBinder.addBinding(2).to(Key.get(String.class, Names.named("b")));

        bindConstant().annotatedWith(Names.named("b")).to("B");
      }
    });

    Binding<Map<Integer, String>> binding = injector.getBinding(new Key<Map<Integer, String>>() {});
    HasDependencies withDependencies = (HasDependencies) binding;
    Key<?> setKey = new Key<Set<Map.Entry<Integer, Provider<String>>>>() {};
    assertEquals(ImmutableSet.<Dependency<?>>of(Dependency.get(setKey)),
        withDependencies.getDependencies());
    Set<String> elements = Sets.newHashSet();
    elements.addAll(recurseForDependencies(injector, withDependencies));
    assertEquals(ImmutableSet.of("A", "B"), elements);
  }

  private Set<String> recurseForDependencies(Injector injector, HasDependencies hasDependencies) {
    Set<String> elements = Sets.newHashSet();
    for (Dependency<?> dependency : hasDependencies.getDependencies()) {
      Binding<?> binding = injector.getBinding(dependency.getKey());
      HasDependencies deps = (HasDependencies) binding;
      if (binding instanceof InstanceBinding) {
        elements.add((String) ((InstanceBinding<?>) binding).getInstance());
      } else {
        elements.addAll(recurseForDependencies(injector, deps));
      }
    }    
    return elements;
  }

  /** We just want to make sure that mapbinder's binding depends on the underlying multibinder. */
  public void testMultibinderDependenciesInToolStage() {
    Injector injector = Guice.createInjector(Stage.TOOL, new AbstractModule() {
      @Override protected void configure() {
          MapBinder<Integer, String> mapBinder
              = MapBinder.newMapBinder(binder(), Integer.class, String.class);
          mapBinder.addBinding(1).toInstance("A");
          mapBinder.addBinding(2).to(Key.get(String.class, Names.named("b")));
  
          bindConstant().annotatedWith(Names.named("b")).to("B");
        }});

    Binding<Map<Integer, String>> binding = injector.getBinding(new Key<Map<Integer, String>>() {});
    HasDependencies withDependencies = (HasDependencies) binding;
    Key<?> setKey = new Key<Set<Map.Entry<Integer, Provider<String>>>>() {};
    assertEquals(ImmutableSet.<Dependency<?>>of(Dependency.get(setKey)),
        withDependencies.getDependencies());
  }
  

  /**
   * Our implementation maintains order, but doesn't guarantee it in the API spec.
   * TODO: specify the iteration order?
   */
  public void testBindOrderEqualsIterationOrder() {
    Injector injector = Guice.createInjector(
        new AbstractModule() {
          @Override protected void configure() {
            MapBinder<String, String> mapBinder
                = MapBinder.newMapBinder(binder(), String.class, String.class);
            mapBinder.addBinding("leonardo").toInstance("blue");
            mapBinder.addBinding("donatello").toInstance("purple");
            install(new AbstractModule() {
              @Override protected void configure() {
                MapBinder.newMapBinder(binder(), String.class, String.class)
                    .addBinding("michaelangelo").toInstance("orange");
              }
            });
          }
        },
        new AbstractModule() {
          @Override protected void configure() {
            MapBinder.newMapBinder(binder(), String.class, String.class)
                .addBinding("raphael").toInstance("red");
          }
        });

    Map<String, String> map = injector.getInstance(new Key<Map<String, String>>() {});
    Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
    assertEquals(Maps.immutableEntry("leonardo", "blue"), iterator.next());
    assertEquals(Maps.immutableEntry("donatello", "purple"), iterator.next());
    assertEquals(Maps.immutableEntry("michaelangelo", "orange"), iterator.next());
    assertEquals(Maps.immutableEntry("raphael", "red"), iterator.next());
  }
  
  /**
   * With overrides, we should get the union of all map bindings.
   */
  public void testModuleOverrideAndMapBindings() {
    Module ab = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(binder(), String.class, String.class);
        multibinder.addBinding("a").toInstance("A");
        multibinder.addBinding("b").toInstance("B");
      }
    };
    Module cd = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(binder(), String.class, String.class);
        multibinder.addBinding("c").toInstance("C");
        multibinder.addBinding("d").toInstance("D");
      }
    };
    Module ef = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(binder(), String.class, String.class);
        multibinder.addBinding("e").toInstance("E");
        multibinder.addBinding("f").toInstance("F");
      }
    };

    Module abcd = Modules.override(ab).with(cd);
    Injector injector = Guice.createInjector(abcd, ef);
    assertEquals(mapOf("a", "A", "b", "B", "c", "C", "d", "D", "e", "E", "f", "F"),
        injector.getInstance(Key.get(mapOfString)));
    assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(abcd, ef), BOTH, false, 0,
        instance("a", "A"), instance("b", "B"), instance("c", "C"), instance("d", "D"), instance(
            "e", "E"), instance("f", "F"));
  }
  
  public void testDeduplicateMapBindings() {
    Module module = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> mapbinder =
            MapBinder.newMapBinder(binder(), String.class, String.class);
        mapbinder.addBinding("a").toInstance("A");
        mapbinder.addBinding("a").toInstance("A");
        mapbinder.addBinding("b").toInstance("B");
        mapbinder.addBinding("b").toInstance("B");
        
      }
    };
    Injector injector = Guice.createInjector(module);
    assertEquals(mapOf("a", "A", "b", "B"),
        injector.getInstance(Key.get(mapOfString)));
    assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(module), BOTH, false, 0,
        instance("a", "A"), instance("b", "B"));
  }
  
  /**
   * With overrides, we should get the union of all map bindings.
   */
  public void testModuleOverrideAndMapBindingsWithPermitDuplicates() {
    Module abc = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(binder(), String.class, String.class);
        multibinder.addBinding("a").toInstance("A");
        multibinder.addBinding("b").toInstance("B");
        multibinder.addBinding("c").toInstance("C");
        multibinder.permitDuplicates();
      }
    };
    Module cd = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(binder(), String.class, String.class);
        multibinder.addBinding("c").toInstance("C");
        multibinder.addBinding("d").toInstance("D");
        multibinder.permitDuplicates();
      }
    };
    Module ef = new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> multibinder = MapBinder.newMapBinder(binder(), String.class, String.class);
        multibinder.addBinding("e").toInstance("E");
        multibinder.addBinding("f").toInstance("F");
        multibinder.permitDuplicates();
      }
    };

    Module abcd = Modules.override(abc).with(cd);
    Injector injector = Guice.createInjector(abcd, ef);
    assertEquals(mapOf("a", "A", "b", "B", "c", "C", "d", "D", "e", "E", "f", "F"),
        injector.getInstance(Key.get(mapOfString)));
    assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(abcd, ef), BOTH, true, 0,
        instance("a", "A"), instance("b", "B"), instance("c", "C"), instance(
            "d", "D"), instance("e", "E"), instance("f", "F"));

  }  

  /** Ensure there are no initialization race conditions in basic map injection. */
  public void testBasicMapDependencyInjection() {
    final AtomicReference<Map<String, String>> injectedMap =
        new AtomicReference<Map<String, String>>();
    final Object anObject = new Object() {
      @Inject void initialize(Map<String, String> map) {
        injectedMap.set(map);
      }
    };
    Module abc = new AbstractModule() {
      @Override protected void configure() {
        requestInjection(anObject);
        MapBinder<String, String> multibinder =
            MapBinder.newMapBinder(binder(), String.class, String.class);
        multibinder.addBinding("a").toInstance("A");
        multibinder.addBinding("b").toInstance("B");
        multibinder.addBinding("c").toInstance("C");
      }
    };
    Guice.createInjector(abc);
    assertEquals(mapOf("a", "A", "b", "B", "c", "C"), injectedMap.get());
  } 

  /** Ensure there are no initialization race conditions in provider multimap injection. */
  public void testProviderMultimapDependencyInjection() {
    final AtomicReference<Map<String, Set<Provider<String>>>> injectedMultimap =
        new AtomicReference<Map<String, Set<Provider<String>>>>();
    final Object anObject = new Object() {
      @Inject void initialize(Map<String, Set<Provider<String>>> multimap) {
        injectedMultimap.set(multimap);
      }
    };
    Module abc = new AbstractModule() {
      @Override protected void configure() {
        requestInjection(anObject);
        MapBinder<String, String> multibinder =
            MapBinder.newMapBinder(binder(), String.class, String.class);
        multibinder.permitDuplicates();
        multibinder.addBinding("a").toInstance("A");
        multibinder.addBinding("b").toInstance("B");
        multibinder.addBinding("c").toInstance("C");
      }
    };
    Guice.createInjector(abc);
    Map<String, String> map = Maps.transformValues(injectedMultimap.get(),
        new Function<Set<Provider<String>>, String>() {
          @Override public String apply(Set<Provider<String>> stringProvidersSet) {
            return Iterables.getOnlyElement(stringProvidersSet).get();
          }
        });
    assertEquals(mapOf("a", "A", "b", "B", "c", "C"), map);
  }
  
  @Retention(RUNTIME) @BindingAnnotation
  @interface Abc {}

  @Retention(RUNTIME) @BindingAnnotation
  @interface De {}

  @SuppressWarnings("unchecked")
  private <K, V> Map<K, V> mapOf(Object... elements) {
    Map<K, V> result = new HashMap<K, V>();
    for (int i = 0; i < elements.length; i += 2) {
      result.put((K)elements[i], (V)elements[i+1]);
    }
    return result;
  }

  @SuppressWarnings("unchecked")
  private <V> Set<V> setOf(V... elements) {
    return new HashSet<V>(Arrays.asList(elements));
  }

  @BindingAnnotation
  @Retention(RetentionPolicy.RUNTIME)
  @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
  private static @interface Marker {}

  @Marker
  public void testMapBinderMatching() throws Exception {
    Method m = MapBinderTest.class.getDeclaredMethod("testMapBinderMatching");
    assertNotNull(m);
    final Annotation marker = m.getAnnotation(Marker.class);
    Injector injector = Guice.createInjector(new AbstractModule() {
      @Override public void configure() {
        MapBinder<Integer, Integer> mb1 =
          MapBinder.newMapBinder(binder(), Integer.class, Integer.class, Marker.class);
        MapBinder<Integer, Integer> mb2 = 
          MapBinder.newMapBinder(binder(), Integer.class, Integer.class, marker);
        mb1.addBinding(1).toInstance(1);
        mb2.addBinding(2).toInstance(2);

        // This assures us that the two binders are equivalent, so we expect the instance added to
        // each to have been added to one set.
        assertEquals(mb1, mb2);
      }
    });
    TypeLiteral<Map<Integer, Integer>> t = new TypeLiteral<Map<Integer, Integer>>() {};
    Map<Integer, Integer> s1 = injector.getInstance(Key.get(t, Marker.class));
    Map<Integer, Integer> s2 = injector.getInstance(Key.get(t, marker));

    // This assures us that the two sets are in fact equal.  They may not be same set (as in Java
    // object identical), but we shouldn't expect that, since probably Guice creates the set each
    // time in case the elements are dependent on scope.
    assertEquals(s1, s2);

    // This ensures that MultiBinder is internally using the correct set name --
    // making sure that instances of marker annotations have the same set name as
    // MarkerAnnotation.class.
    Map<Integer, Integer> expected = new HashMap<Integer, Integer>();
    expected.put(1, 1);
    expected.put(2, 2);
    assertEquals(expected, s1);
  }

  public void testTwoMapBindersAreDistinct() {
    Injector injector = Guice.createInjector(new AbstractModule() {
      @Override protected void configure() {  
        MapBinder.newMapBinder(binder(), String.class, String.class)
            .addBinding("A").toInstance("a");
        
        MapBinder.newMapBinder(binder(), Integer.class, String.class)
            .addBinding(1).toInstance("b");
      }
    });
    Collector collector = new Collector();
    Binding<Map<String, String>> map1 = injector.getBinding(Key.get(mapOfString));
    map1.acceptTargetVisitor(collector);
    assertNotNull(collector.mapbinding);
    MapBinderBinding<?> map1Binding = collector.mapbinding;
  
    Binding<Map<Integer, String>> map2 = injector.getBinding(Key.get(mapOfIntString));
    map2.acceptTargetVisitor(collector);
    assertNotNull(collector.mapbinding);
    MapBinderBinding<?> map2Binding = collector.mapbinding;
  
    List<Binding<String>> bindings = injector.findBindingsByType(stringType);
    assertEquals("should have two elements: " + bindings, 2, bindings.size());
    Binding<String> a = bindings.get(0);
    Binding<String> b = bindings.get(1);
    assertEquals("a", ((InstanceBinding<String>) a).getInstance());
    assertEquals("b", ((InstanceBinding<String>) b).getInstance());
    
    // Make sure the correct elements belong to their own sets.
    assertTrue(map1Binding.containsElement(a));
    assertFalse(map1Binding.containsElement(b));
  
    assertFalse(map2Binding.containsElement(a));
    assertTrue(map2Binding.containsElement(b));
  }

  // Tests for com.google.inject.internal.WeakKeySet not leaking memory.
  public void testWeakKeySet_integration_mapbinder() {
    Key<Map<String, String>> mapKey = Key.get(new TypeLiteral<Map<String, String>>() {});
    
    Injector parentInjector = Guice.createInjector(new AbstractModule() {
          @Override protected void configure() {
            bind(String.class).toInstance("hi");
          }
        });
    WeakKeySetUtils.assertNotBlacklisted(parentInjector, mapKey);

    Injector childInjector = parentInjector.createChildInjector(new AbstractModule() {
      @Override protected void configure() {
        MapBinder<String, String> binder =
            MapBinder.newMapBinder(binder(), String.class, String.class);
        binder.addBinding("bar").toInstance("foo");
      }
    });
    WeakReference<Injector> weakRef = new WeakReference<Injector>(childInjector);
    WeakKeySetUtils.assertBlacklisted(parentInjector, mapKey);
    
    // Clear the ref, GC, and ensure that we are no longer blacklisting.
    childInjector = null;
    
    WeakKeySetUtils.awaitClear(weakRef);
    WeakKeySetUtils.assertNotBlacklisted(parentInjector, mapKey);
  }
}
