blob: 41885c096dea3c9ee551f4555a7925641d8f8761 [file] [log] [blame]
/*
* Copyright (C) 2016 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;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.getOnlyElement;
import static dagger.internal.codegen.BindingRequest.bindingRequest;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PROTECTED;
import static javax.lang.model.element.Modifier.PUBLIC;
import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.MethodSpec;
import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
import dagger.internal.codegen.MethodBindingExpression.MethodImplementationStrategy;
import dagger.internal.codegen.ModifiableBindingMethods.ModifiableBindingMethod;
import dagger.model.BindingKind;
import dagger.model.DependencyRequest;
import java.util.Optional;
/**
* A central repository of code expressions used to access modifiable bindings available to a
* component. A binding is modifiable if it can be modified across implementations of a
* subcomponent. This is only relevant for ahead-of-time subcomponents.
*/
final class ModifiableBindingExpressions {
private final Optional<ModifiableBindingExpressions> parent;
private final ComponentBindingExpressions bindingExpressions;
private final BindingGraph graph;
private final ComponentImplementation componentImplementation;
private final CompilerOptions compilerOptions;
private final DaggerTypes types;
ModifiableBindingExpressions(
Optional<ModifiableBindingExpressions> parent,
ComponentBindingExpressions bindingExpressions,
BindingGraph graph,
ComponentImplementation componentImplementation,
CompilerOptions compilerOptions,
DaggerTypes types) {
this.parent = parent;
this.bindingExpressions = bindingExpressions;
this.graph = graph;
this.componentImplementation = componentImplementation;
this.compilerOptions = compilerOptions;
this.types = types;
}
/**
* Records the binding exposed by the given component method as modifiable, if it is, and returns
* the {@link ModifiableBindingType} associated with the binding.
*/
ModifiableBindingType registerComponentMethodIfModifiable(
ComponentMethodDescriptor componentMethod, MethodSpec method) {
BindingRequest request = bindingRequest(componentMethod.dependencyRequest().get());
ModifiableBindingType modifiableBindingType = getModifiableBindingType(request);
if (modifiableBindingType.isModifiable()) {
componentImplementation.registerModifiableBindingMethod(
modifiableBindingType,
request,
componentMethod.resolvedReturnType(types),
method,
newModifiableBindingWillBeFinalized(modifiableBindingType, request));
}
return modifiableBindingType;
}
/**
* Returns the implementation of a modifiable binding method originally defined in a supertype
* implementation of this subcomponent. Returns {@link Optional#empty()} when the binding cannot
* or should not be modified by the current binding graph.
*/
Optional<ModifiableBindingMethod> reimplementedModifiableBindingMethod(
ModifiableBindingMethod modifiableBindingMethod) {
checkState(componentImplementation.superclassImplementation().isPresent());
ModifiableBindingType newModifiableBindingType =
getModifiableBindingType(modifiableBindingMethod.request());
ModifiableBindingType oldModifiableBindingType = modifiableBindingMethod.type();
boolean modifiableBindingTypeChanged =
!newModifiableBindingType.equals(oldModifiableBindingType);
if (modifiableBindingTypeChanged
&& !newModifiableBindingType.hasBaseClassImplementation()
&& (oldModifiableBindingType.hasBaseClassImplementation()
|| componentImplementation.isAbstract())) {
// We don't want to override one abstract method with another one. However, If the component
// is not abstract (such as a transition from GENERATED_INSTANCE -> MISSING), we must provide
// an implementation like normal.
return Optional.empty();
}
if (modifiableBindingTypeChanged
|| shouldModifyImplementation(
newModifiableBindingType, modifiableBindingMethod.request())) {
MethodSpec baseMethod = modifiableBindingMethod.methodSpec();
boolean markMethodFinal =
knownModifiableBindingWillBeFinalized(modifiableBindingMethod)
// no need to mark the method final if the component implementation will be final
&& componentImplementation.isAbstract();
return Optional.of(
ModifiableBindingMethod.implement(
modifiableBindingMethod,
MethodSpec.methodBuilder(baseMethod.name)
.addModifiers(baseMethod.modifiers.contains(PUBLIC) ? PUBLIC : PROTECTED)
.addModifiers(markMethodFinal ? ImmutableSet.of(FINAL) : ImmutableSet.of())
.returns(baseMethod.returnType)
.addAnnotation(Override.class)
.addCode(
bindingExpressions
.getBindingExpression(modifiableBindingMethod.request())
.getModifiableBindingMethodImplementation(
modifiableBindingMethod, componentImplementation, types))
.build(),
markMethodFinal));
}
return Optional.empty();
}
/**
* Returns true if a modifiable binding method that was registered in a superclass implementation
* of this subcomponent should be marked as "finalized" if it is being overridden by this
* subcomponent implementation. "Finalized" means we should not attempt to modify the binding in
* any subcomponent subclass.
*/
private boolean knownModifiableBindingWillBeFinalized(
ModifiableBindingMethod modifiableBindingMethod) {
ModifiableBindingType newModifiableBindingType =
getModifiableBindingType(modifiableBindingMethod.request());
if (!newModifiableBindingType.isModifiable()) {
// If a modifiable binding has become non-modifiable it is final by definition.
return true;
}
return modifiableBindingWillBeFinalized(
newModifiableBindingType,
shouldModifyImplementation(newModifiableBindingType, modifiableBindingMethod.request()));
}
/**
* Returns true if a newly discovered modifiable binding method, once it is defined in this
* subcomponent implementation, should be marked as "finalized", meaning we should not attempt to
* modify the binding in any subcomponent subclass.
*/
private boolean newModifiableBindingWillBeFinalized(
ModifiableBindingType modifiableBindingType, BindingRequest request) {
return modifiableBindingWillBeFinalized(
modifiableBindingType, shouldModifyImplementation(modifiableBindingType, request));
}
/**
* Returns true if we shouldn't attempt to further modify a modifiable binding once we complete
* the implementation for the current subcomponent.
*/
private boolean modifiableBindingWillBeFinalized(
ModifiableBindingType modifiableBindingType, boolean modifyingBinding) {
switch (modifiableBindingType) {
case MISSING:
case BINDS_METHOD_WITH_MISSING_DEPENDENCY:
case GENERATED_INSTANCE:
case OPTIONAL:
case INJECTION:
// Once we modify any of the above a single time, then they are finalized.
return modifyingBinding;
case MULTIBINDING:
return false;
default:
throw new IllegalStateException(
String.format(
"Building binding expression for unsupported ModifiableBindingType [%s].",
modifiableBindingType));
}
}
/**
* Creates a binding expression for a binding if it may be modified across implementations of a
* subcomponent.
*/
Optional<BindingExpression> maybeCreateModifiableBindingExpression(BindingRequest request) {
ModifiableBindingType type = getModifiableBindingType(request);
if (!type.isModifiable()) {
return Optional.empty();
}
return Optional.of(createModifiableBindingExpression(type, request));
}
/** Creates a binding expression for a modifiable binding. */
private BindingExpression createModifiableBindingExpression(
ModifiableBindingType type, BindingRequest request) {
ResolvedBindings resolvedBindings = graph.resolvedBindings(request);
Optional<ModifiableBindingMethod> matchingModifiableBindingMethod =
componentImplementation.getModifiableBindingMethod(request);
Optional<ComponentMethodDescriptor> matchingComponentMethod =
graph.componentDescriptor().firstMatchingComponentMethod(request);
switch (type) {
case GENERATED_INSTANCE:
// If the subcomponent is abstract then we need to define an (un-implemented)
// DeferredModifiableBindingExpression.
if (componentImplementation.isAbstract()) {
return new DeferredModifiableBindingExpression(
componentImplementation,
type,
resolvedBindings.contributionBinding(),
request,
matchingModifiableBindingMethod,
matchingComponentMethod,
types);
}
// Otherwise return a concrete implementation.
return bindingExpressions.createBindingExpression(resolvedBindings, request);
case MISSING:
// If we need an expression for a missing binding and the current implementation is
// abstract, then we need an (un-implemented) MissingBindingExpression.
if (componentImplementation.isAbstract()) {
return new MissingBindingExpression(
componentImplementation,
request,
matchingModifiableBindingMethod,
matchingComponentMethod,
types);
}
// Otherwise we assume that it is valid to have a missing binding as it is part of a
// dependency chain that has been passively pruned.
// TODO(b/117833324): Identify pruned bindings when generating the subcomponent
// implementation in which the bindings are pruned. If we hold a reference to the binding
// graph used to generate a given implementation then we can compare a implementation's
// graph with its superclass implementation's graph to detect pruned dependency branches.
return new PrunedConcreteMethodBindingExpression();
case BINDS_METHOD_WITH_MISSING_DEPENDENCY:
checkState(componentImplementation.isAbstract());
return new DeferredModifiableBindingExpression(
componentImplementation,
type,
resolvedBindings.contributionBinding(),
request,
matchingModifiableBindingMethod,
matchingComponentMethod,
types);
case OPTIONAL:
case MULTIBINDING:
case INJECTION:
return bindingExpressions.wrapInMethod(
resolvedBindings,
request,
bindingExpressions.createBindingExpression(resolvedBindings, request));
default:
throw new IllegalStateException(
String.format(
"Building binding expression for unsupported ModifiableBindingType [%s].", type));
}
}
/**
* The reason why a binding may need to be modified across implementations of a subcomponent, if
* at all.
*/
ModifiableBindingType getModifiableBindingType(BindingRequest request) {
if (!compilerOptions.aheadOfTimeSubcomponents()) {
return ModifiableBindingType.NONE;
}
// When generating a component the binding is not considered modifiable. Bindings are modifiable
// only across subcomponent implementations.
if (componentImplementation.componentDescriptor().kind().isRoot()) {
return ModifiableBindingType.NONE;
}
if (request.requestKind().filter(RequestKinds::isDerivedFromProvider).isPresent()) {
return ModifiableBindingType.NONE;
}
if (resolvedInThisComponent(request)) {
ResolvedBindings resolvedBindings = graph.resolvedBindings(request);
if (resolvedBindings.contributionBindings().isEmpty()) {
// TODO(ronshapiro): Confirm whether a resolved binding must have a single contribution
// binding.
return ModifiableBindingType.NONE;
}
ContributionBinding binding = resolvedBindings.contributionBinding();
if (binding.requiresGeneratedInstance()) {
return ModifiableBindingType.GENERATED_INSTANCE;
}
if (binding.kind().equals(BindingKind.DELEGATE)
&& graph
.contributionBindings()
.get(getOnlyElement(binding.dependencies()).key())
.isEmpty()) {
return ModifiableBindingType.BINDS_METHOD_WITH_MISSING_DEPENDENCY;
}
if (binding.kind().equals(BindingKind.OPTIONAL) && binding.dependencies().isEmpty()) {
// only empty optional bindings can be modified
return ModifiableBindingType.OPTIONAL;
}
if (binding.isSyntheticMultibinding()) {
return ModifiableBindingType.MULTIBINDING;
}
if (binding.kind().equals(BindingKind.INJECTION)) {
return ModifiableBindingType.INJECTION;
}
} else if (!resolvableBinding(request)) {
return ModifiableBindingType.MISSING;
}
return ModifiableBindingType.NONE;
}
/**
* Returns true if the current binding graph can, and should, modify a binding by overriding a
* modifiable binding method.
*/
private boolean shouldModifyImplementation(
ModifiableBindingType modifiableBindingType, BindingRequest request) {
if (request.requestKind().isPresent()) {
switch (request.requestKind().get()) {
case FUTURE:
// Futures are always requested by a Producer.get() call, so if the binding is modifiable,
// the producer will be wrapped in a modifiable method and the future can refer to that
// method; even if the producer binding is modified, getModifiableProducer().get() will
// never need to be modified. Furthermore, because cancellation is treated by wrapped
// producers, and those producers point to the modifiable producer wrapper methods, we
// never need or want to change the access of these wrapped producers for entry
// methods
return false;
case LAZY:
case PROVIDER_OF_LAZY:
// Lazy and ProviderOfLazy are always created from a Provider, and therefore this request
// never needs to be modifiable. It will refer (via DoubleCheck.lazy() or
// ProviderOfLazy.create()) to the modifiable method and not the framework instance.
return false;
case MEMBERS_INJECTION:
case PRODUCED:
// MEMBERS_INJECTION has a completely different code path for binding expressions, and
// PRODUCED requests are only requestable in @Produces methods, which are hidden from
// generated components inside Producer factories
throw new AssertionError(request);
case INSTANCE:
case PROVIDER:
case PRODUCER:
// These may be modifiable, so run through the regular logic. They're spelled out
// explicitly so that ErrorProne will detect if a new enum value is created and missing
// from this list.
break;
}
}
ResolvedBindings resolvedBindings = graph.resolvedBindings(request);
switch (modifiableBindingType) {
case GENERATED_INSTANCE:
return !componentImplementation.isAbstract();
case MISSING:
// TODO(b/117833324): investigate beder@'s comment about having intermediate component
// ancestors satisfy missing bindings of their children with their own missing binding
// methods so that we can minimize the cases where we need to reach into doubly-nested
// descendant component implementations.
// Implement a missing binding if it is resolvable, or if we're generating a concrete
// subcomponent implementation. If a binding is still missing when the subcomponent
// implementation is concrete then it is assumed to be part of a dependency that would have
// been passively pruned when implementing the full component hierarchy.
return resolvableBinding(request) || !componentImplementation.isAbstract();
case BINDS_METHOD_WITH_MISSING_DEPENDENCY:
DependencyRequest dependency =
getOnlyElement(resolvedBindings.contributionBinding().dependencies());
return !graph.contributionBindings().get(dependency.key()).isEmpty();
case OPTIONAL:
// Only override optional binding methods if we have a non-empty binding.
return !resolvedBindings.contributionBinding().dependencies().isEmpty();
case MULTIBINDING:
// Only modify a multibinding if there are new contributions.
return !componentImplementation
.superclassContributionsMade(request)
.containsAll(resolvedBindings.contributionBinding().dependencies());
case INJECTION:
return !resolvedBindings.contributionBinding().kind().equals(BindingKind.INJECTION);
default:
throw new IllegalStateException(
String.format(
"Overriding modifiable binding method with unsupported ModifiableBindingType [%s].",
modifiableBindingType));
}
}
/**
* Returns true if the binding can be resolved by the graph for this component or any parent
* component.
*/
private boolean resolvableBinding(BindingRequest request) {
for (ModifiableBindingExpressions expressions = this;
expressions != null;
expressions = expressions.parent.orElse(null)) {
if (expressions.resolvedInThisComponent(request)) {
return true;
}
}
return false;
}
/** Returns true if the binding can be resolved by the graph for this component. */
private boolean resolvedInThisComponent(BindingRequest request) {
ResolvedBindings resolvedBindings = graph.resolvedBindings(request);
return resolvedBindings != null
&& !resolvedBindings.bindingsOwnedBy(graph.componentDescriptor()).isEmpty();
}
/**
* Wraps a modifiable binding expression in a method that can be overridden in a subclass
* implementation.
*/
BindingExpression wrapInModifiableMethodBindingExpression(
BindingRequest request,
ResolvedBindings resolvedBindings,
MethodImplementationStrategy methodImplementationStrategy,
BindingExpression wrappedBindingExpression) {
ModifiableBindingType modifiableBindingType = getModifiableBindingType(request);
checkState(modifiableBindingType.isModifiable());
return new ModifiableConcreteMethodBindingExpression(
request,
resolvedBindings,
methodImplementationStrategy,
wrappedBindingExpression,
modifiableBindingType,
componentImplementation,
newModifiableBindingWillBeFinalized(modifiableBindingType, request),
types);
}
}