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