• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 com.google.common.base.Preconditions.checkNotNull;
20 import static com.google.common.base.Preconditions.checkState;
21 import static dagger.hilt.processor.internal.Processors.toClassNames;
22 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
23 import static javax.lang.model.element.Modifier.ABSTRACT;
24 import static javax.lang.model.element.Modifier.PUBLIC;
25 import static javax.lang.model.element.Modifier.STATIC;
26 
27 import com.google.common.collect.ImmutableList;
28 import com.google.common.collect.ImmutableMap;
29 import com.google.common.collect.ImmutableSet;
30 import com.google.common.graph.GraphBuilder;
31 import com.google.common.graph.Graphs;
32 import com.google.common.graph.MutableGraph;
33 import com.squareup.javapoet.AnnotationSpec;
34 import com.squareup.javapoet.ClassName;
35 import com.squareup.javapoet.JavaFile;
36 import com.squareup.javapoet.MethodSpec;
37 import com.squareup.javapoet.TypeSpec;
38 import dagger.hilt.processor.internal.ClassNames;
39 import dagger.hilt.processor.internal.ComponentDescriptor;
40 import dagger.hilt.processor.internal.ComponentNames;
41 import dagger.hilt.processor.internal.Processors;
42 import java.io.IOException;
43 import java.util.HashMap;
44 import java.util.Map;
45 import java.util.Optional;
46 import javax.annotation.processing.ProcessingEnvironment;
47 import javax.lang.model.element.Modifier;
48 import javax.lang.model.element.TypeElement;
49 
50 /** Generates components and any other classes needed for a root. */
51 final class RootGenerator {
52 
generate( ComponentTreeDepsMetadata componentTreeDepsMetadata, RootMetadata metadata, ComponentNames componentNames, ProcessingEnvironment env)53   static void generate(
54       ComponentTreeDepsMetadata componentTreeDepsMetadata,
55       RootMetadata metadata,
56       ComponentNames componentNames,
57       ProcessingEnvironment env)
58       throws IOException {
59     new RootGenerator(
60             componentTreeDepsMetadata,
61             RootMetadata.copyWithNewTree(metadata, filterDescriptors(metadata.componentTree())),
62             componentNames,
63             env)
64         .generateComponents();
65   }
66 
67   private final TypeElement originatingElement;
68   private final RootMetadata metadata;
69   private final ProcessingEnvironment env;
70   private final Root root;
71   private final Map<String, Integer> simpleComponentNamesToDedupeSuffix = new HashMap<>();
72   private final Map<ComponentDescriptor, ClassName> componentNameMap = new HashMap<>();
73   private final ComponentNames componentNames;
74 
RootGenerator( ComponentTreeDepsMetadata componentTreeDepsMetadata, RootMetadata metadata, ComponentNames componentNames, ProcessingEnvironment env)75   private RootGenerator(
76       ComponentTreeDepsMetadata componentTreeDepsMetadata,
77       RootMetadata metadata,
78       ComponentNames componentNames,
79       ProcessingEnvironment env) {
80     this.originatingElement =
81         checkNotNull(
82             env.getElementUtils().getTypeElement(componentTreeDepsMetadata.name().toString()));
83     this.metadata = metadata;
84     this.componentNames = componentNames;
85     this.env = env;
86     this.root = metadata.root();
87   }
88 
generateComponents()89   private void generateComponents() throws IOException {
90 
91     // TODO(bcorso): Consider moving all of this logic into ComponentGenerator?
92     ClassName componentsWrapperClassName = getComponentsWrapperClassName();
93     TypeSpec.Builder componentsWrapper =
94         TypeSpec.classBuilder(componentsWrapperClassName)
95             .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
96             .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build());
97 
98     Processors.addGeneratedAnnotation(componentsWrapper, env, ClassNames.ROOT_PROCESSOR.toString());
99 
100     ImmutableMap<ComponentDescriptor, ClassName> subcomponentBuilderModules =
101         subcomponentBuilderModules(componentsWrapper);
102 
103     ComponentTree componentTree = metadata.componentTree();
104     for (ComponentDescriptor componentDescriptor : componentTree.getComponentDescriptors()) {
105       ImmutableSet<ClassName> modules =
106           ImmutableSet.<ClassName>builder()
107               .addAll(toClassNames(metadata.modules(componentDescriptor.component())))
108               .addAll(
109                   componentTree.childrenOf(componentDescriptor).stream()
110                       .map(subcomponentBuilderModules::get)
111                       .collect(toImmutableSet()))
112               .build();
113 
114       componentsWrapper.addType(
115           new ComponentGenerator(
116                   env,
117                   getComponentClassName(componentDescriptor),
118                   Optional.empty(),
119                   modules,
120                   metadata.entryPoints(componentDescriptor.component()),
121                   metadata.scopes(componentDescriptor.component()),
122                   ImmutableList.of(),
123                   componentAnnotation(componentDescriptor),
124                   componentBuilder(componentDescriptor))
125               .typeSpecBuilder()
126               .addModifiers(Modifier.STATIC)
127               .build());
128     }
129 
130     RootFileFormatter.write(
131         JavaFile.builder(componentsWrapperClassName.packageName(), componentsWrapper.build())
132             .build(),
133         env.getFiler());
134   }
135 
filterDescriptors(ComponentTree componentTree)136   private static ComponentTree filterDescriptors(ComponentTree componentTree) {
137     MutableGraph<ComponentDescriptor> graph =
138         GraphBuilder.from(componentTree.graph()).build();
139 
140     componentTree.graph().nodes().forEach(graph::addNode);
141     componentTree.graph().edges().forEach(graph::putEdge);
142 
143     // Remove components that do not have builders (besides the root component) since if
144     // we didn't find any builder class, then we don't need to generate the component
145     // since it would be inaccessible.
146     componentTree.getComponentDescriptors().stream()
147         .filter(descriptor -> !descriptor.isRoot() && !descriptor.creator().isPresent())
148         .forEach(graph::removeNode);
149 
150     // The graph may still have nodes that are children of components that don't have builders,
151     // so we need to find reachable nodes from the root and create a new graph to remove those.
152     // We reuse the root from the original tree since it should not have been removed.
153     return ComponentTree.from(Graphs.reachableNodes(graph, componentTree.root()));
154   }
155 
subcomponentBuilderModules( TypeSpec.Builder componentsWrapper)156   private ImmutableMap<ComponentDescriptor, ClassName> subcomponentBuilderModules(
157       TypeSpec.Builder componentsWrapper) {
158     ImmutableMap.Builder<ComponentDescriptor, ClassName> modules = ImmutableMap.builder();
159     for (ComponentDescriptor descriptor : metadata.componentTree().getComponentDescriptors()) {
160       // Root component builders don't have subcomponent builder modules
161       if (!descriptor.isRoot() && descriptor.creator().isPresent()) {
162         ClassName component = getComponentClassName(descriptor);
163         ClassName builder = descriptor.creator().get();
164         ClassName module = component.peerClass(component.simpleName() + "BuilderModule");
165         componentsWrapper.addType(subcomponentBuilderModule(component, builder, module));
166         modules.put(descriptor, module);
167       }
168     }
169     return modules.build();
170   }
171 
172   // Generates:
173   // @Module(subcomponents = FooSubcomponent.class)
174   // interface FooSubcomponentBuilderModule {
175   //   @Binds FooSubcomponentInterfaceBuilder bind(FooSubcomponent.Builder builder);
176   // }
subcomponentBuilderModule( ClassName componentName, ClassName builderName, ClassName moduleName)177   private TypeSpec subcomponentBuilderModule(
178       ClassName componentName, ClassName builderName, ClassName moduleName) {
179     TypeSpec.Builder subcomponentBuilderModule =
180         TypeSpec.interfaceBuilder(moduleName)
181             .addOriginatingElement(originatingElement)
182             .addModifiers(ABSTRACT)
183             .addAnnotation(
184                 AnnotationSpec.builder(ClassNames.MODULE)
185                     .addMember("subcomponents", "$T.class", componentName)
186                     .build())
187             .addAnnotation(ClassNames.DISABLE_INSTALL_IN_CHECK)
188             .addMethod(
189                 MethodSpec.methodBuilder("bind")
190                     .addModifiers(ABSTRACT, PUBLIC)
191                     .addAnnotation(ClassNames.BINDS)
192                     .returns(builderName)
193                     .addParameter(componentName.nestedClass("Builder"), "builder")
194                     .build());
195 
196     Processors.addGeneratedAnnotation(
197         subcomponentBuilderModule, env, ClassNames.ROOT_PROCESSOR.toString());
198 
199     return subcomponentBuilderModule.build();
200   }
201 
componentBuilder(ComponentDescriptor descriptor)202   private Optional<TypeSpec> componentBuilder(ComponentDescriptor descriptor) {
203     return descriptor
204         .creator()
205         .map(
206             creator ->
207                 TypeSpec.interfaceBuilder("Builder")
208                     .addOriginatingElement(originatingElement)
209                     .addModifiers(STATIC, ABSTRACT)
210                     .addSuperinterface(creator)
211                     .addAnnotation(componentBuilderAnnotation(descriptor))
212                     .build());
213   }
214 
componentAnnotation(ComponentDescriptor componentDescriptor)215   private ClassName componentAnnotation(ComponentDescriptor componentDescriptor) {
216     if (!componentDescriptor.isRoot()
217         ) {
218       return ClassNames.SUBCOMPONENT;
219     } else {
220       return ClassNames.COMPONENT;
221     }
222   }
223 
componentBuilderAnnotation(ComponentDescriptor componentDescriptor)224   private ClassName componentBuilderAnnotation(ComponentDescriptor componentDescriptor) {
225     if (componentDescriptor.isRoot()) {
226       return ClassNames.COMPONENT_BUILDER;
227     } else {
228       return ClassNames.SUBCOMPONENT_BUILDER;
229     }
230   }
231 
getPartialRootModuleClassName()232   private ClassName getPartialRootModuleClassName() {
233     return getComponentsWrapperClassName().nestedClass("PartialRootModule");
234   }
235 
getComponentsWrapperClassName()236   private ClassName getComponentsWrapperClassName() {
237     return componentNames.generatedComponentsWrapper(root.originatingRootClassname());
238   }
239 
getComponentClassName(ComponentDescriptor componentDescriptor)240   private ClassName getComponentClassName(ComponentDescriptor componentDescriptor) {
241     if (componentNameMap.containsKey(componentDescriptor)) {
242       return componentNameMap.get(componentDescriptor);
243     }
244 
245     // Disallow any component names with the same name as our SingletonComponent because we treat
246     // that component specially and things may break.
247     checkState(
248         componentDescriptor.component().equals(ClassNames.SINGLETON_COMPONENT)
249         || !componentDescriptor.component().simpleName().equals(
250             ClassNames.SINGLETON_COMPONENT.simpleName()),
251         "Cannot have a component with the same simple name as the reserved %s: %s",
252         ClassNames.SINGLETON_COMPONENT.simpleName(),
253         componentDescriptor.component());
254 
255     ClassName generatedComponent =
256         componentNames.generatedComponent(
257             root.originatingRootClassname(), componentDescriptor.component());
258 
259     Integer suffix = simpleComponentNamesToDedupeSuffix.get(generatedComponent.simpleName());
260     if (suffix != null) {
261       // If an entry exists, use the suffix in the map and the replace it with the value incremented
262       generatedComponent = Processors.append(generatedComponent, String.valueOf(suffix));
263       simpleComponentNamesToDedupeSuffix.put(generatedComponent.simpleName(), suffix + 1);
264     } else {
265       // Otherwise, just add an entry for any possible future duplicates
266       simpleComponentNamesToDedupeSuffix.put(generatedComponent.simpleName(), 2);
267     }
268 
269     componentNameMap.put(componentDescriptor, generatedComponent);
270     return generatedComponent;
271   }
272 }
273