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<Snack> 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<Snack> 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() {