All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dagger.hilt.processor.internal.root.RootGenerator Maven / Gradle / Ivy

There is a newer version: 2.52
Show newest version
/*
 * Copyright (C) 2020 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.hilt.processor.internal.root;

import static com.google.common.base.Preconditions.checkState;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;

import androidx.room.compiler.processing.JavaPoetExtKt;
import androidx.room.compiler.processing.XProcessingEnv;
import androidx.room.compiler.processing.XTypeElement;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.Graphs;
import com.google.common.graph.MutableGraph;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import dagger.hilt.processor.internal.ClassNames;
import dagger.hilt.processor.internal.ComponentDescriptor;
import dagger.hilt.processor.internal.ComponentNames;
import dagger.hilt.processor.internal.Processors;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.lang.model.element.Modifier;

/** Generates components and any other classes needed for a root. */
final class RootGenerator {

  static void generate(
      ComponentTreeDepsMetadata componentTreeDepsMetadata,
      RootMetadata metadata,
      ComponentNames componentNames,
      XProcessingEnv env)
      throws IOException {
    new RootGenerator(
            componentTreeDepsMetadata,
            RootMetadata.copyWithNewTree(metadata, filterDescriptors(metadata.componentTree())),
            componentNames,
            env)
        .generateComponents();
  }

  private final XTypeElement originatingElement;
  private final RootMetadata metadata;
  private final XProcessingEnv env;
  private final Root root;
  private final Map simpleComponentNamesToDedupeSuffix = new HashMap<>();
  private final Map componentNameMap = new HashMap<>();
  private final ComponentNames componentNames;

  private RootGenerator(
      ComponentTreeDepsMetadata componentTreeDepsMetadata,
      RootMetadata metadata,
      ComponentNames componentNames,
      XProcessingEnv env) {
    this.originatingElement = env.requireTypeElement(componentTreeDepsMetadata.name().toString());
    this.metadata = metadata;
    this.componentNames = componentNames;
    this.env = env;
    this.root = metadata.root();
  }

  private void generateComponents() throws IOException {

    // TODO(bcorso): Consider moving all of this logic into ComponentGenerator?
    ClassName componentsWrapperClassName = getComponentsWrapperClassName();
    TypeSpec.Builder componentsWrapper =
        TypeSpec.classBuilder(componentsWrapperClassName)
            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
            .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build());

    Processors.addGeneratedAnnotation(componentsWrapper, env, ClassNames.ROOT_PROCESSOR.toString());

    ImmutableMap subcomponentBuilderModules =
        subcomponentBuilderModules(componentsWrapper);

    ComponentTree componentTree = metadata.componentTree();
    for (ComponentDescriptor componentDescriptor : componentTree.getComponentDescriptors()) {
      ImmutableSet modules =
          ImmutableSet.builder()
              .addAll(
                  metadata.modules(componentDescriptor.component()).stream()
                      .map(XTypeElement::getClassName)
                      .collect(toImmutableSet()))
              .addAll(
                  componentTree.childrenOf(componentDescriptor).stream()
                      .map(subcomponentBuilderModules::get)
                      .collect(toImmutableSet()))
              .build();

      componentsWrapper.addType(
          new ComponentGenerator(
                  env,
                  getComponentClassName(componentDescriptor),
                  Optional.empty(),
                  modules,
                  metadata.entryPoints(componentDescriptor.component()),
                  metadata.scopes(componentDescriptor.component()),
                  ImmutableList.of(),
                  componentAnnotation(componentDescriptor),
                  componentBuilder(componentDescriptor))
              .typeSpecBuilder()
              .addModifiers(Modifier.STATIC)
              .build());
    }

    JavaFile componentsWrapperJavaFile =
        JavaFile.builder(componentsWrapperClassName.packageName(), componentsWrapper.build())
            .build();
    RootFileFormatter.write(componentsWrapperJavaFile, env);
  }

  private static ComponentTree filterDescriptors(ComponentTree componentTree) {
    MutableGraph graph =
        GraphBuilder.from(componentTree.graph()).build();

    componentTree.graph().nodes().forEach(graph::addNode);
    componentTree.graph().edges().forEach(graph::putEdge);

    // Remove components that do not have builders (besides the root component) since if
    // we didn't find any builder class, then we don't need to generate the component
    // since it would be inaccessible.
    componentTree.getComponentDescriptors().stream()
        .filter(descriptor -> !descriptor.isRoot() && !descriptor.creator().isPresent())
        .forEach(graph::removeNode);

    // The graph may still have nodes that are children of components that don't have builders,
    // so we need to find reachable nodes from the root and create a new graph to remove those.
    // We reuse the root from the original tree since it should not have been removed.
    return ComponentTree.from(Graphs.reachableNodes(graph, componentTree.root()));
  }

  private ImmutableMap subcomponentBuilderModules(
      TypeSpec.Builder componentsWrapper) {
    ImmutableMap.Builder modules = ImmutableMap.builder();
    for (ComponentDescriptor descriptor : metadata.componentTree().getComponentDescriptors()) {
      // Root component builders don't have subcomponent builder modules
      if (!descriptor.isRoot() && descriptor.creator().isPresent()) {
        ClassName component = getComponentClassName(descriptor);
        ClassName builder = descriptor.creator().get();
        ClassName module = component.peerClass(component.simpleName() + "BuilderModule");
        componentsWrapper.addType(subcomponentBuilderModule(component, builder, module));
        modules.put(descriptor, module);
      }
    }
    return modules.build();
  }

  // Generates:
  // @Module(subcomponents = FooSubcomponent.class)
  // interface FooSubcomponentBuilderModule {
  //   @Binds FooSubcomponentInterfaceBuilder bind(FooSubcomponent.Builder builder);
  // }
  private TypeSpec subcomponentBuilderModule(
      ClassName componentName, ClassName builderName, ClassName moduleName) {
    TypeSpec.Builder subcomponentBuilderModule =
        TypeSpec.interfaceBuilder(moduleName)
            .addModifiers(ABSTRACT)
            .addAnnotation(
                AnnotationSpec.builder(ClassNames.MODULE)
                    .addMember("subcomponents", "$T.class", componentName)
                    .build())
            .addAnnotation(ClassNames.DISABLE_INSTALL_IN_CHECK)
            .addMethod(
                MethodSpec.methodBuilder("bind")
                    .addModifiers(ABSTRACT, PUBLIC)
                    .addAnnotation(ClassNames.BINDS)
                    .returns(builderName)
                    .addParameter(componentName.nestedClass("Builder"), "builder")
                    .build());
    JavaPoetExtKt.addOriginatingElement(subcomponentBuilderModule, originatingElement);
    Processors.addGeneratedAnnotation(
        subcomponentBuilderModule, env, ClassNames.ROOT_PROCESSOR.toString());

    return subcomponentBuilderModule.build();
  }

  private Optional componentBuilder(ComponentDescriptor descriptor) {
    return descriptor
        .creator()
        .map(
            creator -> {
              TypeSpec.Builder builder =
                  TypeSpec.interfaceBuilder("Builder")
                      .addModifiers(STATIC, ABSTRACT)
                      .addSuperinterface(creator)
                      .addAnnotation(componentBuilderAnnotation(descriptor));
              JavaPoetExtKt.addOriginatingElement(builder, originatingElement);
              return builder.build();
            });
  }

  private ClassName componentAnnotation(ComponentDescriptor componentDescriptor) {
    if (!componentDescriptor.isRoot()
        ) {
      return ClassNames.SUBCOMPONENT;
    } else {
      return ClassNames.COMPONENT;
    }
  }

  private ClassName componentBuilderAnnotation(ComponentDescriptor componentDescriptor) {
    if (componentDescriptor.isRoot()) {
      return ClassNames.COMPONENT_BUILDER;
    } else {
      return ClassNames.SUBCOMPONENT_BUILDER;
    }
  }

  private ClassName getPartialRootModuleClassName() {
    return getComponentsWrapperClassName().nestedClass("PartialRootModule");
  }

  private ClassName getComponentsWrapperClassName() {
    return componentNames.generatedComponentsWrapper(root.originatingRootClassname());
  }

  private ClassName getComponentClassName(ComponentDescriptor componentDescriptor) {
    if (componentNameMap.containsKey(componentDescriptor)) {
      return componentNameMap.get(componentDescriptor);
    }

    // Disallow any component names with the same name as our SingletonComponent because we treat
    // that component specially and things may break.
    checkState(
        componentDescriptor.component().equals(ClassNames.SINGLETON_COMPONENT)
        || !componentDescriptor.component().simpleName().equals(
            ClassNames.SINGLETON_COMPONENT.simpleName()),
        "Cannot have a component with the same simple name as the reserved %s: %s",
        ClassNames.SINGLETON_COMPONENT.simpleName(),
        componentDescriptor.component());

    ClassName generatedComponent =
        componentNames.generatedComponent(
            root.originatingRootClassname(), componentDescriptor.component());

    Integer suffix = simpleComponentNamesToDedupeSuffix.get(generatedComponent.simpleName());
    if (suffix != null) {
      // If an entry exists, use the suffix in the map and the replace it with the value incremented
      generatedComponent = Processors.append(generatedComponent, String.valueOf(suffix));
      simpleComponentNamesToDedupeSuffix.put(generatedComponent.simpleName(), suffix + 1);
    } else {
      // Otherwise, just add an entry for any possible future duplicates
      simpleComponentNamesToDedupeSuffix.put(generatedComponent.simpleName(), 2);
    }

    componentNameMap.put(componentDescriptor, generatedComponent);
    return generatedComponent;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy