/* * 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 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 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 componentMethodsBySignature = Multimaps.index(graph.componentDescriptor().entryPointMethods(), this::getMethodSignature); for (List 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 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 cancellationStatements = cancellationStatements(); if (cancellationStatements.size() < STATEMENTS_PER_METHOD) { methodBuilder.addCode(CodeBlocks.concat(cancellationStatements)).build(); } else { ImmutableList 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 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 cancellationKeys = componentImplementation.getCancellableProducerKeys().reverse(); ImmutableList.Builder 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 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 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 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 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 createPartitionedMethods( String methodName, Iterable parameters, List statements, Function 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 makeFinal(Collection 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 initializationParameters() { Map 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 renameParameters( Map 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 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 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 getFactoryMethodParameters( BindingGraph graph) { return Maps.transformValues(graph.factoryMethodParameters(), ParameterSpec::get); } }