• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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