/* * 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 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); 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 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 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); } } Optional 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 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(); } protected Optional 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 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 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 currentSuperImplementation = componentImplementation.superclassImplementation(); currentSuperImplementation.isPresent(); currentSuperImplementation = currentSuperImplementation.get().superclassImplementation()) { Optional 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 types(List 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 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 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 or {@code configureInitilization} method 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(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 configureInitializationRequirements() { ImmutableSet initializationParameters = componentImplementation.getComponentRequirementParameters(); ImmutableSet 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 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(); } /** 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 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 parent; @Inject SubcomponentImplementationBuilder( @ParentComponent Optional 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 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 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 getFactoryMethodParameters( BindingGraph graph) { return Maps.transformValues(graph.factoryMethodParameters(), ParameterSpec::get); } }