com.google.inject.internal.RealOptionalBinder Maven / Gradle / Ivy
/*
* Copyright (C) 2016 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 static com.google.common.base.Preconditions.checkNotNull;
import static com.google.inject.internal.Errors.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.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.inject.Binder;
import com.google.inject.Binding;
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.internal.InternalProviderInstanceBindingImpl.InitializationTiming;
import com.google.inject.multibindings.MultibindingsTargetVisitor;
import com.google.inject.multibindings.OptionalBinderBinding;
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.ProviderWithExtensionVisitor;
import com.google.inject.util.Types;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.reflect.Type;
import java.util.Set;
import javax.inject.Qualifier;
/**
* 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.
*/
public final class RealOptionalBinder implements Module {
public static RealOptionalBinder newRealOptionalBinder(Binder binder, Key type) {
binder = binder.skipSources(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()));
}
@SuppressWarnings("unchecked")
static TypeLiteral> javaOptionalOf(TypeLiteral type) {
return (TypeLiteral>)
TypeLiteral.get(Types.newParameterizedType(java.util.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())));
}
@SuppressWarnings("unchecked")
static TypeLiteral>> javaOptionalOfJavaxProvider(
TypeLiteral type) {
return (TypeLiteral>>)
TypeLiteral.get(
Types.newParameterizedType(
java.util.Optional.class,
newParameterizedType(javax.inject.Provider.class, type.getType())));
}
@SuppressWarnings("unchecked")
static TypeLiteral>> optionalOfJakartaProvider(
TypeLiteral type) {
return (TypeLiteral>>)
TypeLiteral.get(
Types.newParameterizedType(
Optional.class,
newParameterizedType(jakarta.inject.Provider.class, type.getType())));
}
@SuppressWarnings("unchecked")
static
TypeLiteral>> javaOptionalOfJakartaProvider(
TypeLiteral type) {
return (TypeLiteral>>)
TypeLiteral.get(
Types.newParameterizedType(
java.util.Optional.class,
newParameterizedType(jakarta.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())));
}
@SuppressWarnings("unchecked")
static TypeLiteral>> javaOptionalOfProvider(
TypeLiteral type) {
return (TypeLiteral>>)
TypeLiteral.get(
Types.newParameterizedType(
java.util.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);
}
enum Source {
DEFAULT,
ACTUAL
}
@Retention(RUNTIME)
@Qualifier
@interface Default {
String value();
}
@Retention(RUNTIME)
@Qualifier
@interface Actual {
String value();
}
private final BindingSelection bindingSelection;
private final Binder binder;
private RealOptionalBinder(Binder binder, Key typeKey) {
this.bindingSelection = new BindingSelection<>(typeKey);
this.binder = binder;
}
/**
* 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(bindingSelection.getDirectKey())
.toProvider(new RealDirectTypeProvider(bindingSelection));
}
/**
* Returns the key to use for the default binding.
*
* As a side effect this installs support for the 'direct type', so a binding for {@code T}
* will be made available.
*/
Key getKeyForDefaultBinding() {
bindingSelection.checkNotInitialized();
addDirectTypeBinding(binder);
return bindingSelection.getKeyForDefaultBinding();
}
public LinkedBindingBuilder setDefault() {
return binder.bind(getKeyForDefaultBinding());
}
/**
* Returns the key to use for the actual binding, overrides the default if set.
*
* As a side effect this installs support for the 'direct type', so a binding for {@code T}
* will be made available.
*/
Key getKeyForActualBinding() {
bindingSelection.checkNotInitialized();
addDirectTypeBinding(binder);
return bindingSelection.getKeyForActualBinding();
}
public LinkedBindingBuilder setBinding() {
return binder.bind(getKeyForActualBinding());
}
@SuppressWarnings({"unchecked", "rawtypes"}) // we use raw Key to link bindings together.
@Override
public void configure(Binder binder) {
bindingSelection.checkNotInitialized();
Key key = bindingSelection.getDirectKey();
TypeLiteral typeLiteral = key.getTypeLiteral();
// Every OptionalBinder gets the following types bound
// * {cgcb,ju}.Optional>
// * {cgcb,ju}.Optional>
// * {cgcb,ju}.Optional>
// * {cgcb,ju}.Optional
// If setDefault() or setBinding() is called then also
// * T is bound
// cgcb.Optional>
Key>> guavaOptProviderKey = key.ofType(optionalOfProvider(typeLiteral));
binder
.bind(guavaOptProviderKey)
.toProvider(new RealOptionalProviderProvider<>(bindingSelection));
// ju.Optional>
Key>> javaOptProviderKey =
key.ofType(javaOptionalOfProvider(typeLiteral));
binder
.bind(javaOptProviderKey)
.toProvider(new JavaOptionalProviderProvider<>(bindingSelection));
// Provider is assignable to javax.inject.Provider and the provider that the factory contains
// cannot be modified so we can use some rawtypes hackery to share the same implementation.
binder.bind(key.ofType(optionalOfJavaxProvider(typeLiteral))).to((Key) guavaOptProviderKey);
// ju.Optional>
binder.bind(key.ofType(javaOptionalOfJavaxProvider(typeLiteral))).to((Key) javaOptProviderKey);
// Provider is assignable to jakarta.inject.Provider and the provider that the factory contains
// cannot be modified so we can use some rawtypes hackery to share the same implementation.
// cgcb.Optional>
binder.bind(key.ofType(optionalOfJakartaProvider(typeLiteral))).to((Key) guavaOptProviderKey);
// ju.Optional>
binder
.bind(key.ofType(javaOptionalOfJakartaProvider(typeLiteral)))
.to((Key) javaOptProviderKey);
// cgcb.Optional
Key> guavaOptKey = key.ofType(optionalOf(typeLiteral));
binder
.bind(guavaOptKey)
.toProvider(new RealOptionalKeyProvider<>(bindingSelection, guavaOptKey));
// ju.Optional
Key> javaOptKey = key.ofType(javaOptionalOf(typeLiteral));
binder.bind(javaOptKey).toProvider(new JavaOptionalProvider<>(bindingSelection, javaOptKey));
}
/** Provides the binding for {@code java.util.Optional}. */
private static final class JavaOptionalProvider
extends RealOptionalBinderProviderWithDependencies>
implements ProviderWithExtensionVisitor>,
OptionalBinderBinding> {
private final Key> optionalKey;
private Dependency> targetDependency;
private InternalFactory extends T> target;
JavaOptionalProvider(
BindingSelection bindingSelection, Key> optionalKey) {
super(bindingSelection);
this.optionalKey = optionalKey;
}
@Override
void doInitialize() {
if (bindingSelection.getBinding() != null) {
target = bindingSelection.getBinding().getInternalFactory();
targetDependency = bindingSelection.getDependency();
}
}
@Override
protected java.util.Optional doProvision(
InternalContext context, Dependency> currentDependency)
throws InternalProvisionException {
InternalFactory extends T> local = target;
if (local == null) {
return java.util.Optional.empty();
}
Dependency> localDependency = targetDependency;
T result;
try {
// See comments in RealOptionalKeyProvider, about how localDependency may be more specific
// than what we actually need.
result = local.get(context, localDependency, false);
} catch (InternalProvisionException ipe) {
throw ipe.addSource(localDependency);
}
return java.util.Optional.ofNullable(result);
}
@Override
public Set> getDependencies() {
return bindingSelection.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 boolean containsElement(Element element) {
return bindingSelection.containsElement(element);
}
@Override
public Binding> getActualBinding() {
return bindingSelection.getActualBinding();
}
@Override
public Binding> getDefaultBinding() {
return bindingSelection.getDefaultBinding();
}
@Override
public Key> getKey() {
return optionalKey;
}
@Override
public Set> getAlternateKeys() {
Key> key = bindingSelection.getDirectKey();
TypeLiteral> typeLiteral = key.getTypeLiteral();
return ImmutableSet.of(
(Key>) key.ofType(javaOptionalOfProvider(typeLiteral)),
(Key>) key.ofType(javaOptionalOfJavaxProvider(typeLiteral)),
(Key>) key.ofType(javaOptionalOfJakartaProvider(typeLiteral)));
}
}
/** Provides the binding for {@code java.util.Optional>}. */
private static final class JavaOptionalProviderProvider
extends RealOptionalBinderProviderWithDependencies>> {
private java.util.Optional> value;
JavaOptionalProviderProvider(BindingSelection bindingSelection) {
super(bindingSelection);
}
@Override
void doInitialize() {
if (bindingSelection.getBinding() == null) {
value = java.util.Optional.empty();
} else {
value = java.util.Optional.of(bindingSelection.getBinding().getProvider());
}
}
@Override
protected java.util.Optional> doProvision(
InternalContext context, Dependency> dependency) {
return value;
}
@Override
public Set> getDependencies() {
return bindingSelection.providerDependencies();
}
}
/** Provides the binding for T, conditionally installed by calling setBinding/setDefault. */
private static final class RealDirectTypeProvider
extends RealOptionalBinderProviderWithDependencies {
private Key extends T> targetKey;
private InternalFactory extends T> targetFactory;
RealDirectTypeProvider(BindingSelection bindingSelection) {
super(bindingSelection);
}
@Override
void doInitialize() {
BindingImpl targetBinding = bindingSelection.getBinding();
// we only install this factory if they call setBinding()/setDefault() so we know that
// targetBinding will be non-null.
this.targetKey = targetBinding.getKey();
this.targetFactory = targetBinding.getInternalFactory();
}
@Override
protected T doProvision(InternalContext context, Dependency> dependency)
throws InternalProvisionException {
try {
return targetFactory.get(context, dependency, true);
} catch (InternalProvisionException ipe) {
throw ipe.addSource(targetKey);
}
}
@Override
public Set> getDependencies() {
return bindingSelection.dependencies;
}
}
/** Provides the binding for {@code Optional>}. */
private static final class RealOptionalProviderProvider
extends RealOptionalBinderProviderWithDependencies>> {
private Optional> value;
RealOptionalProviderProvider(BindingSelection bindingSelection) {
super(bindingSelection);
}
@Override
void doInitialize() {
if (bindingSelection.getBinding() == null) {
value = Optional.absent();
} else {
value = Optional.of(bindingSelection.getBinding().getProvider());
}
}
@Override
protected Optional> doProvision(InternalContext context, Dependency> dependency) {
return value;
}
@Override
public Set> getDependencies() {
return bindingSelection.providerDependencies();
}
}
/** Provides the binding for {@code Optional}. */
private static final class RealOptionalKeyProvider
extends RealOptionalBinderProviderWithDependencies>
implements ProviderWithExtensionVisitor>, OptionalBinderBinding> {
private final Key> optionalKey;
// These are assigned to non-null values during initialization if and only if we have a binding
// to delegate to.
private Dependency> targetDependency;
private InternalFactory extends T> delegate;
RealOptionalKeyProvider(BindingSelection bindingSelection, Key> optionalKey) {
super(bindingSelection);
this.optionalKey = optionalKey;
}
@Override
void doInitialize() {
if (bindingSelection.getBinding() != null) {
delegate = bindingSelection.getBinding().getInternalFactory();
targetDependency = bindingSelection.getDependency();
}
}
@Override
protected Optional doProvision(InternalContext context, Dependency> currentDependency)
throws InternalProvisionException {
InternalFactory extends T> local = delegate;
if (local == null) {
return Optional.absent();
}
Dependency> localDependency = targetDependency;
T result;
try {
// currentDependency is Optional super T>, so we really just need to set the target
// dependency to ? super T, but we are currently setting it to T. We could hypothetically
// make it easier for our delegate to generate proxies by modifying the dependency, but that
// would also require us to rewrite the key on each call. So for now we don't do it.
result = local.get(context, localDependency, false);
} catch (InternalProvisionException ipe) {
throw ipe.addSource(localDependency);
}
return Optional.fromNullable(result);
}
@Override
public Set> getDependencies() {
return bindingSelection.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 Set> getAlternateKeys() {
Key> key = bindingSelection.getDirectKey();
TypeLiteral> typeLiteral = key.getTypeLiteral();
return ImmutableSet.of(
(Key>) key.ofType(optionalOfProvider(typeLiteral)),
(Key>) key.ofType(optionalOfJavaxProvider(typeLiteral)),
(Key>) key.ofType(optionalOfJakartaProvider(typeLiteral)));
}
@Override
public Binding> getActualBinding() {
return bindingSelection.getActualBinding();
}
@Override
public Binding> getDefaultBinding() {
return bindingSelection.getDefaultBinding();
}
@Override
public boolean containsElement(Element element) {
return bindingSelection.containsElement(element);
}
}
/**
* A helper object that implements the core logic for deciding what the implementation of the
* binding will be.
*
* This also implements the main OptionalBinderBinding logic.
*/
private static final class BindingSelection {
private static final ImmutableSet> MODULE_DEPENDENCIES =
ImmutableSet.>of(Dependency.get(Key.get(Injector.class)));
/*@Nullable */ BindingImpl actualBinding;
/*@Nullable */ BindingImpl defaultBinding;
/*@Nullable */ BindingImpl binding;
private boolean initialized;
private final Key key;
// Until the injector initializes us, we don't know what our dependencies are,
// so initialize to the whole Injector (like Multibinder, and MapBinder indirectly).
private ImmutableSet> dependencies = MODULE_DEPENDENCIES;
private ImmutableSet> providerDependencies = MODULE_DEPENDENCIES;
/** lazily allocated, by {@link #getBindingName}. */
private String bindingName;
/** lazily allocated, by {@link #getKeyForDefaultBinding}. */
private Key defaultBindingKey;
/** lazily allocated, by {@link #getKeyForActualBinding}. */
private Key actualBindingKey;
BindingSelection(Key key) {
this.key = key;
}
void checkNotInitialized() {
checkConfiguration(!initialized, "already initialized");
}
void initialize(InjectorImpl injector, Errors errors) {
// Every one of our providers will call this method, so only execute the logic once.
if (initialized) {
return;
}
actualBinding = injector.getExistingBinding(getKeyForActualBinding());
defaultBinding = injector.getExistingBinding(getKeyForDefaultBinding());
// We should never create Jit bindings, but we can use them if some other binding created it.
BindingImpl userBinding = injector.getExistingBinding(key);
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) {
dependencies = ImmutableSet.>of(Dependency.get(binding.getKey()));
providerDependencies =
ImmutableSet.>of(Dependency.get(providerOf(binding.getKey())));
} else {
dependencies = ImmutableSet.of();
providerDependencies = ImmutableSet.of();
}
checkBindingIsNotRecursive(errors);
initialized = true;
}
private void checkBindingIsNotRecursive(Errors errors) {
if (binding instanceof LinkedBindingImpl) {
LinkedBindingImpl linkedBindingImpl = (LinkedBindingImpl) binding;
if (linkedBindingImpl.getLinkedKey().equals(key)) {
// TODO: b/168656899 check for transitive recursive binding
errors.recursiveBinding(key, linkedBindingImpl.getLinkedKey());
}
}
}
Key getKeyForDefaultBinding() {
if (defaultBindingKey == null) {
defaultBindingKey = key.withAnnotation(new DefaultImpl(getBindingName()));
}
return defaultBindingKey;
}
Key getKeyForActualBinding() {
if (actualBindingKey == null) {
actualBindingKey = key.withAnnotation(new ActualImpl(getBindingName()));
}
return actualBindingKey;
}
Key getDirectKey() {
return key;
}
private String getBindingName() {
// Lazily allocated, most instantiations will never need this because they are deduped during
// module installation.
if (bindingName == null) {
bindingName = Annotations.nameOf(key);
}
return bindingName;
}
BindingImpl getBinding() {
return binding;
}
// Provide default implementations for most of the OptionalBinderBinding interface
BindingImpl getDefaultBinding() {
return defaultBinding;
}
BindingImpl getActualBinding() {
return actualBinding;
}
ImmutableSet> providerDependencies() {
return providerDependencies;
}
ImmutableSet> dependencies() {
return dependencies;
}
/**
* Returns the Dependency for the target binding, throws NoSuchElementException if no target
* exists.
*
* Calls to this method should typically be guarded by checking if {@link #getBinding()}
* returns {@code null}.
*/
Dependency> getDependency() {
return Iterables.getOnlyElement(dependencies);
}
/** Implementation of {@link OptionalBinderBinding#containsElement}. */
boolean containsElement(Element element) {
// We contain it if the provider is us.
if (element instanceof ProviderInstanceBinding) {
javax.inject.Provider> providerInstance =
((ProviderInstanceBinding>) element).getUserSuppliedProvider();
if (providerInstance instanceof RealOptionalBinderProviderWithDependencies) {
return ((RealOptionalBinderProviderWithDependencies, ?>) providerInstance)
.bindingSelection.equals(this);
}
}
if (element instanceof Binding) {
TypeLiteral> typeLiteral = key.getTypeLiteral();
Key> elementKey = ((Binding) element).getKey();
// if it isn't one of the things we bound directly it might be an actual or default key,
// or the javax or jakarta aliases of the optional provider.
return elementKey.equals(getKeyForActualBinding())
|| elementKey.equals(getKeyForDefaultBinding())
|| elementKey.equals(key.ofType(javaOptionalOfJavaxProvider(typeLiteral)))
|| elementKey.equals(key.ofType(optionalOfJavaxProvider(typeLiteral)))
|| elementKey.equals(key.ofType(javaOptionalOfJakartaProvider(typeLiteral)))
|| elementKey.equals(key.ofType(optionalOfJakartaProvider(typeLiteral)));
}
return false; // cannot match;
}
@Override
public boolean equals(Object o) {
return o instanceof BindingSelection && ((BindingSelection) o).key.equals(key);
}
@Override
public int hashCode() {
return key.hashCode();
}
}
@Override
public boolean equals(Object o) {
return o instanceof RealOptionalBinder
&& ((RealOptionalBinder>) o).bindingSelection.equals(bindingSelection);
}
@Override
public int hashCode() {
return bindingSelection.hashCode();
}
/** A base class for ProviderWithDependencies that need equality based on a specific object. */
private abstract static class RealOptionalBinderProviderWithDependencies
extends InternalProviderInstanceBindingImpl.Factory {
protected final BindingSelection bindingSelection;
private boolean initialized = false;
RealOptionalBinderProviderWithDependencies(BindingSelection bindingSelection) {
// We need delayed initialization so we can detect jit bindings created by other bindings
// while not also creating jit bindings ourselves. This ensures we only pick up user bindings
// if the binding would have existed in the injector statically.
super(InitializationTiming.DELAYED);
this.bindingSelection = bindingSelection;
}
@Override
final void initialize(InjectorImpl injector, Errors errors) throws ErrorsException {
if (!initialized) {
initialized = true;
// Note that bindingSelection.initialize has its own guard, because multiple Factory impls
// will delegate to the same bindingSelection (intentionally). The selection has some
// initialization, and each factory impl has other initialization that it may additionally
// do.
bindingSelection.initialize(injector, errors);
doInitialize();
}
}
/**
* Initialize the factory. BindingSelection is guaranteed to be initialized at this point and
* this will be called prior to any provisioning.
*/
abstract void doInitialize();
@Override
public boolean equals(Object obj) {
return obj != null
&& this.getClass() == obj.getClass()
&& bindingSelection.equals(
((RealOptionalBinderProviderWithDependencies, ?>) obj).bindingSelection);
}
@Override
public int hashCode() {
return bindingSelection.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;
}
}