• 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 dagger.hilt.processor.internal.HiltCompilerOptions.isAndroidSuperclassValidationDisabled;
20 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
21 
22 import com.google.auto.common.MoreElements;
23 import com.google.auto.common.MoreTypes;
24 import com.google.auto.value.AutoValue;
25 import com.google.auto.value.extension.memoized.Memoized;
26 import com.google.common.base.Preconditions;
27 import com.google.common.collect.ImmutableSet;
28 import com.google.common.collect.Iterables;
29 import com.squareup.javapoet.AnnotationSpec;
30 import com.squareup.javapoet.ClassName;
31 import com.squareup.javapoet.CodeBlock;
32 import com.squareup.javapoet.ParameterSpec;
33 import com.squareup.javapoet.TypeName;
34 import dagger.hilt.android.processor.internal.AndroidClassNames;
35 import dagger.hilt.processor.internal.BadInputException;
36 import dagger.hilt.processor.internal.ClassNames;
37 import dagger.hilt.processor.internal.Components;
38 import dagger.hilt.processor.internal.KotlinMetadataUtils;
39 import dagger.hilt.processor.internal.ProcessorErrors;
40 import dagger.hilt.processor.internal.Processors;
41 import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
42 import java.util.LinkedHashSet;
43 import java.util.Optional;
44 import java.util.stream.Collectors;
45 import javax.annotation.processing.ProcessingEnvironment;
46 import javax.lang.model.element.AnnotationMirror;
47 import javax.lang.model.element.Element;
48 import javax.lang.model.element.ElementKind;
49 import javax.lang.model.element.Modifier;
50 import javax.lang.model.element.TypeElement;
51 import javax.lang.model.type.TypeKind;
52 import javax.lang.model.type.TypeMirror;
53 
54 /** Metadata class for @AndroidEntryPoint annotated classes. */
55 @AutoValue
56 public abstract class AndroidEntryPointMetadata {
57 
58   /** The class {@link Element} annotated with @AndroidEntryPoint. */
element()59   public abstract TypeElement element();
60 
61   /** The base class {@link Element} given to @AndroidEntryPoint. */
baseElement()62   public abstract TypeElement baseElement();
63 
64   /** The name of the generated base class, beginning with 'Hilt_'. */
generatedClassName()65   public abstract ClassName generatedClassName();
66 
67   /** Returns {@code true} if the class requires bytecode injection to replace the base class. */
requiresBytecodeInjection()68   public abstract boolean requiresBytecodeInjection();
69 
70   /** Returns the {@link AndroidType} for the annotated element. */
androidType()71   public abstract AndroidType androidType();
72 
73   /** Returns {@link Optional} of {@link AndroidEntryPointMetadata}. */
baseMetadata()74   public abstract Optional<AndroidEntryPointMetadata> baseMetadata();
75 
76   /** Returns set of scopes that the component interface should be installed in. */
installInComponents()77   public abstract ImmutableSet<ClassName> installInComponents();
78 
79   /** Returns the component manager this generated Hilt class should use. */
componentManager()80   public abstract TypeName componentManager();
81 
82   /** Returns the initialization arguments for the component manager. */
componentManagerInitArgs()83   public abstract Optional<CodeBlock> componentManagerInitArgs();
84 
85   /**
86    * Returns the metadata for the root most class in the hierarchy.
87    *
88    * <p>If this is the only metadata in the class hierarchy, it returns this.
89    */
90   @Memoized
rootMetadata()91   public AndroidEntryPointMetadata rootMetadata() {
92     return baseMetadata().map(AndroidEntryPointMetadata::rootMetadata).orElse(this);
93   }
94 
isRootMetadata()95   boolean isRootMetadata() {
96     return this.equals(rootMetadata());
97   }
98 
99   /** Returns true if this class allows optional injection. */
allowsOptionalInjection()100   public boolean allowsOptionalInjection() {
101     return Processors.hasAnnotation(element(), AndroidClassNames.OPTIONAL_INJECT);
102   }
103 
104   /** Returns true if any base class (transitively) allows optional injection. */
baseAllowsOptionalInjection()105   public boolean baseAllowsOptionalInjection() {
106     return baseMetadata().isPresent() && baseMetadata().get().allowsOptionalInjection();
107   }
108 
109   /** Returns true if any base class (transitively) uses @AndroidEntryPoint. */
overridesAndroidEntryPointClass()110   public boolean overridesAndroidEntryPointClass() {
111     return baseMetadata().isPresent();
112   }
113 
114   /** The name of the class annotated with @AndroidEntryPoint */
elementClassName()115   public ClassName elementClassName() {
116     return ClassName.get(element());
117   }
118 
119   /** The name of the base class given to @AndroidEntryPoint */
baseClassName()120   public TypeName baseClassName() {
121     return TypeName.get(baseElement().asType());
122   }
123 
124   /** The name of the generated injector for the Hilt class. */
injectorClassName()125   public ClassName injectorClassName() {
126     return Processors.append(
127         Processors.getEnclosedClassName(elementClassName()), "_GeneratedInjector");
128   }
129 
130   /**
131    * The name of inject method for this class. The format is: inject$CLASS. If the class is nested,
132    * will return the full name deliminated with '_'. e.g. Foo.Bar.Baz -> injectFoo_Bar_Baz
133    */
injectMethodName()134   public String injectMethodName() {
135     return "inject" + Processors.getEnclosedName(elementClassName());
136   }
137 
138   /** Returns the @InstallIn annotation for the module providing this class. */
injectorInstallInAnnotation()139   public final AnnotationSpec injectorInstallInAnnotation() {
140     return Components.getInstallInAnnotationSpec(installInComponents());
141   }
142 
componentManagerParam()143   public ParameterSpec componentManagerParam() {
144     return ParameterSpec.builder(componentManager(), "componentManager").build();
145   }
146 
147   /**
148    * Modifiers that should be applied to the generated class.
149    *
150    * <p>Note that the generated class must have public visibility if used by a
151    * public @AndroidEntryPoint-annotated kotlin class. See:
152    * https://discuss.kotlinlang.org/t/why-does-kotlin-prohibit-exposing-restricted-visibility-types/7047
153    */
generatedClassModifiers()154   public Modifier[] generatedClassModifiers() {
155     return isKotlinClass(element()) && element().getModifiers().contains(Modifier.PUBLIC)
156         ? new Modifier[] {Modifier.ABSTRACT, Modifier.PUBLIC}
157         : new Modifier[] {Modifier.ABSTRACT};
158   }
159 
generatedClassName(TypeElement element)160   private static ClassName generatedClassName(TypeElement element) {
161     return Processors.prepend(Processors.getEnclosedClassName(ClassName.get(element)), "Hilt_");
162   }
163 
164   private static final ImmutableSet<ClassName> HILT_ANNOTATION_NAMES =
165       ImmutableSet.of(
166           AndroidClassNames.HILT_ANDROID_APP,
167           AndroidClassNames.ANDROID_ENTRY_POINT);
168 
hiltAnnotations(Element element)169   private static ImmutableSet<? extends AnnotationMirror> hiltAnnotations(Element element) {
170     return element.getAnnotationMirrors().stream()
171         .filter(mirror -> HILT_ANNOTATION_NAMES.contains(ClassName.get(mirror.getAnnotationType())))
172         .collect(toImmutableSet());
173   }
174 
175   /** Returns true if the given element has Android Entry Point metadata. */
hasAndroidEntryPointMetadata(Element element)176   public static boolean hasAndroidEntryPointMetadata(Element element) {
177     return !hiltAnnotations(element).isEmpty();
178   }
179 
180   /** Returns the {@link AndroidEntryPointMetadata} for a @AndroidEntryPoint annotated element. */
of(ProcessingEnvironment env, Element element)181   public static AndroidEntryPointMetadata of(ProcessingEnvironment env, Element element) {
182     LinkedHashSet<Element> inheritanceTrace = new LinkedHashSet<>();
183     inheritanceTrace.add(element);
184     return of(env, element, inheritanceTrace);
185   }
186 
manuallyConstruct( TypeElement element, TypeElement baseElement, ClassName generatedClassName, boolean requiresBytecodeInjection, AndroidType androidType, Optional<AndroidEntryPointMetadata> baseMetadata, ImmutableSet<ClassName> installInComponents, TypeName componentManager, Optional<CodeBlock> componentManagerInitArgs)187   public static AndroidEntryPointMetadata manuallyConstruct(
188       TypeElement element,
189       TypeElement baseElement,
190       ClassName generatedClassName,
191       boolean requiresBytecodeInjection,
192       AndroidType androidType,
193       Optional<AndroidEntryPointMetadata> baseMetadata,
194       ImmutableSet<ClassName> installInComponents,
195       TypeName componentManager,
196       Optional<CodeBlock> componentManagerInitArgs) {
197     return new AutoValue_AndroidEntryPointMetadata(
198         element,
199         baseElement,
200         generatedClassName,
201         requiresBytecodeInjection,
202         androidType,
203         baseMetadata,
204         installInComponents,
205         componentManager,
206         componentManagerInitArgs);
207   }
208 
209   /**
210    * Internal implementation for "of" method, checking inheritance cycle utilizing inheritanceTrace
211    * along the way.
212    */
of( ProcessingEnvironment env, Element element, LinkedHashSet<Element> inheritanceTrace)213   private static AndroidEntryPointMetadata of(
214       ProcessingEnvironment env, Element element, LinkedHashSet<Element> inheritanceTrace) {
215     ImmutableSet<? extends AnnotationMirror> hiltAnnotations = hiltAnnotations(element);
216     ProcessorErrors.checkState(
217         hiltAnnotations.size() == 1,
218         element,
219         "Expected exactly 1 of %s. Found: %s",
220         HILT_ANNOTATION_NAMES,
221         hiltAnnotations);
222     ClassName annotationClassName =
223         ClassName.get(
224             MoreTypes.asTypeElement(Iterables.getOnlyElement(hiltAnnotations).getAnnotationType()));
225 
226     ProcessorErrors.checkState(
227         element.getKind() == ElementKind.CLASS,
228         element,
229         "Only classes can be annotated with @%s",
230         annotationClassName.simpleName());
231     TypeElement androidEntryPointElement = MoreElements.asType(element);
232 
233     ProcessorErrors.checkState(
234         androidEntryPointElement.getTypeParameters().isEmpty(),
235         element,
236         "@%s-annotated classes cannot have type parameters.",
237         annotationClassName.simpleName());
238 
239     final TypeElement androidEntryPointClassValue =
240         Processors.getAnnotationClassValue(
241             env.getElementUtils(),
242             Processors.getAnnotationMirror(androidEntryPointElement, annotationClassName),
243             "value");
244     final TypeElement baseElement;
245     final ClassName generatedClassName;
246     boolean requiresBytecodeInjection =
247         isAndroidSuperclassValidationDisabled(androidEntryPointElement, env)
248             && MoreTypes.isTypeOf(Void.class, androidEntryPointClassValue.asType());
249     if (requiresBytecodeInjection) {
250       baseElement = MoreElements.asType(env.getTypeUtils().asElement(androidEntryPointElement.getSuperclass()));
251       // If this AndroidEntryPoint is a Kotlin class and its base type is also Kotlin and has
252       // default values declared in its constructor then error out because for the short-form
253       // usage of @AndroidEntryPoint the bytecode transformation will be done incorrectly.
254       KotlinMetadataUtil metadataUtil = KotlinMetadataUtils.getMetadataUtil();
255       ProcessorErrors.checkState(
256           !metadataUtil.hasMetadata(androidEntryPointElement)
257               || !metadataUtil.containsConstructorWithDefaultParam(baseElement),
258           baseElement,
259           "The base class, '%s', of the @AndroidEntryPoint, '%s', contains a constructor with "
260               + "default parameters. This is currently not supported by the Gradle plugin. Either "
261               + "specify the base class as described at "
262               + "https://dagger.dev/hilt/gradle-setup#why-use-the-plugin or remove the default value "
263               + "declaration.",
264           baseElement.getQualifiedName(),
265           androidEntryPointElement.getQualifiedName());
266       generatedClassName = generatedClassName(androidEntryPointElement);
267     } else {
268       baseElement = androidEntryPointClassValue;
269       ProcessorErrors.checkState(
270           !MoreTypes.isTypeOf(Void.class, baseElement.asType()),
271           androidEntryPointElement,
272           "Expected @%s to have a value."
273           + " Did you forget to apply the Gradle Plugin? (dagger.hilt.android.plugin)\n"
274           + "See https://dagger.dev/hilt/gradle-setup.html" ,
275           annotationClassName.simpleName());
276 
277       // Check that the root $CLASS extends Hilt_$CLASS
278       String extendsName =
279           env.getTypeUtils()
280               .asElement(androidEntryPointElement.getSuperclass())
281               .getSimpleName()
282               .toString();
283       generatedClassName = generatedClassName(androidEntryPointElement);
284       ProcessorErrors.checkState(
285           extendsName.contentEquals(generatedClassName.simpleName()),
286           androidEntryPointElement,
287           "@%s class expected to extend %s. Found: %s",
288           annotationClassName.simpleName(),
289           generatedClassName.simpleName(),
290           extendsName);
291     }
292 
293     Optional<AndroidEntryPointMetadata> baseMetadata =
294         baseMetadata(env, androidEntryPointElement, baseElement, inheritanceTrace);
295 
296     if (baseMetadata.isPresent()) {
297       return manuallyConstruct(
298           androidEntryPointElement,
299           baseElement,
300           generatedClassName,
301           requiresBytecodeInjection,
302           baseMetadata.get().androidType(),
303           baseMetadata,
304           baseMetadata.get().installInComponents(),
305           baseMetadata.get().componentManager(),
306           baseMetadata.get().componentManagerInitArgs());
307     } else {
308       Type type = Type.of(androidEntryPointElement, baseElement);
309       return manuallyConstruct(
310           androidEntryPointElement,
311           baseElement,
312           generatedClassName,
313           requiresBytecodeInjection,
314           type.androidType,
315           Optional.empty(),
316           ImmutableSet.of(type.component),
317           type.manager,
318           Optional.ofNullable(type.componentManagerInitArgs));
319     }
320   }
321 
baseMetadata( ProcessingEnvironment env, TypeElement element, TypeElement baseElement, LinkedHashSet<Element> inheritanceTrace)322   private static Optional<AndroidEntryPointMetadata> baseMetadata(
323       ProcessingEnvironment env,
324       TypeElement element,
325       TypeElement baseElement,
326       LinkedHashSet<Element> inheritanceTrace) {
327     ProcessorErrors.checkState(
328         inheritanceTrace.add(baseElement),
329         element,
330         cyclicInheritanceErrorMessage(inheritanceTrace, baseElement));
331     if (hasAndroidEntryPointMetadata(baseElement)) {
332       AndroidEntryPointMetadata baseMetadata =
333           AndroidEntryPointMetadata.of(env, baseElement, inheritanceTrace);
334       checkConsistentAnnotations(element, baseMetadata);
335       return Optional.of(baseMetadata);
336     }
337 
338     TypeMirror superClass = baseElement.getSuperclass();
339     // None type is returned if this is an interface or Object
340     if (superClass.getKind() != TypeKind.NONE && superClass.getKind() != TypeKind.ERROR) {
341       Preconditions.checkState(superClass.getKind() == TypeKind.DECLARED);
342       return baseMetadata(env, element, MoreTypes.asTypeElement(superClass), inheritanceTrace);
343     }
344 
345     return Optional.empty();
346   }
347 
cyclicInheritanceErrorMessage( LinkedHashSet<Element> inheritanceTrace, TypeElement cycleEntryPoint)348   private static String cyclicInheritanceErrorMessage(
349       LinkedHashSet<Element> inheritanceTrace, TypeElement cycleEntryPoint) {
350     return String.format(
351         "Cyclic inheritance detected. Make sure the base class of @AndroidEntryPoint "
352             + "is not the annotated class itself or subclass of the annotated class.\n"
353             + "The cyclic inheritance structure: %s --> %s\n",
354         inheritanceTrace.stream()
355             .map(Element::asType)
356             .map(TypeMirror::toString)
357             .collect(Collectors.joining(" --> ")),
358         cycleEntryPoint.asType());
359   }
360 
isKotlinClass(TypeElement typeElement)361   private static boolean isKotlinClass(TypeElement typeElement) {
362     return typeElement.getAnnotationMirrors().stream()
363         .map(mirror -> mirror.getAnnotationType())
364         .anyMatch(type -> ClassName.get(type).equals(ClassNames.KOTLIN_METADATA));
365   }
366 
367   /**
368    * The Android type of the Android Entry Point element. Component splits (like with fragment
369    * bindings) are coalesced.
370    */
371   public enum AndroidType {
372     APPLICATION,
373     ACTIVITY,
374     BROADCAST_RECEIVER,
375     FRAGMENT,
376     SERVICE,
377     VIEW
378   }
379 
380   /** The type of Android Entry Point element. This includes splits for different components. */
381   private static final class Type {
382     private static final Type APPLICATION =
383         new Type(
384             AndroidClassNames.SINGLETON_COMPONENT,
385             AndroidType.APPLICATION,
386             AndroidClassNames.APPLICATION_COMPONENT_MANAGER,
387             null);
388     private static final Type SERVICE =
389         new Type(
390             AndroidClassNames.SERVICE_COMPONENT,
391             AndroidType.SERVICE,
392             AndroidClassNames.SERVICE_COMPONENT_MANAGER,
393             CodeBlock.of("this"));
394     private static final Type BROADCAST_RECEIVER =
395         new Type(
396             AndroidClassNames.SINGLETON_COMPONENT,
397             AndroidType.BROADCAST_RECEIVER,
398             AndroidClassNames.BROADCAST_RECEIVER_COMPONENT_MANAGER,
399             null);
400     private static final Type ACTIVITY =
401         new Type(
402             AndroidClassNames.ACTIVITY_COMPONENT,
403             AndroidType.ACTIVITY,
404             AndroidClassNames.ACTIVITY_COMPONENT_MANAGER,
405             CodeBlock.of("this"));
406     private static final Type FRAGMENT =
407         new Type(
408             AndroidClassNames.FRAGMENT_COMPONENT,
409             AndroidType.FRAGMENT,
410             AndroidClassNames.FRAGMENT_COMPONENT_MANAGER,
411             CodeBlock.of("this"));
412     private static final Type VIEW =
413         new Type(
414             AndroidClassNames.VIEW_WITH_FRAGMENT_COMPONENT,
415             AndroidType.VIEW,
416             AndroidClassNames.VIEW_COMPONENT_MANAGER,
417             CodeBlock.of("this, true /* hasFragmentBindings */"));
418     private static final Type VIEW_NO_FRAGMENT =
419         new Type(
420             AndroidClassNames.VIEW_COMPONENT,
421             AndroidType.VIEW,
422             AndroidClassNames.VIEW_COMPONENT_MANAGER,
423             CodeBlock.of("this, false /* hasFragmentBindings */"));
424 
425     final ClassName component;
426     final AndroidType androidType;
427     final ClassName manager;
428     final CodeBlock componentManagerInitArgs;
429 
Type( ClassName component, AndroidType androidType, ClassName manager, CodeBlock componentManagerInitArgs)430     Type(
431         ClassName component,
432         AndroidType androidType,
433         ClassName manager,
434         CodeBlock componentManagerInitArgs) {
435       this.component = component;
436       this.androidType = androidType;
437       this.manager = manager;
438       this.componentManagerInitArgs = componentManagerInitArgs;
439     }
440 
androidType()441     AndroidType androidType() {
442       return androidType;
443     }
444 
of(TypeElement element, TypeElement baseElement)445     private static Type of(TypeElement element, TypeElement baseElement) {
446       if (Processors.hasAnnotation(element, AndroidClassNames.HILT_ANDROID_APP)) {
447         return forHiltAndroidApp(element, baseElement);
448       }
449       return forAndroidEntryPoint(element, baseElement);
450     }
451 
forHiltAndroidApp(TypeElement element, TypeElement baseElement)452     private static Type forHiltAndroidApp(TypeElement element, TypeElement baseElement) {
453       ProcessorErrors.checkState(
454           Processors.isAssignableFrom(baseElement, AndroidClassNames.APPLICATION),
455           element,
456           "@HiltAndroidApp base class must extend Application. Found: %s",
457           baseElement);
458       return Type.APPLICATION;
459     }
460 
forAndroidEntryPoint(TypeElement element, TypeElement baseElement)461     private static Type forAndroidEntryPoint(TypeElement element, TypeElement baseElement) {
462       if (Processors.isAssignableFrom(baseElement, AndroidClassNames.ACTIVITY)) {
463         ProcessorErrors.checkState(
464             Processors.isAssignableFrom(baseElement, AndroidClassNames.COMPONENT_ACTIVITY),
465             element,
466             "Activities annotated with @AndroidEntryPoint must be a subclass of "
467                 + "androidx.activity.ComponentActivity. (e.g. FragmentActivity, "
468                 + "AppCompatActivity, etc.)"
469             );
470         return Type.ACTIVITY;
471       } else if (Processors.isAssignableFrom(baseElement, AndroidClassNames.SERVICE)) {
472         return Type.SERVICE;
473       } else if (Processors.isAssignableFrom(baseElement, AndroidClassNames.BROADCAST_RECEIVER)) {
474         return Type.BROADCAST_RECEIVER;
475       } else if (Processors.isAssignableFrom(baseElement, AndroidClassNames.FRAGMENT)) {
476         return Type.FRAGMENT;
477       } else if (Processors.isAssignableFrom(baseElement, AndroidClassNames.VIEW)) {
478         boolean withFragmentBindings =
479             Processors.hasAnnotation(element, AndroidClassNames.WITH_FRAGMENT_BINDINGS);
480         return withFragmentBindings ? Type.VIEW : Type.VIEW_NO_FRAGMENT;
481       } else if (Processors.isAssignableFrom(baseElement, AndroidClassNames.APPLICATION)) {
482         throw new BadInputException(
483             "@AndroidEntryPoint cannot be used on an Application. Use @HiltAndroidApp instead.",
484             element);
485       }
486       throw new BadInputException(
487           "@AndroidEntryPoint base class must extend ComponentActivity, (support) Fragment, "
488               + "View, Service, or BroadcastReceiver.",
489           element);
490     }
491   }
492 
checkConsistentAnnotations( TypeElement element, AndroidEntryPointMetadata baseMetadata)493   private static void checkConsistentAnnotations(
494       TypeElement element, AndroidEntryPointMetadata baseMetadata) {
495     TypeElement baseElement = baseMetadata.element();
496     checkAnnotationsMatch(element, baseElement, AndroidClassNames.WITH_FRAGMENT_BINDINGS);
497 
498     ProcessorErrors.checkState(
499         baseMetadata.allowsOptionalInjection()
500             || !Processors.hasAnnotation(element, AndroidClassNames.OPTIONAL_INJECT),
501         element,
502         "@OptionalInject Hilt class cannot extend from a non-optional @AndroidEntryPoint "
503             + "base: %s",
504         element);
505   }
506 
checkAnnotationsMatch( TypeElement element, TypeElement baseElement, ClassName annotationName)507   private static void checkAnnotationsMatch(
508       TypeElement element, TypeElement baseElement, ClassName annotationName) {
509     boolean isAnnotated = Processors.hasAnnotation(element, annotationName);
510     boolean isBaseAnnotated = Processors.hasAnnotation(baseElement, annotationName);
511     ProcessorErrors.checkState(
512         isAnnotated == isBaseAnnotated,
513         element,
514         isBaseAnnotated
515             ? "Classes that extend an @%1$s base class must also be annotated @%1$s"
516                 : "Classes that extend a @AndroidEntryPoint base class must not use @%1$s when the "
517                     + "base class "
518                 + "does not use @%1$s",
519         annotationName.simpleName());
520   }
521 }
522