827 lines
37 KiB
Java
827 lines
37 KiB
Java
/*
|
|
* 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;
|
|
|
|
import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
|
|
import static com.google.auto.common.MoreTypes.asDeclared;
|
|
import static com.google.common.base.Preconditions.checkArgument;
|
|
import static com.google.common.base.Preconditions.checkState;
|
|
import static com.google.common.base.Predicates.in;
|
|
import static com.squareup.javapoet.MethodSpec.constructorBuilder;
|
|
import static com.squareup.javapoet.MethodSpec.methodBuilder;
|
|
import static dagger.internal.codegen.BindingRequest.bindingRequest;
|
|
import static dagger.internal.codegen.ComponentCreatorKind.BUILDER;
|
|
import static dagger.internal.codegen.ComponentImplementation.MethodSpecKind.BUILDER_METHOD;
|
|
import static dagger.internal.codegen.ComponentImplementation.MethodSpecKind.CANCELLATION_LISTENER_METHOD;
|
|
import static dagger.internal.codegen.ComponentImplementation.MethodSpecKind.COMPONENT_METHOD;
|
|
import static dagger.internal.codegen.ComponentImplementation.MethodSpecKind.CONSTRUCTOR;
|
|
import static dagger.internal.codegen.ComponentImplementation.MethodSpecKind.INITIALIZE_METHOD;
|
|
import static dagger.internal.codegen.ComponentImplementation.MethodSpecKind.MODIFIABLE_BINDING_METHOD;
|
|
import static dagger.internal.codegen.ComponentImplementation.TypeSpecKind.COMPONENT_CREATOR;
|
|
import static dagger.internal.codegen.ComponentImplementation.TypeSpecKind.SUBCOMPONENT;
|
|
import static dagger.internal.codegen.DaggerStreams.toImmutableList;
|
|
import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED;
|
|
import static dagger.internal.codegen.javapoet.CodeBlocks.parameterNames;
|
|
import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock;
|
|
import static dagger.producers.CancellationPolicy.Propagation.PROPAGATE;
|
|
import static javax.lang.model.element.Modifier.ABSTRACT;
|
|
import static javax.lang.model.element.Modifier.FINAL;
|
|
import static javax.lang.model.element.Modifier.PRIVATE;
|
|
import static javax.lang.model.element.Modifier.PROTECTED;
|
|
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.ImmutableSet;
|
|
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.google.common.collect.Sets;
|
|
import com.squareup.javapoet.AnnotationSpec;
|
|
import com.squareup.javapoet.ClassName;
|
|
import com.squareup.javapoet.CodeBlock;
|
|
import com.squareup.javapoet.MethodSpec;
|
|
import com.squareup.javapoet.ParameterSpec;
|
|
import com.squareup.javapoet.TypeName;
|
|
import com.squareup.javapoet.TypeSpec;
|
|
import dagger.internal.ComponentDefinitionType;
|
|
import dagger.internal.Preconditions;
|
|
import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
|
|
import dagger.internal.codegen.ComponentImplementation.ConfigureInitializationMethod;
|
|
import dagger.internal.codegen.ModifiableBindingMethods.ModifiableBindingMethod;
|
|
import dagger.internal.codegen.javapoet.AnnotationSpecs;
|
|
import dagger.internal.codegen.javapoet.CodeBlocks;
|
|
import dagger.internal.codegen.langmodel.DaggerElements;
|
|
import dagger.internal.codegen.langmodel.DaggerTypes;
|
|
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. */
|
|
abstract 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";
|
|
|
|
// TODO(ronshapiro): replace this with composition instead of inheritance so we don't have
|
|
// non-final fields
|
|
@Inject BindingGraph graph;
|
|
@Inject ComponentBindingExpressions bindingExpressions;
|
|
@Inject ComponentRequirementExpressions componentRequirementExpressions;
|
|
@Inject ComponentImplementation componentImplementation;
|
|
@Inject ComponentCreatorImplementationFactory componentCreatorImplementationFactory;
|
|
@Inject DaggerTypes types;
|
|
@Inject DaggerElements elements;
|
|
@Inject CompilerOptions compilerOptions;
|
|
@Inject ComponentImplementationFactory componentImplementationFactory;
|
|
@Inject TopLevelImplementationComponent topLevelImplementationComponent;
|
|
private boolean done;
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
final ComponentImplementation build() {
|
|
checkState(
|
|
!done,
|
|
"ComponentImplementationBuilder has already built the ComponentImplementation for [%s].",
|
|
componentImplementation.name());
|
|
setSupertype();
|
|
componentImplementation.setCreatorImplementation(
|
|
componentCreatorImplementationFactory.create(
|
|
componentImplementation, Optional.of(componentImplementation.graph())));
|
|
componentImplementation
|
|
.creatorImplementation()
|
|
.map(ComponentCreatorImplementation::spec)
|
|
.ifPresent(this::addCreatorClass);
|
|
|
|
getLocalAndInheritedMethods(graph.componentTypeElement(), types, elements)
|
|
.forEach(method -> componentImplementation.claimMethodName(method.getSimpleName()));
|
|
componentImplementation
|
|
.superclassImplementation()
|
|
.ifPresent(
|
|
superclassImplementation -> {
|
|
superclassImplementation
|
|
.getAllModifiableMethodNames()
|
|
.forEach(componentImplementation::claimMethodName);
|
|
});
|
|
|
|
addFactoryMethods();
|
|
addInterfaceMethods();
|
|
addChildComponents();
|
|
implementModifiableModuleMethods();
|
|
|
|
addConstructorAndInitializationMethods();
|
|
|
|
if (graph.componentDescriptor().isProduction()) {
|
|
addCancellationListenerImplementation();
|
|
}
|
|
|
|
if (componentImplementation.isAbstract()
|
|
&& !componentImplementation.baseImplementation().isPresent()) {
|
|
componentImplementation.addAnnotation(compilerOptions.toGenerationOptionsAnnotation());
|
|
}
|
|
|
|
if (componentImplementation.shouldEmitModifiableMetadataAnnotations()) {
|
|
componentImplementation.addAnnotation(
|
|
AnnotationSpec.builder(ComponentDefinitionType.class)
|
|
.addMember("value", "$T.class", graph.componentTypeElement())
|
|
.build());
|
|
}
|
|
|
|
done = true;
|
|
return componentImplementation;
|
|
}
|
|
|
|
/** Set the supertype for this generated class. */
|
|
private void setSupertype() {
|
|
if (componentImplementation.superclassImplementation().isPresent()) {
|
|
componentImplementation.addSuperclass(
|
|
componentImplementation.superclassImplementation().get().name());
|
|
} else {
|
|
componentImplementation.addSupertype(graph.componentTypeElement());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds {@code creator} as a nested creator class. Root components and subcomponents will nest
|
|
* this in different classes.
|
|
*/
|
|
protected abstract void addCreatorClass(TypeSpec creator);
|
|
|
|
/** Adds component factory methods. */
|
|
protected abstract void addFactoryMethods();
|
|
|
|
protected 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);
|
|
|
|
if (compilerOptions.aheadOfTimeSubcomponents()) {
|
|
addPossiblyModifiableInterfaceMethod(anyOneMethod, methodSpec);
|
|
} else {
|
|
componentImplementation.addMethod(COMPONENT_METHOD, methodSpec);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a component interface method in ahead-of-time subcomponents mode. If the binding that
|
|
* implements the method is modifiable, registers the method.
|
|
*/
|
|
private void addPossiblyModifiableInterfaceMethod(
|
|
ComponentMethodDescriptor methodDescriptor, MethodSpec implementedComponentMethod) {
|
|
if (methodDescriptor.dependencyRequest().isPresent()
|
|
&& componentImplementation
|
|
.getModifiableBindingMethod(bindingRequest(methodDescriptor.dependencyRequest().get()))
|
|
.isPresent()) {
|
|
// If there are multiple component methods that are modifiable and for the same binding
|
|
// request, implement all but one in the base implementation to delegate to the one that
|
|
// will remain (and be registered) modifiable
|
|
checkState(componentImplementation.isAbstract() && !componentImplementation.isNested());
|
|
componentImplementation.addMethod(
|
|
COMPONENT_METHOD, implementedComponentMethod.toBuilder().addModifiers(FINAL).build());
|
|
} else {
|
|
// TODO(b/117833324): Can this class be the one to interface with ComponentImplementation
|
|
// instead of having it go through ModifiableBindingExpressions?
|
|
bindingExpressions
|
|
.modifiableBindingExpressions()
|
|
.addPossiblyModifiableComponentMethod(methodDescriptor, implementedComponentMethod);
|
|
}
|
|
}
|
|
|
|
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);
|
|
if (componentImplementation.superclassImplementation().isPresent()) {
|
|
methodBuilder.addStatement(
|
|
"super.$L($L)", CANCELLATION_LISTENER_METHOD_NAME, MAY_INTERRUPT_IF_RUNNING);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
Optional<CodeBlock> cancelParentStatement = cancelParentStatement();
|
|
cancelParentStatement.ifPresent(methodBuilder::addCode);
|
|
|
|
if (cancellationStatements.isEmpty()
|
|
&& !cancelParentStatement.isPresent()
|
|
&& componentImplementation.superclassImplementation().isPresent()) {
|
|
// Partial child implementations that have no new cancellations don't need to override
|
|
// the method just to call super().
|
|
return;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
protected Optional<CodeBlock> cancelParentStatement() {
|
|
// Returns empty by default. Overridden in subclass(es) to add a statement if and only if the
|
|
// component being generated is a concrete subcomponent implementation with a parent that
|
|
// allows cancellation to propagate to it from subcomponents.
|
|
return Optional.empty();
|
|
}
|
|
|
|
/**
|
|
* For final components, reimplements all modifiable module methods that may have been modified.
|
|
*/
|
|
private void implementModifiableModuleMethods() {
|
|
if (componentImplementation.isAbstract()) {
|
|
return;
|
|
}
|
|
componentImplementation
|
|
.getAllModifiableModuleMethods()
|
|
.forEach(this::implementModifiableModuleMethod);
|
|
}
|
|
|
|
private void implementModifiableModuleMethod(ComponentRequirement module, String methodName) {
|
|
// TODO(b/117833324): only reimplement methods for modules that were abstract or were repeated
|
|
// by an ancestor component.
|
|
componentImplementation.addMethod(
|
|
MODIFIABLE_BINDING_METHOD,
|
|
methodBuilder(methodName)
|
|
.addAnnotation(Override.class)
|
|
.addModifiers(PROTECTED)
|
|
.returns(TypeName.get(module.type()))
|
|
.addStatement(
|
|
componentRequirementExpressions
|
|
.getExpression(module)
|
|
.getModifiableModuleMethodExpression(componentImplementation.name()))
|
|
.build());
|
|
}
|
|
|
|
private MethodSignature getMethodSignature(ComponentMethodDescriptor method) {
|
|
return MethodSignature.forComponentMethod(
|
|
method, MoreTypes.asDeclared(graph.componentTypeElement().asType()), types);
|
|
}
|
|
|
|
private void addChildComponents() {
|
|
for (BindingGraph subgraph : graph.subgraphs()) {
|
|
// TODO(b/117833324): Can an abstract inner subcomponent implementation be elided if it's
|
|
// totally empty?
|
|
componentImplementation.addChild(
|
|
subgraph.componentDescriptor(), buildChildImplementation(subgraph));
|
|
}
|
|
}
|
|
|
|
private ComponentImplementation buildChildImplementation(BindingGraph childGraph) {
|
|
ComponentImplementation childImplementation =
|
|
compilerOptions.aheadOfTimeSubcomponents()
|
|
? abstractInnerSubcomponent(childGraph)
|
|
: concreteSubcomponent(childGraph);
|
|
return topLevelImplementationComponent
|
|
.currentImplementationSubcomponentBuilder()
|
|
.componentImplementation(childImplementation)
|
|
.bindingGraph(childGraph)
|
|
.parentBuilder(Optional.of(this))
|
|
.parentBindingExpressions(Optional.of(bindingExpressions))
|
|
.parentRequirementExpressions(Optional.of(componentRequirementExpressions))
|
|
.build()
|
|
.subcomponentBuilder()
|
|
.build();
|
|
}
|
|
|
|
/** Creates an inner abstract subcomponent implementation. */
|
|
private ComponentImplementation abstractInnerSubcomponent(BindingGraph childGraph) {
|
|
return componentImplementation.childComponentImplementation(
|
|
childGraph,
|
|
Optional.of(
|
|
componentImplementationFactory.findChildSuperclassImplementation(
|
|
childGraph.componentDescriptor(), componentImplementation)),
|
|
PROTECTED,
|
|
componentImplementation.isAbstract() ? ABSTRACT : FINAL);
|
|
}
|
|
|
|
/** Creates a concrete inner subcomponent implementation. */
|
|
private ComponentImplementation concreteSubcomponent(BindingGraph childGraph) {
|
|
return componentImplementation.childComponentImplementation(
|
|
childGraph,
|
|
Optional.empty(), // superclassImplementation
|
|
PRIVATE,
|
|
FINAL);
|
|
}
|
|
|
|
/** Creates and adds the constructor and methods needed for initializing the component. */
|
|
private void addConstructorAndInitializationMethods() {
|
|
MethodSpec.Builder constructor = componentConstructorBuilder();
|
|
if (!componentImplementation.isAbstract()) {
|
|
implementInitializationMethod(constructor, initializationParameters());
|
|
} else if (componentImplementation.hasInitializations()) {
|
|
addConfigureInitializationMethod();
|
|
}
|
|
componentImplementation.addMethod(CONSTRUCTOR, constructor.build());
|
|
}
|
|
|
|
/** Returns a builder for the component's constructor. */
|
|
private MethodSpec.Builder componentConstructorBuilder() {
|
|
return constructorBuilder()
|
|
.addModifiers(componentImplementation.isAbstract() ? PROTECTED : PRIVATE);
|
|
}
|
|
|
|
/** 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()));
|
|
componentImplementation
|
|
.superConfigureInitializationMethod()
|
|
.ifPresent(
|
|
superConfigureInitializationMethod ->
|
|
addSuperConfigureInitializationCall(
|
|
initializationMethod,
|
|
initializationParameters,
|
|
superConfigureInitializationMethod));
|
|
addInitializeMethods(initializationMethod, initializationParameters.values().asList());
|
|
}
|
|
|
|
/** Creates and adds a {@code configureInitializatoin} method to the component. */
|
|
private void addConfigureInitializationMethod() {
|
|
MethodSpec.Builder method = configureInitializationMethodBuilder();
|
|
ImmutableMap<ComponentRequirement, ParameterSpec> parameters = initializationParameters();
|
|
implementInitializationMethod(method, parameters);
|
|
componentImplementation.setConfigureInitializationMethod(
|
|
ConfigureInitializationMethod.create(method.build(), parameters.keySet()));
|
|
}
|
|
|
|
/** Returns a builder for the component's {@code configureInitialization} method. */
|
|
private MethodSpec.Builder configureInitializationMethodBuilder() {
|
|
String methodName = componentImplementation.getUniqueMethodName("configureInitialization");
|
|
MethodSpec.Builder configureInitialization = methodBuilder(methodName).addModifiers(PROTECTED);
|
|
if (overridesSuperclassConfigureInitialization(configureInitialization.build())) {
|
|
configureInitialization.addAnnotation(Override.class);
|
|
}
|
|
return configureInitialization;
|
|
}
|
|
|
|
/**
|
|
* Returns whether or not the given method overrides a configureInitialization method from a
|
|
* superclass.
|
|
*/
|
|
private boolean overridesSuperclassConfigureInitialization(MethodSpec method) {
|
|
for (Optional<ComponentImplementation> currentSuperImplementation =
|
|
componentImplementation.superclassImplementation();
|
|
currentSuperImplementation.isPresent();
|
|
currentSuperImplementation = currentSuperImplementation.get().superclassImplementation()) {
|
|
Optional<MethodSpec> superConfigureInitializationMethod =
|
|
currentSuperImplementation.get().configureInitializationMethod().map(m -> m.spec());
|
|
if (superConfigureInitializationMethod
|
|
.filter(superMethod -> haveSameSignature(method, superMethod))
|
|
.isPresent()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Returns whether or not methods {@code a} and {@code b} have the same signature. */
|
|
private boolean haveSameSignature(MethodSpec a, MethodSpec b) {
|
|
return a.name.equals(b.name) && types(a.parameters).equals(types(b.parameters));
|
|
}
|
|
|
|
private ImmutableList<TypeName> types(List<ParameterSpec> parameters) {
|
|
return parameters.stream().map(parameter -> parameter.type).collect(toImmutableList());
|
|
}
|
|
|
|
/**
|
|
* Adds a call to the superclass's {@code configureInitialization} method to the given {@code
|
|
* callingMethod}.
|
|
*/
|
|
private void addSuperConfigureInitializationCall(
|
|
MethodSpec.Builder callingMethod,
|
|
ImmutableMap<ComponentRequirement, ParameterSpec> parameters,
|
|
ConfigureInitializationMethod superConfigureInitializationMethod) {
|
|
// This component's constructor may not have all of the parameters that the superclass's
|
|
// configureInitialization method takes, because the superclass configureInitialization method
|
|
// necessarily accepts things that it can't know whether will be needed or not. If they aren't
|
|
// needed (as is the case when the constructor doesn't have a parameter for the module), just
|
|
// pass null to super.configureInitialization for that parameter; it won't be used.
|
|
CodeBlock args =
|
|
superConfigureInitializationMethod.parameters().stream()
|
|
.map(
|
|
requirement ->
|
|
parameters.containsKey(requirement)
|
|
? CodeBlock.of("$N", parameters.get(requirement))
|
|
: CodeBlock.of("null"))
|
|
.collect(toParametersCodeBlock());
|
|
|
|
String qualifier =
|
|
haveSameSignature(callingMethod.build(), superConfigureInitializationMethod.spec())
|
|
? "super."
|
|
: "";
|
|
callingMethod.addStatement(
|
|
qualifier + "$N($L)", superConfigureInitializationMethod.spec(), args);
|
|
}
|
|
|
|
/**
|
|
* 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 or {@code configureInitilization} method 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(componentImplementation.requirements(), ComponentRequirement::toParameterSpec);
|
|
} else if (componentImplementation.isAbstract() && componentImplementation.isNested()) {
|
|
// If we're generating an abstract inner subcomponent, then we are not implementing module
|
|
// instance bindings and have no need for factory method parameters.
|
|
parameters = ImmutableMap.of();
|
|
} else if (graph.factoryMethod().isPresent()) {
|
|
parameters = getFactoryMethodParameters(graph);
|
|
} else if (componentImplementation.isAbstract()) {
|
|
// If we're generating an abstract base implementation of a subcomponent it's acceptable to
|
|
// have neither a creator nor factory method.
|
|
parameters = ImmutableMap.of();
|
|
} else {
|
|
throw new AssertionError(
|
|
"Expected either a component creator or factory method but found neither.");
|
|
}
|
|
|
|
if (componentImplementation.isAbstract()) {
|
|
parameters = Maps.filterKeys(parameters, in(configureInitializationRequirements()));
|
|
}
|
|
return renameParameters(parameters);
|
|
}
|
|
|
|
/**
|
|
* Returns the set of requirements for the configureInitialization method: the parameters that are
|
|
* needed either for initializing a component requirement field or for calling the superclass's
|
|
* {@code configureInitialization} method.
|
|
*/
|
|
private ImmutableSet<ComponentRequirement> configureInitializationRequirements() {
|
|
ImmutableSet<ComponentRequirement> initializationParameters =
|
|
componentImplementation.getComponentRequirementParameters();
|
|
ImmutableSet<ComponentRequirement> superConfigureInitializationRequirements =
|
|
componentImplementation
|
|
.superConfigureInitializationMethod()
|
|
.map(ConfigureInitializationMethod::parameters)
|
|
.orElse(ImmutableSet.of());
|
|
return Sets.union(initializationParameters, superConfigureInitializationRequirements)
|
|
.immutableCopy();
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
|
|
/** Builds a root component implementation. */
|
|
static final class RootComponentImplementationBuilder extends ComponentImplementationBuilder {
|
|
@Inject
|
|
RootComponentImplementationBuilder(ComponentImplementation componentImplementation) {
|
|
checkArgument(!componentImplementation.superclassImplementation().isPresent());
|
|
}
|
|
|
|
@Override
|
|
protected void addCreatorClass(TypeSpec creator) {
|
|
componentImplementation.addType(COMPONENT_CREATOR, creator);
|
|
}
|
|
|
|
@Override
|
|
protected void addFactoryMethods() {
|
|
// 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;
|
|
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 = componentCreatorName();
|
|
factoryMethodName = "build";
|
|
noArgFactoryMethod = true;
|
|
}
|
|
|
|
MethodSpec creatorFactoryMethod =
|
|
methodBuilder(creatorKind.methodName())
|
|
.addModifiers(PUBLIC, STATIC)
|
|
.returns(creatorType)
|
|
.addStatement("return new $T()", componentCreatorName())
|
|
.build();
|
|
componentImplementation.addMethod(BUILDER_METHOD, creatorFactoryMethod);
|
|
if (noArgFactoryMethod && canInstantiateAllRequirements()) {
|
|
componentImplementation.addMethod(
|
|
BUILDER_METHOD,
|
|
methodBuilder("create")
|
|
.returns(ClassName.get(super.graph.componentTypeElement()))
|
|
.addModifiers(PUBLIC, STATIC)
|
|
.addStatement(
|
|
"return new $L().$L()", creatorKind.typeName(), factoryMethodName)
|
|
.build());
|
|
}
|
|
}
|
|
|
|
private Optional<ComponentCreatorDescriptor> creatorDescriptor() {
|
|
return graph.componentDescriptor().creatorDescriptor();
|
|
}
|
|
|
|
/** {@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));
|
|
}
|
|
|
|
private ClassName componentCreatorName() {
|
|
return componentImplementation.creatorImplementation().get().name();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builds a subcomponent implementation. If generating ahead-of-time subcomponents, this may be an
|
|
* abstract base class implementation, an abstract inner implementation, or a concrete
|
|
* implementation that extends an abstract base implementation. Otherwise it represents a private,
|
|
* inner, concrete, final implementation of a subcomponent which extends a user defined type.
|
|
*/
|
|
static final class SubcomponentImplementationBuilder extends ComponentImplementationBuilder {
|
|
final Optional<ComponentImplementationBuilder> parent;
|
|
|
|
@Inject
|
|
SubcomponentImplementationBuilder(
|
|
@ParentComponent Optional<ComponentImplementationBuilder> parent) {
|
|
this.parent = parent;
|
|
}
|
|
|
|
@Override
|
|
protected 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(SUBCOMPONENT, creator);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void addFactoryMethods() {
|
|
// Only construct instances of subcomponents that have concrete implementations.
|
|
if (!componentImplementation.isAbstract()) {
|
|
// Use the parent's factory method to create this subcomponent if the
|
|
// subcomponent was not added via {@link dagger.Module#subcomponents()}.
|
|
graph.factoryMethod().ifPresent(this::createSubcomponentFactoryMethod);
|
|
}
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
@Override
|
|
protected void addInterfaceMethods() {
|
|
if (componentImplementation.superclassImplementation().isPresent()) {
|
|
// Since we're overriding a subcomponent implementation we add to its implementation given
|
|
// an expanded binding graph.
|
|
|
|
ComponentImplementation superclassImplementation =
|
|
componentImplementation.superclassImplementation().get();
|
|
for (ModifiableBindingMethod superclassModifiableBindingMethod :
|
|
superclassImplementation.getModifiableBindingMethods().values()) {
|
|
bindingExpressions
|
|
.modifiableBindingExpressions()
|
|
.possiblyReimplementedMethod(superclassModifiableBindingMethod)
|
|
.ifPresent(componentImplementation::addImplementedModifiableBindingMethod);
|
|
}
|
|
} else {
|
|
super.addInterfaceMethods();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected 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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|