dagger.internal.codegen.binding.LegacyBindingGraphFactory Maven / Gradle / Ivy
Show all versions of dagger-compiler Show documentation
/*
* Copyright (C) 2014 The Dagger Authors.
*
* 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 dagger.internal.codegen.binding;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedFactoryType;
import static dagger.internal.codegen.extension.DaggerCollectors.onlyElement;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.model.BindingKind.ASSISTED_INJECTION;
import static dagger.internal.codegen.model.BindingKind.DELEGATE;
import static dagger.internal.codegen.model.BindingKind.INJECTION;
import static dagger.internal.codegen.model.BindingKind.OPTIONAL;
import static dagger.internal.codegen.model.BindingKind.SUBCOMPONENT_CREATOR;
import static dagger.internal.codegen.model.RequestKind.MEMBERS_INJECTION;
import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf;
import static java.util.function.Predicate.isEqual;
import androidx.room.compiler.processing.XTypeElement;
import com.google.auto.value.AutoValue;
import com.google.auto.value.extension.memoized.Memoized;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import dagger.Reusable;
import dagger.internal.codegen.base.ContributionType;
import dagger.internal.codegen.base.Keys;
import dagger.internal.codegen.base.MapType;
import dagger.internal.codegen.base.SetType;
import dagger.internal.codegen.compileroption.CompilerOptions;
import dagger.internal.codegen.model.BindingGraph.ComponentNode;
import dagger.internal.codegen.model.BindingKind;
import dagger.internal.codegen.model.ComponentPath;
import dagger.internal.codegen.model.DaggerTypeElement;
import dagger.internal.codegen.model.DependencyRequest;
import dagger.internal.codegen.model.Key;
import dagger.internal.codegen.model.RequestKind;
import dagger.internal.codegen.model.Scope;
import dagger.internal.codegen.xprocessing.XTypeNames;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import javax.inject.Inject;
/** A factory for {@link BindingGraph} objects. */
public final class LegacyBindingGraphFactory {
static boolean useLegacyBindingGraphFactory(
CompilerOptions compilerOptions, ComponentDescriptor componentDescriptor) {
return !compilerOptions.useBindingGraphFix();
}
static boolean hasStrictMultibindingsExemption(
CompilerOptions compilerOptions, ContributionBinding binding) {
// We only give the exemption to multibound map contributions.
if (!binding.contributionType().equals(ContributionType.MAP)) {
return false;
}
return !compilerOptions.strictMultibindingValidation();
}
private final InjectBindingRegistry injectBindingRegistry;
private final KeyFactory keyFactory;
private final BindingFactory bindingFactory;
private final BindingNode.Factory bindingNodeFactory;
private final ComponentDeclarations.Factory componentDeclarationsFactory;
private final LegacyBindingGraphConverter legacyBindingGraphConverter;
private final CompilerOptions compilerOptions;
@Inject
LegacyBindingGraphFactory(
InjectBindingRegistry injectBindingRegistry,
KeyFactory keyFactory,
BindingFactory bindingFactory,
BindingNode.Factory bindingNodeFactory,
ComponentDeclarations.Factory componentDeclarationsFactory,
LegacyBindingGraphConverter legacyBindingGraphConverter,
CompilerOptions compilerOptions) {
this.injectBindingRegistry = injectBindingRegistry;
this.keyFactory = keyFactory;
this.bindingFactory = bindingFactory;
this.bindingNodeFactory = bindingNodeFactory;
this.componentDeclarationsFactory = componentDeclarationsFactory;
this.legacyBindingGraphConverter = legacyBindingGraphConverter;
this.compilerOptions = compilerOptions;
}
/**
* Creates a binding graph for a component.
*
* @param createFullBindingGraph if {@code true}, the binding graph will include all bindings;
* otherwise it will include only bindings reachable from at least one entry point
*/
public BindingGraph create(
ComponentDescriptor componentDescriptor, boolean createFullBindingGraph) {
return legacyBindingGraphConverter.convert(
createLegacyBindingGraph(Optional.empty(), componentDescriptor, createFullBindingGraph),
createFullBindingGraph);
}
private LegacyBindingGraph createLegacyBindingGraph(
Optional parentResolver,
ComponentDescriptor componentDescriptor,
boolean createFullBindingGraph) {
Resolver requestResolver = new Resolver(parentResolver, componentDescriptor);
componentDescriptor.entryPointMethods().stream()
.map(method -> method.dependencyRequest().get())
.forEach(
entryPoint -> {
if (entryPoint.kind().equals(MEMBERS_INJECTION)) {
requestResolver.resolveMembersInjection(entryPoint.key());
} else {
requestResolver.resolve(entryPoint.key());
}
});
if (createFullBindingGraph) {
// Resolve the keys for all bindings in all modules, stripping any multibinding contribution
// identifier so that the multibinding itself is resolved.
requestResolver.declarations.allDeclarations().stream()
// TODO(b/349155899): Consider resolving all declarations in full binding graph mode, not
// just those from modules.
.filter(declaration -> declaration.contributingModule().isPresent())
.map(Declaration::key)
.map(Key::withoutMultibindingContributionIdentifier)
.forEach(requestResolver::resolve);
}
// Resolve all bindings for subcomponents, creating subgraphs for all subcomponents that have
// been detected during binding resolution. If a binding for a subcomponent is never resolved,
// no BindingGraph will be created for it and no implementation will be generated. This is
// done in a queue since resolving one subcomponent might resolve a key for a subcomponent
// from a parent graph. This is done until no more new subcomponents are resolved.
Set resolvedSubcomponents = new HashSet<>();
ImmutableList.Builder subgraphs = ImmutableList.builder();
for (ComponentDescriptor subcomponent :
Iterables.consumingIterable(requestResolver.subcomponentsToResolve)) {
if (resolvedSubcomponents.add(subcomponent)) {
subgraphs.add(
createLegacyBindingGraph(
Optional.of(requestResolver), subcomponent, createFullBindingGraph));
}
}
return new LegacyBindingGraph(requestResolver, subgraphs.build());
}
/** Represents a fully resolved binding graph. */
static final class LegacyBindingGraph {
private final Resolver resolver;
private final ImmutableList resolvedSubgraphs;
private final ComponentNode componentNode;
LegacyBindingGraph(Resolver resolver, ImmutableList resolvedSubgraphs) {
this.resolver = resolver;
this.resolvedSubgraphs = resolvedSubgraphs;
this.componentNode =
ComponentNodeImpl.create(resolver.componentPath, resolver.componentDescriptor);
}
/** Returns the {@link ComponentNode} associated with this binding graph. */
public ComponentNode componentNode() {
return componentNode;
}
/** Returns the {@link ComponentPath} associated with this binding graph. */
public ComponentPath componentPath() {
return resolver.componentPath;
}
/** Returns the {@link ComponentDescriptor} associated with this binding graph. */
public ComponentDescriptor componentDescriptor() {
return resolver.componentDescriptor;
}
/**
* Returns the {@link LegacyResolvedBindings} in this graph or a parent graph that matches the
* given request.
*
* An exception is thrown if there are no resolved bindings found for the request; however,
* this should never happen since all dependencies should have been resolved at this point.
*/
public LegacyResolvedBindings resolvedBindings(BindingRequest request) {
return request.isRequestKind(RequestKind.MEMBERS_INJECTION)
? resolver.getResolvedMembersInjectionBindings(request.key())
: resolver.getResolvedContributionBindings(request.key());
}
/**
* Returns all {@link LegacyResolvedBindings} for the given request.
*
*
Note that this only returns the bindings resolved in this component. Bindings resolved in
* parent components are not included.
*/
public Iterable resolvedBindings() {
// Don't return an immutable collection - this is only ever used for looping over all bindings
// in the graph. Copying is wasteful, especially if is a hashing collection, since the values
// should all, by definition, be distinct.
return Iterables.concat(
resolver.resolvedMembersInjectionBindings.values(),
resolver.resolvedContributionBindings.values());
}
/** Returns the resolved subgraphs. */
public ImmutableList subgraphs() {
return resolvedSubgraphs;
}
}
/**
* The collection of bindings that have been resolved for a key. For valid graphs, contains
* exactly one binding.
*
* Separate {@link LegacyResolvedBindings} instances should be used if a {@link
* MembersInjectionBinding} and a {@link ProvisionBinding} for the same key exist in the same
* component. (This will only happen if a type has an {@code @Inject} constructor and members, the
* component has a members injection method, and the type is also requested normally.)
*/
@AutoValue
abstract static class LegacyResolvedBindings {
/**
* Creates a {@link LegacyResolvedBindings} appropriate for when there are no bindings for a
* key.
*/
static LegacyResolvedBindings create(Key key) {
return create(key, ImmutableSet.of());
}
/** Creates a {@link LegacyResolvedBindings} for a single binding. */
static LegacyResolvedBindings create(Key key, BindingNode bindingNode) {
return create(key, ImmutableSet.of(bindingNode));
}
/** Creates a {@link LegacyResolvedBindings} for multiple bindings. */
static LegacyResolvedBindings create(Key key, ImmutableSet bindingNodes) {
return new AutoValue_LegacyBindingGraphFactory_LegacyResolvedBindings(key, bindingNodes);
}
/** The binding key for which the {@link #bindings()} have been resolved. */
abstract Key key();
/** All binding nodes for {@link #key()}, regardless of which component owns them. */
abstract ImmutableSet bindingNodes();
// Computing the hash code is an expensive operation.
@Memoized
@Override
public abstract int hashCode();
// Suppresses ErrorProne warning that hashCode was overridden w/o equals
@Override
public abstract boolean equals(Object other);
/** All bindings for {@link #key()}, regardless of which component owns them. */
final ImmutableSet bindings() {
return bindingNodes().stream()
.map(BindingNode::delegate)
.collect(toImmutableSet());
}
/** Returns {@code true} if there are no {@link #bindings()}. */
final boolean isEmpty() {
return bindingNodes().isEmpty();
}
/** All bindings for {@link #key()} that are owned by a component. */
ImmutableSet bindingNodesOwnedBy(ComponentPath componentPath) {
return bindingNodes().stream()
.filter(bindingNode -> bindingNode.componentPath().equals(componentPath))
.collect(toImmutableSet());
}
/** Returns the binding node representing the given binding, or throws ISE if none exist. */
final BindingNode forBinding(Binding binding) {
return bindingNodes().stream()
.filter(bindingNode -> bindingNode.delegate().equals(binding))
.collect(onlyElement());
}
}
private final class Resolver {
final ComponentPath componentPath;
final Optional parentResolver;
final ComponentDescriptor componentDescriptor;
final ComponentDeclarations declarations;
final Map resolvedContributionBindings = new LinkedHashMap<>();
final Map resolvedMembersInjectionBindings = new LinkedHashMap<>();
final Deque cycleStack = new ArrayDeque<>();
final Map keyDependsOnLocalBindingsCache = new HashMap<>();
final Map bindingDependsOnLocalBindingsCache = new HashMap<>();
final Queue subcomponentsToResolve = new ArrayDeque<>();
Resolver(Optional parentResolver, ComponentDescriptor componentDescriptor) {
this.parentResolver = parentResolver;
this.componentDescriptor = checkNotNull(componentDescriptor);
DaggerTypeElement componentType = DaggerTypeElement.from(componentDescriptor.typeElement());
componentPath =
parentResolver.isPresent()
? parentResolver.get().componentPath.childPath(componentType)
: ComponentPath.create(ImmutableList.of(componentType));
declarations =
componentDeclarationsFactory.create(
parentResolver.map(parent -> parent.componentDescriptor),
componentDescriptor);
subcomponentsToResolve.addAll(
componentDescriptor.childComponentsDeclaredByFactoryMethods().values());
subcomponentsToResolve.addAll(
componentDescriptor.childComponentsDeclaredByBuilderEntryPoints().values());
}
/**
* Returns the resolved contribution bindings for the given {@link Key}:
*
*
* - All explicit bindings for:
*
* - the requested key
*
- {@code Set
} if the requested key's type is {@code Set>}
* - {@code Map
>} if the requested key's type is {@code Map>}.
*
* - An implicit {@link Inject @Inject}-annotated constructor binding if there is one and
* there are no explicit bindings or synthetic bindings.
*
*/
LegacyResolvedBindings lookUpBindings(Key requestKey) {
Set bindings = new LinkedHashSet<>();
Set multibindingContributions = new LinkedHashSet<>();
Set multibindingDeclarations = new LinkedHashSet<>();
Set optionalBindingDeclarations = new LinkedHashSet<>();
Set subcomponentDeclarations = new LinkedHashSet<>();
// Gather all bindings, multibindings, optional, and subcomponent declarations/contributions.
for (Resolver resolver : getResolverLineage()) {
bindings.addAll(resolver.getLocalExplicitBindings(requestKey));
multibindingContributions.addAll(resolver.getLocalMultibindingContributions(requestKey));
multibindingDeclarations.addAll(resolver.declarations.multibindings(requestKey));
subcomponentDeclarations.addAll(resolver.declarations.subcomponents(requestKey));
// The optional binding declarations are keyed by the unwrapped type.
keyFactory.unwrapOptional(requestKey)
.map(resolver.declarations::optionalBindings)
.ifPresent(optionalBindingDeclarations::addAll);
}
// Add synthetic multibinding
if (!multibindingContributions.isEmpty() || !multibindingDeclarations.isEmpty()) {
if (MapType.isMap(requestKey)) {
bindings.add(bindingFactory.multiboundMap(requestKey, multibindingContributions));
} else if (SetType.isSet(requestKey)) {
bindings.add(bindingFactory.multiboundSet(requestKey, multibindingContributions));
} else {
throw new AssertionError("Unexpected type in multibinding key: " + requestKey);
}
}
// Add synthetic optional binding
if (!optionalBindingDeclarations.isEmpty()) {
ImmutableSet optionalContributions =
lookUpBindings(keyFactory.unwrapOptional(requestKey).get()).bindings();
bindings.add(
optionalContributions.isEmpty()
? bindingFactory.syntheticAbsentOptionalDeclaration(requestKey)
: bindingFactory.syntheticPresentOptionalDeclaration(
requestKey, optionalContributions));
}
// Add subcomponent creator binding
if (!subcomponentDeclarations.isEmpty()) {
ContributionBinding binding =
bindingFactory.subcomponentCreatorBinding(
ImmutableSet.copyOf(subcomponentDeclarations));
bindings.add(binding);
addSubcomponentToOwningResolver(binding);
}
// Add members injector binding
if (isTypeOf(requestKey.type().xprocessing(), XTypeNames.MEMBERS_INJECTOR)) {
injectBindingRegistry.getOrFindMembersInjectorBinding(requestKey).ifPresent(bindings::add);
}
// Add Assisted Factory binding
if (isDeclared(requestKey.type().xprocessing())
&& isAssistedFactoryType(requestKey.type().xprocessing().getTypeElement())) {
bindings.add(
bindingFactory.assistedFactoryBinding(
requestKey.type().xprocessing().getTypeElement(),
Optional.of(requestKey.type().xprocessing())));
}
// If there are no bindings, add the implicit @Inject-constructed binding if there is one.
if (bindings.isEmpty()) {
injectBindingRegistry
.getOrFindInjectionBinding(requestKey)
.filter(this::isCorrectlyScopedInSubcomponent)
.ifPresent(bindings::add);
}
return LegacyResolvedBindings.create(
requestKey,
bindings.stream()
.map(
binding -> {
Optional bindingNodeOwnedByAncestor =
getBindingNodeOwnedByAncestor(requestKey, binding);
// If a binding is owned by an ancestor we use the corresponding BindingNode
// instance directly rather than creating a new instance to avoid accidentally
// including additional multi/optional/subcomponent declarations that don't
// exist in the ancestor's BindingNode instance.
return bindingNodeOwnedByAncestor.isPresent()
? bindingNodeOwnedByAncestor.get()
: bindingNodeFactory.forContributionBindings(
componentPath,
binding,
multibindingDeclarations,
optionalBindingDeclarations,
subcomponentDeclarations);
})
.collect(toImmutableSet()));
}
/**
* Returns true if this binding graph resolution is for a subcomponent and the {@code @Inject}
* binding's scope correctly matches one of the components in the current component ancestry.
* If not, it means the binding is not owned by any of the currently known components, and will
* be owned by a future ancestor (or, if never owned, will result in an incompatibly scoped
* binding error at the root component).
*/
private boolean isCorrectlyScopedInSubcomponent(ContributionBinding binding) {
checkArgument(binding.kind() == INJECTION || binding.kind() == ASSISTED_INJECTION);
if (!rootComponent().isSubcomponent()
|| !binding.scope().isPresent()
|| binding.scope().get().isReusable()) {
return true;
}
Resolver owningResolver = getOwningResolver(binding).orElse(this);
ComponentDescriptor owningComponent = owningResolver.componentDescriptor;
return owningComponent.scopes().contains(binding.scope().get());
}
private ComponentDescriptor rootComponent() {
return parentResolver.map(Resolver::rootComponent).orElse(componentDescriptor);
}
/** Returns the resolved members injection bindings for the given {@link Key}. */
LegacyResolvedBindings lookUpMembersInjectionBinding(Key requestKey) {
// no explicit deps for members injection, so just look it up
Optional binding =
injectBindingRegistry.getOrFindMembersInjectionBinding(requestKey);
return binding.isPresent()
? LegacyResolvedBindings.create(
requestKey,
bindingNodeFactory.forMembersInjectionBinding(componentPath, binding.get()))
: LegacyResolvedBindings.create(requestKey);
}
/**
* When a binding is resolved for a {@link SubcomponentDeclaration}, adds corresponding {@link
* ComponentDescriptor subcomponent} to a queue in the owning component's resolver. The queue
* will be used to detect which subcomponents need to be resolved.
*/
private void addSubcomponentToOwningResolver(ContributionBinding subcomponentCreatorBinding) {
checkArgument(subcomponentCreatorBinding.kind().equals(SUBCOMPONENT_CREATOR));
Resolver owningResolver = getOwningResolver(subcomponentCreatorBinding).get();
XTypeElement builderType =
subcomponentCreatorBinding.key().type().xprocessing().getTypeElement();
owningResolver.subcomponentsToResolve.add(
owningResolver.componentDescriptor.getChildComponentWithBuilderType(builderType));
}
private ImmutableSet createDelegateBindings(
ImmutableSet delegateDeclarations) {
ImmutableSet.Builder builder = ImmutableSet.builder();
for (DelegateDeclaration delegateDeclaration : delegateDeclarations) {
builder.add(createDelegateBinding(delegateDeclaration));
}
return builder.build();
}
/**
* Creates one (and only one) delegate binding for a delegate declaration, based on the resolved
* bindings of the right-hand-side of a {@link dagger.Binds} method. If there are duplicate
* bindings for the dependency key, there should still be only one binding for the delegate key.
*/
private ContributionBinding createDelegateBinding(DelegateDeclaration delegateDeclaration) {
Key delegateKey = delegateDeclaration.delegateRequest().key();
if (cycleStack.contains(delegateKey)) {
return bindingFactory.unresolvedDelegateBinding(delegateDeclaration);
}
LegacyResolvedBindings resolvedDelegate;
try {
cycleStack.push(delegateKey);
resolvedDelegate = lookUpBindings(delegateKey);
} finally {
cycleStack.pop();
}
if (resolvedDelegate.bindings().isEmpty()) {
// This is guaranteed to result in a missing binding error, so it doesn't matter if the
// binding is a Provision or Production, except if it is a @IntoMap method, in which
// case the key will be of type Map>, which will be "upgraded" into a
// Map> if it's requested in a ProductionComponent. This may result in a
// strange error, that the RHS needs to be provided with an @Inject or @Provides
// annotated method, but a user should be able to figure out if a @Produces annotation
// is needed.
// TODO(gak): revisit how we model missing delegates if/when we clean up how we model
// binding declarations
return bindingFactory.unresolvedDelegateBinding(delegateDeclaration);
}
// It doesn't matter which of these is selected, since they will later on produce a
// duplicate binding error.
ContributionBinding explicitDelegate =
(ContributionBinding) resolvedDelegate.bindings().iterator().next();
return bindingFactory.delegateBinding(delegateDeclaration, explicitDelegate);
}
/**
* Returns a {@link BindingNode} for the given binding that is owned by an ancestor component,
* if one exists. Otherwise returns {@link Optional#empty()}.
*/
private Optional getBindingNodeOwnedByAncestor(
Key requestKey, ContributionBinding binding) {
if (canBeResolvedInParent(requestKey, binding)) {
// Resolve in the parent to make sure we have the most recent multi/optional contributions.
parentResolver.get().resolve(requestKey);
if (!requiresResolution(binding)) {
return Optional.of(getPreviouslyResolvedBindings(requestKey).get().forBinding(binding));
}
}
return Optional.empty();
}
private boolean canBeResolvedInParent(Key requestKey, ContributionBinding binding) {
if (parentResolver.isEmpty()) {
return false;
}
Optional owningResolver = getOwningResolver(binding);
if (owningResolver.isPresent()) {
return !owningResolver.get().equals(this);
}
return !Keys.isComponentOrCreator(requestKey)
// TODO(b/305748522): Allow caching for assisted injection bindings.
&& binding.kind() != BindingKind.ASSISTED_INJECTION
&& getPreviouslyResolvedBindings(requestKey).isPresent()
&& getPreviouslyResolvedBindings(requestKey).get().bindings().contains(binding);
}
private Optional getOwningResolver(ContributionBinding binding) {
// TODO(ronshapiro): extract the different pieces of this method into their own methods
if ((binding.scope().isPresent() && binding.scope().get().isProductionScope())
|| binding.kind().equals(BindingKind.PRODUCTION)) {
for (Resolver requestResolver : getResolverLineage()) {
// Resolve @Inject @ProductionScope bindings at the highest production component.
if (binding.kind().equals(INJECTION)
&& requestResolver.componentDescriptor.isProduction()) {
return Optional.of(requestResolver);
}
// Resolve explicit @Produces and @ProductionScope bindings at the highest component that
// installs the binding.
if (requestResolver.containsExplicitBinding(binding)) {
return Optional.of(requestResolver);
}
}
}
if (binding.scope().isPresent() && binding.scope().get().isReusable()) {
for (Resolver requestResolver : getResolverLineage().reverse()) {
// If a @Reusable binding was resolved in an ancestor, use that component.
LegacyResolvedBindings resolvedBindings =
requestResolver.resolvedContributionBindings.get(binding.key());
if (resolvedBindings != null && resolvedBindings.bindings().contains(binding)) {
return Optional.of(requestResolver);
}
}
// If a @Reusable binding was not resolved in any ancestor, resolve it here.
return Optional.empty();
}
// TODO(b/359893922): we currently iterate from child to parent to find an owning resolver,
// but we probably want to iterate from parent to child to catch missing bindings in
// misconfigured repeated modules.
for (Resolver requestResolver : getResolverLineage().reverse()) {
if (requestResolver.containsExplicitBinding(binding)) {
return Optional.of(requestResolver);
}
}
// look for scope separately. we do this for the case where @Singleton can appear twice
// in the † compatibility mode
Optional bindingScope = binding.scope();
if (bindingScope.isPresent()) {
for (Resolver requestResolver : getResolverLineage().reverse()) {
if (requestResolver.componentDescriptor.scopes().contains(bindingScope.get())) {
return Optional.of(requestResolver);
}
}
}
return Optional.empty();
}
private boolean containsExplicitBinding(ContributionBinding binding) {
return declarations.bindings(binding.key()).contains(binding)
|| resolverContainsDelegateDeclarationForBinding(binding)
|| !declarations.subcomponents(binding.key()).isEmpty();
}
/** Returns true if {@code binding} was installed in a module in this resolver's component. */
private boolean resolverContainsDelegateDeclarationForBinding(ContributionBinding binding) {
if (!binding.kind().equals(DELEGATE)) {
return false;
}
if (hasStrictMultibindingsExemption(compilerOptions, binding)) {
return false;
}
return declarations.delegates(binding.key()).stream()
.anyMatch(
declaration ->
declaration.contributingModule().equals(binding.contributingModule())
&& declaration.bindingElement().equals(binding.bindingElement()));
}
/** Returns the resolver lineage from parent to child. */
private ImmutableList getResolverLineage() {
ImmutableList.Builder resolverList = ImmutableList.builder();
for (Optional currentResolver = Optional.of(this);
currentResolver.isPresent();
currentResolver = currentResolver.get().parentResolver) {
resolverList.add(currentResolver.get());
}
return resolverList.build().reverse();
}
/**
* Returns the explicit {@link ContributionBinding}s that match the {@code key} from this
* resolver.
*/
private ImmutableSet getLocalExplicitBindings(Key key) {
return ImmutableSet.builder()
.addAll(declarations.bindings(key))
.addAll(createDelegateBindings(declarations.delegates(key)))
.build();
}
/**
* Returns the explicit multibinding contributions that contribute to the map or set requested
* by {@code key} from this resolver.
*/
private ImmutableSet getLocalMultibindingContributions(Key key) {
return ImmutableSet.builder()
.addAll(declarations.multibindingContributions(key))
.addAll(createDelegateBindings(declarations.delegateMultibindingContributions(key)))
.build();
}
/**
* Returns the {@link OptionalBindingDeclaration}s that match the {@code key} from this and all
* ancestor resolvers.
*/
private ImmutableSet getOptionalBindingDeclarations(Key key) {
Optional unwrapped = keyFactory.unwrapOptional(key);
if (unwrapped.isEmpty()) {
return ImmutableSet.of();
}
ImmutableSet.Builder declarations = ImmutableSet.builder();
for (Resolver resolver : getResolverLineage()) {
declarations.addAll(resolver.declarations.optionalBindings(unwrapped.get()));
}
return declarations.build();
}
/**
* Returns the {@link LegacyResolvedBindings} for {@code key} that was resolved in this resolver
* or an ancestor resolver. Only checks for {@link ContributionBinding}s as {@link
* MembersInjectionBinding}s are not inherited.
*/
private Optional getPreviouslyResolvedBindings(Key key) {
Optional result =
Optional.ofNullable(resolvedContributionBindings.get(key));
if (result.isPresent()) {
return result;
} else if (parentResolver.isPresent()) {
return parentResolver.get().getPreviouslyResolvedBindings(key);
} else {
return Optional.empty();
}
}
private void resolveMembersInjection(Key key) {
LegacyResolvedBindings bindings = lookUpMembersInjectionBinding(key);
resolveDependencies(bindings);
resolvedMembersInjectionBindings.put(key, bindings);
}
void resolve(Key key) {
// If we find a cycle, stop resolving. The original request will add it with all of the
// other resolved deps.
if (cycleStack.contains(key)) {
return;
}
// If the binding was previously resolved in this (sub)component, don't resolve it again.
if (resolvedContributionBindings.containsKey(key)) {
return;
}
cycleStack.push(key);
try {
LegacyResolvedBindings bindings = lookUpBindings(key);
resolvedContributionBindings.put(key, bindings);
resolveDependencies(bindings);
} finally {
cycleStack.pop();
}
}
/**
* {@link #resolve(Key) Resolves} each of the dependencies of the bindings owned by this
* component.
*/
private void resolveDependencies(LegacyResolvedBindings resolvedBindings) {
for (BindingNode binding : resolvedBindings.bindingNodesOwnedBy(componentPath)) {
for (DependencyRequest dependency : binding.dependencies()) {
resolve(dependency.key());
}
}
}
private LegacyResolvedBindings getResolvedContributionBindings(Key key) {
if (resolvedContributionBindings.containsKey(key)) {
return resolvedContributionBindings.get(key);
}
if (parentResolver.isPresent()) {
return parentResolver.get().getResolvedContributionBindings(key);
}
throw new AssertionError("No resolved bindings for key: " + key);
}
private LegacyResolvedBindings getResolvedMembersInjectionBindings(Key key) {
return resolvedMembersInjectionBindings.get(key);
}
private boolean requiresResolution(Binding binding) {
return new RequiresResolutionChecker().requiresResolution(binding);
}
private final class RequiresResolutionChecker {
private final Set