From d3fccc36657bb639a181bbb1b9afb85a97c32947 Mon Sep 17 00:00:00 2001 From: Brad Corso Date: Mon, 7 Jun 2021 12:29:56 -0700 Subject: [PATCH] Merge ComponentImplementationBuilder into ComponentImplementation. This CL moves all of the ComponentImplementation generation logic into ComponentImplementation and deletes ComponentImplementationBuilder. Before this CL, the generation logic was split across both files, making it difficult to find and understand the order in which things are generated into the component TypeSpec. RELNOTES=N/A PiperOrigin-RevId: 377978631 --- .../MethodSignature.java | 6 +- .../ComponentGenerator.java | 17 +- .../ComponentHjarGenerator.java | 1 + .../ComponentImplementationBuilder.java | 270 ------------------ .../ComponentImplementationFactory.java | 51 ---- .../CurrentImplementationSubcomponent.java | 29 +- ...ComponentCreatorImplementationFactory.java | 5 +- .../writing/ComponentImplementation.java | 197 ++++++++++++- 8 files changed, 230 insertions(+), 346 deletions(-) rename java/dagger/internal/codegen/{componentgenerator => binding}/MethodSignature.java (94%) delete mode 100644 java/dagger/internal/codegen/componentgenerator/ComponentImplementationBuilder.java delete mode 100644 java/dagger/internal/codegen/componentgenerator/ComponentImplementationFactory.java rename java/dagger/internal/codegen/{componentgenerator => writing}/ComponentCreatorImplementationFactory.java (98%) diff --git a/java/dagger/internal/codegen/componentgenerator/MethodSignature.java b/java/dagger/internal/codegen/binding/MethodSignature.java similarity index 94% rename from java/dagger/internal/codegen/componentgenerator/MethodSignature.java rename to java/dagger/internal/codegen/binding/MethodSignature.java index 99b05a44a9f..0a35755ef62 100644 --- a/java/dagger/internal/codegen/componentgenerator/MethodSignature.java +++ b/java/dagger/internal/codegen/binding/MethodSignature.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package dagger.internal.codegen.componentgenerator; +package dagger.internal.codegen.binding; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; @@ -31,7 +31,7 @@ /** A class that defines proper {@code equals} and {@code hashcode} for a method signature. */ @AutoValue -abstract class MethodSignature { +public abstract class MethodSignature { abstract String name(); @@ -39,7 +39,7 @@ abstract class MethodSignature { abstract ImmutableList> thrownTypes(); - static MethodSignature forComponentMethod( + public static MethodSignature forComponentMethod( ComponentMethodDescriptor componentMethod, DeclaredType componentType, DaggerTypes types) { ExecutableType methodType = MoreTypes.asExecutable(types.asMemberOf(componentType, componentMethod.methodElement())); diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentGenerator.java b/java/dagger/internal/codegen/componentgenerator/ComponentGenerator.java index 07ffb2db678..d8aa9438ad2 100644 --- a/java/dagger/internal/codegen/componentgenerator/ComponentGenerator.java +++ b/java/dagger/internal/codegen/componentgenerator/ComponentGenerator.java @@ -26,6 +26,7 @@ import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.writing.ComponentImplementation; +import java.util.Optional; import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.lang.model.SourceVersion; @@ -33,16 +34,16 @@ /** Generates the implementation of the abstract types annotated with {@link Component}. */ final class ComponentGenerator extends SourceFileGenerator { - private final ComponentImplementationFactory componentImplementationFactory; + private final TopLevelImplementationComponent.Factory topLevelImplementationComponentFactory; @Inject ComponentGenerator( Filer filer, DaggerElements elements, SourceVersion sourceVersion, - ComponentImplementationFactory componentImplementationFactory) { + TopLevelImplementationComponent.Factory topLevelImplementationComponentFactory) { super(filer, elements, sourceVersion); - this.componentImplementationFactory = componentImplementationFactory; + this.topLevelImplementationComponentFactory = topLevelImplementationComponentFactory; } @Override @@ -53,7 +54,15 @@ public Element originatingElement(BindingGraph input) { @Override public ImmutableList topLevelTypes(BindingGraph bindingGraph) { ComponentImplementation componentImplementation = - componentImplementationFactory.createComponentImplementation(bindingGraph); + topLevelImplementationComponentFactory + .create(bindingGraph) + .currentImplementationSubcomponentBuilder() + .bindingGraph(bindingGraph) + .parentImplementation(Optional.empty()) + .parentBindingExpressions(Optional.empty()) + .parentRequirementExpressions(Optional.empty()) + .build() + .componentImplementation(); verify( componentImplementation .name() diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java b/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java index 508325c06c3..5e68ff1d245 100644 --- a/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java +++ b/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java @@ -45,6 +45,7 @@ import dagger.internal.codegen.binding.ComponentCreatorKind; import dagger.internal.codegen.binding.ComponentDescriptor; import dagger.internal.codegen.binding.ComponentRequirement; +import dagger.internal.codegen.binding.MethodSignature; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentImplementationBuilder.java b/java/dagger/internal/codegen/componentgenerator/ComponentImplementationBuilder.java deleted file mode 100644 index d9f34d70cc1..00000000000 --- a/java/dagger/internal/codegen/componentgenerator/ComponentImplementationBuilder.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * 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.MoreTypes.asDeclared; -import static com.google.common.base.Preconditions.checkState; -import static com.squareup.javapoet.MethodSpec.methodBuilder; -import static dagger.internal.codegen.binding.ComponentCreatorKind.BUILDER; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; -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.COMPONENT_METHOD; -import static dagger.internal.codegen.writing.ComponentImplementation.TypeSpecKind.COMPONENT_CREATOR; -import static dagger.internal.codegen.writing.ComponentImplementation.TypeSpecKind.SUBCOMPONENT; -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.Iterables; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimaps; -import com.squareup.javapoet.ClassName; -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.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.ComponentImplementation.ShardImplementation; -import dagger.internal.codegen.writing.ComponentRequirementExpressions; -import dagger.internal.codegen.writing.ParentComponent; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; -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 final Optional parent; - private final BindingGraph graph; - private final ComponentBindingExpressions bindingExpressions; - private final ComponentRequirementExpressions componentRequirementExpressions; - private final ComponentImplementation componentImplementation; - private final ShardImplementation componentShard; - 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; - - // All fields/methods/types added by this class apply only to the component shard. - this.componentShard = componentImplementation.getComponentShard(); - } - - /** - * 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()); - - componentCreatorImplementationFactory.create() - .map(ComponentCreatorImplementation::spec) - .ifPresent(this::addCreatorClass); - - addFactoryMethods(); - addInterfaceMethods(); - addChildComponents(); - - done = true; - return componentImplementation; - } - - private void addCreatorClass(TypeSpec creator) { - if (parent.isPresent()) { - // In an inner implementation of a subcomponent the creator is a peer class. - parent.get().getComponentShard().addType(SUBCOMPONENT, creator); - } else { - componentShard.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); - - componentShard.addMethod(COMPONENT_METHOD, methodSpec); - } - } - - private MethodSignature getMethodSignature(ComponentMethodDescriptor method) { - return MethodSignature.forComponentMethod( - method, MoreTypes.asDeclared(graph.componentTypeElement().asType()), types); - } - - private void addChildComponents() { - for (BindingGraph subgraph : graph.subgraphs()) { - componentShard.addType(SUBCOMPONENT, childComponent(subgraph)); - } - } - - private TypeSpec childComponent(BindingGraph childGraph) { - return topLevelImplementationComponent - .currentImplementationSubcomponentBuilder() - .bindingGraph(childGraph) - .parentImplementation(Optional.of(componentImplementation)) - .parentBindingExpressions(Optional.of(bindingExpressions)) - .parentRequirementExpressions(Optional.of(componentRequirementExpressions)) - .build() - .componentImplementationBuilder() - .build() - .generate(); - } - - 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(); - componentShard.addMethod(BUILDER_METHOD, creatorFactoryMethod); - if (noArgFactoryMethod && canInstantiateAllRequirements()) { - componentShard.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, 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)", - componentShard.name(), - parameterNames( - ImmutableList.builder() - .addAll( - componentImplementation.creatorComponentFields().stream() - .map(field -> ParameterSpec.builder(field.type, field.name).build()) - .collect(toImmutableList())) - .addAll(params) - .build())); - - parent.get().getComponentShard().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); - } -} diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentImplementationFactory.java b/java/dagger/internal/codegen/componentgenerator/ComponentImplementationFactory.java deleted file mode 100644 index 0be2acedcd9..00000000000 --- a/java/dagger/internal/codegen/componentgenerator/ComponentImplementationFactory.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 dagger.internal.codegen.binding.BindingGraph; -import dagger.internal.codegen.writing.ComponentImplementation; -import java.util.Optional; -import javax.inject.Inject; -import javax.inject.Singleton; - -// TODO(bcorso): We don't need a separate class for this anymore. Merge it into ComponentGenerator. -/** Factory for {@link ComponentImplementation}s. */ -@Singleton -final class ComponentImplementationFactory { - private final TopLevelImplementationComponent.Factory topLevelImplementationComponentFactory; - - @Inject - ComponentImplementationFactory( - TopLevelImplementationComponent.Factory topLevelImplementationComponentFactory) { - this.topLevelImplementationComponentFactory = topLevelImplementationComponentFactory; - } - - /** Returns a top-level (non-nested) component implementation for a binding graph. */ - ComponentImplementation createComponentImplementation(BindingGraph bindingGraph) { - // TODO(dpb): explore using optional bindings for the "parent" bindings - return topLevelImplementationComponentFactory - .create(bindingGraph) - .currentImplementationSubcomponentBuilder() - .bindingGraph(bindingGraph) - .parentImplementation(Optional.empty()) - .parentBindingExpressions(Optional.empty()) - .parentRequirementExpressions(Optional.empty()) - .build() - .componentImplementationBuilder() - .build(); - } -} diff --git a/java/dagger/internal/codegen/componentgenerator/CurrentImplementationSubcomponent.java b/java/dagger/internal/codegen/componentgenerator/CurrentImplementationSubcomponent.java index 3e6ef3bd8a7..ebed227dd94 100644 --- a/java/dagger/internal/codegen/componentgenerator/CurrentImplementationSubcomponent.java +++ b/java/dagger/internal/codegen/componentgenerator/CurrentImplementationSubcomponent.java @@ -17,25 +17,50 @@ package dagger.internal.codegen.componentgenerator; import dagger.BindsInstance; +import dagger.Module; +import dagger.Provides; import dagger.Subcomponent; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.writing.ComponentBindingExpressions; import dagger.internal.codegen.writing.ComponentImplementation; +import dagger.internal.codegen.writing.ComponentImplementation.ChildComponentImplementationFactory; import dagger.internal.codegen.writing.ComponentRequirementExpressions; import dagger.internal.codegen.writing.ParentComponent; import dagger.internal.codegen.writing.PerComponentImplementation; import java.util.Optional; +import javax.inject.Provider; /** * A subcomponent that injects all objects that are responsible for creating a single {@link * ComponentImplementation} instance. Each child {@link ComponentImplementation} will have its own * instance of {@link CurrentImplementationSubcomponent}. */ -@Subcomponent +@Subcomponent( + modules = CurrentImplementationSubcomponent.ChildComponentImplementationFactoryModule.class) @PerComponentImplementation // This only needs to be public because the type is referenced by generated component. public interface CurrentImplementationSubcomponent { - ComponentImplementationBuilder componentImplementationBuilder(); + ComponentImplementation componentImplementation(); + + /** A module to bind the {@link ChildComponentImplementationFactory}. */ + @Module + interface ChildComponentImplementationFactoryModule { + @Provides + static ChildComponentImplementationFactory provideChildComponentImplementationFactory( + CurrentImplementationSubcomponent.Builder currentImplementationSubcomponentBuilder, + Provider componentImplementatation, + Provider componentBindingExpressions, + Provider componentRequirementExpressions) { + return childGraph -> + currentImplementationSubcomponentBuilder + .bindingGraph(childGraph) + .parentImplementation(Optional.of(componentImplementatation.get())) + .parentBindingExpressions(Optional.of(componentBindingExpressions.get())) + .parentRequirementExpressions(Optional.of(componentRequirementExpressions.get())) + .build() + .componentImplementation(); + } + } /** Returns the builder for {@link CurrentImplementationSubcomponent}. */ @Subcomponent.Builder diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentCreatorImplementationFactory.java b/java/dagger/internal/codegen/writing/ComponentCreatorImplementationFactory.java similarity index 98% rename from java/dagger/internal/codegen/componentgenerator/ComponentCreatorImplementationFactory.java rename to java/dagger/internal/codegen/writing/ComponentCreatorImplementationFactory.java index 38a6d20fba7..6e4aa424a26 100644 --- a/java/dagger/internal/codegen/componentgenerator/ComponentCreatorImplementationFactory.java +++ b/java/dagger/internal/codegen/writing/ComponentCreatorImplementationFactory.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package dagger.internal.codegen.componentgenerator; +package dagger.internal.codegen.writing; import static com.google.auto.common.MoreTypes.asDeclared; import static com.google.common.base.Preconditions.checkArgument; @@ -52,9 +52,6 @@ import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.internal.codegen.writing.ComponentCreatorImplementation; -import dagger.internal.codegen.writing.ComponentImplementation; -import dagger.internal.codegen.writing.ModuleProxies; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; diff --git a/java/dagger/internal/codegen/writing/ComponentImplementation.java b/java/dagger/internal/codegen/writing/ComponentImplementation.java index 74fee5087b4..61037e50f35 100644 --- a/java/dagger/internal/codegen/writing/ComponentImplementation.java +++ b/java/dagger/internal/codegen/writing/ComponentImplementation.java @@ -16,6 +16,7 @@ package dagger.internal.codegen.writing; +import static com.google.auto.common.MoreTypes.asDeclared; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; @@ -33,6 +34,7 @@ import static dagger.internal.codegen.javapoet.AnnotationSpecs.suppressWarnings; import static dagger.internal.codegen.javapoet.CodeBlocks.parameterNames; import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; +import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.COMPONENT_METHOD; import static dagger.producers.CancellationPolicy.Propagation.PROPAGATE; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; @@ -44,9 +46,12 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.Sets; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; @@ -54,6 +59,7 @@ import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; +import dagger.internal.Preconditions; import dagger.internal.codegen.base.UniqueNameSet; import dagger.internal.codegen.binding.Binding; import dagger.internal.codegen.binding.BindingGraph; @@ -62,34 +68,53 @@ import dagger.internal.codegen.binding.ComponentCreatorDescriptor; import dagger.internal.codegen.binding.ComponentCreatorKind; import dagger.internal.codegen.binding.ComponentDescriptor; +import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; import dagger.internal.codegen.binding.ComponentRequirement; import dagger.internal.codegen.binding.KeyVariableNamer; +import dagger.internal.codegen.binding.MethodSignature; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.javapoet.CodeBlocks; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.javapoet.TypeSpecs; +import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.langmodel.DaggerTypes; +import dagger.internal.codegen.writing.ComponentImplementation.FieldSpecKind; +import dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; +import dagger.internal.codegen.writing.ComponentImplementation.TypeSpecKind; import dagger.model.BindingGraph.Node; import dagger.model.Key; import dagger.model.RequestKind; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import javax.inject.Inject; +import javax.inject.Provider; +import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; /** The implementation of a component type. */ @PerComponentImplementation public final class ComponentImplementation { + /** A factory for creating a {@link ComponentImplementation}. */ + public interface ChildComponentImplementationFactory { + /** Creates a {@link ComponentImplementation} for the given {@code childGraph}. */ + ComponentImplementation create(BindingGraph childGraph); + } + /** A type of field that this component can contain. */ public enum FieldSpecKind { /** A field for a component shard. */ - COMPONENT_SHARD, + COMPONENT_SHARD_FIELD, /** A field required by the component, e.g. module instances. */ COMPONENT_REQUIREMENT_FIELD, @@ -153,6 +178,9 @@ public enum TypeSpecKind { /** A provider class for a component provision. */ COMPONENT_PROVISION_FACTORY, + /** A class for a component shard. */ + COMPONENT_SHARD_TYPE, + /** A class for the subcomponent or subcomponent builder. */ SUBCOMPONENT } @@ -226,24 +254,42 @@ private static ImmutableList> bindingPartitions( private final List shardInitializations = new ArrayList<>(); private final List shardCancellations = new ArrayList<>(); private final Optional parent; + private final ChildComponentImplementationFactory childComponentImplementationFactory; + private final Provider bindingExpressionsProvider; + private final Provider + componentCreatorImplementationFactoryProvider; private final BindingGraph graph; private final ComponentNames componentNames; private final CompilerOptions compilerOptions; private final DaggerElements elements; + private final DaggerTypes types; + private final KotlinMetadataUtil metadataUtil; private final ImmutableMap componentFieldsByImplementation; @Inject ComponentImplementation( @ParentComponent Optional parent, + ChildComponentImplementationFactory childComponentImplementationFactory, + // Inject as Provider<> to prevent a cycle. + Provider bindingExpressionsProvider, + Provider componentCreatorImplementationFactoryProvider, BindingGraph graph, ComponentNames componentNames, CompilerOptions compilerOptions, - DaggerElements elements) { + DaggerElements elements, + DaggerTypes types, + KotlinMetadataUtil metadataUtil) { this.parent = parent; + this.childComponentImplementationFactory = childComponentImplementationFactory; + this.bindingExpressionsProvider = bindingExpressionsProvider; + this.componentCreatorImplementationFactoryProvider = + componentCreatorImplementationFactoryProvider; this.graph = graph; this.componentNames = componentNames; this.compilerOptions = compilerOptions; this.elements = elements; + this.types = types; + this.metadataUtil = metadataUtil; // The first group of keys belong to the component itself. We call this the componentShard. this.componentShard = new ShardImplementation(getComponentName(graph, parent, componentNames)); @@ -602,20 +648,16 @@ public void claimMethodName(CharSequence name) { } /** Generates the component and returns the resulting {@link TypeSpec.Builder}. */ - public TypeSpec generate() { + private TypeSpec generate() { TypeSpec.Builder builder = classBuilder(name); if (isComponentShard()) { TypeSpecs.addSupertype(builder, graph.componentTypeElement()); - - // Generate all shards and add them to this component implementation. - for (ShardImplementation shard : ImmutableSet.copyOf(shardsByBinding.values())) { - if (shardFieldsByImplementation.containsKey(shard)) { - builder.addField(shardFieldsByImplementation.get(shard)); - TypeSpec shardTypeSpec = shard.generate(); - builder.addType(shardTypeSpec); - } - } + addCreator(); + addFactoryMethods(); + addInterfaceMethods(); + addChildComponents(); + addShards(); } addConstructorAndInitializationMethods(); @@ -649,6 +691,137 @@ private ImmutableSet modifiers() { : ImmutableSet.of(FINAL); } + private void addCreator() { + componentCreatorImplementationFactoryProvider + .get() + .create() + .map(ComponentCreatorImplementation::spec) + .ifPresent( + creator -> { + if (parent.isPresent()) { + // In an inner implementation of a subcomponent the creator is a peer class. + parent.get().componentShard.addType(TypeSpecKind.SUBCOMPONENT, creator); + } else { + addType(TypeSpecKind.COMPONENT_CREATOR, creator); + } + }); + } + + private void addFactoryMethods() { + if (parent.isPresent()) { + graph.factoryMethod().ifPresent(this::createSubcomponentFactoryMethod); + } else { + createRootComponentFactoryMethod(); + } + } + + 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 = getCreatorName(); + factoryMethodName = "build"; + noArgFactoryMethod = true; + } + + MethodSpec creatorFactoryMethod = + methodBuilder(creatorKind.methodName()) + .addModifiers(PUBLIC, STATIC) + .returns(creatorType) + .addStatement("return new $T()", getCreatorName()) + .build(); + addMethod(MethodSpecKind.BUILDER_METHOD, creatorFactoryMethod); + if (noArgFactoryMethod && canInstantiateAllRequirements()) { + addMethod( + MethodSpecKind.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, metadataUtil)); + } + + private void createSubcomponentFactoryMethod(ExecutableElement factoryMethod) { + checkState(parent.isPresent()); + Collection params = + Maps.transformValues(graph.factoryMethodParameters(), ParameterSpec::get).values(); + DeclaredType parentType = asDeclared(parent.get().graph().componentTypeElement().asType()); + 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)", + name(), + parameterNames( + ImmutableList.builder() + .addAll( + creatorComponentFields().stream() + .map(field -> ParameterSpec.builder(field.type, field.name).build()) + .collect(toImmutableList())) + .addAll(params) + .build())); + + parent.get().getComponentShard().addMethod(COMPONENT_METHOD, method.build()); + } + + private void addInterfaceMethods() { + // Each component method may have been declared by several supertypes. We want to implement + // only one method for each distinct signature. + DeclaredType componentType = asDeclared(graph.componentTypeElement().asType()); + Set signatures = Sets.newHashSet(); + for (ComponentMethodDescriptor method : graph.componentDescriptor().entryPointMethods()) { + if (signatures.add(MethodSignature.forComponentMethod(method, componentType, types))) { + addMethod(COMPONENT_METHOD, bindingExpressionsProvider.get().getComponentMethod(method)); + } + } + } + + private void addChildComponents() { + for (BindingGraph subgraph : graph.subgraphs()) { + addType( + TypeSpecKind.SUBCOMPONENT, + childComponentImplementationFactory.create(subgraph).generate()); + } + } + + private void addShards() { + // Generate all shards and add them to this component implementation. + for (ShardImplementation shard : ImmutableSet.copyOf(shardsByBinding.values())) { + if (shardFieldsByImplementation.containsKey(shard)) { + addField(FieldSpecKind.COMPONENT_SHARD_FIELD, shardFieldsByImplementation.get(shard)); + TypeSpec shardTypeSpec = shard.generate(); + addType(TypeSpecKind.COMPONENT_SHARD_TYPE, shardTypeSpec); + } + } + } + /** Creates and adds the constructor and methods needed for initializing the component. */ private void addConstructorAndInitializationMethods() { MethodSpec.Builder constructor = constructorBuilder().addModifiers(PRIVATE);