com.google.inject.multibindings.OptionalBinder Maven / Gradle / Ivy
/**
* Copyright (C) 2014 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.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.inject.multibindings.Multibinder.checkConfiguration;
import static com.google.inject.util.Types.newParameterizedType;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Binder;
import com.google.inject.Binding;
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.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.Element;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderLookup;
import com.google.inject.spi.ProviderWithDependencies;
import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.spi.Toolable;
import com.google.inject.util.Types;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Set;
import javax.inject.Qualifier;
/**
* An API to bind optional values, optionally with a default value.
* OptionalBinder fulfills two roles:
* - It allows a framework to define an injection point that may or
* may not be bound by users.
*
- It allows a framework to supply a default value that can be changed
* by users.
*
*
* When an OptionalBinder is added, it will always supply the bindings:
* {@code Optional} and {@code Optional>}. If
* {@link #setBinding} or {@link #setDefault} are called, it will also
* bind {@code T}.
*
* {@code setDefault} is intended for use by frameworks that need a default
* value. User code can call {@code setBinding} to override the default.
* Warning: Even if setBinding is called, the default binding
* will still exist in the object graph. If it is a singleton, it will be
* instantiated in {@code Stage.PRODUCTION}.
*
*
If setDefault or setBinding are linked to Providers, the Provider may return
* {@code null}. If it does, the Optional bindings will be absent. Binding
* setBinding to a Provider that returns null will not cause OptionalBinder
* to fall back to the setDefault binding.
*
*
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.
*
*
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
* is injected (unless the binding is also scoped, or an optional of provider is
* injected).
*
*
Annotations are used to create different optionals of the same key/value
* type. Each distinct annotation gets its own independent binding.
*
*
* public class FrameworkModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Renamer.class);
* }
* }
*
* With this module, an {@link Optional}{@code } can now be
* injected. With no other bindings, the optional will be absent.
* Users can specify bindings in one of two ways:
*
* Option 1:
*
* public class UserRenamerModule extends AbstractModule {
* protected void configure() {
* bind(Renamer.class).to(ReplacingRenamer.class);
* }
* }
*
* or Option 2:
*
* public class UserRenamerModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Renamer.class)
* .setBinding().to(ReplacingRenamer.class);
* }
* }
* With both options, the {@code Optional} will be present and supply the
* ReplacingRenamer.
*
* Default values can be supplied using:
*
* public class FrameworkModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
* .setDefault().toInstance(DEFAULT_LOOKUP_URL);
* }
* }
* With the above module, code can inject an {@code @LookupUrl String} and it
* will supply the DEFAULT_LOOKUP_URL. A user can change this value by binding
*
* public class UserLookupModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
* .setBinding().toInstance(CUSTOM_LOOKUP_URL);
* }
* }
* ... which will override the default value.
*
* 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,
*
* public class FrameworkModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
* .setDefault().toInstance(DEFAULT_LOOKUP_URL);
* }
* }
* public class UserLookupModule extends AbstractModule {
* protected void configure() {
* bind(Key.get(String.class, LookupUrl.class)).toInstance(CUSTOM_LOOKUP_URL);
* }
* }
* ... would generate an error, because both the framework and the user are trying to bind
* {@code @LookupUrl String}.
*
* @author [email protected] (Sam Berlin)
* @since 4.0
*/
public abstract class OptionalBinder {
/* Reflectively capture java 8's Optional types so we can bind them if we're running in java8. */
private static final Class> JAVA_OPTIONAL_CLASS;
private static final Method JAVA_EMPTY_METHOD;
private static final Method JAVA_OF_NULLABLE_METHOD;
static {
Class> optional = null;
Method empty = null;
Method ofNullable = null;
boolean useJavaOptional = false;
try {
optional = Class.forName("java.util.Optional");
empty = optional.getDeclaredMethod("empty");
ofNullable = optional.getDeclaredMethod("ofNullable", Object.class);
useJavaOptional = true;
} catch (ClassNotFoundException ignored) {
} catch (NoSuchMethodException ignored) {
} catch (SecurityException ignored) {
}
JAVA_OPTIONAL_CLASS = useJavaOptional ? optional : null;
JAVA_EMPTY_METHOD = useJavaOptional ? empty : null;
JAVA_OF_NULLABLE_METHOD = useJavaOptional ? ofNullable : null;
}
private OptionalBinder() {}
public static OptionalBinder newOptionalBinder(Binder binder, Class type) {
return newRealOptionalBinder(binder, Key.get(type));
}
public static OptionalBinder newOptionalBinder(Binder binder, TypeLiteral type) {
return newRealOptionalBinder(binder, Key.get(type));
}
public static OptionalBinder newOptionalBinder(Binder binder, Key type) {
return newRealOptionalBinder(binder, type);
}
static RealOptionalBinder newRealOptionalBinder(Binder binder, Key type) {
binder = binder.skipSources(OptionalBinder.class, RealOptionalBinder.class);
RealOptionalBinder optionalBinder = new RealOptionalBinder(binder, type);
binder.install(optionalBinder);
return optionalBinder;
}
@SuppressWarnings("unchecked")
static TypeLiteral> optionalOf(
TypeLiteral type) {
return (TypeLiteral>) TypeLiteral.get(
Types.newParameterizedType(Optional.class, type.getType()));
}
static TypeLiteral> javaOptionalOf(
TypeLiteral type) {
checkState(JAVA_OPTIONAL_CLASS != null, "java.util.Optional not found");
return TypeLiteral.get(Types.newParameterizedType(JAVA_OPTIONAL_CLASS, type.getType()));
}
@SuppressWarnings("unchecked")
static TypeLiteral>> optionalOfJavaxProvider(
TypeLiteral type) {
return (TypeLiteral>>) TypeLiteral.get(
Types.newParameterizedType(Optional.class,
newParameterizedType(javax.inject.Provider.class, type.getType())));
}
static TypeLiteral> javaOptionalOfJavaxProvider(
TypeLiteral type) {
checkState(JAVA_OPTIONAL_CLASS != null, "java.util.Optional not found");
return TypeLiteral.get(Types.newParameterizedType(JAVA_OPTIONAL_CLASS,
newParameterizedType(javax.inject.Provider.class, type.getType())));
}
@SuppressWarnings("unchecked")
static TypeLiteral>> optionalOfProvider(TypeLiteral type) {
return (TypeLiteral>>) TypeLiteral.get(Types.newParameterizedType(
Optional.class, newParameterizedType(Provider.class, type.getType())));
}
static TypeLiteral> javaOptionalOfProvider(TypeLiteral type) {
checkState(JAVA_OPTIONAL_CLASS != null, "java.util.Optional not found");
return TypeLiteral.get(Types.newParameterizedType(JAVA_OPTIONAL_CLASS,
newParameterizedType(Provider.class, type.getType())));
}
@SuppressWarnings("unchecked")
static Key> providerOf(Key key) {
Type providerT = Types.providerOf(key.getTypeLiteral().getType());
return (Key>) key.ofType(providerT);
}
/**
* Returns a binding builder used to set the default value that will be injected.
* The binding set by this method will be ignored if {@link #setBinding} is called.
*
* It is an error to call this method without also calling one of the {@code to}
* methods on the returned binding builder.
*/
public abstract LinkedBindingBuilder setDefault();
/**
* Returns a binding builder used to set the actual value that will be injected.
* This overrides any binding set by {@link #setDefault}.
*
* It is an error to call this method without also calling one of the {@code to}
* methods on the returned binding builder.
*/
public abstract LinkedBindingBuilder setBinding();
enum Source { DEFAULT, ACTUAL }
@Retention(RUNTIME)
@Qualifier
@interface Default {
String value();
}
@Retention(RUNTIME)
@Qualifier
@interface Actual {
String value();
}
/**
* The actual OptionalBinder plays several roles. It implements Module to hide that
* fact from the public API, and installs the various bindings that are exposed to the user.
*/
static final class RealOptionalBinder extends OptionalBinder implements Module {
private final Key typeKey;
private final Key> optionalKey;
private final Key>> optionalJavaxProviderKey;
private final Key>> optionalProviderKey;
private final Provider>> optionalProviderT;
private final Key defaultKey;
private final Key actualKey;
private final Key javaOptionalKey;
private final Key javaOptionalJavaxProviderKey;
private final Key javaOptionalProviderKey;
/** the target injector's binder. non-null until initialization, null afterwards */
private Binder binder;
/** the default binding, for the SPI. */
private Binding defaultBinding;
/** the actual binding, for the SPI */
private Binding actualBinding;
/** the dependencies -- initialized with defaults & overridden when tooled. */
private Set> dependencies;
/** the dependencies -- initialized with defaults & overridden when tooled. */
private Set> providerDependencies;
private RealOptionalBinder(Binder binder, Key typeKey) {
this.binder = binder;
this.typeKey = checkNotNull(typeKey);
TypeLiteral literal = typeKey.getTypeLiteral();
this.optionalKey = typeKey.ofType(optionalOf(literal));
this.optionalJavaxProviderKey = typeKey.ofType(optionalOfJavaxProvider(literal));
this.optionalProviderKey = typeKey.ofType(optionalOfProvider(literal));
this.optionalProviderT = binder.getProvider(optionalProviderKey);
String name = RealElement.nameOf(typeKey);
this.defaultKey = Key.get(typeKey.getTypeLiteral(), new DefaultImpl(name));
this.actualKey = Key.get(typeKey.getTypeLiteral(), new ActualImpl(name));
// Until the injector initializes us, we don't know what our dependencies are,
// so initialize to the whole Injector (like Multibinder, and MapBinder indirectly).
this.dependencies = ImmutableSet.>of(Dependency.get(Key.get(Injector.class)));
this.providerDependencies =
ImmutableSet.>of(Dependency.get(Key.get(Injector.class)));
if (JAVA_OPTIONAL_CLASS != null) {
this.javaOptionalKey = typeKey.ofType(javaOptionalOf(literal));
this.javaOptionalJavaxProviderKey = typeKey.ofType(javaOptionalOfJavaxProvider(literal));
this.javaOptionalProviderKey = typeKey.ofType(javaOptionalOfProvider(literal));
} else {
this.javaOptionalKey = null;
this.javaOptionalJavaxProviderKey = null;
this.javaOptionalProviderKey = null;
}
}
/**
* Adds a binding for T. Multiple calls to this are safe, and will be collapsed as duplicate
* bindings.
*/
private void addDirectTypeBinding(Binder binder) {
binder.bind(typeKey).toProvider(new RealDirectTypeProvider());
}
Key getKeyForDefaultBinding() {
checkConfiguration(!isInitialized(), "already initialized");
addDirectTypeBinding(binder);
return defaultKey;
}
@Override public LinkedBindingBuilder setDefault() {
return binder.bind(getKeyForDefaultBinding());
}
Key getKeyForActualBinding() {
checkConfiguration(!isInitialized(), "already initialized");
addDirectTypeBinding(binder);
return actualKey;
}
@Override public LinkedBindingBuilder setBinding() {
return binder.bind(getKeyForActualBinding());
}
@Override public void configure(Binder binder) {
checkConfiguration(!isInitialized(), "OptionalBinder was already initialized");
binder.bind(optionalProviderKey).toProvider(new RealOptionalProviderProvider());
// Optional is immutable, so it's safe to expose Optional> as
// Optional> (since Guice provider implements javax Provider).
@SuppressWarnings({"unchecked", "cast"})
Key massagedOptionalProviderKey = (Key) optionalProviderKey;
binder.bind(optionalJavaxProviderKey).to(massagedOptionalProviderKey);
binder.bind(optionalKey).toProvider(new RealOptionalKeyProvider());
// Bind the java-8 types if we know them.
bindJava8Optional(binder);
}
@SuppressWarnings("unchecked")
private void bindJava8Optional(Binder binder) {
if (JAVA_OPTIONAL_CLASS != null) {
binder.bind(javaOptionalKey).toProvider(new JavaOptionalProvider());
binder.bind(javaOptionalProviderKey).toProvider(new JavaOptionalProviderProvider());
// for the javax version we reuse the guice version since they're type-compatible.
binder.bind(javaOptionalJavaxProviderKey).to(javaOptionalProviderKey);
}
}
@SuppressWarnings("rawtypes")
final class JavaOptionalProvider extends RealOptionalBinderProviderWithDependencies
implements ProviderWithExtensionVisitor, OptionalBinderBinding {
private JavaOptionalProvider() {
super(typeKey);
}
@Override public Object get() {
Optional> optional = optionalProviderT.get();
try {
if (optional.isPresent()) {
return JAVA_OF_NULLABLE_METHOD.invoke(JAVA_OPTIONAL_CLASS, optional.get().get());
} else {
return JAVA_EMPTY_METHOD.invoke(JAVA_OPTIONAL_CLASS);
}
} catch (IllegalAccessException e) {
throw new SecurityException(e);
} catch (IllegalArgumentException e) {
throw new IllegalStateException(e);
} catch (InvocationTargetException e) {
throw Throwables.propagate(e.getCause());
}
}
@Override public Set> getDependencies() {
return dependencies;
}
@SuppressWarnings("unchecked")
@Override public Object acceptExtensionVisitor(BindingTargetVisitor visitor,
ProviderInstanceBinding binding) {
if (visitor instanceof MultibindingsTargetVisitor) {
return ((MultibindingsTargetVisitor) visitor).visit(this);
} else {
return visitor.visit(binding);
}
}
@Override public boolean containsElement(Element element) {
return RealOptionalBinder.this.containsElement(element);
}
@Override public Binding getActualBinding() {
return RealOptionalBinder.this.getActualBinding();
}
@Override public Binding getDefaultBinding() {
return RealOptionalBinder.this.getDefaultBinding();
}
@Override public Key getKey() {
return javaOptionalKey;
}
}
@SuppressWarnings("rawtypes")
final class JavaOptionalProviderProvider extends RealOptionalBinderProviderWithDependencies {
private JavaOptionalProviderProvider() {
super(typeKey);
}
@Override public Object get() {
Optional> optional = optionalProviderT.get();
try {
if (optional.isPresent()) {
return JAVA_OF_NULLABLE_METHOD.invoke(JAVA_OPTIONAL_CLASS, optional.get());
} else {
return JAVA_EMPTY_METHOD.invoke(JAVA_OPTIONAL_CLASS);
}
} catch (IllegalAccessException e) {
throw new SecurityException(e);
} catch (IllegalArgumentException e) {
throw new IllegalStateException(e);
} catch (InvocationTargetException e) {
throw Throwables.propagate(e.getCause());
}
}
@Override public Set> getDependencies() {
return providerDependencies;
}
}
final class RealDirectTypeProvider extends RealOptionalBinderProviderWithDependencies {
private RealDirectTypeProvider() {
super(typeKey);
}
@Override public T get() {
Optional> optional = optionalProviderT.get();
if (optional.isPresent()) {
return optional.get().get();
}
// Let Guice handle blowing up if the injection point doesn't have @Nullable
// (If it does have @Nullable, that's fine. This would only happen if
// setBinding/setDefault themselves were bound to 'null').
return null;
}
@Override public Set> getDependencies() {
return dependencies;
}
}
final class RealOptionalProviderProvider
extends RealOptionalBinderProviderWithDependencies>> {
private Optional> optional;
private RealOptionalProviderProvider() {
super(typeKey);
}
@Toolable @Inject void initialize(Injector injector) {
RealOptionalBinder.this.binder = null;
actualBinding = injector.getExistingBinding(actualKey);
defaultBinding = injector.getExistingBinding(defaultKey);
Binding userBinding = injector.getExistingBinding(typeKey);
Binding binding = null;
if (actualBinding != null) {
// TODO(sameb): Consider exposing an option that will allow
// ACTUAL to fallback to DEFAULT if ACTUAL's provider returns null.
// Right now, an ACTUAL binding can convert from present -> absent
// if it's bound to a provider that returns null.
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) {
optional = Optional.of(binding.getProvider());
RealOptionalBinder.this.dependencies =
ImmutableSet.>of(Dependency.get(binding.getKey()));
RealOptionalBinder.this.providerDependencies =
ImmutableSet.>of(Dependency.get(providerOf(binding.getKey())));
} else {
optional = Optional.absent();
RealOptionalBinder.this.dependencies = ImmutableSet.of();
RealOptionalBinder.this.providerDependencies = ImmutableSet.of();
}
}
@Override public Optional> get() {
return optional;
}
@Override public Set> getDependencies() {
return providerDependencies;
}
}
final class RealOptionalKeyProvider
extends RealOptionalBinderProviderWithDependencies>
implements ProviderWithExtensionVisitor>,
OptionalBinderBinding>,
Provider> {
private RealOptionalKeyProvider() {
super(typeKey);
}
@Override public Optional get() {
Optional> optional = optionalProviderT.get();
if (optional.isPresent()) {
return Optional.fromNullable(optional.get().get());
} else {
return Optional.absent();
}
}
@Override public Set> getDependencies() {
return dependencies;
}
@SuppressWarnings("unchecked")
@Override
public R acceptExtensionVisitor(BindingTargetVisitor visitor,
ProviderInstanceBinding extends B> binding) {
if (visitor instanceof MultibindingsTargetVisitor) {
return ((MultibindingsTargetVisitor, R>) visitor).visit(this);
} else {
return visitor.visit(binding);
}
}
@Override public Key> getKey() {
return optionalKey;
}
@Override public Binding> getActualBinding() {
return RealOptionalBinder.this.getActualBinding();
}
@Override public Binding> getDefaultBinding() {
return RealOptionalBinder.this.getDefaultBinding();
}
@Override public boolean containsElement(Element element) {
return RealOptionalBinder.this.containsElement(element);
}
}
private Binding> getActualBinding() {
if (isInitialized()) {
return actualBinding;
} else {
throw new UnsupportedOperationException(
"getActualBinding() not supported from Elements.getElements, requires an Injector.");
}
}
private Binding> getDefaultBinding() {
if (isInitialized()) {
return defaultBinding;
} else {
throw new UnsupportedOperationException(
"getDefaultBinding() not supported from Elements.getElements, requires an Injector.");
}
}
private boolean containsElement(Element element) {
Key> elementKey;
if (element instanceof Binding) {
elementKey = ((Binding>) element).getKey();
} else if (element instanceof ProviderLookup) {
elementKey = ((ProviderLookup>) element).getKey();
} else {
return false; // cannot match;
}
return elementKey.equals(optionalKey)
|| elementKey.equals(optionalProviderKey)
|| elementKey.equals(optionalJavaxProviderKey)
|| elementKey.equals(defaultKey)
|| elementKey.equals(actualKey)
|| matchesJ8Keys(elementKey)
|| matchesTypeKey(element, elementKey);
}
private boolean matchesJ8Keys(Key> elementKey) {
if (JAVA_OPTIONAL_CLASS != null) {
return elementKey.equals(javaOptionalKey)
|| elementKey.equals(javaOptionalProviderKey)
|| elementKey.equals(javaOptionalJavaxProviderKey);
}
return false;
}
/** Returns true if the key & element indicate they were bound by this OptionalBinder. */
private boolean matchesTypeKey(Element element, Key> elementKey) {
// Just doing .equals(typeKey) isn't enough, because the user can bind that themselves.
return elementKey.equals(typeKey)
&& element instanceof ProviderInstanceBinding
&& (((ProviderInstanceBinding) element)
.getUserSuppliedProvider() instanceof RealOptionalBinderProviderWithDependencies);
}
private boolean isInitialized() {
return binder == null;
}
@Override public boolean equals(Object o) {
return o instanceof RealOptionalBinder
&& ((RealOptionalBinder>) o).typeKey.equals(typeKey);
}
@Override public int hashCode() {
return typeKey.hashCode();
}
/**
* A base class for ProviderWithDependencies that need equality based on a specific object.
*/
private abstract static class RealOptionalBinderProviderWithDependencies implements
ProviderWithDependencies {
private final Object equality;
public RealOptionalBinderProviderWithDependencies(Object equality) {
this.equality = equality;
}
@Override public boolean equals(Object obj) {
return obj != null && this.getClass() == obj.getClass()
&& equality.equals(((RealOptionalBinderProviderWithDependencies>) obj).equality);
}
@Override public int hashCode() {
return equality.hashCode();
}
}
}
static class DefaultImpl extends BaseAnnotation implements Default {
public DefaultImpl(String value) {
super(Default.class, value);
}
}
static class ActualImpl extends BaseAnnotation implements Actual {
public ActualImpl(String value) {
super(Actual.class, value);
}
}
abstract static class BaseAnnotation implements Serializable, Annotation {
private final String value;
private final Class extends Annotation> clazz;
BaseAnnotation(Class extends Annotation> clazz, String value) {
this.clazz = checkNotNull(clazz, "clazz");
this.value = checkNotNull(value, "value");
}
public String value() {
return this.value;
}
@Override public int hashCode() {
// This is specified in java.lang.Annotation.
return (127 * "value".hashCode()) ^ value.hashCode();
}
@Override public boolean equals(Object o) {
// We check against each annotation type instead of BaseAnnotation
// so that we can compare against generated annotation implementations.
if (o instanceof Actual && clazz == Actual.class) {
Actual other = (Actual) o;
return value.equals(other.value());
} else if (o instanceof Default && clazz == Default.class) {
Default other = (Default) o;
return value.equals(other.value());
}
return false;
}
@Override public String toString() {
return "@" + clazz.getName() + (value.isEmpty() ? "" : "(value=" + value + ")");
}
@Override public Class extends Annotation> annotationType() {
return clazz;
}
private static final long serialVersionUID = 0;
}
}