1 /* 2 * Copyright (C) 2019 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.android.processor.internal.androidentrypoint; 18 19 import static androidx.room.compiler.processing.JavaPoetExtKt.toAnnotationSpec; 20 import static com.google.common.collect.Iterables.getOnlyElement; 21 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; 22 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 23 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 24 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 25 import static java.util.stream.Collectors.joining; 26 27 import androidx.room.compiler.processing.JavaPoetExtKt; 28 import androidx.room.compiler.processing.XAnnotation; 29 import androidx.room.compiler.processing.XConstructorElement; 30 import androidx.room.compiler.processing.XElement; 31 import androidx.room.compiler.processing.XTypeElement; 32 import androidx.room.compiler.processing.XVariableElement; 33 import com.google.common.base.Preconditions; 34 import com.google.common.collect.ImmutableList; 35 import com.google.common.collect.ImmutableMap; 36 import com.google.common.collect.ImmutableSet; 37 import com.squareup.javapoet.AnnotationSpec; 38 import com.squareup.javapoet.ClassName; 39 import com.squareup.javapoet.CodeBlock; 40 import com.squareup.javapoet.FieldSpec; 41 import com.squareup.javapoet.MethodSpec; 42 import com.squareup.javapoet.ParameterSpec; 43 import com.squareup.javapoet.TypeName; 44 import com.squareup.javapoet.TypeSpec; 45 import dagger.hilt.android.processor.internal.AndroidClassNames; 46 import dagger.hilt.android.processor.internal.androidentrypoint.AndroidEntryPointMetadata.AndroidType; 47 import dagger.hilt.processor.internal.ClassNames; 48 import java.util.List; 49 import java.util.Optional; 50 import java.util.stream.Collectors; 51 import javax.lang.model.element.Modifier; 52 53 /** Helper class for writing Hilt generators. */ 54 final class Generators { 55 private static final ImmutableMap<ClassName, String> SUPPRESS_ANNOTATION_PROPERTY_NAME = 56 ImmutableMap.<ClassName, String>builder() 57 .put(ClassNames.SUPPRESS_WARNINGS, "value") 58 .put(ClassNames.KOTLIN_SUPPRESS, "names") 59 .build(); 60 addGeneratedBaseClassJavadoc(TypeSpec.Builder builder, ClassName annotation)61 static void addGeneratedBaseClassJavadoc(TypeSpec.Builder builder, ClassName annotation) { 62 builder.addJavadoc("A generated base class to be extended by the @$T annotated class. If using" 63 + " the Gradle plugin, this is swapped as the base class via bytecode transformation.", 64 annotation); 65 } 66 67 /** Copies all constructors with arguments to the builder. */ copyConstructors(XTypeElement baseClass, TypeSpec.Builder builder)68 static void copyConstructors(XTypeElement baseClass, TypeSpec.Builder builder) { 69 copyConstructors(baseClass, CodeBlock.builder().build(), builder); 70 } 71 72 /** Copies all constructors with arguments along with an appended body to the builder. */ copyConstructors(XTypeElement baseClass, CodeBlock body, TypeSpec.Builder builder)73 static void copyConstructors(XTypeElement baseClass, CodeBlock body, TypeSpec.Builder builder) { 74 ImmutableList<XConstructorElement> constructors = 75 baseClass.getConstructors().stream() 76 .filter(constructor -> !constructor.isPrivate()) 77 .collect(toImmutableList()); 78 79 if (constructors.size() == 1 80 && getOnlyElement(constructors).getParameters().isEmpty() 81 && body.isEmpty()) { 82 // No need to copy the constructor if the default constructor will handle it. 83 return; 84 } 85 86 constructors.forEach(constructor -> builder.addMethod(copyConstructor(constructor, body))); 87 } 88 89 /** Returns Optional with AnnotationSpec for Nullable if found on element, empty otherwise. */ getNullableAnnotationSpec(XElement element)90 private static Optional<AnnotationSpec> getNullableAnnotationSpec(XElement element) { 91 for (XAnnotation annotation : element.getAllAnnotations()) { 92 if (annotation.getClassName().simpleName().contentEquals("Nullable")) { 93 AnnotationSpec annotationSpec = toAnnotationSpec(annotation); 94 // If using the android internal Nullable, convert it to the externally-visible version. 95 return AndroidClassNames.NULLABLE_INTERNAL.equals(annotationSpec.type) 96 ? Optional.of(AnnotationSpec.builder(AndroidClassNames.NULLABLE).build()) 97 : Optional.of(annotationSpec); 98 } 99 } 100 return Optional.empty(); 101 } 102 103 /** 104 * Returns a ParameterSpec of the input parameter, @Nullable annotated if existing in original 105 * (this does not handle Nullable type annotations). 106 */ getParameterSpecWithNullable(XVariableElement parameter)107 private static ParameterSpec getParameterSpecWithNullable(XVariableElement parameter) { 108 ParameterSpec.Builder builder = 109 ParameterSpec.builder(parameter.getType().getTypeName(), getSimpleName(parameter)); 110 getNullableAnnotationSpec(parameter).ifPresent(builder::addAnnotation); 111 return builder.build(); 112 } 113 114 /** 115 * Returns a {@link MethodSpec} for a constructor matching the given {@link XConstructorElement} 116 * constructor signature, and just calls super. If the constructor is {@link 117 * android.annotation.TargetApi} guarded, adds the TargetApi as well. 118 */ 119 // Example: 120 // Foo(Param1 param1, Param2 param2) { 121 // super(param1, param2); 122 // } copyConstructor(XConstructorElement constructor)123 static MethodSpec copyConstructor(XConstructorElement constructor) { 124 return copyConstructor(constructor, CodeBlock.builder().build()); 125 } 126 copyConstructor(XConstructorElement constructor, CodeBlock body)127 private static MethodSpec copyConstructor(XConstructorElement constructor, CodeBlock body) { 128 List<ParameterSpec> params = 129 constructor.getParameters().stream() 130 .map(Generators::getParameterSpecWithNullable) 131 .collect(Collectors.toList()); 132 133 final MethodSpec.Builder builder = 134 MethodSpec.constructorBuilder() 135 .addParameters(params) 136 .addStatement( 137 "super($L)", params.stream().map(param -> param.name).collect(joining(", "))) 138 .addCode(body); 139 140 constructor.getAllAnnotations().stream() 141 .filter(a -> a.getTypeElement().hasAnnotation(AndroidClassNames.TARGET_API)) 142 .collect(toOptional()) 143 .map(JavaPoetExtKt::toAnnotationSpec) 144 .ifPresent(builder::addAnnotation); 145 146 return builder.build(); 147 } 148 149 /** Copies SuppressWarnings annotations from the annotated element to the generated element. */ copySuppressAnnotations(XElement element, TypeSpec.Builder builder)150 static void copySuppressAnnotations(XElement element, TypeSpec.Builder builder) { 151 ImmutableSet<String> suppressValues = 152 SUPPRESS_ANNOTATION_PROPERTY_NAME.keySet().stream() 153 .filter(element::hasAnnotation) 154 .flatMap( 155 annotation -> 156 element 157 .getAnnotation(annotation) 158 .getAsStringList(SUPPRESS_ANNOTATION_PROPERTY_NAME.get(annotation)) 159 .stream()) 160 .collect(toImmutableSet()); 161 162 if (!suppressValues.isEmpty()) { 163 // Replace kotlin Suppress with java SuppressWarnings, as the generated file is java. 164 AnnotationSpec.Builder annotation = AnnotationSpec.builder(ClassNames.SUPPRESS_WARNINGS); 165 suppressValues.forEach(value -> annotation.addMember("value", "$S", value)); 166 builder.addAnnotation(annotation.build()); 167 } 168 } 169 170 /** 171 * Copies the Android lint annotations from the annotated element to the generated element. 172 * 173 * <p>Note: For now we only copy over {@link android.annotation.TargetApi}. 174 */ copyLintAnnotations(XElement element, TypeSpec.Builder builder)175 static void copyLintAnnotations(XElement element, TypeSpec.Builder builder) { 176 if (element.hasAnnotation(AndroidClassNames.TARGET_API)) { 177 builder.addAnnotation(toAnnotationSpec(element.getAnnotation(AndroidClassNames.TARGET_API))); 178 } 179 } 180 181 // @Override 182 // public CompT generatedComponent() { 183 // return componentManager().generatedComponent(); 184 // } addComponentOverride(AndroidEntryPointMetadata metadata, TypeSpec.Builder builder)185 static void addComponentOverride(AndroidEntryPointMetadata metadata, TypeSpec.Builder builder) { 186 if (metadata.overridesAndroidEntryPointClass()) { 187 // We don't need to override this method if we are extending a Hilt type. 188 return; 189 } 190 builder 191 .addSuperinterface(ClassNames.GENERATED_COMPONENT_MANAGER_HOLDER) 192 .addMethod( 193 MethodSpec.methodBuilder("generatedComponent") 194 .addAnnotation(Override.class) 195 .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 196 .returns(TypeName.OBJECT) 197 .addStatement("return $L.generatedComponent()", componentManagerCallBlock(metadata)) 198 .build()); 199 } 200 201 /** Adds the inject() and optionally the componentManager() methods to allow for injection. */ addInjectionMethods(AndroidEntryPointMetadata metadata, TypeSpec.Builder builder)202 static void addInjectionMethods(AndroidEntryPointMetadata metadata, TypeSpec.Builder builder) { 203 switch (metadata.androidType()) { 204 case ACTIVITY: 205 case FRAGMENT: 206 case VIEW: 207 case SERVICE: 208 addComponentManagerMethods(metadata, builder); 209 // fall through 210 case BROADCAST_RECEIVER: 211 addInjectAndMaybeOptionalInjectMethod(metadata, builder); 212 break; 213 default: 214 throw new AssertionError(); 215 } 216 } 217 218 // @Override 219 // public FragmentComponentManager componentManager() { 220 // if (componentManager == null) { 221 // synchronize (componentManagerLock) { 222 // if (componentManager == null) { 223 // componentManager = createComponentManager(); 224 // } 225 // } 226 // } 227 // return componentManager; 228 // } addComponentManagerMethods( AndroidEntryPointMetadata metadata, TypeSpec.Builder typeSpecBuilder)229 private static void addComponentManagerMethods( 230 AndroidEntryPointMetadata metadata, TypeSpec.Builder typeSpecBuilder) { 231 if (metadata.overridesAndroidEntryPointClass()) { 232 // We don't need to override this method if we are extending a Hilt type. 233 return; 234 } 235 236 ParameterSpec managerParam = metadata.componentManagerParam(); 237 typeSpecBuilder.addField(componentManagerField(metadata)); 238 239 typeSpecBuilder.addMethod(createComponentManagerMethod(metadata)); 240 241 MethodSpec.Builder methodSpecBuilder = 242 MethodSpec.methodBuilder("componentManager") 243 .addAnnotation(Override.class) 244 .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 245 .returns(managerParam.type) 246 .beginControlFlow("if ($N == null)", managerParam); 247 248 // Views do not do double-checked locking because this is called from the constructor 249 if (metadata.androidType() != AndroidEntryPointMetadata.AndroidType.VIEW) { 250 typeSpecBuilder.addField(componentManagerLockField()); 251 252 methodSpecBuilder 253 .beginControlFlow("synchronized (componentManagerLock)") 254 .beginControlFlow("if ($N == null)", managerParam); 255 } 256 257 methodSpecBuilder 258 .addStatement("$N = createComponentManager()", managerParam) 259 .endControlFlow(); 260 261 if (metadata.androidType() != AndroidEntryPointMetadata.AndroidType.VIEW) { 262 methodSpecBuilder 263 .endControlFlow() 264 .endControlFlow(); 265 } 266 267 methodSpecBuilder.addStatement("return $N", managerParam); 268 269 typeSpecBuilder.addMethod(methodSpecBuilder.build()); 270 } 271 272 // protected FragmentComponentManager createComponentManager() { 273 // return new FragmentComponentManager(initArgs); 274 // } createComponentManagerMethod(AndroidEntryPointMetadata metadata)275 private static MethodSpec createComponentManagerMethod(AndroidEntryPointMetadata metadata) { 276 Preconditions.checkState( 277 metadata.componentManagerInitArgs().isPresent(), 278 "This method should not have been called for metadata where the init args are not" 279 + " present."); 280 return MethodSpec.methodBuilder("createComponentManager") 281 .addModifiers(Modifier.PROTECTED) 282 .returns(metadata.componentManager()) 283 .addStatement( 284 "return new $T($L)", 285 metadata.componentManager(), 286 metadata.componentManagerInitArgs().get()) 287 .build(); 288 } 289 290 // private volatile ComponentManager componentManager; componentManagerField(AndroidEntryPointMetadata metadata)291 private static FieldSpec componentManagerField(AndroidEntryPointMetadata metadata) { 292 ParameterSpec managerParam = metadata.componentManagerParam(); 293 FieldSpec.Builder builder = FieldSpec.builder(managerParam.type, managerParam.name) 294 .addModifiers(Modifier.PRIVATE); 295 296 // Views do not need volatile since these are set in the constructor if ever set. 297 if (metadata.androidType() != AndroidEntryPointMetadata.AndroidType.VIEW) { 298 builder.addModifiers(Modifier.VOLATILE); 299 } 300 301 return builder.build(); 302 } 303 304 // private final Object componentManagerLock = new Object(); componentManagerLockField()305 private static FieldSpec componentManagerLockField() { 306 return FieldSpec.builder(TypeName.get(Object.class), "componentManagerLock") 307 .addModifiers(Modifier.PRIVATE, Modifier.FINAL) 308 .initializer("new Object()") 309 .build(); 310 } 311 312 // protected void inject() { 313 // if (!injected) { 314 // generatedComponent().inject$CLASS(($CLASS) this); 315 // injected = true; 316 // } 317 // } addInjectAndMaybeOptionalInjectMethod( AndroidEntryPointMetadata metadata, TypeSpec.Builder typeSpecBuilder)318 private static void addInjectAndMaybeOptionalInjectMethod( 319 AndroidEntryPointMetadata metadata, TypeSpec.Builder typeSpecBuilder) { 320 MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("inject") 321 .addModifiers(Modifier.PROTECTED); 322 323 // Check if the parent is a Hilt type. If it isn't or if it is but it 324 // wasn't injected by hilt, then return. 325 // Object parent = ...depends on type... 326 // if (!optionalInjectParentUsesHilt()) { 327 // return; 328 // 329 if (metadata.allowsOptionalInjection()) { 330 CodeBlock parentCodeBlock; 331 if (metadata.androidType() != AndroidType.BROADCAST_RECEIVER) { 332 parentCodeBlock = CodeBlock.of("optionalInjectGetParent()"); 333 334 // Also, add the optionalInjectGetParent method we just used. This is a separate method so 335 // other parts of the code when dealing with @OptionalInject. BroadcastReceiver can't have 336 // this method since the context is only accessible as a parameter to receive()/inject(). 337 typeSpecBuilder.addMethod(MethodSpec.methodBuilder("optionalInjectGetParent") 338 .addModifiers(Modifier.PRIVATE) 339 .returns(TypeName.OBJECT) 340 .addStatement("return $L", getParentCodeBlock(metadata)) 341 .build()); 342 } else { 343 // For BroadcastReceiver, use the "context" field that is on the stack. 344 parentCodeBlock = CodeBlock.of( 345 "$T.getApplication(context.getApplicationContext())", ClassNames.CONTEXTS); 346 } 347 348 methodSpecBuilder 349 .beginControlFlow("if (!optionalInjectParentUsesHilt($L))", parentCodeBlock) 350 .addStatement("return") 351 .endControlFlow(); 352 353 // Add the optionalInjectParentUsesHilt used above. 354 typeSpecBuilder.addMethod(MethodSpec.methodBuilder("optionalInjectParentUsesHilt") 355 .addModifiers(Modifier.PRIVATE) 356 .addParameter(TypeName.OBJECT, "parent") 357 .returns(TypeName.BOOLEAN) 358 .addStatement("return (parent instanceof $T) " 359 + "&& (!(parent instanceof $T) || (($T) parent).wasInjectedByHilt())", 360 ClassNames.GENERATED_COMPONENT_MANAGER, 361 AndroidClassNames.INJECTED_BY_HILT, 362 AndroidClassNames.INJECTED_BY_HILT) 363 .build()); 364 } 365 366 // Only add @Override if an ancestor extends a generated Hilt class. 367 // When using bytecode injection, this isn't always guaranteed. 368 if (metadata.overridesAndroidEntryPointClass() 369 && ancestorExtendsGeneratedHiltClass(metadata)) { 370 methodSpecBuilder.addAnnotation(Override.class); 371 } 372 typeSpecBuilder.addField(injectedField(metadata)); 373 374 switch (metadata.androidType()) { 375 case ACTIVITY: 376 case FRAGMENT: 377 case VIEW: 378 case SERVICE: 379 methodSpecBuilder 380 .beginControlFlow("if (!injected)") 381 .addStatement("injected = true") 382 .addStatement( 383 "(($T) $L).$L($L)", 384 metadata.injectorClassName(), 385 generatedComponentCallBlock(metadata), 386 metadata.injectMethodName(), 387 unsafeCastThisTo(metadata.elementClassName())) 388 .endControlFlow(); 389 break; 390 case BROADCAST_RECEIVER: 391 typeSpecBuilder.addField(injectedLockField()); 392 393 methodSpecBuilder 394 .addParameter(ParameterSpec.builder(AndroidClassNames.CONTEXT, "context").build()) 395 .beginControlFlow("if (!injected)") 396 .beginControlFlow("synchronized (injectedLock)") 397 .beginControlFlow("if (!injected)") 398 .addStatement( 399 "(($T) $T.generatedComponent(context)).$L($L)", 400 metadata.injectorClassName(), 401 metadata.componentManager(), 402 metadata.injectMethodName(), 403 unsafeCastThisTo(metadata.elementClassName())) 404 .addStatement("injected = true") 405 .endControlFlow() 406 .endControlFlow() 407 .endControlFlow(); 408 break; 409 default: 410 throw new AssertionError(); 411 } 412 413 // Also add a wasInjectedByHilt method if needed. 414 // Even if we aren't optionally injected, if we override an optionally injected Hilt class 415 // we also need to override the wasInjectedByHilt method. 416 if (metadata.allowsOptionalInjection() || metadata.baseAllowsOptionalInjection()) { 417 typeSpecBuilder.addMethod( 418 MethodSpec.methodBuilder("wasInjectedByHilt") 419 .addAnnotation(Override.class) 420 .addModifiers(Modifier.PUBLIC) 421 .returns(boolean.class) 422 .addStatement("return injected") 423 .build()); 424 // Only add the interface though if this class allows optional injection (not that it 425 // really matters since if the base allows optional injection the class implements the 426 // interface anyway). But it is probably better to be consistent about only optionally 427 // injected classes extend the interface. 428 if (metadata.allowsOptionalInjection()) { 429 typeSpecBuilder.addSuperinterface(AndroidClassNames.INJECTED_BY_HILT); 430 } 431 } 432 433 typeSpecBuilder.addMethod(methodSpecBuilder.build()); 434 } 435 getParentCodeBlock(AndroidEntryPointMetadata metadata)436 private static CodeBlock getParentCodeBlock(AndroidEntryPointMetadata metadata) { 437 switch (metadata.androidType()) { 438 case ACTIVITY: 439 case SERVICE: 440 return CodeBlock.of("$T.getApplication(getApplicationContext())", ClassNames.CONTEXTS); 441 case FRAGMENT: 442 return CodeBlock.of("getHost()"); 443 case VIEW: 444 return CodeBlock.of( 445 "$L.maybeGetParentComponentManager()", componentManagerCallBlock(metadata)); 446 case BROADCAST_RECEIVER: 447 // Broadcast receivers receive a "context" parameter that make it so this code block 448 // isn't really usable anywhere 449 throw new AssertionError("BroadcastReceiver types should not get here"); 450 default: 451 throw new AssertionError(); 452 } 453 } 454 455 /** 456 * Returns the call to {@code generatedComponent()} with casts if needed. 457 * 458 * <p>A cast is required when the root generated Hilt class uses bytecode injection because 459 * subclasses won't have access to the {@code generatedComponent()} method in that case. 460 */ generatedComponentCallBlock(AndroidEntryPointMetadata metadata)461 private static CodeBlock generatedComponentCallBlock(AndroidEntryPointMetadata metadata) { 462 return CodeBlock.of( 463 "$L.generatedComponent()", 464 !metadata.isRootMetadata() && metadata.rootMetadata().requiresBytecodeInjection() 465 ? unsafeCastThisTo(ClassNames.GENERATED_COMPONENT_MANAGER_HOLDER) 466 : "this"); 467 } 468 469 /** 470 * Returns the call to {@code componentManager()} with casts if needed. 471 * 472 * <p>A cast is required when the root generated Hilt class uses bytecode injection because 473 * subclasses won't have access to the {@code componentManager()} method in that case. 474 */ componentManagerCallBlock(AndroidEntryPointMetadata metadata)475 private static CodeBlock componentManagerCallBlock(AndroidEntryPointMetadata metadata) { 476 return CodeBlock.of( 477 "$L.componentManager()", 478 !metadata.isRootMetadata() && metadata.rootMetadata().requiresBytecodeInjection() 479 ? unsafeCastThisTo(ClassNames.GENERATED_COMPONENT_MANAGER_HOLDER) 480 : "this"); 481 } 482 unsafeCastThisTo(ClassName castType)483 static CodeBlock unsafeCastThisTo(ClassName castType) { 484 return CodeBlock.of("$T.<$T>unsafeCast(this)", ClassNames.UNSAFE_CASTS, castType); 485 } 486 487 /** Returns {@code true} if the an ancestor annotated class extends the generated class */ ancestorExtendsGeneratedHiltClass(AndroidEntryPointMetadata metadata)488 private static boolean ancestorExtendsGeneratedHiltClass(AndroidEntryPointMetadata metadata) { 489 while (metadata.baseMetadata().isPresent()) { 490 metadata = metadata.baseMetadata().get(); 491 if (!metadata.requiresBytecodeInjection()) { 492 return true; 493 } 494 } 495 return false; 496 } 497 498 // private boolean injected = false; injectedField(AndroidEntryPointMetadata metadata)499 private static FieldSpec injectedField(AndroidEntryPointMetadata metadata) { 500 FieldSpec.Builder builder = FieldSpec.builder(TypeName.BOOLEAN, "injected") 501 .addModifiers(Modifier.PRIVATE); 502 503 // Broadcast receivers do double-checked locking so this needs to be volatile 504 if (metadata.androidType() == AndroidEntryPointMetadata.AndroidType.BROADCAST_RECEIVER) { 505 builder.addModifiers(Modifier.VOLATILE); 506 } 507 508 // Views should not add an initializer here as this runs after the super constructor 509 // and may reset state set during the super constructor call. 510 if (metadata.androidType() != AndroidEntryPointMetadata.AndroidType.VIEW) { 511 builder.initializer("false"); 512 } 513 return builder.build(); 514 } 515 516 // private final Object injectedLock = new Object(); injectedLockField()517 private static FieldSpec injectedLockField() { 518 return FieldSpec.builder(TypeName.OBJECT, "injectedLock") 519 .addModifiers(Modifier.PRIVATE, Modifier.FINAL) 520 .initializer("new $T()", TypeName.OBJECT) 521 .build(); 522 } 523 Generators()524 private Generators() {} 525 } 526