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