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