• 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 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