Files
Android11_RK3568_Lubancat2/external/dagger2/java/dagger/internal/codegen/ModifiableBindingExpressions.java
2023-05-14 11:44:44 +00:00

527 lines
23 KiB
Java

/*
* 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 java.util.stream.Collectors.toList;
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.ComponentImplementation.MethodSpecKind;
import dagger.internal.codegen.MethodBindingExpression.MethodImplementationStrategy;
import dagger.internal.codegen.ModifiableBindingMethods.ModifiableBindingMethod;
import dagger.internal.codegen.langmodel.DaggerTypes;
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;
}
/**
* Adds {@code method} to the component implementation. If the binding for the method is
* modifiable, also registers the relevant modifiable binding information.
*/
void addPossiblyModifiableComponentMethod(
ComponentMethodDescriptor componentMethod, MethodSpec method) {
BindingRequest request = bindingRequest(componentMethod.dependencyRequest().get());
ModifiableBindingType modifiableBindingType = getModifiableBindingType(request);
if (modifiableBindingType.isModifiable()) {
componentImplementation.addModifiableComponentMethod(
modifiableBindingType,
request,
componentMethod.resolvedReturnType(types),
method,
newModifiableBindingWillBeFinalized(modifiableBindingType, request));
} else {
componentImplementation.addMethod(MethodSpecKind.COMPONENT_METHOD, method);
}
}
/**
* 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> possiblyReimplementedMethod(
ModifiableBindingMethod modifiableBindingMethod) {
checkState(componentImplementation.superclassImplementation().isPresent());
BindingRequest request = modifiableBindingMethod.request();
ModifiableBindingType newModifiableBindingType = getModifiableBindingType(request);
ModifiableBindingType oldModifiableBindingType = modifiableBindingMethod.type();
boolean modifiableBindingTypeChanged =
!newModifiableBindingType.equals(oldModifiableBindingType);
ResolvedBindings resolvedBindings = graph.resolvedBindings(request);
// Don't reimplement modifiable bindings that were perceived to be provision bindings in a
// superclass implementation but are now production bindings.
if ((modifiableBindingTypeChanged
// Optional bindings don't need the same treatment since the only transition they can
// make is empty -> present. In that case, the Producer<Optional<T>> will be overridden
// and the absentOptionalProvider() will be a dangling reference that is never attempted
// to be overridden.
|| newModifiableBindingType.equals(ModifiableBindingType.MULTIBINDING))
&& resolvedBindings != null
&& resolvedBindings.bindingType().equals(BindingType.PRODUCTION)
&& !request.canBeSatisfiedByProductionBinding()) {
return oldModifiableBindingType.hasBaseClassImplementation()
? Optional.empty()
: Optional.of(
reimplementedMethod(
modifiableBindingMethod,
newModifiableBindingType,
new PrunedConcreteMethodBindingExpression(),
componentImplementation.isAbstract()));
}
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, request)) {
boolean markMethodFinal =
knownModifiableBindingWillBeFinalized(modifiableBindingMethod)
// no need to mark the method final if the component implementation will be final
&& componentImplementation.isAbstract();
return Optional.of(
reimplementedMethod(
modifiableBindingMethod,
newModifiableBindingType,
bindingExpressions.getBindingExpression(request),
markMethodFinal));
}
return Optional.empty();
}
/**
* Returns a new {@link ModifiableBindingMethod} that overrides {@code supertypeMethod} and is
* implemented with {@code bindingExpression}.
*/
private ModifiableBindingMethod reimplementedMethod(
ModifiableBindingMethod supertypeMethod,
ModifiableBindingType newModifiableBindingType,
BindingExpression bindingExpression,
boolean markMethodFinal) {
MethodSpec baseMethod = supertypeMethod.methodSpec();
return supertypeMethod.reimplement(
newModifiableBindingType,
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(
bindingExpression.getModifiableBindingMethodImplementation(
supertypeMethod, componentImplementation, types))
.build(),
markMethodFinal);
}
/**
* 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().isSubcomponent()) {
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) {
ResolvedBindings resolvedBindings = graph.resolvedBindings(request);
if (request.requestKind().isPresent()) {
switch (request.requestKind().get()) {
case FUTURE:
// Futures backed by production bindings 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
//
// Futures backed by provision bindings are inlined and contain no wrapping producer, so
// if the binding is modifiable and is resolved as a provision binding in a superclass
// but later resolved as a production binding, we can't take the same shortcut as before.
Optional<ComponentImplementation> superclassImplementation =
componentImplementation.superclassImplementation();
if (superclassImplementation.isPresent()) {
if (superclassImplementation.get().isDeserializedImplementation()) {
// TODO(b/117833324): consider serializing the binding type so that we don't need to
// branch here. Or, instead, consider removing this optimization entirely if there
// aren't that many FUTURE entry point methods to justify the extra code.
break;
} else {
return bindingTypeChanged(request, resolvedBindings);
}
}
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;
}
}
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().stream()
.map(DependencyRequest::key)
.collect(toList()));
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 {@code true} if the {@link BindingType} for {@code request} is not the same in this
* implementation and it's superclass implementation.
*/
private boolean bindingTypeChanged(BindingRequest request, ResolvedBindings resolvedBindings) {
BindingGraph superclassGraph =
componentImplementation.superclassImplementation().get().graph();
ResolvedBindings superclassBindings = superclassGraph.resolvedBindings(request);
return superclassBindings != null
&& resolvedBindings != null
&& !superclassBindings.bindingType().equals(resolvedBindings.bindingType());
}
/**
* 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);
}
}