New multibindings extension.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@452 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/build.xml b/build.xml
index 3f3bae5..a3aa0ee 100644
--- a/build.xml
+++ b/build.xml
@@ -31,6 +31,7 @@
     <ant antfile="struts2/plugin/build.xml" target="jar" inheritAll="false"/>
     <ant antfile="extensions/assistedinject/build.xml" target="jar" inheritAll="false"/>
     <ant antfile="extensions/throwingproviders/build.xml" target="jar" inheritAll="false"/>
+    <ant antfile="extensions/multibindings/build.xml" target="jar" inheritAll="false"/>
     <ant antfile="extensions/commands/build.xml" target="jar" inheritAll="false"/>
 
     <copy toDir="${build.dir}/dist"> 
@@ -49,6 +50,9 @@
       <fileset dir="extensions/throwingproviders/build" includes="*.jar"/>
     </copy>
     <copy toDir="${build.dir}/dist">
+      <fileset dir="extensions/multibindings/build" includes="*.jar"/>
+    </copy>
+    <copy toDir="${build.dir}/dist">
       <fileset dir="extensions/commands/build" includes="*.jar"/>
     </copy>
 
@@ -121,6 +125,7 @@
     <ant dir="struts2/plugin" antfile="build.xml" target="clean"/>
     <ant dir="extensions/assistedinject" antfile="build.xml" target="clean"/>
     <ant dir="extensions/throwingproviders" antfile="build.xml" target="clean"/>
+    <ant dir="extensions/multibindings" antfile="build.xml" target="clean"/>
     <ant dir="extensions/commands" antfile="build.xml" target="clean"/>
   </target>
   
diff --git a/extensions/multibindings/build.properties b/extensions/multibindings/build.properties
new file mode 100644
index 0000000..53a1312
--- /dev/null
+++ b/extensions/multibindings/build.properties
@@ -0,0 +1,5 @@
+lib.dir=../../lib
+src.dir=src
+test.dir=test
+build.dir=build
+test.class=com.google.inject.multibindings.MultibinderTest
diff --git a/extensions/multibindings/build.xml b/extensions/multibindings/build.xml
new file mode 100644
index 0000000..fba0d9e
--- /dev/null
+++ b/extensions/multibindings/build.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+
+<project name="guice-multibindings" basedir="." default="jar">
+
+  <import file="../../common.xml"/>
+  
+  <path id="compile.classpath">
+    <fileset dir="${lib.dir}" includes="*.jar"/>
+    <fileset dir="${lib.dir}/build" includes="*.jar"/>
+    <fileset dir="../../build/dist" includes="*.jar"/>
+  </path>
+
+  <target name="jar" depends="compile"
+       description="Build jar.">
+    <mkdir dir="${build.dir}"/>
+    <jar destfile="${build.dir}/${ant.project.name}-${version}.jar">
+      <fileset dir="${build.dir}/classes"/>
+    </jar>
+  </target>
+
+</project>
diff --git a/extensions/multibindings/multibindings.iml b/extensions/multibindings/multibindings.iml
new file mode 100644
index 0000000..bc42261
--- /dev/null
+++ b/extensions/multibindings/multibindings.iml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module" module-name="guice" />
+    <orderEntryProperties />
+  </component>
+</module>
+
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java b/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
new file mode 100644
index 0000000..97ffd09
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
@@ -0,0 +1,305 @@
+/**
+ * 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 com.google.inject.*;
+import com.google.inject.binder.LinkedBindingBuilder;
+import static com.google.inject.internal.Objects.nonNull;
+import com.google.inject.internal.TypeWithArgument;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.reflect.Type;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * An API to bind multiple values separately, only to later inject them as a
+ * complete collection. Multibinder is intended for use in your application's
+ * module:
+ * <pre><code>
+ * public class SnacksModule extends AbstractModule {
+ *   protected void configure() {
+ *     Multibinder&lt;Snack&gt; multibinder
+ *         = Multibinder.newSetBinder(binder(), Snack.class);
+ *     multibinder.addBinding().toInstance(new Twix());
+ *     multibinder.addBinding().toProvider(SnickersProvider.class);
+ *     multibinder.addBinding().to(Skittles.class);
+ *   }
+ * }</code></pre>
+ *
+ * <p>With this binding, a {@link Set}{@code <Snack>} can now be injected:
+ * <pre><code>
+ * class SnackMachine {
+ *   {@literal @}Inject
+ *   public SnackMachine(Set&lt;Snack&gt; snacks) { ... }
+ * }</code></pre>
+ *
+ * <p>Create multibindings from different modules is supported. For example, it
+ * is okay to have both {@code CandyModule} and {@code ChipsModule} to both
+ * create their own {@code Multibinder<Snack>}, and to each contribute bindings
+ * to the set of snacks. When that set is injected, it will contain elements
+ * from both modules.
+ *
+ * <p>Elements are resolved at set injection time. If an element is bound to a
+ * provider, that provider's get method will be called each time the set is
+ * injected (unless the binding is also scoped).
+ *
+ * <p>Annotations are be used to create different sets of the same element
+ * type. Each distinct annotation gets its own independent collection of
+ * elements.
+ *
+ * <p><strong>Elements must be distinct.</strong> If multiple bound elements
+ * have the same value, set injection will fail.
+ *
+ * <p><strong>Elements must be non-null.</strong> If any set element is null,
+ * set injection will fail.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public abstract class Multibinder<T> {
+  private Multibinder() {}
+
+  /**
+   * Returns a new multibinder that collects instances of {@code type} in a
+   * {@link Set} that is itself bound with no binding annotation.
+   */
+  static <T> Multibinder<T> newSetBinder(Binder binder, Type type) {
+    RealMultibinder<T> result = new RealMultibinder<T>(binder, type, null);
+    binder.install(result);
+    return result;
+  }
+
+  /**
+   * Returns a new multibinder that collects instances of {@code type} in a
+   * {@link Set} that is itself bound with {@code annotation}.
+   */
+  static <T> Multibinder<T> newSetBinder(Binder binder, Type type, Annotation annotation) {
+    nonNull(annotation, "annotation");
+    RealMultibinder<T> result = new RealMultibinder<T>(binder, type, annotation);
+    binder.install(result);
+    return result;
+  }
+
+  /**
+   * Returns a new multibinder that collects instances of {@code type} in a
+   * {@link Set} that is itself bound with {@code annotationType}.
+   */
+  static <T> Multibinder<T> newSetBinder(Binder binder, Type type,
+      Class<? extends Annotation> annotationType) {
+    nonNull(annotationType, "annotationType");
+    RealMultibinder<T> result = new RealMultibinder<T>(binder, type, annotationType);
+    binder.install(result);
+    return result;
+  }
+
+  /**
+   * Returns a binding builder used to add a new element in the set. Each
+   * bound element must have a distinct value. Bound providers will be
+   * evaluated each time the set is injected.
+   *
+   * <p>It is an error to call this method without also calling one of the
+   * {@code to} methods on the returned binding builder.
+   *
+   * <p>Scoping elements independently is supported. Use the {@code in} method
+   * to specify a binding scope.
+   */
+  public abstract LinkedBindingBuilder<T> addBinding();
+
+  /**
+   * The actual multibinder plays several roles:
+   *
+   * <p>As a Multibinder, it acts as a factory for LinkedBindingBuilders for
+   * each of the set's elements. Each binding is given an annotation that
+   * identifies it as a part of this set.
+   *
+   * <p>As a Module, it installs the binding to the set itself. 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 multibinders can be created for the same target collection, but
+   * only one is bound. Since the list of bindings is retrieved from the
+   * injector itself (and not the multibinder), each multibinder has access to
+   * all contributions from all multibinders.
+   *
+   * <p>As a Provider, this constructs the set instances.
+   *
+   * <p>We use a subclass to hide 'implements Module, Provider' from the public
+   * API.
+   */
+  private static final class RealMultibinder<T>
+      extends Multibinder<T> implements Module, Provider<Set<T>> {
+    private final Type elementType;
+    private final Object bindingAnnotation;
+    private final String setName;
+
+    // non-null until this multibinding is initialized
+    private Binder binder;
+    private List<Provider<T>> providers;
+
+    private RealMultibinder(Binder binder, Type elementType,
+        /* @Nullable */ Object bindingAnnotation) {
+      this.binder = nonNull(binder, "binder");
+      this.elementType = nonNull(elementType, "elementType");
+      this.bindingAnnotation = bindingAnnotation;
+
+      if (bindingAnnotation == null) {
+        setName = "";
+      } else if (bindingAnnotation instanceof Class) {
+        setName = "@" + ((Class) bindingAnnotation).getName();
+      } else if (bindingAnnotation instanceof Annotation) {
+        // we're hosed if the user doesn't provide a reasonable toString()
+        setName = bindingAnnotation.toString();
+      } else {
+        throw new IllegalArgumentException("Not an annotation " + bindingAnnotation);
+      }
+    }
+
+    @SuppressWarnings("unchecked")
+    public void configure(Binder binder) {
+      if (isInitialized()) {
+        throw new IllegalStateException("Multibinder was already initialized");
+      }
+
+      Key<Set<T>> setKey;
+      Type setType = new TypeWithArgument(Set.class, elementType);
+      if (bindingAnnotation instanceof Annotation) {
+        setKey = (Key<Set<T>>) Key.get(setType, (Annotation) bindingAnnotation);
+      } else if (bindingAnnotation instanceof Class<?>) {
+        setKey = (Key<Set<T>>) Key.get(setType, (Class<? extends Annotation>) bindingAnnotation);
+      } else {
+        setKey = (Key<Set<T>>) Key.get(setType);
+      }
+
+      binder.bind(setKey).toProvider(this);
+    }
+
+    @SuppressWarnings("unchecked")
+    public LinkedBindingBuilder<T> addBinding() {
+      if (isInitialized()) {
+        throw new IllegalStateException("Multibinder was already initialized");
+      }
+
+      return (LinkedBindingBuilder<T>) binder.bind(
+          Key.get(elementType, new RealElement(setName)));
+    }
+
+    /**
+     * Invoked by Guice at Injector-creation time to prepare providers for each
+     * element in this set. At this time the set's size is known, but its
+     * contents are only evaluated when get() is invoked.
+     */
+    @Inject void initialize(Injector injector) {
+      providers = new ArrayList<Provider<T>>();
+      for (Map.Entry<Key<?>, Binding<?>> entry : injector.getBindings().entrySet()) {
+        if (keyMatches(entry.getKey())) {
+          @SuppressWarnings("unchecked")
+          Binding<T> binding = (Binding<T>) entry.getValue();
+          providers.add(binding.getProvider());
+        }
+      }
+
+      this.binder = null;
+    }
+
+    private boolean keyMatches(Key<?> key) {
+      return key.getAnnotation() instanceof Element
+          && ((Element) key.getAnnotation()).setName().equals(setName);
+    }
+
+    private boolean isInitialized() {
+      return binder == null;
+    }
+
+    public Set<T> get() {
+      if (!isInitialized()) {
+        throw new IllegalStateException("Multibinder is not initialized");
+      }
+
+      Set<T> result = new LinkedHashSet<T>();
+      for (Provider<T> provider : providers) {
+        final T newValue = provider.get();
+        if (newValue == null) {
+          throw new IllegalStateException("Set injection failed due to null element");
+        }
+        if (!result.add(newValue)) {
+          throw new IllegalStateException("Set injection failed due to duplicated element \""
+              + newValue + "\"");
+        }
+      }
+      return Collections.unmodifiableSet(result);
+    }
+
+    public boolean equals(Object o) {
+      return o instanceof Module
+          && ((RealMultibinder)o ).setName.equals(setName);
+    }
+    public int hashCode() {
+      return setName.hashCode();
+    }
+  }
+
+  /**
+   * An internal binding annotation applied to each element in a multibinding.
+   * All elements are assigned a globally-unique id to allow different modules
+   * to contribute multibindings independently.
+   */
+  @Retention(RUNTIME) @BindingAnnotation
+  private @interface Element {
+    String setName();
+    int uniqueId();
+  }
+
+  private static class RealElement implements Element {
+    private static final AtomicInteger nextUniqueId = new AtomicInteger(1);
+
+    private final int uniqueId = nextUniqueId.getAndIncrement();
+    private final String setName;
+
+    RealElement(String setName) {
+      this.setName = setName;
+    }
+
+    public String setName() {
+      return setName;
+    }
+
+    public int uniqueId() {
+      return uniqueId;
+    }
+
+    public Class<? extends Annotation> annotationType() {
+      return Element.class;
+    }
+
+    @Override public String toString() {
+      return "@" + Element.class.getName() + "(uniqueId=" + uniqueId
+          + ",setName=" + setName + ")";
+    }
+
+    @Override public boolean equals(Object o) {
+      return o instanceof Element
+          && ((Element) o).uniqueId() == uniqueId()
+          && ((Element) o).setName().equals(setName());
+    }
+
+    @Override public int hashCode() {
+      return 127 * ("uniqueId".hashCode() ^ uniqueId)
+          + 127 * ("setName".hashCode() ^ setName.hashCode());
+    }
+  }
+}
\ No newline at end of file
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/package-info.java b/extensions/multibindings/src/com/google/inject/multibindings/package-info.java
new file mode 100644
index 0000000..5ec1898
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Extension for binding multiple instances in a collection.
+ */
+package com.google.inject.multibindings;
\ No newline at end of file
diff --git a/extensions/multibindings/test/com/google/inject/multibindings/MultibinderTest.java b/extensions/multibindings/test/com/google/inject/multibindings/MultibinderTest.java
new file mode 100644
index 0000000..ed5c9a8
--- /dev/null
+++ b/extensions/multibindings/test/com/google/inject/multibindings/MultibinderTest.java
@@ -0,0 +1,232 @@
+/**
+ * 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 com.google.inject.*;
+import com.google.inject.name.Names;
+import static com.google.inject.name.Names.named;
+import com.google.inject.util.Providers;
+import junit.framework.TestCase;
+
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class MultibinderTest extends TestCase {
+
+  final TypeLiteral<Set<String>> setOfString = new TypeLiteral<Set<String>>() {};
+  final TypeLiteral<Set<Integer>> setOfInteger = new TypeLiteral<Set<Integer>>() {};
+
+  public void testMultibinderAggregatesMultipleModules() {
+    Module abc = new AbstractModule() {
+      protected void configure() {
+        Multibinder<String> multibinder = Multibinder.newSetBinder(binder(), String.class);
+        multibinder.addBinding().toInstance("A");
+        multibinder.addBinding().toInstance("B");
+        multibinder.addBinding().toInstance("C");
+      }
+    };
+    Module de = new AbstractModule() {
+      protected void configure() {
+        Multibinder<String> multibinder = Multibinder.newSetBinder(binder(), String.class);
+        multibinder.addBinding().toInstance("D");
+        multibinder.addBinding().toInstance("E");
+      }
+    };
+
+    Injector injector = Guice.createInjector(abc, de);
+    Set<String> abcde = injector.getInstance(Key.get(setOfString));
+
+    assertEquals(setOf("A", "B", "C", "D", "E"), abcde);
+  }
+
+  public void testMultibinderAggregationForAnnotationInstance() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        Multibinder<String> multibinder
+            = Multibinder.newSetBinder(binder(), String.class, Names.named("abc"));
+        multibinder.addBinding().toInstance("A");
+        multibinder.addBinding().toInstance("B");
+
+        multibinder = Multibinder.newSetBinder(binder(), String.class, Names.named("abc"));
+        multibinder.addBinding().toInstance("C");
+      }
+    });
+
+    Set<String> abcde = injector.getInstance(Key.get(setOfString, Names.named("abc")));
+    assertEquals(setOf("A", "B", "C"), abcde);
+  }
+
+  public void testMultibinderAggregationForAnnotationType() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        Multibinder<String> multibinder
+            = Multibinder.newSetBinder(binder(), String.class, Abc.class);
+        multibinder.addBinding().toInstance("A");
+        multibinder.addBinding().toInstance("B");
+
+        multibinder = Multibinder.newSetBinder(binder(), String.class, Abc.class);
+        multibinder.addBinding().toInstance("C");
+      }
+    });
+
+    Set<String> abcde = injector.getInstance(Key.get(setOfString, Abc.class));
+    assertEquals(setOf("A", "B", "C"), abcde);
+  }
+
+  public void testMultibinderWithMultipleAnnotationValueSets() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        Multibinder<String> abcMultibinder
+            = Multibinder.newSetBinder(binder(), String.class, named("abc"));
+        abcMultibinder.addBinding().toInstance("A");
+        abcMultibinder.addBinding().toInstance("B");
+        abcMultibinder.addBinding().toInstance("C");
+
+        Multibinder<String> deMultibinder
+            = Multibinder.newSetBinder(binder(), String.class, named("de"));
+        deMultibinder.addBinding().toInstance("D");
+        deMultibinder.addBinding().toInstance("E");
+      }
+    });
+
+    Set<String> abc = injector.getInstance(Key.get(setOfString, named("abc")));
+    Set<String> de = injector.getInstance(Key.get(setOfString, named("de")));
+    assertEquals(setOf("A", "B", "C"), abc);
+    assertEquals(setOf("D", "E"), de);
+  }
+
+  public void testMultibinderWithMultipleAnnotationTypeSets() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        Multibinder<String> abcMultibinder
+            = Multibinder.newSetBinder(binder(), String.class, Abc.class);
+        abcMultibinder.addBinding().toInstance("A");
+        abcMultibinder.addBinding().toInstance("B");
+        abcMultibinder.addBinding().toInstance("C");
+
+        Multibinder<String> deMultibinder
+            = Multibinder.newSetBinder(binder(), String.class, De.class);
+        deMultibinder.addBinding().toInstance("D");
+        deMultibinder.addBinding().toInstance("E");
+      }
+    });
+
+    Set<String> abc = injector.getInstance(Key.get(setOfString, Abc.class));
+    Set<String> de = injector.getInstance(Key.get(setOfString, De.class));
+    assertEquals(setOf("A", "B", "C"), abc);
+    assertEquals(setOf("D", "E"), de);
+  }
+
+  public void testMultibinderWithEmptySet() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        Multibinder.newSetBinder(binder(), String.class);
+      }
+    });
+
+    Set<String> set = injector.getInstance(Key.get(setOfString));
+    assertEquals(Collections.emptySet(), set);
+  }
+
+  public void testMultibinderSetIsUnmodifiable() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        Multibinder.newSetBinder(binder(), String.class)
+            .addBinding().toInstance("A");
+      }
+    });
+
+    Set<String> set = injector.getInstance(Key.get(setOfString));
+    try {
+      set.clear();
+      fail();
+    } catch(UnsupportedOperationException expected) {
+    }
+  }
+
+  public void testMultibinderSetIsLazy() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        Multibinder.newSetBinder(binder(), Integer.class)
+            .addBinding().toProvider(new Provider<Integer>() {
+          int nextValue = 1;
+          public Integer get() {
+            return nextValue++;
+          }
+        });
+      }
+    });
+
+    assertEquals(setOf(1), injector.getInstance(Key.get(setOfInteger)));
+    assertEquals(setOf(2), injector.getInstance(Key.get(setOfInteger)));
+    assertEquals(setOf(3), injector.getInstance(Key.get(setOfInteger)));
+  }
+
+  public void testMultibinderSetForbidsDuplicateElements() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        final Multibinder<String> multibinder = Multibinder.newSetBinder(binder(), String.class);
+        multibinder.addBinding().toInstance("A");
+        multibinder.addBinding().toInstance("A");
+      }
+    });
+
+    try {
+      injector.getInstance(Key.get(setOfString));
+      fail();
+    } catch(IllegalStateException expected) {
+      assertEquals("Set injection failed due to duplicated element \"A\"",
+          expected.getMessage());
+    }
+  }
+
+  public void testMultibinderSetForbidsNullElements() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        Multibinder.newSetBinder(binder(), String.class)
+            .addBinding().toProvider(Providers.<String>of(null));
+      }
+    });
+
+    try {
+      injector.getInstance(Key.get(setOfString));
+      fail();
+    } catch(IllegalStateException expected) {
+      assertEquals("Set injection failed due to null element",
+          expected.getMessage());
+    }
+  }
+
+  @Retention(RUNTIME) @BindingAnnotation
+  @interface Abc {}
+
+  @Retention(RUNTIME) @BindingAnnotation
+  @interface De {}
+
+  private <T> Set<T> setOf(T... elements) {
+    HashSet<T> result = new HashSet<T>();
+    result.addAll(Arrays.asList(elements));
+    return result;
+  }
+}
diff --git a/guice.iml b/guice.iml
index a451284..bdec154 100644
--- a/guice.iml
+++ b/guice.iml
@@ -111,7 +111,6 @@
         <SOURCES />
       </library>
     </orderEntry>
-    <orderEntry type="module" module-name="commands" />
     <orderEntryProperties />
   </component>
 </module>
diff --git a/guice.ipr b/guice.ipr
index 97df9a3..04f02db 100644
--- a/guice.ipr
+++ b/guice.ipr
@@ -333,6 +333,7 @@
       <module fileurl="file://$PROJECT_DIR$/extensions/commands/commands.iml" filepath="$PROJECT_DIR$/extensions/commands/commands.iml" />
       <module fileurl="file://$PROJECT_DIR$/extensions/compiletime/compiletime.iml" filepath="$PROJECT_DIR$/extensions/compiletime/compiletime.iml" />
       <module fileurl="file://$PROJECT_DIR$/guice.iml" filepath="$PROJECT_DIR$/guice.iml" />
+      <module fileurl="file://$PROJECT_DIR$/extensions/multibindings/multibindings.iml" filepath="$PROJECT_DIR$/extensions/multibindings/multibindings.iml" />
       <module fileurl="file://$PROJECT_DIR$/servlet/servlet.iml" filepath="$PROJECT_DIR$/servlet/servlet.iml" />
       <module fileurl="file://$PROJECT_DIR$/spring/spring.iml" filepath="$PROJECT_DIR$/spring/spring.iml" />
       <module fileurl="file://$PROJECT_DIR$/struts2/example/struts2-example.iml" filepath="$PROJECT_DIR$/struts2/example/struts2-example.iml" />
diff --git a/src/com/google/inject/ProvisionException.java b/src/com/google/inject/ProvisionException.java
index 919e148..c426e85 100644
--- a/src/com/google/inject/ProvisionException.java
+++ b/src/com/google/inject/ProvisionException.java
@@ -16,11 +16,9 @@
 
 package com.google.inject;
 
-import static com.google.inject.internal.ErrorMessages.ERROR_WHILE_LOCATING_FIELD;
-import static com.google.inject.internal.ErrorMessages.ERROR_WHILE_LOCATING_PARAMETER;
-import static com.google.inject.internal.ErrorMessages.ERROR_WHILE_LOCATING_VALUE;
-import com.google.inject.internal.StackTraceElements;
 import com.google.inject.internal.ErrorMessages;
+import static com.google.inject.internal.ErrorMessages.*;
+import com.google.inject.internal.StackTraceElements;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
@@ -39,7 +37,7 @@
   private final List<InjectionPoint<?>> contexts
       = new ArrayList<InjectionPoint<?>>(5);
 
-  ProvisionException(Throwable cause, String errorMessage) {
+  public ProvisionException(Throwable cause, String errorMessage) {
     super(errorMessage, cause);
     this.errorMessage = errorMessage;
   }
@@ -48,7 +46,7 @@
    * Add an injection point that was being resolved when this exception
    * occurred.
    */
-  public void addContext(InjectionPoint<?> injectionPoint) {
+  void addContext(InjectionPoint<?> injectionPoint) {
     this.contexts.add(injectionPoint);
   }
 
diff --git a/src/com/google/inject/internal/TypeWithArgument.java b/src/com/google/inject/internal/TypeWithArgument.java
new file mode 100644
index 0000000..8ad8d66
--- /dev/null
+++ b/src/com/google/inject/internal/TypeWithArgument.java
@@ -0,0 +1,62 @@
+/**
+ * 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 java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+
+/**
+ * @author crazybob@google.com (Bob Lee)
+ */
+public class TypeWithArgument implements ParameterizedType {
+
+  private final Type rawType;
+  private final Type[] typeArguments;
+
+  public TypeWithArgument(Type rawType, Type... typeArguments) {
+    this.rawType = rawType;
+    this.typeArguments = typeArguments.clone();
+  }
+
+  public Type[] getActualTypeArguments() {
+    return typeArguments.clone();
+  }
+
+  public Type getRawType() {
+    return rawType;
+  }
+
+  public Type getOwnerType() {
+    return null;
+  }
+
+  @Override public int hashCode() {
+    return Arrays.hashCode(typeArguments) ^ rawType.hashCode();
+  }
+
+  @Override public boolean equals(Object other) {
+    if (!(other instanceof ParameterizedType)) {
+      return false;
+    }
+
+    ParameterizedType that = (ParameterizedType) other;
+    return getRawType().equals(that.getRawType())
+        && Arrays.equals(getActualTypeArguments(), that.getActualTypeArguments())
+        && that.getOwnerType() == null;
+  }
+}
\ No newline at end of file
diff --git a/src/com/google/inject/internal/UniqueAnnotations.java b/src/com/google/inject/internal/UniqueAnnotations.java
index 01b51c3..5092b6d 100644
--- a/src/com/google/inject/internal/UniqueAnnotations.java
+++ b/src/com/google/inject/internal/UniqueAnnotations.java
@@ -57,7 +57,7 @@
       }
 
       @Override public int hashCode() {
-        return 127 * "value".hashCode() ^ value;
+        return 127 * ("value".hashCode() ^ value);
       }
     };
   }
diff --git a/test/com/google/inject/TypeLiteralTest.java b/test/com/google/inject/TypeLiteralTest.java
index 389c74d..7cd3ee8 100644
--- a/test/com/google/inject/TypeLiteralTest.java
+++ b/test/com/google/inject/TypeLiteralTest.java
@@ -16,9 +16,11 @@
 
 package com.google.inject;
 
-import java.util.List;
+import com.google.inject.internal.TypeWithArgument;
 import junit.framework.TestCase;
 
+import java.util.List;
+
 /**
  * @author crazybob@google.com (Bob Lee)
  */
@@ -28,7 +30,8 @@
     TypeLiteral<List<String>> a = new TypeLiteral<List<String>>() {};
     TypeLiteral<List<String>> b = new TypeLiteral<List<String>>(
         new TypeWithArgument(List.class, String.class)) {};
-    assertEquals(a, b);
+    assertTrue(b.equals(a) && a.equals(b));
+    assertEquals(a.hashCode(), b.hashCode());
   }
 
   public void testEquality() {