Add support for OptionalBinder to link to normal bindings of that type if
neither setDefault nor setBinding are called. From the javadoc, example:
* <pre><code>
* public class FrameworkModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Renamer.class);
* }
* }</code></pre>
*
* <p>With this module, an {@link Optional}{@code <Renamer>} can now be
* injected. With no other bindings, the optional will be absent.
* Users can specify bindings in one of two ways:
*
* <p>Option 1:
* <pre><code>
* public class UserRenamerModule extends AbstractModule {
* protected void configure() {
* bind(Renamer.class).to(ReplacingRenamer.class);
* }
* }</code></pre>
*
* <p>or Option 2:
* <pre><code>
* public class UserRenamerModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Renamer.class)
* .setBinding().to(ReplacingRenamer.class);
* }
* }</code></pre>
* With both options, the {@code Optional<Renamer>} will be present and supply the
* ReplacingRenamer.
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=70835975
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/OptionalBinder.java b/extensions/multibindings/src/com/google/inject/multibindings/OptionalBinder.java
index 4d4dfac..4a8a488 100644
--- a/extensions/multibindings/src/com/google/inject/multibindings/OptionalBinder.java
+++ b/extensions/multibindings/src/com/google/inject/multibindings/OptionalBinder.java
@@ -76,9 +76,11 @@
* setBinding to a Provider that returns null will not cause OptionalBinder
* to fall back to the setDefault binding.
*
- * <p>If neither setDefault nor setBinding are called, the optionals will be
- * absent. Otherwise, the optionals will return present if they are bound
- * to a non-null value.
+ * <p>If neither setDefault nor setBinding are called, it will try to link to a
+ * user-supplied binding of the same type. If no binding exists, the optionals
+ * will be absent. Otherwise, if a user-supplied binding of that type exists,
+ * or if setBinding or setDefault are called, the optionals will return present
+ * if they are bound to a non-null value.
*
* <p>Values are resolved at injection time. If a value is bound to a
* provider, that provider's get method will be called each time the optional
@@ -96,9 +98,18 @@
* }</code></pre>
*
* <p>With this module, an {@link Optional}{@code <Renamer>} can now be
- * injected. With no other bindings, the optional will be absent. However,
- * once a user adds a binding:
+ * injected. With no other bindings, the optional will be absent.
+ * Users can specify bindings in one of two ways:
*
+ * <p>Option 1:
+ * <pre><code>
+ * public class UserRenamerModule extends AbstractModule {
+ * protected void configure() {
+ * bind(Renamer.class).to(ReplacingRenamer.class);
+ * }
+ * }</code></pre>
+ *
+ * <p>or Option 2:
* <pre><code>
* public class UserRenamerModule extends AbstractModule {
* protected void configure() {
@@ -106,8 +117,8 @@
* .setBinding().to(ReplacingRenamer.class);
* }
* }</code></pre>
- * .. then the {@code Optional<Renamer>} will be present and supply the
- * ReplacingRenamer.
+ * With both options, the {@code Optional<Renamer>} will be present and supply the
+ * ReplacingRenamer.
*
* <p>Default values can be supplied using:
* <pre><code>
@@ -127,6 +138,24 @@
* }
* }</code></pre>
* ... which will override the default value.
+ *
+ * <p>If one module uses setDefault the only way to override the default is to use setBinding.
+ * It is an error for a user to specify the binding without using OptionalBinder if
+ * setDefault or setBinding are called. For example,
+ * <pre><code>
+ * public class FrameworkModule extends AbstractModule {
+ * protected void configure() {
+ * OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
+ * .setDefault().to(DEFAULT_LOOKUP_URL);
+ * }
+ * }
+ * public class UserLookupModule extends AbstractModule {
+ * protected void configure() {
+ * bind(Key.get(String.class, LookupUrl.class)).to(CUSTOM_LOOKUP_URL);
+ * }
+ * }</code></pre>
+ * ... would generate an error, because both the framework and the user are trying to bind
+ * {@code @LookupUrl String}.
*
* @author sameb@google.com (Sam Berlin)
*/
@@ -257,7 +286,7 @@
*/
private void addDirectTypeBinding(Binder binder) {
binder.bind(typeKey).toProvider(new RealOptionalBinderProviderWithDependencies<T>(typeKey) {
- public T get() {
+ @Override public T get() {
Optional<Provider<T>> optional = optionalProviderT.get();
if (optional.isPresent()) {
return optional.get().get();
@@ -268,7 +297,7 @@
return null;
}
- public Set<Dependency<?>> getDependencies() {
+ @Override public Set<Dependency<?>> getDependencies() {
return dependencies;
}
});
@@ -286,7 +315,7 @@
return binder.bind(actualKey);
}
- public void configure(Binder binder) {
+ @Override public void configure(Binder binder) {
checkConfiguration(!isInitialized(), "OptionalBinder was already initialized");
binder.bind(optionalProviderKey).toProvider(
@@ -297,6 +326,7 @@
RealOptionalBinder.this.binder = null;
actualBinding = injector.getExistingBinding(actualKey);
defaultBinding = injector.getExistingBinding(defaultKey);
+ Binding<T> userBinding = injector.getExistingBinding(typeKey);
Binding<T> binding = null;
if (actualBinding != null) {
// TODO(sameb): Consider exposing an option that will allow
@@ -306,6 +336,12 @@
binding = actualBinding;
} else if (defaultBinding != null) {
binding = defaultBinding;
+ } else if (userBinding != null) {
+ // If neither the actual or default is set, then we fallback
+ // to the value bound to the type itself and consider that the
+ // "actual binding" for the SPI.
+ binding = userBinding;
+ actualBinding = userBinding;
}
if (binding != null) {
@@ -321,11 +357,11 @@
}
}
- public Optional<Provider<T>> get() {
+ @Override public Optional<Provider<T>> get() {
return optional;
}
- public Set<Dependency<?>> getDependencies() {
+ @Override public Set<Dependency<?>> getDependencies() {
return providerDependencies;
}
});
@@ -348,7 +384,7 @@
super(typeKey);
}
- public Optional<T> get() {
+ @Override public Optional<T> get() {
Optional<Provider<T>> optional = optionalProviderT.get();
if (optional.isPresent()) {
return Optional.fromNullable(optional.get().get());
@@ -357,11 +393,12 @@
}
}
- public Set<Dependency<?>> getDependencies() {
+ @Override public Set<Dependency<?>> getDependencies() {
return dependencies;
}
@SuppressWarnings("unchecked")
+ @Override
public <B, R> R acceptExtensionVisitor(BindingTargetVisitor<B, R> visitor,
ProviderInstanceBinding<? extends B> binding) {
if (visitor instanceof MultibindingsTargetVisitor) {
@@ -371,11 +408,11 @@
}
}
- public Key<Optional<T>> getKey() {
+ @Override public Key<Optional<T>> getKey() {
return optionalKey;
}
- public Binding<?> getActualBinding() {
+ @Override public Binding<?> getActualBinding() {
if (isInitialized()) {
return actualBinding;
} else {
@@ -384,7 +421,7 @@
}
}
- public Binding<?> getDefaultBinding() {
+ @Override public Binding<?> getDefaultBinding() {
if (isInitialized()) {
return defaultBinding;
} else {
@@ -393,7 +430,7 @@
}
}
- public boolean containsElement(Element element) {
+ @Override public boolean containsElement(Element element) {
Key<?> elementKey;
if (element instanceof Binding) {
elementKey = ((Binding<?>) element).getKey();
@@ -445,14 +482,12 @@
this.equality = equality;
}
- @Override
- public boolean equals(Object obj) {
+ @Override public boolean equals(Object obj) {
return this.getClass() == obj.getClass()
&& equality.equals(((RealOptionalBinderProviderWithDependencies<?>) obj).equality);
}
- @Override
- public int hashCode() {
+ @Override public int hashCode() {
return equality.hashCode();
}
}
diff --git a/extensions/multibindings/test/com/google/inject/multibindings/OptionalBinderTest.java b/extensions/multibindings/test/com/google/inject/multibindings/OptionalBinderTest.java
index f4c46ca..d2bceb8 100644
--- a/extensions/multibindings/test/com/google/inject/multibindings/OptionalBinderTest.java
+++ b/extensions/multibindings/test/com/google/inject/multibindings/OptionalBinderTest.java
@@ -126,10 +126,10 @@
injector.getInstance(Key.get(optionalOfJavaxProviderString));
assertFalse(optionalJxP.isPresent());
- assertOptionalVisitor(stringKey, setOf(module), VisitType.BOTH, 0, null, null);
+ assertOptionalVisitor(stringKey, setOf(module), VisitType.BOTH, 0, null, null, null);
}
- public void testAbsentWithUserBoundValue() {
+ public void testUsesUserBoundValue() {
Module module = new AbstractModule() {
@Override protected void configure() {
OptionalBinder.newOptionalBinder(binder(), String.class);
@@ -141,16 +141,22 @@
assertEquals("foo", injector.getInstance(String.class));
Optional<String> optional = injector.getInstance(Key.get(optionalOfString));
- assertFalse(optional.isPresent());
+ assertEquals("foo", optional.get());
Optional<Provider<String>> optionalP = injector.getInstance(Key.get(optionalOfProviderString));
- assertFalse(optionalP.isPresent());
+ assertEquals("foo", optionalP.get().get());
Optional<javax.inject.Provider<String>> optionalJxP =
injector.getInstance(Key.get(optionalOfJavaxProviderString));
- assertFalse(optionalJxP.isPresent());
+ assertEquals("foo", optionalJxP.get().get());
- assertOptionalVisitor(stringKey, setOf(module), VisitType.BOTH, 0, null, null);
+ assertOptionalVisitor(stringKey,
+ setOf(module),
+ VisitType.BOTH,
+ 0,
+ null,
+ null,
+ providerInstance("foo"));
}
public void testSetDefault() {
@@ -175,7 +181,7 @@
assertTrue(optionalJxP.isPresent());
assertEquals("a", optionalJxP.get().get());
- assertOptionalVisitor(stringKey, setOf(module), VisitType.BOTH, 0, instance("a"), null);
+ assertOptionalVisitor(stringKey, setOf(module), VisitType.BOTH, 0, instance("a"), null, null);
}
public void testSetBinding() {
@@ -200,7 +206,7 @@
assertTrue(optionalJxP.isPresent());
assertEquals("a", optionalJxP.get().get());
- assertOptionalVisitor(stringKey, setOf(module), VisitType.BOTH, 0, null, instance("a"));
+ assertOptionalVisitor(stringKey, setOf(module), VisitType.BOTH, 0, null, instance("a"), null);
}
public void testSetBindingOverridesDefault() {
@@ -233,7 +239,8 @@
VisitType.BOTH,
0,
instance("a"),
- instance("b"));
+ instance("b"),
+ null);
}
public void testSpreadAcrossModules() {
@@ -274,7 +281,8 @@
VisitType.BOTH,
0,
instance("a"),
- instance("b"));
+ instance("b"),
+ null);
}
public void testExactSameBindingCollapses_defaults() {
@@ -302,7 +310,7 @@
assertTrue(optionalJxP.isPresent());
assertEquals("a", optionalJxP.get().get());
- assertOptionalVisitor(stringKey, setOf(module), VisitType.BOTH, 0, instance("a"), null);
+ assertOptionalVisitor(stringKey, setOf(module), VisitType.BOTH, 0, instance("a"), null, null);
}
public void testExactSameBindingCollapses_actual() {
@@ -330,7 +338,7 @@
assertTrue(optionalJxP.isPresent());
assertEquals("a", optionalJxP.get().get());
- assertOptionalVisitor(stringKey, setOf(module), VisitType.BOTH, 0, null, instance("a"));
+ assertOptionalVisitor(stringKey, setOf(module), VisitType.BOTH, 0, null, instance("a"), null);
}
public void testDifferentBindingsFail_defaults() {
@@ -443,7 +451,8 @@
VisitType.BOTH,
0,
instance("a"),
- instance("b"));
+ instance("b"),
+ null);
}
public void testMultipleDifferentOptionals() {
@@ -464,10 +473,10 @@
assertEquals("b", injector.getInstance(bKey));
assertEquals("c", injector.getInstance(cKey));
- assertOptionalVisitor(stringKey, setOf(module), VisitType.BOTH, 3, instance("a"), null);
- assertOptionalVisitor(intKey, setOf(module), VisitType.BOTH, 3, instance(1), null);
- assertOptionalVisitor(bKey, setOf(module), VisitType.BOTH, 3, instance("b"), null);
- assertOptionalVisitor(cKey, setOf(module), VisitType.BOTH, 3, instance("c"), null);
+ assertOptionalVisitor(stringKey, setOf(module), VisitType.BOTH, 3, instance("a"), null, null);
+ assertOptionalVisitor(intKey, setOf(module), VisitType.BOTH, 3, instance(1), null, null);
+ assertOptionalVisitor(bKey, setOf(module), VisitType.BOTH, 3, instance("b"), null, null);
+ assertOptionalVisitor(cKey, setOf(module), VisitType.BOTH, 3, instance("c"), null, null);
}
public void testOptionalIsAppropriatelyLazy() {
@@ -533,6 +542,7 @@
VisitType.BOTH,
0,
SpiUtils.<String>providerInstance(null),
+ null,
null);
}
@@ -563,7 +573,8 @@
VisitType.BOTH,
0,
null,
- SpiUtils.<String>providerInstance(null));
+ SpiUtils.<String>providerInstance(null),
+ null);
}
// TODO(sameb): Maybe change this?
@@ -595,7 +606,8 @@
VisitType.BOTH,
0,
instance("a"),
- SpiUtils.<String>providerInstance(null));
+ SpiUtils.<String>providerInstance(null),
+ null);
}
public void testSourceLinesInException() {
@@ -699,7 +711,8 @@
VisitType.BOTH,
0,
instance("A"),
- instance("B"));
+ instance("B"),
+ null);
}
public void testModuleOverrideRepeatedInstalls_toKey() {
@@ -726,7 +739,8 @@
VisitType.BOTH,
0,
linked(aKey),
- linked(bKey));
+ linked(bKey),
+ null);
}
public void testModuleOverrideRepeatedInstalls_toProviderInstance() {
@@ -751,7 +765,8 @@
VisitType.BOTH,
0,
providerInstance("A"),
- providerInstance("B"));
+ providerInstance("B"),
+ null);
}
private static class AStringProvider implements Provider<String> {
@@ -785,7 +800,8 @@
VisitType.BOTH,
0,
providerKey(Key.get(AStringProvider.class)),
- providerKey(Key.get(BStringProvider.class)));
+ providerKey(Key.get(BStringProvider.class)),
+ null);
}
private static class StringGrabber {
diff --git a/extensions/multibindings/test/com/google/inject/multibindings/SpiUtils.java b/extensions/multibindings/test/com/google/inject/multibindings/SpiUtils.java
index 9517425..7e34ffe 100644
--- a/extensions/multibindings/test/com/google/inject/multibindings/SpiUtils.java
+++ b/extensions/multibindings/test/com/google/inject/multibindings/SpiUtils.java
@@ -466,32 +466,43 @@
* @param expectedOtherOptionalBindings the # of other optional bindings we expect to see.
* @param expectedDefault the expected default binding, or null if none
* @param expectedActual the expected actual binding, or null if none
+ * @param expectedUserLinkedActual the user binding that is the actual binding, used if
+ * neither the default nor actual are set and a user binding existed for the type.
*/
static <T> void assertOptionalVisitor(Key<T> keyType,
Iterable<? extends Module> modules,
VisitType visitType,
int expectedOtherOptionalBindings,
BindResult<?> expectedDefault,
- BindResult<?> expectedActual) {
+ BindResult<?> expectedActual,
+ BindResult<?> expectedUserLinkedActual) {
if (visitType == null) {
fail("must test something");
}
if (visitType == BOTH || visitType == INJECTOR) {
optionalInjectorTest(keyType, modules, expectedOtherOptionalBindings, expectedDefault,
- expectedActual);
+ expectedActual, expectedUserLinkedActual);
}
if (visitType == BOTH || visitType == MODULE) {
optionalModuleTest(keyType, modules, expectedOtherOptionalBindings, expectedDefault,
- expectedActual);
+ expectedActual, expectedUserLinkedActual);
}
}
@SuppressWarnings("unchecked")
- private static <T> void optionalInjectorTest(Key<T> keyType, Iterable<? extends Module> modules,
- int expectedOtherOptionalBindings, BindResult<?> expectedDefault,
- BindResult<?> expectedActual) {
+ private static <T> void optionalInjectorTest(Key<T> keyType,
+ Iterable<? extends Module> modules,
+ int expectedOtherOptionalBindings,
+ BindResult<?> expectedDefault,
+ BindResult<?> expectedActual,
+ BindResult<?> expectedUserLinkedActual) {
+ if (expectedUserLinkedActual != null) {
+ assertNull("cannot have actual if expecting user binding", expectedActual);
+ assertNull("cannot have default if expecting user binding", expectedDefault);
+ }
+
Key<Optional<T>> optionalKey =
keyType.ofType(OptionalBinder.optionalOf(keyType.getTypeLiteral()));
Injector injector = Guice.createInjector(modules);
@@ -510,12 +521,16 @@
matches(optionalBinder.getDefaultBinding(), expectedDefault));
}
- if (expectedActual == null) {
+ if (expectedActual == null && expectedUserLinkedActual == null) {
assertNull(optionalBinder.getActualBinding());
- } else {
+ } else if (expectedActual != null) {
assertTrue("expectedActual: " + expectedActual + ", actualActual: "
+ optionalBinder.getActualBinding(),
matches(optionalBinder.getActualBinding(), expectedActual));
+ } else if (expectedUserLinkedActual != null) {
+ assertTrue("expectedUserLinkedActual: " + expectedUserLinkedActual + ", actualActual: "
+ + optionalBinder.getActualBinding(),
+ matches(optionalBinder.getActualBinding(), expectedUserLinkedActual));
}
@@ -582,9 +597,16 @@
}
@SuppressWarnings("unchecked")
- private static <T> void optionalModuleTest(Key<T> keyType, Iterable<? extends Module> modules,
- int expectedOtherOptionalBindings, BindResult<?> expectedDefault,
- BindResult<?> expectedActual) {
+ private static <T> void optionalModuleTest(Key<T> keyType,
+ Iterable<? extends Module> modules,
+ int expectedOtherOptionalBindings,
+ BindResult<?> expectedDefault,
+ BindResult<?> expectedActual,
+ BindResult<?> expectedUserLinkedActual) {
+ if (expectedUserLinkedActual != null) {
+ assertNull("cannot have actual if expecting user binding", expectedActual);
+ assertNull("cannot have default if expecting user binding", expectedDefault);
+ }
Set<Element> elements = ImmutableSet.copyOf(Elements.getElements(modules));
Map<Key<?>, Binding<?>> indexed = index(elements);
Key<Optional<T>> optionalKey =