dagger.internal.codegen.writing.ComponentRequestRepresentations Maven / Gradle / Ivy
Show all versions of dagger-compiler Show documentation
/*
* 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 androidx.room.compiler.processing.XTypeKt.isVoid;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.getOnlyElement;
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 static dagger.internal.codegen.xprocessing.MethodSpecs.overriding;
import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
import static dagger.internal.codegen.xprocessing.XProcessingEnvs.isPreJava8SourceVersion;
import androidx.room.compiler.processing.XMethodElement;
import androidx.room.compiler.processing.XProcessingEnv;
import androidx.room.compiler.processing.XType;
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.base.MapType;
import dagger.internal.codegen.base.OptionalType;
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.javapoet.Expression;
import dagger.internal.codegen.model.DependencyRequest;
import dagger.internal.codegen.model.RequestKind;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;
/** 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, or perhaps this use a flattened ImmutableMap, built from its
// parents? If so, maybe make RequestRepresentation.Factory create it.
private final Optional 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 Map representations = new HashMap<>();
private final XProcessingEnv processingEnv;
@Inject
ComponentRequestRepresentations(
@ParentComponent Optional parent,
BindingGraph graph,
ComponentImplementation componentImplementation,
ComponentRequirementExpressions componentRequirementExpressions,
MembersInjectionBindingRepresentation.Factory membersInjectionBindingRepresentationFactory,
ProvisionBindingRepresentation.Factory provisionBindingRepresentationFactory,
ProductionBindingRepresentation.Factory productionBindingRepresentationFactory,
XProcessingEnv processingEnv) {
this.parent = parent;
this.graph = graph;
this.componentImplementation = componentImplementation;
this.membersInjectionBindingRepresentationFactory =
membersInjectionBindingRepresentationFactory;
this.provisionBindingRepresentationFactory = provisionBindingRepresentationFactory;
this.productionBindingRepresentationFactory = productionBindingRepresentationFactory;
this.componentRequirementExpressions = checkNotNull(componentRequirementExpressions);
this.processingEnv = processingEnv;
}
/**
* 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 getCreateMethodArgumentsCodeBlocks(
ContributionBinding binding, ClassName requestingClass) {
ImmutableList.Builder 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.
*
* 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) {
XType dependencyType = dependencyRequest.key().type().xprocessing();
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(dependencyType.getRawType());
}
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 overriding(componentMethod.methodElement(), graph.componentTypeElement().getType())
.addCode(
request.isRequestKind(RequestKind.MEMBERS_INJECTION)
? getMembersInjectionComponentMethodImplementation(request, componentMethod)
: getContributionComponentMethodImplementation(request, componentMethod))
.build();
}
private CodeBlock getMembersInjectionComponentMethodImplementation(
BindingRequest request, ComponentMethodDescriptor componentMethod) {
checkArgument(request.isRequestKind(RequestKind.MEMBERS_INJECTION));
XMethodElement methodElement = componentMethod.methodElement();
RequestRepresentation requestRepresentation = getRequestRepresentation(request);
MembersInjectionBinding binding =
((MembersInjectionRequestRepresentation) requestRepresentation).binding();
if (binding.injectionSites().isEmpty()) {
// If there are no injection sites either do nothing (if the return type is void) or return
// the input instance as-is.
return isVoid(methodElement.getReturnType())
? CodeBlock.of("")
: CodeBlock.of(
"return $L;", getSimpleName(getOnlyElement(methodElement.getParameters())));
}
Expression expression = getComponentMethodExpression(requestRepresentation, componentMethod);
return isVoid(methodElement.getReturnType())
? CodeBlock.of("$L;", expression.codeBlock())
: CodeBlock.of("return $L;", expression.codeBlock());
}
private CodeBlock getContributionComponentMethodImplementation(
BindingRequest request, ComponentMethodDescriptor componentMethod) {
checkArgument(!request.isRequestKind(RequestKind.MEMBERS_INJECTION));
Expression expression =
getComponentMethodExpression(getRequestRepresentation(request), componentMethod);
return CodeBlock.of("return $L;", expression.codeBlock());
}
private Expression getComponentMethodExpression(
RequestRepresentation requestRepresentation, ComponentMethodDescriptor componentMethod) {
Expression expression =
requestRepresentation.getDependencyExpressionForComponentMethod(
componentMethod, componentImplementation);
// Cast if the expression type does not match the component method's return type. This is useful
// for types that have protected accessibility to the component but are not accessible to other
// classes, e.g. shards, that may need to handle the implementation of the binding.
XType returnType =
componentMethod.methodElement()
.asMemberOf(componentImplementation.graph().componentTypeElement().getType())
.getReturnType();
// When compiling with -source 7, javac's type inference isn't strong enough to match things
// like Optional> to Optional>.
if (isPreJava8SourceVersion(processingEnv)
&& (MapType.isMapOfProvider(returnType)
|| OptionalType.isOptionalProviderType(returnType))) {
return expression.castTo(returnType.getRawType());
}
return !isVoid(returnType) && !expression.type().isAssignableTo(returnType)
? expression.castTo(returnType)
: expression;
}
/** Returns the {@link RequestRepresentation} for the given {@link BindingRequest}. */
RequestRepresentation getRequestRepresentation(BindingRequest request) {
Optional 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((ContributionBinding) binding);
case PRODUCTION:
return productionBindingRepresentationFactory.create((ContributionBinding) binding);
}
throw new AssertionError();
}
}