• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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