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