1 /* 2 * Copyright (C) 2019 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.hilt.processor.internal.root; 18 19 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 20 import static java.util.Comparator.comparing; 21 22 import com.google.common.base.Joiner; 23 import com.google.common.base.Utf8; 24 import com.google.common.collect.ImmutableCollection; 25 import com.google.common.collect.ImmutableList; 26 import com.squareup.javapoet.AnnotationSpec; 27 import com.squareup.javapoet.ClassName; 28 import com.squareup.javapoet.JavaFile; 29 import com.squareup.javapoet.TypeName; 30 import com.squareup.javapoet.TypeSpec; 31 import dagger.hilt.processor.internal.ClassNames; 32 import dagger.hilt.processor.internal.Processors; 33 import java.io.IOException; 34 import java.util.ArrayList; 35 import java.util.Comparator; 36 import java.util.List; 37 import java.util.Optional; 38 import java.util.Set; 39 import javax.annotation.processing.ProcessingEnvironment; 40 import javax.lang.model.element.Modifier; 41 42 /** Generates a Dagger component or subcomponent interface. */ 43 final class ComponentGenerator { 44 private static final Joiner JOINER = Joiner.on("."); 45 private static final Comparator<ClassName> SIMPLE_NAME_SORTER = 46 Comparator.comparing((ClassName c) -> JOINER.join(c.simpleNames())) 47 .thenComparing(ClassName::compareTo); 48 private static final Comparator<TypeName> TYPE_NAME_SORTER = comparing(TypeName::toString); 49 50 private final ProcessingEnvironment processingEnv; 51 private final ClassName name; 52 private final Optional<ClassName> superclass; 53 private final ImmutableList<ClassName> modules; 54 private final ImmutableList<TypeName> entryPoints; 55 private final ImmutableCollection<ClassName> scopes; 56 private final ImmutableList<AnnotationSpec> extraAnnotations; 57 private final ClassName componentAnnotation; 58 private final Optional<TypeSpec> componentBuilder; 59 ComponentGenerator( ProcessingEnvironment processingEnv, ClassName name, Optional<ClassName> superclass, Set<? extends ClassName> modules, Set<? extends TypeName> entryPoints, ImmutableCollection<ClassName> scopes, ImmutableList<AnnotationSpec> extraAnnotations, ClassName componentAnnotation, Optional<TypeSpec> componentBuilder)60 public ComponentGenerator( 61 ProcessingEnvironment processingEnv, 62 ClassName name, 63 Optional<ClassName> superclass, 64 Set<? extends ClassName> modules, 65 Set<? extends TypeName> entryPoints, 66 ImmutableCollection<ClassName> scopes, 67 ImmutableList<AnnotationSpec> extraAnnotations, 68 ClassName componentAnnotation, 69 Optional<TypeSpec> componentBuilder) { 70 this.processingEnv = processingEnv; 71 this.name = name; 72 this.superclass = superclass; 73 this.modules = modules.stream().sorted(SIMPLE_NAME_SORTER).collect(toImmutableList()); 74 this.entryPoints = entryPoints.stream().sorted(TYPE_NAME_SORTER).collect(toImmutableList()); 75 this.scopes = scopes; 76 this.extraAnnotations = extraAnnotations; 77 this.componentAnnotation = componentAnnotation; 78 this.componentBuilder = componentBuilder; 79 } 80 typeSpecBuilder()81 public TypeSpec.Builder typeSpecBuilder() throws IOException { 82 TypeSpec.Builder builder = 83 TypeSpec.classBuilder(name) 84 // Public because components from a scope below must reference to create 85 .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) 86 .addAnnotation(getComponentAnnotation()); 87 88 componentBuilder.ifPresent(builder::addType); 89 90 scopes.forEach(builder::addAnnotation); 91 92 addEntryPoints(builder); 93 94 superclass.ifPresent(builder::superclass); 95 96 builder.addAnnotations(extraAnnotations); 97 98 return builder; 99 } 100 101 /** Returns the component annotation with the list of modules to install for the component. */ getComponentAnnotation()102 private AnnotationSpec getComponentAnnotation() { 103 AnnotationSpec.Builder builder = AnnotationSpec.builder(componentAnnotation); 104 modules.forEach(module -> builder.addMember("modules", "$T.class", module)); 105 return builder.build(); 106 } 107 108 /** 109 * Adds entry points to the component. 110 * 111 * See b/140979968. If the entry points exceed 65763 bytes, we have to partition them to avoid the 112 * limit. To be safe, we split at 60000 bytes. 113 */ addEntryPoints(TypeSpec.Builder builder)114 private void addEntryPoints(TypeSpec.Builder builder) throws IOException { 115 int currBytes = 0; 116 List<Integer> partitionIndexes = new ArrayList<>(); 117 118 partitionIndexes.add(0); 119 for (int i = 0; i < entryPoints.size(); i++) { 120 // This over estimates the actual length because it includes the fully qualified name (FQN). 121 // TODO(bcorso): Have a better way to estimate the upper bound. For example, most types will 122 // not include the FQN, but we'll have to consider all of the different subtypes of TypeName, 123 // simple name collisions, etc... 124 int nextBytes = Utf8.encodedLength(entryPoints.get(i).toString()); 125 126 // To be safe, we split at 60000 to account for the component name, spaces, commas, etc... 127 if (currBytes + nextBytes > 60000) { 128 partitionIndexes.add(i); 129 currBytes = 0; 130 } 131 132 currBytes += nextBytes; 133 } 134 partitionIndexes.add(entryPoints.size()); 135 136 if (partitionIndexes.size() <= 2) { 137 // No extra partitions are needed, so just add all of the entrypoints as is. 138 builder.addSuperinterfaces(entryPoints); 139 } else { 140 // Create interfaces for each partition. 141 // The partitioned interfaces will be added to the component instead of the real entry points. 142 for (int i = 1; i < partitionIndexes.size(); i++) { 143 int startIndex = partitionIndexes.get(i - 1); 144 int endIndex = partitionIndexes.get(i); 145 builder.addSuperinterface( 146 createPartitionInterface(entryPoints.subList(startIndex, endIndex), i)); 147 } 148 } 149 } 150 createPartitionInterface(List<TypeName> partition, int partitionIndex)151 private ClassName createPartitionInterface(List<TypeName> partition, int partitionIndex) 152 throws IOException { 153 // TODO(bcorso): Nest the partion inside the HiltComponents wrapper rather than appending name 154 ClassName partitionName = 155 Processors.append( 156 Processors.getEnclosedClassName(name), "_EntryPointPartition" + partitionIndex); 157 TypeSpec.Builder builder = 158 TypeSpec.interfaceBuilder(partitionName) 159 .addModifiers(Modifier.ABSTRACT) 160 .addSuperinterfaces(partition); 161 162 Processors.addGeneratedAnnotation(builder, processingEnv, ClassNames.ROOT_PROCESSOR.toString()); 163 164 JavaFile.builder(name.packageName(), builder.build()).build().writeTo(processingEnv.getFiler()); 165 return partitionName; 166 } 167 } 168