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