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