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.internal.codegen.componentgenerator; 18 19 import static com.google.common.base.CaseFormat.LOWER_CAMEL; 20 import static com.google.common.base.CaseFormat.UPPER_CAMEL; 21 import static com.google.common.base.Preconditions.checkArgument; 22 import static com.google.common.collect.Iterables.getOnlyElement; 23 import static com.squareup.javapoet.MethodSpec.constructorBuilder; 24 import static dagger.internal.codegen.base.ComponentCreatorKind.BUILDER; 25 import static dagger.internal.codegen.javapoet.TypeSpecs.addSupertype; 26 import static dagger.internal.codegen.langmodel.Accessibility.isElementAccessibleFrom; 27 import static dagger.internal.codegen.writing.ComponentNames.getTopLevelClassName; 28 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 29 import static dagger.internal.codegen.xprocessing.XTypeElements.getAllUnimplementedMethods; 30 import static javax.lang.model.element.Modifier.FINAL; 31 import static javax.lang.model.element.Modifier.PRIVATE; 32 import static javax.lang.model.element.Modifier.PUBLIC; 33 import static javax.lang.model.element.Modifier.STATIC; 34 35 import androidx.room.compiler.processing.XElement; 36 import androidx.room.compiler.processing.XFiler; 37 import androidx.room.compiler.processing.XMethodElement; 38 import androidx.room.compiler.processing.XProcessingEnv; 39 import androidx.room.compiler.processing.XType; 40 import androidx.room.compiler.processing.XTypeElement; 41 import com.google.common.base.Ascii; 42 import com.google.common.collect.ImmutableList; 43 import com.google.common.collect.Sets; 44 import com.squareup.javapoet.ClassName; 45 import com.squareup.javapoet.MethodSpec; 46 import com.squareup.javapoet.TypeName; 47 import com.squareup.javapoet.TypeSpec; 48 import dagger.internal.codegen.base.ComponentCreatorKind; 49 import dagger.internal.codegen.base.SourceFileGenerator; 50 import dagger.internal.codegen.binding.ComponentCreatorDescriptor; 51 import dagger.internal.codegen.binding.ComponentDescriptor; 52 import dagger.internal.codegen.binding.ComponentRequirement; 53 import dagger.internal.codegen.binding.MethodSignature; 54 import dagger.internal.codegen.compileroption.CompilerOptions; 55 import dagger.internal.codegen.javapoet.TypeNames; 56 import dagger.internal.codegen.xprocessing.MethodSpecs; 57 import java.util.Set; 58 import java.util.stream.Stream; 59 import javax.inject.Inject; 60 61 /** 62 * A component generator that emits only API, without any actual implementation. 63 * 64 * <p>When compiling a header jar (hjar), Bazel needs to run annotation processors that generate 65 * API, like Dagger, to see what code they might output. Full binding graph analysis is costly and 66 * unnecessary from the perspective of the header compiler; it's sole goal is to pass along a 67 * slimmed down version of what will be the jar for a particular compilation, whether or not that 68 * compilation succeeds. If it does not, the compilation pipeline will fail, even if header 69 * compilation succeeded. 70 * 71 * <p>The components emitted by this processing step include all of the API elements exposed by the 72 * normal step. Method bodies are omitted as Turbine ignores them entirely. 73 */ 74 final class ComponentHjarGenerator extends SourceFileGenerator<ComponentDescriptor> { 75 private final XProcessingEnv processingEnv; 76 private final CompilerOptions compilerOptions; 77 78 @Inject ComponentHjarGenerator( XFiler filer, XProcessingEnv processingEnv, CompilerOptions compilerOptions)79 ComponentHjarGenerator( 80 XFiler filer, XProcessingEnv processingEnv, CompilerOptions compilerOptions) { 81 super(filer, processingEnv); 82 this.processingEnv = processingEnv; 83 this.compilerOptions = compilerOptions; 84 } 85 86 @Override originatingElement(ComponentDescriptor input)87 public XElement originatingElement(ComponentDescriptor input) { 88 return input.typeElement(); 89 } 90 91 @Override topLevelTypes(ComponentDescriptor componentDescriptor)92 public ImmutableList<TypeSpec.Builder> topLevelTypes(ComponentDescriptor componentDescriptor) { 93 ClassName generatedTypeName = getTopLevelClassName(componentDescriptor); 94 TypeSpec.Builder generatedComponent = 95 TypeSpec.classBuilder(generatedTypeName) 96 .addModifiers(FINAL) 97 .addMethod(privateConstructor()); 98 if (componentDescriptor.typeElement().isPublic()) { 99 generatedComponent.addModifiers(PUBLIC); 100 } 101 102 XTypeElement componentElement = componentDescriptor.typeElement(); 103 if (compilerOptions.generatedClassExtendsComponent()) { 104 addSupertype(generatedComponent, componentElement); 105 } 106 107 TypeName builderMethodReturnType; 108 ComponentCreatorKind creatorKind; 109 boolean noArgFactoryMethod; 110 if (componentDescriptor.creatorDescriptor().isPresent()) { 111 ComponentCreatorDescriptor creatorDescriptor = componentDescriptor.creatorDescriptor().get(); 112 builderMethodReturnType = creatorDescriptor.typeElement().getClassName(); 113 creatorKind = creatorDescriptor.kind(); 114 noArgFactoryMethod = creatorDescriptor.factoryParameters().isEmpty(); 115 } else { 116 TypeSpec.Builder builder = 117 TypeSpec.classBuilder("Builder") 118 .addModifiers(STATIC, FINAL) 119 .addMethod(privateConstructor()); 120 if (componentDescriptor.typeElement().isPublic()) { 121 builder.addModifiers(PUBLIC); 122 } 123 124 ClassName builderClassName = generatedTypeName.nestedClass("Builder"); 125 builderMethodReturnType = builderClassName; 126 creatorKind = BUILDER; 127 noArgFactoryMethod = true; 128 componentRequirements(componentDescriptor) 129 .map(requirement -> builderSetterMethod(requirement.typeElement(), builderClassName)) 130 .forEach(builder::addMethod); 131 builder.addMethod(builderBuildMethod(componentDescriptor)); 132 generatedComponent.addType(builder.build()); 133 } 134 135 generatedComponent.addMethod(staticCreatorMethod(builderMethodReturnType, creatorKind)); 136 137 if (noArgFactoryMethod 138 && !hasBindsInstanceMethods(componentDescriptor) 139 && componentRequirements(componentDescriptor) 140 .noneMatch(ComponentRequirement::requiresAPassedInstance)) { 141 generatedComponent.addMethod(createMethod(componentDescriptor)); 142 } 143 144 if (compilerOptions.generatedClassExtendsComponent()) { 145 XType componentType = componentElement.getType(); 146 // TODO(ronshapiro): unify with ComponentImplementationBuilder 147 Set<MethodSignature> methodSignatures = 148 Sets.newHashSetWithExpectedSize(componentDescriptor.componentMethods().size()); 149 componentDescriptor.componentMethods().stream() 150 .filter( 151 method -> 152 methodSignatures.add( 153 MethodSignature.forComponentMethod(method, componentType, processingEnv))) 154 .forEach( 155 method -> 156 generatedComponent.addMethod( 157 emptyComponentMethod(componentElement, method.methodElement()))); 158 159 if (componentDescriptor.isProduction()) { 160 generatedComponent 161 .addSuperinterface(TypeNames.CANCELLATION_LISTENER) 162 .addMethod(onProducerFutureCancelledMethod()); 163 } 164 } 165 166 return ImmutableList.of(generatedComponent); 167 } 168 emptyComponentMethod(XTypeElement typeElement, XMethodElement baseMethod)169 private MethodSpec emptyComponentMethod(XTypeElement typeElement, XMethodElement baseMethod) { 170 return MethodSpecs.overriding(baseMethod, typeElement.getType()).build(); 171 } 172 privateConstructor()173 private static MethodSpec privateConstructor() { 174 return constructorBuilder().addModifiers(PRIVATE).build(); 175 } 176 177 /** 178 * Returns the {@link ComponentRequirement}s for a component that does not have a {@link 179 * ComponentDescriptor#creatorDescriptor()}. 180 */ componentRequirements(ComponentDescriptor component)181 private static Stream<ComponentRequirement> componentRequirements(ComponentDescriptor component) { 182 // TODO(b/152802759): See if you can merge logics that normal component processing and hjar 183 // component processing use. So that there would't be a duplicated logic (like the lines below) 184 // everytime we modify the generated code for the component. 185 checkArgument(!component.isSubcomponent()); 186 return Stream.concat( 187 component.dependencies().stream(), 188 component.modules().stream() 189 .filter( 190 module -> 191 !module.moduleElement().isAbstract() 192 && isElementAccessibleFrom( 193 module.moduleElement(), 194 component.typeElement().getClassName().packageName())) 195 .map(module -> ComponentRequirement.forModule(module.moduleElement().getType())) 196 // If the user hasn't defined an explicit creator/builder then we need to prune out the 197 // module requirements that don't require a module instance to match the non-hjar 198 // implementation. 199 .filter( 200 requirement -> 201 component.creatorDescriptor().isPresent() 202 || requirement.requiresModuleInstance())); 203 } 204 hasBindsInstanceMethods(ComponentDescriptor componentDescriptor)205 private boolean hasBindsInstanceMethods(ComponentDescriptor componentDescriptor) { 206 return componentDescriptor.creatorDescriptor().isPresent() 207 && getAllUnimplementedMethods(componentDescriptor.creatorDescriptor().get().typeElement()) 208 .stream() 209 .anyMatch(method -> isBindsInstance(method)); 210 } 211 isBindsInstance(XMethodElement method)212 private static boolean isBindsInstance(XMethodElement method) { 213 return method.hasAnnotation(TypeNames.BINDS_INSTANCE) 214 || (method.getParameters().size() == 1 215 && getOnlyElement(method.getParameters()).hasAnnotation(TypeNames.BINDS_INSTANCE)); 216 } 217 builderSetterMethod( XTypeElement componentRequirement, ClassName builderClass)218 private static MethodSpec builderSetterMethod( 219 XTypeElement componentRequirement, ClassName builderClass) { 220 String simpleName = UPPER_CAMEL.to(LOWER_CAMEL, getSimpleName(componentRequirement)); 221 return MethodSpec.methodBuilder(simpleName) 222 .addModifiers(PUBLIC) 223 .addParameter(componentRequirement.getClassName(), simpleName) 224 .returns(builderClass) 225 .build(); 226 } 227 builderBuildMethod(ComponentDescriptor component)228 private static MethodSpec builderBuildMethod(ComponentDescriptor component) { 229 return MethodSpec.methodBuilder("build") 230 .addModifiers(PUBLIC) 231 .returns(component.typeElement().getClassName()) 232 .build(); 233 } 234 staticCreatorMethod( TypeName creatorMethodReturnType, ComponentCreatorKind creatorKind)235 private static MethodSpec staticCreatorMethod( 236 TypeName creatorMethodReturnType, ComponentCreatorKind creatorKind) { 237 return MethodSpec.methodBuilder(Ascii.toLowerCase(creatorKind.typeName())) 238 .addModifiers(PUBLIC, STATIC) 239 .returns(creatorMethodReturnType) 240 .build(); 241 } 242 createMethod(ComponentDescriptor componentDescriptor)243 private static MethodSpec createMethod(ComponentDescriptor componentDescriptor) { 244 return MethodSpec.methodBuilder("create") 245 .addModifiers(PUBLIC, STATIC) 246 .returns(componentDescriptor.typeElement().getClassName()) 247 .build(); 248 } 249 onProducerFutureCancelledMethod()250 private static MethodSpec onProducerFutureCancelledMethod() { 251 return MethodSpec.methodBuilder("onProducerFutureCancelled") 252 .addModifiers(PUBLIC) 253 .addParameter(TypeName.BOOLEAN, "mayInterruptIfRunning") 254 .build(); 255 } 256 } 257