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