blob: e1af04f149654047b50bcbdefb697f2f29e2f401 [file] [log] [blame]
/*
* Copyright (C) 2021 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.writing;
import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
import static dagger.internal.codegen.binding.BindingRequest.bindingRequest;
import static dagger.internal.codegen.writing.BindingRepresentations.scope;
import static dagger.internal.codegen.writing.DelegateRequestRepresentation.isBindsScopeStrongerThanDependencyScope;
import static dagger.spi.model.BindingKind.DELEGATE;
import static dagger.spi.model.BindingKind.MULTIBOUND_MAP;
import static dagger.spi.model.BindingKind.MULTIBOUND_SET;
import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
import dagger.internal.codegen.binding.BindingGraph;
import dagger.internal.codegen.binding.BindingRequest;
import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor;
import dagger.internal.codegen.binding.FrameworkType;
import dagger.internal.codegen.binding.ProvisionBinding;
import dagger.internal.codegen.compileroption.CompilerOptions;
import dagger.internal.codegen.langmodel.DaggerTypes;
import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression;
import dagger.spi.model.BindingKind;
import dagger.spi.model.RequestKind;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.lang.model.element.TypeElement;
/**
* A binding representation that wraps code generation methods that satisfy all kinds of request for
* that binding.
*/
final class ProvisionBindingRepresentation implements BindingRepresentation {
private final BindingGraph graph;
private final boolean isFastInit;
private final ProvisionBinding binding;
private final ComponentImplementation componentImplementation;
private final ComponentMethodRequestRepresentation.Factory
componentMethodRequestRepresentationFactory;
private final DelegateRequestRepresentation.Factory delegateRequestRepresentationFactory;
private final DerivedFromFrameworkInstanceRequestRepresentation.Factory
derivedFromFrameworkInstanceRequestRepresentationFactory;
private final ImmediateFutureRequestRepresentation.Factory
immediateFutureRequestRepresentationFactory;
private final PrivateMethodRequestRepresentation.Factory
privateMethodRequestRepresentationFactory;
private final AssistedPrivateMethodRequestRepresentation.Factory
assistedPrivateMethodRequestRepresentationFactory;
private final ProducerNodeInstanceRequestRepresentation.Factory
producerNodeInstanceRequestRepresentationFactory;
private final ProviderInstanceRequestRepresentation.Factory
providerInstanceRequestRepresentationFactory;
private final UnscopedDirectInstanceRequestRepresentationFactory
unscopedDirectInstanceRequestRepresentationFactory;
private final ProducerFromProviderCreationExpression.Factory
producerFromProviderCreationExpressionFactory;
private final UnscopedFrameworkInstanceCreationExpressionFactory
unscopedFrameworkInstanceCreationExpressionFactory;
private final SwitchingProviders switchingProviders;
private final Map<BindingRequest, RequestRepresentation> requestRepresentations = new HashMap<>();
@AssistedInject
ProvisionBindingRepresentation(
@Assisted ProvisionBinding binding,
SwitchingProviders switchingProviders,
BindingGraph graph,
ComponentImplementation componentImplementation,
ComponentMethodRequestRepresentation.Factory componentMethodRequestRepresentationFactory,
DelegateRequestRepresentation.Factory delegateRequestRepresentationFactory,
DerivedFromFrameworkInstanceRequestRepresentation.Factory
derivedFromFrameworkInstanceRequestRepresentationFactory,
ImmediateFutureRequestRepresentation.Factory immediateFutureRequestRepresentationFactory,
PrivateMethodRequestRepresentation.Factory privateMethodRequestRepresentationFactory,
AssistedPrivateMethodRequestRepresentation.Factory
assistedPrivateMethodRequestRepresentationFactory,
ProducerNodeInstanceRequestRepresentation.Factory
producerNodeInstanceRequestRepresentationFactory,
ProviderInstanceRequestRepresentation.Factory providerInstanceRequestRepresentationFactory,
UnscopedDirectInstanceRequestRepresentationFactory
unscopedDirectInstanceRequestRepresentationFactory,
ProducerFromProviderCreationExpression.Factory producerFromProviderCreationExpressionFactory,
UnscopedFrameworkInstanceCreationExpressionFactory
unscopedFrameworkInstanceCreationExpressionFactory,
CompilerOptions compilerOptions,
DaggerTypes types) {
this.binding = binding;
this.switchingProviders = switchingProviders;
this.graph = graph;
this.componentImplementation = componentImplementation;
this.componentMethodRequestRepresentationFactory = componentMethodRequestRepresentationFactory;
this.delegateRequestRepresentationFactory = delegateRequestRepresentationFactory;
this.derivedFromFrameworkInstanceRequestRepresentationFactory =
derivedFromFrameworkInstanceRequestRepresentationFactory;
this.immediateFutureRequestRepresentationFactory = immediateFutureRequestRepresentationFactory;
this.privateMethodRequestRepresentationFactory = privateMethodRequestRepresentationFactory;
this.producerNodeInstanceRequestRepresentationFactory =
producerNodeInstanceRequestRepresentationFactory;
this.providerInstanceRequestRepresentationFactory =
providerInstanceRequestRepresentationFactory;
this.unscopedDirectInstanceRequestRepresentationFactory =
unscopedDirectInstanceRequestRepresentationFactory;
this.producerFromProviderCreationExpressionFactory =
producerFromProviderCreationExpressionFactory;
this.unscopedFrameworkInstanceCreationExpressionFactory =
unscopedFrameworkInstanceCreationExpressionFactory;
this.assistedPrivateMethodRequestRepresentationFactory =
assistedPrivateMethodRequestRepresentationFactory;
TypeElement rootComponent =
componentImplementation.rootComponentImplementation().componentDescriptor().typeElement();
this.isFastInit = compilerOptions.fastInit(rootComponent);
}
@Override
public RequestRepresentation getRequestRepresentation(BindingRequest request) {
return reentrantComputeIfAbsent(
requestRepresentations, request, this::getRequestRepresentationUncached);
}
private RequestRepresentation getRequestRepresentationUncached(BindingRequest request) {
switch (request.requestKind()) {
case INSTANCE:
return instanceRequestRepresentation();
case PROVIDER:
return providerRequestRepresentation();
case LAZY:
case PRODUCED:
case PROVIDER_OF_LAZY:
return derivedFromFrameworkInstanceRequestRepresentationFactory.create(
request, FrameworkType.PROVIDER);
case PRODUCER:
return producerFromProviderRequestRepresentation();
case FUTURE:
return immediateFutureRequestRepresentationFactory.create(binding.key());
case MEMBERS_INJECTION:
throw new IllegalArgumentException();
}
throw new AssertionError();
}
/**
* Returns a binding expression that uses a {@link javax.inject.Provider} for provision bindings.
*/
private RequestRepresentation frameworkInstanceRequestRepresentation() {
// In default mode, we always use the static factory creation strategy. In fastInit mode, we
// prefer to use a SwitchingProvider instead of static factories in order to reduce class
// loading; however, we allow static factories that can reused across multiple bindings, e.g.
// {@code MapFactory} or {@code SetFactory}.
// TODO(bcorso): Consider merging the static factory creation logic into CreationExpressions?
Optional<MemberSelect> staticMethod = staticFactoryCreation();
if (staticMethod.isPresent()) {
return providerInstanceRequestRepresentationFactory.create(binding, staticMethod::get);
}
FrameworkInstanceCreationExpression frameworkInstanceCreationExpression =
unscopedFrameworkInstanceCreationExpressionFactory.create(binding);
if (useSwitchingProvider()) {
// First try to get the instance expression via getRequestRepresentation(). However, if that
// expression is a DerivedFromFrameworkInstanceRequestRepresentation (e.g. fooProvider.get()),
// then we can't use it to create an instance within the SwitchingProvider since that would
// cause a cycle. In such cases, we try to use the unscopedDirectInstanceRequestRepresentation
// directly, or else fall back to default mode.
BindingRequest instanceRequest = bindingRequest(binding.key(), RequestKind.INSTANCE);
if (usesDirectInstanceExpression()) {
frameworkInstanceCreationExpression =
switchingProviders.newFrameworkInstanceCreationExpression(
binding, getRequestRepresentation(instanceRequest));
} else {
RequestRepresentation unscopedInstanceExpression =
unscopedDirectInstanceRequestRepresentationFactory.create(binding);
frameworkInstanceCreationExpression =
switchingProviders.newFrameworkInstanceCreationExpression(
binding,
requiresMethodEncapsulation()
? privateMethodRequestRepresentationFactory.create(
instanceRequest, binding, unscopedInstanceExpression)
: unscopedInstanceExpression);
}
}
return providerInstanceRequestRepresentationFactory.create(
binding,
new FrameworkFieldInitializer(
componentImplementation,
binding,
binding.scope().isPresent()
? scope(binding, frameworkInstanceCreationExpression)
: frameworkInstanceCreationExpression));
}
/**
* If {@code resolvedBindings} is an unscoped provision binding with no factory arguments, then we
* don't need a field to hold its factory. In that case, this method returns the static member
* select that returns the factory.
*/
// TODO(wanyingd): no-op members injector is currently handled in
// `MembersInjectorProviderCreationExpression`, we should inline the logic here so we won't create
// an extra field for it.
private Optional<MemberSelect> staticFactoryCreation() {
if (binding.dependencies().isEmpty() && !binding.scope().isPresent()) {
switch (binding.kind()) {
case MULTIBOUND_MAP:
return Optional.of(StaticMemberSelects.emptyMapFactory(binding));
case MULTIBOUND_SET:
return Optional.of(StaticMemberSelects.emptySetFactory(binding));
case PROVISION:
if (!isFastInit && !binding.requiresModuleInstance()) {
return Optional.of(StaticMemberSelects.factoryCreateNoArgumentMethod(binding));
}
break;
case INJECTION:
if (!isFastInit) {
return Optional.of(StaticMemberSelects.factoryCreateNoArgumentMethod(binding));
}
break;
default:
return Optional.empty();
}
}
return Optional.empty();
}
/**
* Returns a binding expression for {@link RequestKind#PROVIDER} requests.
*
* <p>{@code @Binds} bindings that don't {@linkplain #needsCaching() need to be cached} can use a
* {@link DelegateRequestRepresentation}.
*
* <p>Otherwise, return a {@link FrameworkInstanceRequestRepresentation}.
*/
private RequestRepresentation providerRequestRepresentation() {
if (binding.kind().equals(DELEGATE) && !needsCaching()) {
return delegateRequestRepresentationFactory.create(binding, RequestKind.PROVIDER);
}
return frameworkInstanceRequestRepresentation();
}
/**
* Returns a binding expression that uses a {@link dagger.producers.Producer} field for a
* provision binding.
*/
private FrameworkInstanceRequestRepresentation producerFromProviderRequestRepresentation() {
return producerNodeInstanceRequestRepresentationFactory.create(
binding,
new FrameworkFieldInitializer(
componentImplementation,
binding,
producerFromProviderCreationExpressionFactory.create(binding)));
}
/** Returns a binding expression for {@link RequestKind#INSTANCE} requests. */
private RequestRepresentation instanceRequestRepresentation() {
return usesDirectInstanceExpression()
? directInstanceExpression()
: derivedFromFrameworkInstanceRequestRepresentationFactory.create(
bindingRequest(binding.key(), RequestKind.INSTANCE), FrameworkType.PROVIDER);
}
private boolean usesDirectInstanceExpression() {
switch (binding.kind()) {
case MEMBERS_INJECTOR:
// Currently, we always use a framework instance for MembersInjectors, e.g.
// InstanceFactory.create(Foo_MembersInjector.create(...)).
// TODO(b/199889259): Consider optimizing this for fastInit mode.
return false;
case ASSISTED_INJECTION:
case ASSISTED_FACTORY:
// We choose not to use a direct expression for assisted injection/factory in default mode
// because they technically act more similar to a Provider than an instance, so we cache
// them using a field in the component similar to Provider requests. This should also be the
// case in FastInit, but it hasn't been implemented yet. We also don't need to check for
// caching since assisted bindings can't be scoped.
return isFastInit;
default:
// We don't need to use Provider#get() if there's no caching, so use a direct instance.
// TODO(bcorso): This can be optimized in cases where we know a Provider field already
// exists, in which case even if it's not scoped we might as well call Provider#get().
return !needsCaching();
}
}
private boolean useSwitchingProvider() {
if (!isFastInit) {
return false;
}
switch (binding.kind()) {
case BOUND_INSTANCE:
case COMPONENT:
case COMPONENT_DEPENDENCY:
case DELEGATE:
case MEMBERS_INJECTOR: // TODO(b/199889259): Consider optimizing this for fastInit mode.
// These binding kinds avoid SwitchingProvider when the backing instance already exists,
// e.g. a component provider can use FactoryInstance.create(this).
return false;
case MULTIBOUND_SET:
case MULTIBOUND_MAP:
case OPTIONAL:
// These binding kinds avoid SwitchingProvider when their are no dependencies,
// e.g. a multibound set with no dependency can use a singleton, SetFactory.empty().
return !binding.dependencies().isEmpty();
case INJECTION:
case PROVISION:
case ASSISTED_INJECTION:
case ASSISTED_FACTORY:
case COMPONENT_PROVISION:
case SUBCOMPONENT_CREATOR:
case PRODUCTION:
case COMPONENT_PRODUCTION:
case MEMBERS_INJECTION:
return true;
}
throw new AssertionError(String.format("No such binding kind: %s", binding.kind()));
}
private boolean requiresMethodEncapsulation() {
switch (binding.kind()) {
case COMPONENT:
case COMPONENT_PROVISION:
case SUBCOMPONENT_CREATOR:
case COMPONENT_DEPENDENCY:
case MULTIBOUND_SET:
case MULTIBOUND_MAP:
case BOUND_INSTANCE:
case ASSISTED_FACTORY:
case ASSISTED_INJECTION:
case INJECTION:
case PROVISION:
// These binding kinds satify a binding request with a component method or a private
// method when the requested binding has dependencies. The method will wrap the logic of
// creating the binding instance. Without the encapsulation, we might see many level of
// nested instance creation code in a single statement to satisfy all dependencies of a
// binding request.
return !binding.dependencies().isEmpty();
case MEMBERS_INJECTOR:
case PRODUCTION:
case COMPONENT_PRODUCTION:
case OPTIONAL:
case DELEGATE:
case MEMBERS_INJECTION:
return false;
}
throw new AssertionError(String.format("No such binding kind: %s", binding.kind()));
}
private RequestRepresentation directInstanceExpression() {
RequestRepresentation directInstanceExpression =
unscopedDirectInstanceRequestRepresentationFactory.create(binding);
if (binding.kind() == BindingKind.ASSISTED_INJECTION) {
BindingRequest request = bindingRequest(binding.key(), RequestKind.INSTANCE);
return assistedPrivateMethodRequestRepresentationFactory.create(
request, binding, directInstanceExpression);
}
return requiresMethodEncapsulation()
? wrapInMethod(RequestKind.INSTANCE, directInstanceExpression)
: directInstanceExpression;
}
/**
* Returns a binding expression that uses a given one as the body of a method that users call. If
* a component provision method matches it, it will be the method implemented. If it does not
* match a component provision method and the binding is modifiable, then a new public modifiable
* binding method will be written. If the binding doesn't match a component method and is not
* modifiable, then a new private method will be written.
*/
RequestRepresentation wrapInMethod(
RequestKind requestKind, RequestRepresentation bindingExpression) {
// If we've already wrapped the expression, then use the delegate.
if (bindingExpression instanceof MethodRequestRepresentation) {
return bindingExpression;
}
BindingRequest request = bindingRequest(binding.key(), requestKind);
Optional<ComponentMethodDescriptor> matchingComponentMethod =
graph.componentDescriptor().firstMatchingComponentMethod(request);
ShardImplementation shardImplementation = componentImplementation.shardImplementation(binding);
// Consider the case of a request from a component method like:
//
// DaggerMyComponent extends MyComponent {
// @Overrides
// Foo getFoo() {
// <FOO_BINDING_REQUEST>
// }
// }
//
// Normally, in this case we would return a ComponentMethodRequestRepresentation rather than a
// PrivateMethodRequestRepresentation so that #getFoo() can inline the implementation rather
// than
// create an unnecessary private method and return that. However, with sharding we don't want to
// inline the implementation because that would defeat some of the class pool savings if those
// fields had to communicate across shards. Thus, when a key belongs to a separate shard use a
// PrivateMethodRequestRepresentation and put the private method in the shard.
if (matchingComponentMethod.isPresent() && shardImplementation.isComponentShard()) {
ComponentMethodDescriptor componentMethod = matchingComponentMethod.get();
return componentMethodRequestRepresentationFactory.create(bindingExpression, componentMethod);
} else {
return privateMethodRequestRepresentationFactory.create(request, binding, bindingExpression);
}
}
/**
* Returns {@code true} if the component needs to make sure the provided value is cached.
*
* <p>The component needs to cache the value for scoped bindings except for {@code @Binds}
* bindings whose scope is no stronger than their delegate's.
*/
private boolean needsCaching() {
if (!binding.scope().isPresent()) {
return false;
}
if (binding.kind().equals(DELEGATE)) {
return isBindsScopeStrongerThanDependencyScope(binding, graph);
}
return true;
}
@AssistedFactory
static interface Factory {
ProvisionBindingRepresentation create(ProvisionBinding binding);
}
}