blob: 04cb80f942ec6cf0028677f47c019abd2879d525 [file] [log] [blame]
/*
* Copyright (C) 2015 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.componentgenerator;
import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
import static com.google.auto.common.MoreTypes.asDeclared;
import static com.google.common.base.Preconditions.checkState;
import static com.squareup.javapoet.MethodSpec.constructorBuilder;
import static com.squareup.javapoet.MethodSpec.methodBuilder;
import static dagger.internal.codegen.binding.BindingRequest.bindingRequest;
import static dagger.internal.codegen.binding.ComponentCreatorKind.BUILDER;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED;
import static dagger.internal.codegen.javapoet.CodeBlocks.parameterNames;
import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.BUILDER_METHOD;
import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.CANCELLATION_LISTENER_METHOD;
import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.COMPONENT_METHOD;
import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.CONSTRUCTOR;
import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.INITIALIZE_METHOD;
import static dagger.internal.codegen.writing.ComponentImplementation.TypeSpecKind.COMPONENT_CREATOR;
import static dagger.internal.codegen.writing.ComponentImplementation.TypeSpecKind.SUBCOMPONENT;
import static dagger.producers.CancellationPolicy.Propagation.PROPAGATE;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
import com.google.auto.common.MoreTypes;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeSpec;
import dagger.internal.Preconditions;
import dagger.internal.codegen.binding.BindingGraph;
import dagger.internal.codegen.binding.ComponentCreatorDescriptor;
import dagger.internal.codegen.binding.ComponentCreatorKind;
import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor;
import dagger.internal.codegen.binding.ComponentRequirement;
import dagger.internal.codegen.binding.FrameworkType;
import dagger.internal.codegen.javapoet.AnnotationSpecs;
import dagger.internal.codegen.javapoet.CodeBlocks;
import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
import dagger.internal.codegen.langmodel.DaggerElements;
import dagger.internal.codegen.langmodel.DaggerTypes;
import dagger.internal.codegen.writing.ComponentBindingExpressions;
import dagger.internal.codegen.writing.ComponentCreatorImplementation;
import dagger.internal.codegen.writing.ComponentImplementation;
import dagger.internal.codegen.writing.ComponentRequirementExpressions;
import dagger.internal.codegen.writing.ParentComponent;
import dagger.model.Key;
import dagger.producers.internal.CancellationListener;
import dagger.producers.internal.Producers;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import javax.inject.Inject;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.DeclaredType;
/** A builder of {@link ComponentImplementation}s. */
// This only needs to be public because it's referenced in an entry point.
public final class ComponentImplementationBuilder {
private static final String MAY_INTERRUPT_IF_RUNNING = "mayInterruptIfRunning";
/**
* How many statements per {@code initialize()} or {@code onProducerFutureCancelled()} method
* before they get partitioned.
*/
private static final int STATEMENTS_PER_METHOD = 100;
private static final String CANCELLATION_LISTENER_METHOD_NAME = "onProducerFutureCancelled";
private final Optional<ComponentImplementationBuilder> parent;
private final BindingGraph graph;
private final ComponentBindingExpressions bindingExpressions;
private final ComponentRequirementExpressions componentRequirementExpressions;
private final ComponentImplementation componentImplementation;
private final ComponentCreatorImplementationFactory componentCreatorImplementationFactory;
private final TopLevelImplementationComponent topLevelImplementationComponent;
private final DaggerTypes types;
private final DaggerElements elements;
private final KotlinMetadataUtil metadataUtil;
private boolean done;
@Inject
ComponentImplementationBuilder(
@ParentComponent Optional<ComponentImplementationBuilder> parent,
BindingGraph graph,
ComponentBindingExpressions bindingExpressions,
ComponentRequirementExpressions componentRequirementExpressions,
ComponentImplementation componentImplementation,
ComponentCreatorImplementationFactory componentCreatorImplementationFactory,
TopLevelImplementationComponent topLevelImplementationComponent,
DaggerTypes types,
DaggerElements elements,
KotlinMetadataUtil metadataUtil) {
this.parent = parent;
this.graph = graph;
this.bindingExpressions = bindingExpressions;
this.componentRequirementExpressions = componentRequirementExpressions;
this.componentImplementation = componentImplementation;
this.componentCreatorImplementationFactory = componentCreatorImplementationFactory;
this.types = types;
this.elements = elements;
this.topLevelImplementationComponent = topLevelImplementationComponent;
this.metadataUtil = metadataUtil;
}
/**
* Returns a {@link ComponentImplementation} for this component. This is only intended to be
* called once (and will throw on successive invocations). If the component must be regenerated,
* use a new instance.
*/
ComponentImplementation build() {
checkState(
!done,
"ComponentImplementationBuilder has already built the ComponentImplementation for [%s].",
componentImplementation.name());
setSupertype();
componentCreatorImplementationFactory.create()
.map(ComponentCreatorImplementation::spec)
.ifPresent(this::addCreatorClass);
getLocalAndInheritedMethods(graph.componentTypeElement(), types, elements)
.forEach(method -> componentImplementation.claimMethodName(method.getSimpleName()));
addFactoryMethods();
addInterfaceMethods();
addChildComponents();
addConstructorAndInitializationMethods();
if (graph.componentDescriptor().isProduction()) {
addCancellationListenerImplementation();
}
done = true;
return componentImplementation;
}
/** Set the supertype for this generated class. */
private void setSupertype() {
componentImplementation.addSupertype(graph.componentTypeElement());
}
private void addCreatorClass(TypeSpec creator) {
if (parent.isPresent()) {
// In an inner implementation of a subcomponent the creator is a peer class.
parent.get().componentImplementation.addType(SUBCOMPONENT, creator);
} else {
componentImplementation.addType(COMPONENT_CREATOR, creator);
}
}
private void addFactoryMethods() {
if (parent.isPresent()) {
graph.factoryMethod().ifPresent(this::createSubcomponentFactoryMethod);
} else {
createRootComponentFactoryMethod();
}
}
private void addInterfaceMethods() {
// Each component method may have been declared by several supertypes. We want to implement
// only one method for each distinct signature.
ImmutableListMultimap<MethodSignature, ComponentMethodDescriptor> componentMethodsBySignature =
Multimaps.index(graph.componentDescriptor().entryPointMethods(), this::getMethodSignature);
for (List<ComponentMethodDescriptor> methodsWithSameSignature :
Multimaps.asMap(componentMethodsBySignature).values()) {
ComponentMethodDescriptor anyOneMethod = methodsWithSameSignature.stream().findAny().get();
MethodSpec methodSpec = bindingExpressions.getComponentMethod(anyOneMethod);
componentImplementation.addMethod(COMPONENT_METHOD, methodSpec);
}
}
private void addCancellationListenerImplementation() {
componentImplementation.addSupertype(elements.getTypeElement(CancellationListener.class));
componentImplementation.claimMethodName(CANCELLATION_LISTENER_METHOD_NAME);
ImmutableList<ParameterSpec> parameters =
ImmutableList.of(ParameterSpec.builder(boolean.class, MAY_INTERRUPT_IF_RUNNING).build());
MethodSpec.Builder methodBuilder =
methodBuilder(CANCELLATION_LISTENER_METHOD_NAME)
.addModifiers(PUBLIC)
.addAnnotation(Override.class)
.addParameters(parameters);
ImmutableList<CodeBlock> cancellationStatements = cancellationStatements();
if (cancellationStatements.size() < STATEMENTS_PER_METHOD) {
methodBuilder.addCode(CodeBlocks.concat(cancellationStatements)).build();
} else {
ImmutableList<MethodSpec> cancelProducersMethods =
createPartitionedMethods(
"cancelProducers",
parameters,
cancellationStatements,
methodName -> methodBuilder(methodName).addModifiers(PRIVATE));
for (MethodSpec cancelProducersMethod : cancelProducersMethods) {
methodBuilder.addStatement("$N($L)", cancelProducersMethod, MAY_INTERRUPT_IF_RUNNING);
componentImplementation.addMethod(CANCELLATION_LISTENER_METHOD, cancelProducersMethod);
}
}
cancelParentStatement().ifPresent(methodBuilder::addCode);
componentImplementation.addMethod(CANCELLATION_LISTENER_METHOD, methodBuilder.build());
}
private ImmutableList<CodeBlock> cancellationStatements() {
// Reversing should order cancellations starting from entry points and going down to leaves
// rather than the other way around. This shouldn't really matter but seems *slightly*
// preferable because:
// When a future that another future depends on is cancelled, that cancellation will propagate
// up the future graph toward the entry point. Cancelling in reverse order should ensure that
// everything that depends on a particular node has already been cancelled when that node is
// cancelled, so there's no need to propagate. Otherwise, when we cancel a leaf node, it might
// propagate through most of the graph, making most of the cancel calls that follow in the
// onProducerFutureCancelled method do nothing.
ImmutableList<Key> cancellationKeys =
componentImplementation.getCancellableProducerKeys().reverse();
ImmutableList.Builder<CodeBlock> cancellationStatements = ImmutableList.builder();
for (Key cancellationKey : cancellationKeys) {
cancellationStatements.add(
CodeBlock.of(
"$T.cancel($L, $N);",
Producers.class,
bindingExpressions
.getDependencyExpression(
bindingRequest(cancellationKey, FrameworkType.PRODUCER_NODE),
componentImplementation.name())
.codeBlock(),
MAY_INTERRUPT_IF_RUNNING));
}
return cancellationStatements.build();
}
private Optional<CodeBlock> cancelParentStatement() {
if (!shouldPropagateCancellationToParent()) {
return Optional.empty();
}
return Optional.of(
CodeBlock.builder()
.addStatement(
"$T.this.$N($N)",
parent.get().componentImplementation.name(),
CANCELLATION_LISTENER_METHOD_NAME,
MAY_INTERRUPT_IF_RUNNING)
.build());
}
private boolean shouldPropagateCancellationToParent() {
return parent.isPresent()
&& parent
.get()
.componentImplementation
.componentDescriptor()
.cancellationPolicy()
.map(policy -> policy.fromSubcomponents().equals(PROPAGATE))
.orElse(false);
}
private MethodSignature getMethodSignature(ComponentMethodDescriptor method) {
return MethodSignature.forComponentMethod(
method, MoreTypes.asDeclared(graph.componentTypeElement().asType()), types);
}
private void addChildComponents() {
for (BindingGraph subgraph : graph.subgraphs()) {
componentImplementation.addType(SUBCOMPONENT, childComponent(subgraph));
}
}
private TypeSpec childComponent(BindingGraph childGraph) {
return topLevelImplementationComponent
.currentImplementationSubcomponentBuilder()
.componentImplementation(subcomponent(childGraph))
.bindingGraph(childGraph)
.parentBuilder(Optional.of(this))
.parentBindingExpressions(Optional.of(bindingExpressions))
.parentRequirementExpressions(Optional.of(componentRequirementExpressions))
.build()
.componentImplementationBuilder()
.build()
.generate()
.build();
}
/** Creates an inner subcomponent implementation. */
private ComponentImplementation subcomponent(BindingGraph childGraph) {
return componentImplementation.childComponentImplementation(childGraph);
}
/** Creates and adds the constructor and methods needed for initializing the component. */
private void addConstructorAndInitializationMethods() {
MethodSpec.Builder constructor = constructorBuilder().addModifiers(PRIVATE);
implementInitializationMethod(constructor, initializationParameters());
componentImplementation.addMethod(CONSTRUCTOR, constructor.build());
}
/** Adds parameters and code to the given {@code initializationMethod}. */
private void implementInitializationMethod(
MethodSpec.Builder initializationMethod,
ImmutableMap<ComponentRequirement, ParameterSpec> initializationParameters) {
initializationMethod.addParameters(initializationParameters.values());
initializationMethod.addCode(
CodeBlocks.concat(componentImplementation.getComponentRequirementInitializations()));
addInitializeMethods(initializationMethod, initializationParameters.values().asList());
}
/**
* Adds any necessary {@code initialize} methods to the component and adds calls to them to the
* given {@code callingMethod}.
*/
private void addInitializeMethods(
MethodSpec.Builder callingMethod, ImmutableList<ParameterSpec> parameters) {
// TODO(cgdecker): It's not the case that each initialize() method has need for all of the
// given parameters. In some cases, those parameters may have already been assigned to fields
// which could be referenced instead. In other cases, an initialize method may just not need
// some of the parameters because the set of initializations in that partition does not
// include any reference to them. Right now, the Dagger code has no way of getting that
// information because, among other things, componentImplementation.getImplementations() just
// returns a bunch of CodeBlocks with no semantic information. Additionally, we may not know
// yet whether a field will end up needing to be created for a specific requirement, and we
// don't want to create a field that ends up only being used during initialization.
CodeBlock args = parameterNames(parameters);
ImmutableList<MethodSpec> methods =
createPartitionedMethods(
"initialize",
makeFinal(parameters),
componentImplementation.getInitializations(),
methodName ->
methodBuilder(methodName)
.addModifiers(PRIVATE)
/* TODO(gak): Strictly speaking, we only need the suppression here if we are
* also initializing a raw field in this method, but the structure of this
* code makes it awkward to pass that bit through. This will be cleaned up
* when we no longer separate fields and initialization as we do now. */
.addAnnotation(AnnotationSpecs.suppressWarnings(UNCHECKED)));
for (MethodSpec method : methods) {
callingMethod.addStatement("$N($L)", method, args);
componentImplementation.addMethod(INITIALIZE_METHOD, method);
}
}
/**
* Creates one or more methods, all taking the given {@code parameters}, which partition the given
* list of {@code statements} among themselves such that no method has more than {@code
* STATEMENTS_PER_METHOD} statements in it and such that the returned methods, if called in order,
* will execute the {@code statements} in the given order.
*/
private ImmutableList<MethodSpec> createPartitionedMethods(
String methodName,
Iterable<ParameterSpec> parameters,
List<CodeBlock> statements,
Function<String, MethodSpec.Builder> methodBuilderCreator) {
return Lists.partition(statements, STATEMENTS_PER_METHOD).stream()
.map(
partition ->
methodBuilderCreator
.apply(componentImplementation.getUniqueMethodName(methodName))
.addParameters(parameters)
.addCode(CodeBlocks.concat(partition))
.build())
.collect(toImmutableList());
}
/** Returns the given parameters with a final modifier added. */
private final ImmutableList<ParameterSpec> makeFinal(Collection<ParameterSpec> parameters) {
return parameters.stream()
.map(param -> param.toBuilder().addModifiers(FINAL).build())
.collect(toImmutableList());
}
/**
* Returns the parameters for the constructor as a map from the requirement the parameter fulfills
* to the spec for the parameter.
*/
private final ImmutableMap<ComponentRequirement, ParameterSpec> initializationParameters() {
Map<ComponentRequirement, ParameterSpec> parameters;
if (componentImplementation.componentDescriptor().hasCreator()) {
parameters = Maps.toMap(graph.componentRequirements(), ComponentRequirement::toParameterSpec);
} else if (graph.factoryMethod().isPresent()) {
parameters = getFactoryMethodParameters(graph);
} else {
throw new AssertionError(
"Expected either a component creator or factory method but found neither.");
}
return renameParameters(parameters);
}
/**
* Renames the given parameters to guarantee their names do not conflict with fields in the
* component to ensure that a parameter is never referenced where a reference to a field was
* intended.
*/
// TODO(cgdecker): This is a bit kludgy; it would be preferable to either qualify the field
// references with "this." or "super." when needed to disambiguate between field and parameter,
// but that would require more context than is currently available when the code referencing a
// field is generated.
private ImmutableMap<ComponentRequirement, ParameterSpec> renameParameters(
Map<ComponentRequirement, ParameterSpec> parameters) {
return ImmutableMap.copyOf(
Maps.transformEntries(
parameters,
(requirement, parameter) ->
renameParameter(
parameter,
componentImplementation.getParameterName(requirement, parameter.name))));
}
private ParameterSpec renameParameter(ParameterSpec parameter, String newName) {
return ParameterSpec.builder(parameter.type, newName)
.addAnnotations(parameter.annotations)
.addModifiers(parameter.modifiers)
.build();
}
private void createRootComponentFactoryMethod() {
checkState(!parent.isPresent());
// Top-level components have a static method that returns a builder or factory for the
// component. If the user defined a @Component.Builder or @Component.Factory, an
// implementation of their type is returned. Otherwise, an autogenerated Builder type is
// returned.
// TODO(cgdecker): Replace this abomination with a small class?
// Better yet, change things so that an autogenerated builder type has a descriptor of sorts
// just like a user-defined creator type.
ComponentCreatorKind creatorKind;
ClassName creatorType;
String factoryMethodName;
boolean noArgFactoryMethod;
Optional<ComponentCreatorDescriptor> creatorDescriptor =
graph.componentDescriptor().creatorDescriptor();
if (creatorDescriptor.isPresent()) {
ComponentCreatorDescriptor descriptor = creatorDescriptor.get();
creatorKind = descriptor.kind();
creatorType = ClassName.get(descriptor.typeElement());
factoryMethodName = descriptor.factoryMethod().getSimpleName().toString();
noArgFactoryMethod = descriptor.factoryParameters().isEmpty();
} else {
creatorKind = BUILDER;
creatorType = componentImplementation.getCreatorName();
factoryMethodName = "build";
noArgFactoryMethod = true;
}
MethodSpec creatorFactoryMethod =
methodBuilder(creatorKind.methodName())
.addModifiers(PUBLIC, STATIC)
.returns(creatorType)
.addStatement("return new $T()", componentImplementation.getCreatorName())
.build();
componentImplementation.addMethod(BUILDER_METHOD, creatorFactoryMethod);
if (noArgFactoryMethod && canInstantiateAllRequirements()) {
componentImplementation.addMethod(
BUILDER_METHOD,
methodBuilder("create")
.returns(ClassName.get(graph.componentTypeElement()))
.addModifiers(PUBLIC, STATIC)
.addStatement("return new $L().$L()", creatorKind.typeName(), factoryMethodName)
.build());
}
}
/** {@code true} if all of the graph's required dependencies can be automatically constructed */
private boolean canInstantiateAllRequirements() {
return !Iterables.any(
graph.componentRequirements(),
dependency -> dependency.requiresAPassedInstance(elements, types, metadataUtil));
}
private void createSubcomponentFactoryMethod(ExecutableElement factoryMethod) {
checkState(parent.isPresent());
Collection<ParameterSpec> params = getFactoryMethodParameters(graph).values();
MethodSpec.Builder method = MethodSpec.overriding(factoryMethod, parentType(), types);
params.forEach(
param -> method.addStatement("$T.checkNotNull($N)", Preconditions.class, param));
method.addStatement(
"return new $T($L)", componentImplementation.name(), parameterNames(params));
parent.get().componentImplementation.addMethod(COMPONENT_METHOD, method.build());
}
private DeclaredType parentType() {
return asDeclared(parent.get().graph.componentTypeElement().asType());
}
/**
* Returns the map of {@link ComponentRequirement}s to {@link ParameterSpec}s for the given
* graph's factory method.
*/
private static Map<ComponentRequirement, ParameterSpec> getFactoryMethodParameters(
BindingGraph graph) {
return Maps.transformValues(graph.factoryMethodParameters(), ParameterSpec::get);
}
}