• 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.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