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