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