blob: 324cef67549ba53efe6ade9128833636fc340039 [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.writing;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
import static dagger.internal.codegen.binding.BindingRequest.bindingRequest;
import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock;
import static dagger.internal.codegen.langmodel.Accessibility.isRawTypeAccessible;
import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom;
import com.google.common.collect.ImmutableList;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.MethodSpec;
import dagger.internal.codegen.binding.Binding;
import dagger.internal.codegen.binding.BindingGraph;
import dagger.internal.codegen.binding.BindingRequest;
import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor;
import dagger.internal.codegen.binding.ComponentRequirement;
import dagger.internal.codegen.binding.ContributionBinding;
import dagger.internal.codegen.binding.FrameworkType;
import dagger.internal.codegen.binding.FrameworkTypeMapper;
import dagger.internal.codegen.binding.MembersInjectionBinding;
import dagger.internal.codegen.binding.ProductionBinding;
import dagger.internal.codegen.binding.ProvisionBinding;
import dagger.internal.codegen.javapoet.Expression;
import dagger.internal.codegen.langmodel.DaggerTypes;
import dagger.internal.codegen.xprocessing.MethodSpecs;
import dagger.spi.model.DependencyRequest;
import dagger.spi.model.RequestKind;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;
import javax.lang.model.type.TypeMirror;
/** A central repository of code expressions used to access any binding available to a component. */
@PerComponentImplementation
public final class ComponentRequestRepresentations {
// TODO(dpb,ronshapiro): refactor this and ComponentRequirementExpressions into a
// HierarchicalComponentMap<K, V>, or perhaps this use a flattened ImmutableMap, built from its
// parents? If so, maybe make RequestRepresentation.Factory create it.
private final Optional<ComponentRequestRepresentations> parent;
private final BindingGraph graph;
private final ComponentImplementation componentImplementation;
private final ComponentRequirementExpressions componentRequirementExpressions;
private final MembersInjectionBindingRepresentation.Factory
membersInjectionBindingRepresentationFactory;
private final ProvisionBindingRepresentation.Factory provisionBindingRepresentationFactory;
private final ProductionBindingRepresentation.Factory productionBindingRepresentationFactory;
private final ExperimentalSwitchingProviderDependencyRepresentation.Factory
experimentalSwitchingProviderDependencyRepresentationFactory;
private final DaggerTypes types;
private final Map<Binding, BindingRepresentation> representations = new HashMap<>();
private final Map<Binding, ExperimentalSwitchingProviderDependencyRepresentation>
experimentalSwitchingProviderDependencyRepresentations = new HashMap<>();
@Inject
ComponentRequestRepresentations(
@ParentComponent Optional<ComponentRequestRepresentations> parent,
BindingGraph graph,
ComponentImplementation componentImplementation,
ComponentRequirementExpressions componentRequirementExpressions,
MembersInjectionBindingRepresentation.Factory membersInjectionBindingRepresentationFactory,
ProvisionBindingRepresentation.Factory provisionBindingRepresentationFactory,
ProductionBindingRepresentation.Factory productionBindingRepresentationFactory,
ExperimentalSwitchingProviderDependencyRepresentation.Factory
experimentalSwitchingProviderDependencyRepresentationFactory,
DaggerTypes types) {
this.parent = parent;
this.graph = graph;
this.componentImplementation = componentImplementation;
this.membersInjectionBindingRepresentationFactory =
membersInjectionBindingRepresentationFactory;
this.provisionBindingRepresentationFactory = provisionBindingRepresentationFactory;
this.productionBindingRepresentationFactory = productionBindingRepresentationFactory;
this.experimentalSwitchingProviderDependencyRepresentationFactory =
experimentalSwitchingProviderDependencyRepresentationFactory;
this.componentRequirementExpressions = checkNotNull(componentRequirementExpressions);
this.types = types;
}
/**
* Returns an expression that evaluates to the value of a binding request for a binding owned by
* this component or an ancestor.
*
* @param requestingClass the class that will contain the expression
* @throws IllegalStateException if there is no binding expression that satisfies the request
*/
public Expression getDependencyExpression(BindingRequest request, ClassName requestingClass) {
return getRequestRepresentation(request).getDependencyExpression(requestingClass);
}
/**
* Equivalent to {@link #getDependencyExpression(BindingRequest, ClassName)} that is used only
* when the request is for implementation of a component method.
*
* @throws IllegalStateException if there is no binding expression that satisfies the request
*/
Expression getDependencyExpressionForComponentMethod(
BindingRequest request,
ComponentMethodDescriptor componentMethod,
ComponentImplementation componentImplementation) {
return getRequestRepresentation(request)
.getDependencyExpressionForComponentMethod(componentMethod, componentImplementation);
}
/**
* Returns the {@link CodeBlock} for the method arguments used with the factory {@code create()}
* method for the given {@link ContributionBinding binding}.
*/
CodeBlock getCreateMethodArgumentsCodeBlock(
ContributionBinding binding, ClassName requestingClass) {
return makeParametersCodeBlock(getCreateMethodArgumentsCodeBlocks(binding, requestingClass));
}
private ImmutableList<CodeBlock> getCreateMethodArgumentsCodeBlocks(
ContributionBinding binding, ClassName requestingClass) {
ImmutableList.Builder<CodeBlock> arguments = ImmutableList.builder();
if (binding.requiresModuleInstance()) {
arguments.add(
componentRequirementExpressions.getExpressionDuringInitialization(
ComponentRequirement.forModule(binding.contributingModule().get().getType()),
requestingClass));
}
binding.dependencies().stream()
.map(dependency -> frameworkRequest(binding, dependency))
.map(request -> getDependencyExpression(request, requestingClass))
.map(Expression::codeBlock)
.forEach(arguments::add);
return arguments.build();
}
private static BindingRequest frameworkRequest(
ContributionBinding binding, DependencyRequest dependency) {
// TODO(bcorso): See if we can get rid of FrameworkTypeMatcher
FrameworkType frameworkType =
FrameworkTypeMapper.forBindingType(binding.bindingType())
.getFrameworkType(dependency.kind());
return BindingRequest.bindingRequest(dependency.key(), frameworkType);
}
/**
* Returns an expression that evaluates to the value of a dependency request, for passing to a
* binding method, an {@code @Inject}-annotated constructor or member, or a proxy for one.
*
* <p>If the method is a generated static {@link InjectionMethods injection method}, each
* parameter will be {@link Object} if the dependency's raw type is inaccessible. If that is the
* case for this dependency, the returned expression will use a cast to evaluate to the raw type.
*
* @param requestingClass the class that will contain the expression
*/
Expression getDependencyArgumentExpression(
DependencyRequest dependencyRequest, ClassName requestingClass) {
TypeMirror dependencyType = dependencyRequest.key().type().java();
BindingRequest bindingRequest = bindingRequest(dependencyRequest);
Expression dependencyExpression = getDependencyExpression(bindingRequest, requestingClass);
if (dependencyRequest.kind().equals(RequestKind.INSTANCE)
&& !isTypeAccessibleFrom(dependencyType, requestingClass.packageName())
&& isRawTypeAccessible(dependencyType, requestingClass.packageName())) {
return dependencyExpression.castTo(types.erasure(dependencyType));
}
return dependencyExpression;
}
/** Returns the implementation of a component method. */
public MethodSpec getComponentMethod(ComponentMethodDescriptor componentMethod) {
checkArgument(componentMethod.dependencyRequest().isPresent());
BindingRequest request = bindingRequest(componentMethod.dependencyRequest().get());
return MethodSpecs.overriding(
componentMethod.methodElement(), graph.componentTypeElement().getType())
.addCode(
getRequestRepresentation(request)
.getComponentMethodImplementation(componentMethod, componentImplementation))
.build();
}
/** Returns the {@link RequestRepresentation} for the given {@link BindingRequest}. */
RequestRepresentation getRequestRepresentation(BindingRequest request) {
Optional<Binding> localBinding =
request.isRequestKind(RequestKind.MEMBERS_INJECTION)
? graph.localMembersInjectionBinding(request.key())
: graph.localContributionBinding(request.key());
if (localBinding.isPresent()) {
return getBindingRepresentation(localBinding.get()).getRequestRepresentation(request);
}
checkArgument(parent.isPresent(), "no expression found for %s", request);
return parent.get().getRequestRepresentation(request);
}
private BindingRepresentation getBindingRepresentation(Binding binding) {
return reentrantComputeIfAbsent(
representations, binding, this::getBindingRepresentationUncached);
}
private BindingRepresentation getBindingRepresentationUncached(Binding binding) {
switch (binding.bindingType()) {
case MEMBERS_INJECTION:
return membersInjectionBindingRepresentationFactory.create(
(MembersInjectionBinding) binding);
case PROVISION:
return provisionBindingRepresentationFactory.create((ProvisionBinding) binding);
case PRODUCTION:
return productionBindingRepresentationFactory.create((ProductionBinding) binding);
}
throw new AssertionError();
}
/**
* Returns an {@link ExperimentalSwitchingProviderDependencyRepresentation} for the requested
* binding to satisfy dependency requests on it from experimental switching providers. Cannot be
* used for Members Injection requests.
*/
ExperimentalSwitchingProviderDependencyRepresentation
getExperimentalSwitchingProviderDependencyRepresentation(BindingRequest request) {
checkState(
componentImplementation.compilerMode().isExperimentalMergedMode(),
"Compiler mode should be experimentalMergedMode!");
Optional<Binding> localBinding = graph.localContributionBinding(request.key());
if (localBinding.isPresent()) {
return reentrantComputeIfAbsent(
experimentalSwitchingProviderDependencyRepresentations,
localBinding.get(),
binding ->
experimentalSwitchingProviderDependencyRepresentationFactory.create(
(ProvisionBinding) binding));
}
checkArgument(parent.isPresent(), "no expression found for %s", request);
return parent.get().getExperimentalSwitchingProviderDependencyRepresentation(request);
}
}