• 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.internal.codegen.extension.DaggerStreams.toImmutableSet;
21 import static javax.lang.model.element.Modifier.ABSTRACT;
22 import static javax.lang.model.element.Modifier.PUBLIC;
23 import static javax.lang.model.element.Modifier.STATIC;
24 
25 import androidx.room.compiler.processing.JavaPoetExtKt;
26 import androidx.room.compiler.processing.XProcessingEnv;
27 import androidx.room.compiler.processing.XTypeElement;
28 import com.google.common.collect.ImmutableList;
29 import com.google.common.collect.ImmutableMap;
30 import com.google.common.collect.ImmutableSet;
31 import com.google.common.graph.GraphBuilder;
32 import com.google.common.graph.Graphs;
33 import com.google.common.graph.MutableGraph;
34 import com.squareup.javapoet.AnnotationSpec;
35 import com.squareup.javapoet.ClassName;
36 import com.squareup.javapoet.JavaFile;
37 import com.squareup.javapoet.MethodSpec;
38 import com.squareup.javapoet.TypeSpec;
39 import dagger.hilt.processor.internal.ClassNames;
40 import dagger.hilt.processor.internal.ComponentDescriptor;
41 import dagger.hilt.processor.internal.ComponentNames;
42 import dagger.hilt.processor.internal.Processors;
43 import java.io.IOException;
44 import java.util.HashMap;
45 import java.util.Map;
46 import java.util.Optional;
47 import javax.lang.model.element.Modifier;
48 
49 /** Generates components and any other classes needed for a root. */
50 final class RootGenerator {
51 
generate( ComponentTreeDepsMetadata componentTreeDepsMetadata, RootMetadata metadata, ComponentNames componentNames, XProcessingEnv env)52   static void generate(
53       ComponentTreeDepsMetadata componentTreeDepsMetadata,
54       RootMetadata metadata,
55       ComponentNames componentNames,
56       XProcessingEnv env)
57       throws IOException {
58     new RootGenerator(
59             componentTreeDepsMetadata,
60             RootMetadata.copyWithNewTree(metadata, filterDescriptors(metadata.componentTree())),
61             componentNames,
62             env)
63         .generateComponents();
64   }
65 
66   private final XTypeElement originatingElement;
67   private final RootMetadata metadata;
68   private final XProcessingEnv env;
69   private final Root root;
70   private final Map<String, Integer> simpleComponentNamesToDedupeSuffix = new HashMap<>();
71   private final Map<ComponentDescriptor, ClassName> componentNameMap = new HashMap<>();
72   private final ComponentNames componentNames;
73 
RootGenerator( ComponentTreeDepsMetadata componentTreeDepsMetadata, RootMetadata metadata, ComponentNames componentNames, XProcessingEnv env)74   private RootGenerator(
75       ComponentTreeDepsMetadata componentTreeDepsMetadata,
76       RootMetadata metadata,
77       ComponentNames componentNames,
78       XProcessingEnv env) {
79     this.originatingElement = env.requireTypeElement(componentTreeDepsMetadata.name().toString());
80     this.metadata = metadata;
81     this.componentNames = componentNames;
82     this.env = env;
83     this.root = metadata.root();
84   }
85 
generateComponents()86   private void generateComponents() throws IOException {
87 
88     // TODO(bcorso): Consider moving all of this logic into ComponentGenerator?
89     ClassName componentsWrapperClassName = getComponentsWrapperClassName();
90     TypeSpec.Builder componentsWrapper =
91         TypeSpec.classBuilder(componentsWrapperClassName)
92             .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
93             .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build());
94 
95     Processors.addGeneratedAnnotation(componentsWrapper, env, ClassNames.ROOT_PROCESSOR.toString());
96 
97     ImmutableMap<ComponentDescriptor, ClassName> subcomponentBuilderModules =
98         subcomponentBuilderModules(componentsWrapper);
99 
100     ComponentTree componentTree = metadata.componentTree();
101     for (ComponentDescriptor componentDescriptor : componentTree.getComponentDescriptors()) {
102       ImmutableSet<ClassName> modules =
103           ImmutableSet.<ClassName>builder()
104               .addAll(
105                   metadata.modules(componentDescriptor.component()).stream()
106                       .map(XTypeElement::getClassName)
107                       .collect(toImmutableSet()))
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     JavaFile componentsWrapperJavaFile =
131         JavaFile.builder(componentsWrapperClassName.packageName(), componentsWrapper.build())
132             .build();
133     RootFileFormatter.write(componentsWrapperJavaFile, env);
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             .addModifiers(ABSTRACT)
182             .addAnnotation(
183                 AnnotationSpec.builder(ClassNames.MODULE)
184                     .addMember("subcomponents", "$T.class", componentName)
185                     .build())
186             .addAnnotation(ClassNames.DISABLE_INSTALL_IN_CHECK)
187             .addMethod(
188                 MethodSpec.methodBuilder("bind")
189                     .addModifiers(ABSTRACT, PUBLIC)
190                     .addAnnotation(ClassNames.BINDS)
191                     .returns(builderName)
192                     .addParameter(componentName.nestedClass("Builder"), "builder")
193                     .build());
194     JavaPoetExtKt.addOriginatingElement(subcomponentBuilderModule, originatingElement);
195     Processors.addGeneratedAnnotation(
196         subcomponentBuilderModule, env, ClassNames.ROOT_PROCESSOR.toString());
197 
198     return subcomponentBuilderModule.build();
199   }
200 
componentBuilder(ComponentDescriptor descriptor)201   private Optional<TypeSpec> componentBuilder(ComponentDescriptor descriptor) {
202     return descriptor
203         .creator()
204         .map(
205             creator -> {
206               TypeSpec.Builder builder =
207                   TypeSpec.interfaceBuilder("Builder")
208                       .addModifiers(STATIC, ABSTRACT)
209                       .addSuperinterface(creator)
210                       .addAnnotation(componentBuilderAnnotation(descriptor));
211               JavaPoetExtKt.addOriginatingElement(builder, originatingElement);
212               return builder.build();
213             });
214   }
215 
componentAnnotation(ComponentDescriptor componentDescriptor)216   private ClassName componentAnnotation(ComponentDescriptor componentDescriptor) {
217     if (!componentDescriptor.isRoot()
218         ) {
219       return ClassNames.SUBCOMPONENT;
220     } else {
221       return ClassNames.COMPONENT;
222     }
223   }
224 
componentBuilderAnnotation(ComponentDescriptor componentDescriptor)225   private ClassName componentBuilderAnnotation(ComponentDescriptor componentDescriptor) {
226     if (componentDescriptor.isRoot()) {
227       return ClassNames.COMPONENT_BUILDER;
228     } else {
229       return ClassNames.SUBCOMPONENT_BUILDER;
230     }
231   }
232 
getPartialRootModuleClassName()233   private ClassName getPartialRootModuleClassName() {
234     return getComponentsWrapperClassName().nestedClass("PartialRootModule");
235   }
236 
getComponentsWrapperClassName()237   private ClassName getComponentsWrapperClassName() {
238     return componentNames.generatedComponentsWrapper(root.originatingRootClassname());
239   }
240 
getComponentClassName(ComponentDescriptor componentDescriptor)241   private ClassName getComponentClassName(ComponentDescriptor componentDescriptor) {
242     if (componentNameMap.containsKey(componentDescriptor)) {
243       return componentNameMap.get(componentDescriptor);
244     }
245 
246     // Disallow any component names with the same name as our SingletonComponent because we treat
247     // that component specially and things may break.
248     checkState(
249         componentDescriptor.component().equals(ClassNames.SINGLETON_COMPONENT)
250         || !componentDescriptor.component().simpleName().equals(
251             ClassNames.SINGLETON_COMPONENT.simpleName()),
252         "Cannot have a component with the same simple name as the reserved %s: %s",
253         ClassNames.SINGLETON_COMPONENT.simpleName(),
254         componentDescriptor.component());
255 
256     ClassName generatedComponent =
257         componentNames.generatedComponent(
258             root.originatingRootClassname(), componentDescriptor.component());
259 
260     Integer suffix = simpleComponentNamesToDedupeSuffix.get(generatedComponent.simpleName());
261     if (suffix != null) {
262       // If an entry exists, use the suffix in the map and the replace it with the value incremented
263       generatedComponent = Processors.append(generatedComponent, String.valueOf(suffix));
264       simpleComponentNamesToDedupeSuffix.put(generatedComponent.simpleName(), suffix + 1);
265     } else {
266       // Otherwise, just add an entry for any possible future duplicates
267       simpleComponentNamesToDedupeSuffix.put(generatedComponent.simpleName(), 2);
268     }
269 
270     componentNameMap.put(componentDescriptor, generatedComponent);
271     return generatedComponent;
272   }
273 }
274