dagger.internal.codegen.writing.ComponentImplementation Maven / Gradle / Ivy
Show all versions of dagger-compiler Show documentation
/*
 * Copyright (C) 2016 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.writing;
import static androidx.room.compiler.codegen.compat.XConverters.toJavaPoet;
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;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Suppliers.memoize;
import static dagger.internal.codegen.base.ComponentCreatorKind.BUILDER;
import static dagger.internal.codegen.binding.SourceFiles.simpleVariableName;
import static dagger.internal.codegen.extension.DaggerStreams.instancesOf;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap;
import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.COMPONENT_METHOD;
import static dagger.internal.codegen.xprocessing.XAnnotationSpecs.Suppression.UNCHECKED;
import static dagger.internal.codegen.xprocessing.XAnnotationSpecs.suppressWarnings;
import static dagger.internal.codegen.xprocessing.XCodeBlocks.concat;
import static dagger.internal.codegen.xprocessing.XCodeBlocks.isEmpty;
import static dagger.internal.codegen.xprocessing.XCodeBlocks.makeParametersCodeBlock;
import static dagger.internal.codegen.xprocessing.XCodeBlocks.parameterNames;
import static dagger.internal.codegen.xprocessing.XCodeBlocks.toParametersCodeBlock;
import static dagger.internal.codegen.xprocessing.XCodeBlocks.toXPoet;
import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
import static dagger.internal.codegen.xprocessing.XFunSpecs.constructorBuilder;
import static dagger.internal.codegen.xprocessing.XFunSpecs.methodBuilder;
import static dagger.internal.codegen.xprocessing.XFunSpecs.overriding;
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 static javax.tools.Diagnostic.Kind.ERROR;
import androidx.room.compiler.codegen.XClassName;
import androidx.room.compiler.codegen.XCodeBlock;
import androidx.room.compiler.codegen.XFunSpec;
import androidx.room.compiler.codegen.XParameterSpec;
import androidx.room.compiler.codegen.XPropertySpec;
import androidx.room.compiler.codegen.XTypeName;
import androidx.room.compiler.codegen.XTypeSpec;
import androidx.room.compiler.processing.XExecutableParameterElement;
import androidx.room.compiler.processing.XMessager;
import androidx.room.compiler.processing.XMethodElement;
import androidx.room.compiler.processing.XProcessingEnv;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XTypeElement;
import androidx.room.compiler.processing.XVariableElement;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
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.MultimapBuilder;
import com.squareup.javapoet.CodeBlock;
import dagger.internal.codegen.base.ComponentCreatorKind;
import dagger.internal.codegen.base.UniqueNameSet;
import dagger.internal.codegen.binding.Binding;
import dagger.internal.codegen.binding.BindingGraph;
import dagger.internal.codegen.binding.BindingNode;
import dagger.internal.codegen.binding.BindingRequest;
import dagger.internal.codegen.binding.CancellationPolicy;
import dagger.internal.codegen.binding.ComponentCreatorDescriptor;
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.binding.ModuleDescriptor;
import dagger.internal.codegen.compileroption.CompilerOptions;
import dagger.internal.codegen.langmodel.Accessibility;
import dagger.internal.codegen.model.BindingGraph.Node;
import dagger.internal.codegen.model.Key;
import dagger.internal.codegen.model.RequestKind;
import dagger.internal.codegen.xprocessing.XFunSpecs;
import dagger.internal.codegen.xprocessing.XParameterSpecs;
import dagger.internal.codegen.xprocessing.XPropertySpecs;
import dagger.internal.codegen.xprocessing.XTypeElements;
import dagger.internal.codegen.xprocessing.XTypeNames;
import dagger.internal.codegen.xprocessing.XTypeSpecs;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.lang.model.element.Modifier;
/** 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);
  }
  /** Compiler Modes. */
  public enum CompilerMode {
    DEFAULT,
    FAST_INIT;
    public boolean isFastInit() {
      return this == CompilerMode.FAST_INIT;
    }
  }
  /** A type of field that this component can contain. */
  public enum FieldSpecKind {
    /** A field for a component shard. */
    COMPONENT_SHARD_FIELD,
    /** A field required by the component, e.g. module instances. */
    COMPONENT_REQUIREMENT_FIELD,
    /** A framework field for type T, e.g. {@code Provider}. */
    FRAMEWORK_FIELD,
    /** A static field that always returns an absent {@code Optional} value for the binding. */
    ABSENT_OPTIONAL_FIELD
  }
  /** A type of method that this component can contain. */
  // TODO(bcorso, dpb): Change the oder to constructor, initialize, component, then private
  // (including MIM and AOM—why treat those separately?).
  public enum MethodSpecKind {
    /** The component constructor. */
    CONSTRUCTOR,
    /** A builder method for the component. (Only used by the root component.) */
    BUILDER_METHOD,
    /** A private method that wraps dependency expressions. */
    PRIVATE_METHOD,
    /** An initialization method that initializes component requirements and framework types. */
    INITIALIZE_METHOD,
    /** An implementation of a component interface method. */
    COMPONENT_METHOD,
    /** A private method that encapsulates members injection logic for a binding. */
    MEMBERS_INJECTION_METHOD,
    /** A static method that always returns an absent {@code Optional} value for the binding. */
    ABSENT_OPTIONAL_METHOD,
    /**
     * The {@link dagger.producers.internal.CancellationListener#onProducerFutureCancelled(boolean)}
     * method for a production component.
     */
    CANCELLATION_LISTENER_METHOD
  }
  /** A type of nested class that this component can contain. */
  public enum TypeSpecKind {
    /** A factory class for a present optional binding. */
    PRESENT_FACTORY,
    /** A class for the component creator (only used by the root component.) */
    COMPONENT_CREATOR,
    /** A provider class for a component provision. */
    COMPONENT_PROVISION_FACTORY,
    /** A class for the component/subcomponent or subcomponent builder implementation. */
    COMPONENT_IMPL,
    /** A class for a component shard. */
    COMPONENT_SHARD_TYPE
  }
  /**
   * Returns the {@link ShardImplementation} for each binding in this graph.
   *
   * Each shard contains approximately {@link CompilerOptions#keysPerComponentShard()} bindings.
   *
   * 
If more than 1 shard is needed, we iterate the strongly connected nodes to make sure of two
   * things: 1) bindings are put in shards in reverse topological order (i.e., bindings in Shard{i}
   * do not depend on bindings in Shard{i+j}) and 2) bindings belonging to the same cycle are put in
   * the same shard. These two guarantees allow us to initialize each shard in a well defined order.
   */
  private static ImmutableMap createShardsByBinding(
      ShardImplementation componentShard, BindingGraph graph, CompilerOptions compilerOptions) {
    ImmutableList> partitions = bindingPartitions(graph, compilerOptions);
    ImmutableMap.Builder builder = ImmutableMap.builder();
    for (int i = 0; i < partitions.size(); i++) {
      ShardImplementation shard = i == 0 ? componentShard : componentShard.createShard();
      partitions.get(i).forEach(binding -> builder.put(binding, shard));
    }
    return builder.build();
  }
  private static ImmutableList> bindingPartitions(
      BindingGraph graph, CompilerOptions compilerOptions) {
    int bindingsPerShard = compilerOptions.keysPerComponentShard(graph.componentTypeElement());
    int maxPartitions = (graph.localBindingNodes().size() / bindingsPerShard) + 1;
    if (maxPartitions <= 1) {
      return ImmutableList.of(
          graph.localBindingNodes().stream().map(BindingNode::delegate).collect(toImmutableList()));
    }
    // Iterate through all SCCs in order until all bindings local to this component are partitioned.
    List currPartition = new ArrayList<>(bindingsPerShard);
    ImmutableList.Builder> partitions =
        ImmutableList.builderWithExpectedSize(maxPartitions);
    for (ImmutableSet nodes : graph.topLevelBindingGraph().stronglyConnectedNodes()) {
      nodes.stream()
          .flatMap(instancesOf(BindingNode.class))
          .filter(bindingNode -> bindingNode.componentPath().equals(graph.componentPath()))
          .map(BindingNode::delegate)
          .forEach(currPartition::add);
      if (currPartition.size() >= bindingsPerShard) {
        partitions.add(ImmutableList.copyOf(currPartition));
        currPartition = new ArrayList<>(bindingsPerShard);
      }
    }
    if (!currPartition.isEmpty()) {
      partitions.add(ImmutableList.copyOf(currPartition));
    }
    return partitions.build();
  }
  /** The boolean parameter of the onProducerFutureCancelled method. */
  public static final XParameterSpec MAY_INTERRUPT_IF_RUNNING_PARAM =
      XParameterSpecs.of("mayInterruptIfRunning", XTypeName.PRIMITIVE_BOOLEAN);
  private static final String CANCELLATION_LISTENER_METHOD_NAME = "onProducerFutureCancelled";
  /**
   * How many statements per {@code initialize()} or {@code onProducerFutureCancelled()} method
   * before they get partitioned.
   *
   * This value has been set based on empirical performance analysis. If this number is too
   * large, some Android runtimes will not ahead-of-time compile the generated code. See
   * b/316617683.
   */
  private static final int STATEMENTS_PER_METHOD = 25;
  private final ShardImplementation componentShard;
  private final Supplier> shardsByBinding;
  private final Map shardFieldsByImplementation =
      new HashMap<>();
  private final List shardInitializations = new ArrayList<>();
  private final List shardCancellations = new ArrayList<>();
  private final Optional parent;
  private final ChildComponentImplementationFactory childComponentImplementationFactory;
  private final Provider topLevelImplementationProvider;
  private final Provider componentRequestRepresentationsProvider;
  private final Provider
      componentCreatorImplementationFactoryProvider;
  private final BindingGraph graph;
  private final ComponentNames componentNames;
  private final CompilerOptions compilerOptions;
  private final ImmutableMap
      componentFieldsByImplementation;
  private final XMessager messager;
  private final CompilerMode compilerMode;
  private final XProcessingEnv processingEnv;
  @Inject
  ComponentImplementation(
      @ParentComponent Optional parent,
      ChildComponentImplementationFactory childComponentImplementationFactory,
      // Inject as Provider<> to prevent a cycle.
      @TopLevel Provider topLevelImplementationProvider,
      Provider componentRequestRepresentationsProvider,
      Provider componentCreatorImplementationFactoryProvider,
      BindingGraph graph,
      ComponentNames componentNames,
      CompilerOptions compilerOptions,
      XMessager messager,
      XProcessingEnv processingEnv) {
    this.parent = parent;
    this.childComponentImplementationFactory = childComponentImplementationFactory;
    this.topLevelImplementationProvider = topLevelImplementationProvider;
    this.componentRequestRepresentationsProvider = componentRequestRepresentationsProvider;
    this.componentCreatorImplementationFactoryProvider =
        componentCreatorImplementationFactoryProvider;
    this.graph = graph;
    this.componentNames = componentNames;
    this.compilerOptions = compilerOptions;
    this.processingEnv = processingEnv;
    // The first group of keys belong to the component itself. We call this the componentShard.
    this.componentShard = new ShardImplementation(componentNames.get(graph.componentPath()));
    // Claim the method names for all local and inherited methods on the component type.
    XTypeElements.getAllNonPrivateInstanceMethods(graph.componentTypeElement()).stream()
        .forEach(method -> componentShard.componentMethodNames.claim(getSimpleName(method)));
    // Create the shards for this component, indexed by binding.
    this.shardsByBinding =
        memoize(() -> createShardsByBinding(componentShard, graph, compilerOptions));
    // Create and claim the fields for this and all ancestor components stored as fields.
    this.componentFieldsByImplementation =
        createComponentFieldsByImplementation(this, compilerOptions);
    this.messager = messager;
    XTypeElement typeElement = rootComponentImplementation().componentDescriptor().typeElement();
    this.compilerMode =
        compilerOptions.fastInit(typeElement) ? CompilerMode.FAST_INIT : CompilerMode.DEFAULT;
  }
  /**
   * Returns the shard for a given {@link Binding}.
   *
   * Each set of {@link CompilerOptions#keysPerShard()} will get its own shard instance.
   */
  public ShardImplementation shardImplementation(Binding binding) {
    checkState(
        shardsByBinding.get().containsKey(binding),
        "No shard in %s for: %s",
        name().getCanonicalName(),
        binding);
    return shardsByBinding.get().get(binding);
  }
  /** Returns the {@link GeneratedImplementation} for the top-level generated class. */
  private GeneratedImplementation topLevelImplementation() {
    return topLevelImplementationProvider.get();
  }
  /** Returns the root {@link ComponentImplementation}. */
  public ComponentImplementation rootComponentImplementation() {
    return parent.map(ComponentImplementation::rootComponentImplementation).orElse(this);
  }
  /** Returns a reference to this implementation when called from a different class. */
  public XCodeBlock componentFieldReference() {
    // TODO(bcorso): This currently relies on all requesting classes having a reference to the
    // component with the same name, which is kind of sketchy. Try to think of a better way that
    // can accomodate the component missing in some classes if it's not used.
    return XCodeBlock.of("%N", componentFieldsByImplementation.get(this));
  }
  /** Returns the fields for all components in the component path. */
  public ImmutableList componentFields() {
    return ImmutableList.copyOf(componentFieldsByImplementation.values());
  }
  /** Returns the fields for all components in the component path except the current component. */
  public ImmutableList creatorComponentFields() {
    return componentFieldsByImplementation.entrySet().stream()
        .filter(entry -> !this.equals(entry.getKey()))
        .map(Map.Entry::getValue)
        .collect(toImmutableList());
  }
  private static ImmutableMap
      createComponentFieldsByImplementation(
          ComponentImplementation componentImplementation, CompilerOptions compilerOptions) {
    checkArgument(
        componentImplementation.componentShard != null,
        "The component shard must be set before computing the component fields.");
    ImmutableList.Builder builder = ImmutableList.builder();
    for (ComponentImplementation curr = componentImplementation;
        curr != null;
        curr = curr.parent.orElse(null)) {
      builder.add(curr);
    }
    // For better readability when adding these fields/parameters to generated code, we collect the
    // component implementations in reverse order so that parents appear before children.
    return builder.build().reverse().stream()
        .collect(
            toImmutableMap(
                componentImpl -> componentImpl,
                componentImpl -> {
                  XClassName component =
                      componentImpl
                          .graph
                          .componentPath()
                          .currentComponent()
                          .xprocessing()
                          .asClassName();
                  XClassName fieldType = componentImpl.name();
                  String fieldName =
                      componentImpl.isNested()
                          ? simpleVariableName(componentImpl.name())
                          : simpleVariableName(component);
                  XPropertySpecs.Builder field =
                      XPropertySpecs.builder(
                          fieldName.equals(componentImpl.name().getSimpleName())
                              ? "_" + fieldName
                              : fieldName,
                          fieldType,
                          PRIVATE,
                          FINAL);
                  componentImplementation.componentShard.componentFieldNames.claim(fieldName);
                  return field.build();
                }));
  }
  /** Returns the shard representing the {@link ComponentImplementation} itself. */
  public ShardImplementation getComponentShard() {
    return componentShard;
  }
  /** Returns the binding graph for the component being generated. */
  public BindingGraph graph() {
    return componentShard.graph();
  }
  /** Returns the descriptor for the component being generated. */
  public ComponentDescriptor componentDescriptor() {
    return componentShard.componentDescriptor();
  }
  /** Returns the name of the component. */
  public XClassName name() {
    return componentShard.name;
  }
  /** Returns if the current compile mode is fast init. */
  public CompilerMode compilerMode() {
    return compilerMode;
  }
  /** Returns whether or not the implementation is nested within another class. */
  private boolean isNested() {
    return XTypeNames.enclosingClassName(name()) != null;
  }
  /**
   * Returns the name of the creator class for this component. It will be a sibling of this
   * generated class unless this is a top-level component, in which case it will be nested.
   */
  public XClassName getCreatorName() {
    return componentNames.getCreatorName(graph.componentPath());
  }
  /** Generates the component and returns the resulting {@link XTypeSpec}. */
  public XTypeSpec generate() {
    return componentShard.generate();
  }
  /**
   * The implementation of a shard.
   *
   * The purpose of a shard is to allow a component implementation to be split into multiple
   * classes, where each class owns the creation logic for a set of keys. Sharding is useful for
   * large component implementations, where a single component implementation class can reach size
   * limitations, such as the constant pool size.
   *
   * 
When generating the actual sources, the creation logic within the first instance of {@link
   * ShardImplementation} will go into the component implementation class itself (e.g. {@code
   * MySubcomponentImpl}). Each subsequent instance of {@link ShardImplementation} will generate a
   * nested "shard" class within the component implementation (e.g. {@code
   * MySubcomponentImpl.Shard1}, {@code MySubcomponentImpl.Shard2}, etc).
   */
  public final class ShardImplementation implements GeneratedImplementation {
    private final XClassName name;
    private final UniqueNameSet componentFieldNames = new UniqueNameSet();
    private final UniqueNameSet componentMethodNames = new UniqueNameSet();
    private final UniqueNameSet componentClassNames = new UniqueNameSet();
    private final UniqueNameSet assistedParamNames = new UniqueNameSet();
    private final List initializations = new ArrayList<>();
    private final SwitchingProviders switchingProviders;
    private final Map cancellations = new LinkedHashMap<>();
    private final Map uniqueAssistedName = new LinkedHashMap<>();
    private final List componentRequirementInitializations = new ArrayList<>();
    private final ImmutableMap constructorParameters;
    private final ListMultimap fieldSpecsMap =
        MultimapBuilder.enumKeys(FieldSpecKind.class).arrayListValues().build();
    private final ListMultimap methodSpecsMap =
        MultimapBuilder.enumKeys(MethodSpecKind.class).arrayListValues().build();
    private final ListMultimap typeSpecsMap =
        MultimapBuilder.enumKeys(TypeSpecKind.class).arrayListValues().build();
    private final List> typeSuppliers = new ArrayList<>();
    private boolean initialized = false; // This is used for initializing assistedParamNames.
    private ShardImplementation(XClassName name) {
      this.name = name;
      this.switchingProviders = new SwitchingProviders(this, processingEnv);
      if (graph.componentDescriptor().isProduction()) {
        claimMethodName(CANCELLATION_LISTENER_METHOD_NAME);
      }
      // Build the map of constructor parameters for this shard and claim the field names to prevent
      // collisions between the constructor parameters and fields.
      constructorParameters =
          constructorRequirements(graph).stream()
              .collect(
                  toImmutableMap(
                      requirement -> requirement,
                      requirement ->
                          XParameterSpecs.of(
                              getUniqueFieldName(requirement.variableName() + "Param"),
                              requirement.type().asTypeName(),
                              requirement.getNullability())));
    }
    private ShardImplementation createShard() {
      checkState(isComponentShard(), "Only the componentShard can create other shards.");
      return new ShardImplementation(
          topLevelImplementation()
              .name()
              .nestedClass(
                  topLevelImplementation()
                      .getUniqueClassName(getComponentShard().name().getSimpleName() + "Shard")));
    }
    /** Returns the {@link SwitchingProviders} class for this shard. */
    public SwitchingProviders getSwitchingProviders() {
      return switchingProviders;
    }
    /** Returns the {@link ComponentImplementation} that owns this shard. */
    public ComponentImplementation getComponentImplementation() {
      return ComponentImplementation.this;
    }
    /**
     * Returns {@code true} if this shard represents the component implementation rather than a
     * separate {@code Shard} class.
     */
    public boolean isComponentShard() {
      return this == componentShard;
    }
    /** Returns the fields for all components in the component path by component implementation. */
    public ImmutableMap componentFieldsByImplementation() {
      return componentFieldsByImplementation;
    }
    /** Returns a reference to this implementation when called from a different class. */
    public XCodeBlock shardFieldReference() {
      if (!isComponentShard() && !shardFieldsByImplementation.containsKey(this)) {
        // Add the shard if this is the first time it's requested by something.
        String shardFieldName =
            componentShard.getUniqueFieldName(UPPER_CAMEL.to(LOWER_CAMEL, name.getSimpleName()));
        XPropertySpec shardField = XPropertySpecs.builder(shardFieldName, name).build();
        shardFieldsByImplementation.put(this, shardField);
      }
      // TODO(bcorso): This currently relies on all requesting classes having a reference to the
      // component with the same name, which is kind of sketchy. Try to think of a better way that
      // can accomodate the component missing in some classes if it's not used.
      return isComponentShard()
          ? componentFieldReference()
          : XCodeBlock.of(
              "%L.%N", componentFieldReference(), shardFieldsByImplementation.get(this));
    }
    // TODO(ronshapiro): see if we can remove this method and instead inject it in the objects that
    // need it.
    /** Returns the binding graph for the component being generated. */
    public BindingGraph graph() {
      return graph;
    }
    /** Returns the descriptor for the component being generated. */
    public ComponentDescriptor componentDescriptor() {
      return graph.componentDescriptor();
    }
    @Override
    public XClassName name() {
      return name;
    }
    /**
     * Returns the name of the creator implementation class for the given subcomponent creator
     * {@link Key}.
     */
    XClassName getSubcomponentCreatorSimpleName(Key creatorKey) {
      return componentNames.getSubcomponentCreatorName(graph.componentPath(), creatorKey);
    }
    /**
     * Returns an accessible type for this shard implementation, returns Object if the type is not
     * accessible.
     *
     * This method checks accessibility for public types and package private types.
     */
    XTypeName accessibleTypeName(XType type) {
      return Accessibility.accessibleTypeName(type, name(), processingEnv);
    }
    /**
     * Returns {@code true} if {@code type} is accessible from the generated component.
     *
     * 
This method checks accessibility for public types and package private types.
     */
    boolean isTypeAccessible(XType type) {
      return Accessibility.isTypeAccessibleFrom(type, name.getPackageName());
    }
    // TODO(dpb): Consider taking XPropertySpec, and returning identical XPropertySpec with unique
    // name?
    /** Adds the given field to the component. */
    @Override
    public void addField(FieldSpecKind fieldKind, XPropertySpec fieldSpec) {
      fieldSpecsMap.put(fieldKind, fieldSpec);
    }
    // TODO(dpb): Consider taking XFunSpec, and returning identical XFunSpec with unique name?
    /** Adds the given method to the component. */
    @Override
    public void addMethod(MethodSpecKind methodKind, XFunSpec methodSpec) {
      methodSpecsMap.put(methodKind, methodSpec);
    }
    /** Adds the given type to the component. */
    @Override
    public void addType(TypeSpecKind typeKind, XTypeSpec typeSpec) {
      typeSpecsMap.put(typeKind, typeSpec);
    }
    /** Adds a {@link Supplier} for the SwitchingProvider for the component. */
    @Override
    public void addTypeSupplier(Supplier typeSpecSupplier) {
      typeSuppliers.add(typeSpecSupplier);
    }
    /** Adds the given code block to the initialize methods of the component. */
    void addInitialization(XCodeBlock codeBlock) {
      initializations.add(codeBlock);
    }
    /** Adds the given code block that initializes a {@link ComponentRequirement}. */
    void addComponentRequirementInitialization(XCodeBlock codeBlock) {
      componentRequirementInitializations.add(codeBlock);
    }
    /**
     * Adds the given cancellation statement to the cancellation listener method of the component.
     */
    void addCancellation(Key key, XCodeBlock codeBlock) {
      // Store cancellations by key to avoid adding the same cancellation twice.
      cancellations.putIfAbsent(key, codeBlock);
    }
    /** Returns a new, unique field name for the component based on the given name. */
    String getUniqueFieldName(String name) {
      return componentFieldNames.getUniqueName(name);
    }
    String getUniqueAssistedParamName(String name) {
      if (!initialized) {
        // Assisted params will be used in switching provider, so they can't conflict with component
        // field names in switching provider. {@link UniqueNameSet#getUniqueName} will add the
        // component field names to the unique set if it does not exists. If the name already exists
        // in the set, then a dedupe will be performed automatically on the passed in name, and the
        // newly created unique name will then be added to the set.
        componentFieldsByImplementation()
            .values()
            .forEach(fieldSpec -> assistedParamNames.getUniqueName(toJavaPoet(fieldSpec).name));
        initialized = true;
      }
      return assistedParamNames.getUniqueName(name);
    }
    public String getUniqueFieldNameForAssistedParam(XExecutableParameterElement parameter) {
      if (uniqueAssistedName.containsKey(parameter)) {
        return uniqueAssistedName.get(parameter);
      }
      String name = getUniqueAssistedParamName(parameter.getJvmName());
      uniqueAssistedName.put(parameter, name);
      return name;
    }
    /** Returns a new, unique nested class name for the component based on the given name. */
    public String getUniqueMethodName(String name) {
      return componentMethodNames.getUniqueName(name);
    }
    /** Returns a new, unique method name for a getter method for the given request. */
    String getUniqueMethodName(BindingRequest request) {
      return uniqueMethodName(request, KeyVariableNamer.name(request.key()));
    }
    @Override
    public String getUniqueClassName(String name) {
      return componentClassNames.getUniqueName(name);
    }
    private String uniqueMethodName(BindingRequest request, String bindingName) {
      // This name is intentionally made to match the name for fields in fastInit
      // in order to reduce the constant pool size. b/162004246
      String baseMethodName =
          bindingName
              + (request.isRequestKind(RequestKind.INSTANCE)
                  ? ""
                  : UPPER_UNDERSCORE.to(UPPER_CAMEL, request.kindName()));
      return getUniqueMethodName(baseMethodName);
    }
    /**
     * Gets the parameter name to use for the given requirement for this component, starting with
     * the given base name if no parameter name has already been selected for the requirement.
     */
    public String getParameterName(ComponentRequirement requirement) {
      return toJavaPoet(constructorParameters.get(requirement)).name;
    }
    /** Claims a new method name for the component. Does nothing if method name already exists. */
    public void claimMethodName(CharSequence name) {
      componentMethodNames.claim(name);
    }
    public boolean isShardClassPrivate() {
      return modifiers().contains(PRIVATE);
    }
    @Override
    public XTypeSpec generate() {
      XTypeSpecs.Builder builder = XTypeSpecs.classBuilder(name);
      // Ksp requires explicitly associating input classes that are generated with the output class,
      // otherwise, the cached generated classes won't be discoverable in an incremental build.
      if (processingEnv.getBackend() == XProcessingEnv.Backend.KSP) {
        graph.componentDescriptor().modules().stream()
            .filter(ModuleDescriptor::isImplicitlyIncluded)
            .forEach(module -> builder.addOriginatingElement(module.moduleElement()));
      }
      if (isComponentShard()) {
        builder.superType(graph.componentTypeElement());
        addCreator();
        addFactoryMethods();
        addInterfaceMethods();
        addChildComponents();
        addShards();
      }
      addConstructorAndInitializationMethods();
      if (graph.componentDescriptor().isProduction()) {
        if (isComponentShard() || !cancellations.isEmpty()) {
          builder.superType(processingEnv.requireTypeElement(XTypeNames.CANCELLATION_LISTENER));
          addCancellationListenerImplementation();
        }
      }
      modifiers().forEach(builder::addModifiers);
      fieldSpecsMap.asMap().values().forEach(builder::addProperties);
      methodSpecsMap.asMap().values().forEach(builder::addFunctions);
      typeSpecsMap.asMap().values().forEach(builder::addTypes);
      typeSuppliers.stream().map(Supplier::get).forEach(builder::addType);
      if (!compilerOptions.generatedClassExtendsComponent()
          && isComponentShard()
          && graph.componentPath().atRoot()) {
        topLevelImplementation().addType(TypeSpecKind.COMPONENT_IMPL, builder.build());
        return topLevelImplementation().generate();
      }
      return builder.build();
    }
    private ImmutableSet modifiers() {
      return isNested() || !isComponentShard()
          ? ImmutableSet.of(PRIVATE, STATIC, FINAL)
          : graph.componentTypeElement().isPublic()
              // TODO(ronshapiro): perhaps all generated components should be non-public?
              ? ImmutableSet.of(PUBLIC, FINAL)
              : ImmutableSet.of(FINAL);
    }
    private void addCreator() {
      componentCreatorImplementationFactoryProvider
          .get()
          .create()
          .map(ComponentCreatorImplementation::spec)
          .ifPresent(
              creator -> topLevelImplementation().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;
      XClassName creatorType;
      String factoryMethodName;
      boolean noArgFactoryMethod;
      Optional creatorDescriptor =
          graph.componentDescriptor().creatorDescriptor();
      if (creatorDescriptor.isPresent()) {
        ComponentCreatorDescriptor descriptor = creatorDescriptor.get();
        creatorKind = descriptor.kind();
        creatorType = descriptor.typeElement().asClassName();
        factoryMethodName = getSimpleName(descriptor.factoryMethod());
        noArgFactoryMethod = descriptor.factoryParameters().isEmpty();
      } else {
        creatorKind = BUILDER;
        creatorType = getCreatorName();
        factoryMethodName = "build";
        noArgFactoryMethod = true;
      }
      validateMethodNameDoesNotOverrideGeneratedCreator(creatorKind.methodName());
      claimMethodName(creatorKind.methodName());
      topLevelImplementation()
          .addMethod(
              MethodSpecKind.BUILDER_METHOD,
              XFunSpecs.methodBuilder(creatorKind.methodName())
                  .addModifiers(PUBLIC, STATIC)
                  .returns(creatorType)
                  .addStatement("return %L", XCodeBlock.ofNewInstance(getCreatorName(), ""))
                  .build());
      if (noArgFactoryMethod && canInstantiateAllRequirements()) {
        validateMethodNameDoesNotOverrideGeneratedCreator("create");
        claimMethodName("create");
        topLevelImplementation()
            .addMethod(
                MethodSpecKind.BUILDER_METHOD,
                methodBuilder("create")
                    .returns(graph.componentTypeElement().asClassName())
                    .addModifiers(PUBLIC, STATIC)
                    .addStatement(
                        // TODO(bcorso): Convert this to XCodeBlock.ofNewInstance().
                        toXPoet(
                            CodeBlock.of(
                                "return new $L().$L()", creatorKind.typeName(), factoryMethodName)))
                    .build());
      }
    }
    // TODO(bcorso): This can be removed once we delete generatedClassExtendsComponent flag.
    private void validateMethodNameDoesNotOverrideGeneratedCreator(String creatorName) {
      // Check if there is any client added method has the same signature as generated creatorName.
      XTypeElements.getAllMethods(graph.componentTypeElement()).stream()
          .filter(method -> getSimpleName(method).contentEquals(creatorName))
          .filter(method -> method.getParameters().isEmpty())
          .filter(method -> !method.isStatic())
          .forEach(
              (XMethodElement method) ->
                  messager.printMessage(
                      ERROR,
                      String.format(
                          "The method %s.%s() conflicts with a method of the same name Dagger is "
                          + "trying to generate as a way to instantiate the component. Please "
                              + "choose a different name for your method.",
                          method.getEnclosingElement().getClassName().canonicalName(),
                          getSimpleName(method))));
    }
    /** {@code true} if all of the graph's required dependencies can be automatically constructed */
    private boolean canInstantiateAllRequirements() {
      return !Iterables.any(
          graph.componentRequirements(), ComponentRequirement::requiresAPassedInstance);
    }
    private void createSubcomponentFactoryMethod(XMethodElement factoryMethod) {
      checkState(parent.isPresent());
      XType parentType = parent.get().graph().componentTypeElement().getType();
      XFunSpecs.Builder method = overriding(factoryMethod, parentType);
      // Use the parameter names from the overriding method, which may be different from the
      // parameter names at the declaration site if it is pulled in as a class dependency from a
      // separate build unit (see https://github.com/google/dagger/issues/3401).
      method.getParameters()
          .forEach(
              param ->
                  method.addStatement(
                      "%T.checkNotNull(%N)",
                      XTypeNames.DAGGER_PRECONDITIONS,
                      param.getName())); // SUPPRESS_GET_NAME_CHECK
      method.addStatement(
          "return %L",
          XCodeBlock.ofNewInstance(
              name(),
              "%L",
              Stream.concat(
                      creatorComponentFields().stream().map(field -> XCodeBlock.of("%N", field)),
                      method.getParameters().stream()
                          .map(param -> XCodeBlock.of("%N", param.getName()))) // SUPPRESS_GET_NAME_CHECK
                  .collect(toParametersCodeBlock())));
      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.
      XType componentType = graph.componentTypeElement().getType();
      Set methodDescriptors = new HashSet<>();
      for (ComponentMethodDescriptor method : graph.entryPointMethods()) {
        MethodSignature signature =
            MethodSignature.forComponentMethod(method, componentType, processingEnv);
        if (methodDescriptors.add(signature)) {
          addMethod(
              COMPONENT_METHOD,
              componentRequestRepresentationsProvider.get().getComponentMethod(method));
        }
      }
    }
    private void addChildComponents() {
      for (BindingGraph subgraph : graph.subgraphs()) {
        topLevelImplementation()
            .addType(
                TypeSpecKind.COMPONENT_IMPL,
                childComponentImplementationFactory.create(subgraph).generate());
      }
    }
    private void addShards() {
      // Generate all shards and add them to this component implementation.
      for (ShardImplementation shard : ImmutableSet.copyOf(shardsByBinding.get().values())) {
        if (shardFieldsByImplementation.containsKey(shard)) {
          addField(FieldSpecKind.COMPONENT_SHARD_FIELD, shardFieldsByImplementation.get(shard));
          topLevelImplementation().addType(TypeSpecKind.COMPONENT_SHARD_TYPE, shard.generate());
        }
      }
    }
    /** Creates and adds the constructor and methods needed for initializing the component. */
    private void addConstructorAndInitializationMethods() {
      XFunSpecs.Builder constructor = constructorBuilder();
      // TODO(bcorso): remove once dagger.generatedClassExtendsComponent flag is removed.
      if (!isShardClassPrivate()) {
        constructor.addModifiers(PRIVATE);
      }
      ImmutableList parameters = constructorParameters.values().asList();
      // Add a constructor parameter and initialization for each component field. We initialize
      // these fields immediately so that we don't need to be pass them to each initialize method
      // and shard constructor.
      componentFieldsByImplementation()
          .forEach(
              (componentImplementation, field) -> {
                if (isComponentShard()
                    && componentImplementation.equals(ComponentImplementation.this)) {
                  // For the self-referenced component field,
                  // just initialize it in the initializer.
                  addField(
                      FieldSpecKind.COMPONENT_REQUIREMENT_FIELD,
                      XPropertySpecs.builder(
                              field.getName(), field.getType()) // SUPPRESS_GET_NAME_CHECK
                          .addModifiers(toJavaPoet(field).modifiers)
                          .initializer("this")
                          .build());
                } else {
                  addField(FieldSpecKind.COMPONENT_REQUIREMENT_FIELD, field);
                  constructor.addStatement("this.%1N = %1N", field);
                  constructor.addParameter(
                      field.getName(), field.getType());  // SUPPRESS_GET_NAME_CHECK
                }
              });
      if (isComponentShard()) {
        constructor.addCode(concat(componentRequirementInitializations));
      }
      constructor.addParameters(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.
      XCodeBlock args = parameterNames(parameters);
      ImmutableList initializationMethods =
          createPartitionedMethods(
              "initialize",
              // TODO(bcorso): Rather than passing in all of the constructor parameters, keep track
              // of which parameters are used during initialization and only pass those. This could
              // be useful for FastInit, where most of the initializations are just calling
              // SwitchingProvider with no parameters.
              makeFinal(parameters),
              initializations,
              methodName ->
                  methodBuilder(methodName)
                      /* 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(suppressWarnings(UNCHECKED)));
      for (XFunSpec initializationMethod : initializationMethods) {
        constructor.addStatement("%N(%L)", initializationMethod, args);
        addMethod(MethodSpecKind.INITIALIZE_METHOD, initializationMethod);
      }
      if (isComponentShard()) {
        constructor.addCode(concat(shardInitializations));
      } else {
        // This initialization is called from the componentShard, so we need to use those args.
        XCodeBlock componentArgs =
            parameterNames(componentShard.constructorParameters.values().asList());
        XCodeBlock componentFields =
            componentFieldsByImplementation().values().stream()
                .map(field -> XCodeBlock.of("%N", field))
                .collect(toParametersCodeBlock());
        shardInitializations.add(
            XCodeBlock.of(
                "%N = %L;",
                shardFieldsByImplementation.get(this),
                XCodeBlock.ofNewInstance(
                    name,
                    "%L",
                    isEmpty(componentArgs)
                        ? componentFields
                        : makeParametersCodeBlock(
                            ImmutableList.of(componentFields, componentArgs)))));
      }
      addMethod(MethodSpecKind.CONSTRUCTOR, constructor.build());
    }
    private void addCancellationListenerImplementation() {
      XFunSpecs.Builder methodBuilder =
          methodBuilder(CANCELLATION_LISTENER_METHOD_NAME)
              .addModifiers(PUBLIC)
              .addAnnotation(Override.class)
              .addParameter(MAY_INTERRUPT_IF_RUNNING_PARAM);
      // 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.
      if (isComponentShard()) {
        methodBuilder.addCode(concat(ImmutableList.copyOf(shardCancellations).reverse()));
      } else if (!cancellations.isEmpty()) {
        shardCancellations.add(
            XCodeBlock.of(
                "%N.%N(%N);",
                shardFieldsByImplementation.get(this),
                CANCELLATION_LISTENER_METHOD_NAME,
                MAY_INTERRUPT_IF_RUNNING_PARAM.getName())); // SUPPRESS_GET_NAME_CHECK
      }
      ImmutableList cancellationStatements =
          ImmutableList.copyOf(cancellations.values()).reverse();
      if (cancellationStatements.size() < STATEMENTS_PER_METHOD) {
        methodBuilder.addCode(concat(cancellationStatements));
      } else {
        ImmutableList cancelProducersMethods =
            createPartitionedMethods(
                "cancelProducers",
                ImmutableList.of(MAY_INTERRUPT_IF_RUNNING_PARAM),
                cancellationStatements,
                methodName -> methodBuilder(methodName).addModifiers(PRIVATE));
        for (XFunSpec cancelProducersMethod : cancelProducersMethods) {
          methodBuilder.addStatement(
              "%N(%N)",
              cancelProducersMethod,
              MAY_INTERRUPT_IF_RUNNING_PARAM.getName()); // SUPPRESS_GET_NAME_CHECK
          addMethod(MethodSpecKind.CANCELLATION_LISTENER_METHOD, cancelProducersMethod);
        }
      }
      if (isComponentShard()) {
        cancelParentStatement().ifPresent(methodBuilder::addCode);
      }
      addMethod(MethodSpecKind.CANCELLATION_LISTENER_METHOD, methodBuilder.build());
    }
    private Optional cancelParentStatement() {
      if (!shouldPropagateCancellationToParent()) {
        return Optional.empty();
      }
      return Optional.of(
          XCodeBlock.builder()
              .addStatement(
                  "%L.%N(%N)",
                  parent.get().componentFieldReference(),
                  CANCELLATION_LISTENER_METHOD_NAME,
                  MAY_INTERRUPT_IF_RUNNING_PARAM.getName()) // SUPPRESS_GET_NAME_CHECK
              .build());
    }
    private boolean shouldPropagateCancellationToParent() {
      return parent.isPresent()
          && parent
              .get()
              .componentDescriptor()
              .cancellationPolicy()
              .map(policy -> policy.equals(CancellationPolicy.PROPAGATE))
              .orElse(false);
    }
    /**
     * 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,
        Collection parameters,
        List statements,
        Function methodBuilderCreator) {
      return Lists.partition(statements, STATEMENTS_PER_METHOD).stream()
          .map(
              partition ->
                  methodBuilderCreator
                      .apply(getUniqueMethodName(methodName))
                      .addModifiers(PRIVATE)
                      .addParameters(parameters)
                      .addCode(concat(partition))
                      .build())
          .collect(toImmutableList());
    }
  }
  private static ImmutableList constructorRequirements(BindingGraph graph) {
    if (graph.componentDescriptor().hasCreator()) {
      return graph.componentRequirements().asList();
    } else if (graph.factoryMethod().isPresent()) {
      return graph.factoryMethodParameters().keySet().asList();
    } else {
      throw new AssertionError(
          "Expected either a component creator or factory method but found neither.");
    }
  }
  private static ImmutableList makeFinal(List parameters) {
    return parameters.stream()
        .map(
            param ->
                XParameterSpecs.builder(param.getName(), param.getType()) // SUPPRESS_GET_NAME_CHECK
                    .addModifiers(FINAL)
                    .build())
        .collect(toImmutableList());
  }
}