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