/* * Copyright (C) 2019 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; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static java.util.Comparator.comparing; import com.google.common.base.Joiner; import com.google.common.base.Utf8; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; /** Generates a Dagger component or subcomponent interface. */ // TODO(bcorso): Make this non-public public final class ComponentGenerator { private static final Joiner JOINER = Joiner.on("."); private static final Comparator SIMPLE_NAME_SORTER = Comparator.comparing((ClassName c) -> JOINER.join(c.simpleNames())) .thenComparing(ClassName::compareTo); private static final Comparator TYPE_NAME_SORTER = comparing(TypeName::toString); private final ProcessingEnvironment processingEnv; private final ClassName name; private final TypeElement rootElement; private final Optional superclass; private final ImmutableList modules; private final ImmutableList entryPoints; private final ImmutableCollection scopes; private final ImmutableList extraAnnotations; private final ClassName componentAnnotation; private final Optional componentBuilder; public ComponentGenerator( ProcessingEnvironment processingEnv, ClassName name, TypeElement rootElement, Optional superclass, Set modules, Set entryPoints, ImmutableCollection scopes, ImmutableList extraAnnotations, ClassName componentAnnotation, Optional componentBuilder) { this.processingEnv = processingEnv; this.name = name; this.rootElement = rootElement; this.superclass = superclass; this.modules = modules.stream().sorted(SIMPLE_NAME_SORTER).collect(toImmutableList()); this.entryPoints = entryPoints.stream().sorted(TYPE_NAME_SORTER).collect(toImmutableList()); this.scopes = scopes; this.extraAnnotations = extraAnnotations; this.componentAnnotation = componentAnnotation; this.componentBuilder = componentBuilder; } public TypeSpec generate() throws IOException { TypeSpec.Builder generator = TypeSpec.classBuilder(name) // Public because components from a scope below must reference to create .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) .addOriginatingElement(rootElement) .addAnnotation(getComponentAnnotation()); componentBuilder.ifPresent(generator::addType); scopes.forEach(generator::addAnnotation); addEntryPoints(generator); superclass.ifPresent(generator::superclass); generator.addAnnotations(extraAnnotations); return generator.build(); } /** Returns the component annotation with the list of modules to install for the component. */ private AnnotationSpec getComponentAnnotation() { AnnotationSpec.Builder builder = AnnotationSpec.builder(componentAnnotation); modules.forEach(module -> builder.addMember("modules", "$T.class", module)); return builder.build(); } /** * Adds entry points to the component. * * See b/140979968. If the entry points exceed 65763 bytes, we have to partition them to avoid the * limit. To be safe, we split at 60000 bytes. */ private void addEntryPoints(TypeSpec.Builder builder) throws IOException { int currBytes = 0; List partitionIndexes = new ArrayList<>(); partitionIndexes.add(0); for (int i = 0; i < entryPoints.size(); i++) { // This over estimates the actual length because it includes the fully qualified name (FQN). // TODO(bcorso): Have a better way to estimate the upper bound. For example, most types will // not include the FQN, but we'll have to consider all of the different subtypes of TypeName, // simple name collisions, etc... int nextBytes = Utf8.encodedLength(entryPoints.get(i).toString()); // To be safe, we split at 60000 to account for the component name, spaces, commas, etc... if (currBytes + nextBytes > 60000) { partitionIndexes.add(i); currBytes = 0; } currBytes += nextBytes; } partitionIndexes.add(entryPoints.size()); if (partitionIndexes.size() <= 2) { // No extra partitions are needed, so just add all of the entrypoints as is. builder.addSuperinterfaces(entryPoints); } else { // Create interfaces for each partition. // The partitioned interfaces will be added to the component instead of the real entry points. for (int i = 1; i < partitionIndexes.size(); i++) { int startIndex = partitionIndexes.get(i - 1); int endIndex = partitionIndexes.get(i); builder.addSuperinterface( createPartitionInterface(entryPoints.subList(startIndex, endIndex), i)); } } } private ClassName createPartitionInterface(List partition, int partitionIndex) throws IOException { // TODO(bcorso): Nest the partion inside the HiltComponents wrapper rather than appending name ClassName partitionName = Processors.append( Processors.getEnclosedClassName(name), "_EntryPointPartition" + partitionIndex); TypeSpec.Builder builder = TypeSpec.interfaceBuilder(partitionName) .addOriginatingElement(rootElement) .addModifiers(Modifier.ABSTRACT) .addSuperinterfaces(partition); Processors.addGeneratedAnnotation(builder, processingEnv, ClassNames.ROOT_PROCESSOR.toString()); JavaFile.builder(name.packageName(), builder.build()).build().writeTo(processingEnv.getFiler()); return partitionName; } }