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.android.processor.internal.androidentrypoint.HiltCompilerOptions.BooleanOption.DISABLE_ANDROID_SUPERCLASS_VALIDATION; 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 String prefix = "Hilt_"; 162 return Processors.prepend(Processors.getEnclosedClassName(ClassName.get(element)), prefix); 163 } 164 165 private static final ImmutableSet<ClassName> HILT_ANNOTATION_NAMES = 166 ImmutableSet.of( 167 AndroidClassNames.HILT_ANDROID_APP, 168 AndroidClassNames.ANDROID_ENTRY_POINT); 169 hiltAnnotations(Element element)170 private static ImmutableSet<? extends AnnotationMirror> hiltAnnotations(Element element) { 171 return element.getAnnotationMirrors().stream() 172 .filter(mirror -> HILT_ANNOTATION_NAMES.contains(ClassName.get(mirror.getAnnotationType()))) 173 .collect(toImmutableSet()); 174 } 175 176 /** Returns true if the given element has Android Entry Point metadata. */ hasAndroidEntryPointMetadata(Element element)177 public static boolean hasAndroidEntryPointMetadata(Element element) { 178 return !hiltAnnotations(element).isEmpty(); 179 } 180 181 /** Returns the {@link AndroidEntryPointMetadata} for a @AndroidEntryPoint annotated element. */ of(ProcessingEnvironment env, Element element)182 public static AndroidEntryPointMetadata of(ProcessingEnvironment env, Element element) { 183 LinkedHashSet<Element> inheritanceTrace = new LinkedHashSet<>(); 184 inheritanceTrace.add(element); 185 return of(env, element, inheritanceTrace); 186 } 187 manuallyConstruct( TypeElement element, TypeElement baseElement, ClassName generatedClassName, boolean requiresBytecodeInjection, AndroidType androidType, Optional<AndroidEntryPointMetadata> baseMetadata, ImmutableSet<ClassName> installInComponents, TypeName componentManager, Optional<CodeBlock> componentManagerInitArgs)188 public static AndroidEntryPointMetadata manuallyConstruct( 189 TypeElement element, 190 TypeElement baseElement, 191 ClassName generatedClassName, 192 boolean requiresBytecodeInjection, 193 AndroidType androidType, 194 Optional<AndroidEntryPointMetadata> baseMetadata, 195 ImmutableSet<ClassName> installInComponents, 196 TypeName componentManager, 197 Optional<CodeBlock> componentManagerInitArgs) { 198 return new AutoValue_AndroidEntryPointMetadata( 199 element, 200 baseElement, 201 generatedClassName, 202 requiresBytecodeInjection, 203 androidType, 204 baseMetadata, 205 installInComponents, 206 componentManager, 207 componentManagerInitArgs); 208 } 209 210 /** 211 * Internal implementation for "of" method, checking inheritance cycle utilizing inheritanceTrace 212 * along the way. 213 */ of( ProcessingEnvironment env, Element element, LinkedHashSet<Element> inheritanceTrace)214 private static AndroidEntryPointMetadata of( 215 ProcessingEnvironment env, Element element, LinkedHashSet<Element> inheritanceTrace) { 216 ImmutableSet<? extends AnnotationMirror> hiltAnnotations = hiltAnnotations(element); 217 ProcessorErrors.checkState( 218 hiltAnnotations.size() == 1, 219 element, 220 "Expected exactly 1 of %s. Found: %s", 221 HILT_ANNOTATION_NAMES, 222 hiltAnnotations); 223 ClassName annotationClassName = 224 ClassName.get( 225 MoreTypes.asTypeElement(Iterables.getOnlyElement(hiltAnnotations).getAnnotationType())); 226 227 ProcessorErrors.checkState( 228 element.getKind() == ElementKind.CLASS, 229 element, 230 "Only classes can be annotated with @%s", 231 annotationClassName.simpleName()); 232 TypeElement androidEntryPointElement = MoreElements.asType(element); 233 234 ProcessorErrors.checkState( 235 androidEntryPointElement.getTypeParameters().isEmpty(), 236 element, 237 "@%s-annotated classes cannot have type parameters.", 238 annotationClassName.simpleName()); 239 240 final TypeElement androidEntryPointClassValue = 241 Processors.getAnnotationClassValue( 242 env.getElementUtils(), 243 Processors.getAnnotationMirror(androidEntryPointElement, annotationClassName), 244 "value"); 245 final TypeElement baseElement; 246 final ClassName generatedClassName; 247 boolean requiresBytecodeInjection = 248 DISABLE_ANDROID_SUPERCLASS_VALIDATION.get(env) 249 && MoreTypes.isTypeOf(Void.class, androidEntryPointClassValue.asType()); 250 if (requiresBytecodeInjection) { 251 baseElement = MoreElements.asType(env.getTypeUtils().asElement(androidEntryPointElement.getSuperclass())); 252 // If this AndroidEntryPoint is a Kotlin class and its base type is also Kotlin and has 253 // default values declared in its constructor then error out because for the short-form 254 // usage of @AndroidEntryPoint the bytecode transformation will be done incorrectly. 255 KotlinMetadataUtil metadataUtil = KotlinMetadataUtils.getMetadataUtil(); 256 ProcessorErrors.checkState( 257 !metadataUtil.hasMetadata(androidEntryPointElement) 258 || !metadataUtil.containsConstructorWithDefaultParam(baseElement), 259 baseElement, 260 "The base class, '%s', of the @AndroidEntryPoint, '%s', contains a constructor with " 261 + "default parameters. This is currently not supported by the Gradle plugin. Either " 262 + "specify the base class as described at " 263 + "https://dagger.dev/hilt/gradle-setup#why-use-the-plugin or remove the default value " 264 + "declaration.", 265 baseElement.getQualifiedName(), 266 androidEntryPointElement.getQualifiedName()); 267 generatedClassName = generatedClassName(androidEntryPointElement); 268 } else { 269 baseElement = androidEntryPointClassValue; 270 ProcessorErrors.checkState( 271 !MoreTypes.isTypeOf(Void.class, baseElement.asType()), 272 androidEntryPointElement, 273 "Expected @%s to have a value." 274 + " Did you forget to apply the Gradle Plugin? (dagger.hilt.android.plugin)\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 env.getTypeUtils() 281 .asElement(androidEntryPointElement.getSuperclass()) 282 .getSimpleName() 283 .toString(); 284 generatedClassName = generatedClassName(androidEntryPointElement); 285 ProcessorErrors.checkState( 286 extendsName.contentEquals(generatedClassName.simpleName()), 287 androidEntryPointElement, 288 "@%s class expected to extend %s. Found: %s", 289 annotationClassName.simpleName(), 290 generatedClassName.simpleName(), 291 extendsName); 292 } 293 294 Optional<AndroidEntryPointMetadata> baseMetadata = 295 baseMetadata(env, 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( ProcessingEnvironment env, TypeElement element, TypeElement baseElement, LinkedHashSet<Element> inheritanceTrace)323 private static Optional<AndroidEntryPointMetadata> baseMetadata( 324 ProcessingEnvironment env, 325 TypeElement element, 326 TypeElement baseElement, 327 LinkedHashSet<Element> inheritanceTrace) { 328 ProcessorErrors.checkState( 329 inheritanceTrace.add(baseElement), 330 element, 331 cyclicInheritanceErrorMessage(inheritanceTrace, baseElement)); 332 if (hasAndroidEntryPointMetadata(baseElement)) { 333 AndroidEntryPointMetadata baseMetadata = 334 AndroidEntryPointMetadata.of(env, baseElement, inheritanceTrace); 335 checkConsistentAnnotations(element, baseMetadata); 336 return Optional.of(baseMetadata); 337 } 338 339 TypeMirror superClass = baseElement.getSuperclass(); 340 // None type is returned if this is an interface or Object 341 if (superClass.getKind() != TypeKind.NONE && superClass.getKind() != TypeKind.ERROR) { 342 Preconditions.checkState(superClass.getKind() == TypeKind.DECLARED); 343 return baseMetadata(env, element, MoreTypes.asTypeElement(superClass), inheritanceTrace); 344 } 345 346 return Optional.empty(); 347 } 348 cyclicInheritanceErrorMessage( LinkedHashSet<Element> inheritanceTrace, TypeElement cycleEntryPoint)349 private static String cyclicInheritanceErrorMessage( 350 LinkedHashSet<Element> inheritanceTrace, TypeElement cycleEntryPoint) { 351 return String.format( 352 "Cyclic inheritance detected. Make sure the base class of @AndroidEntryPoint " 353 + "is not the annotated class itself or subclass of the annotated class.\n" 354 + "The cyclic inheritance structure: %s --> %s\n", 355 inheritanceTrace.stream() 356 .map(Element::asType) 357 .map(TypeMirror::toString) 358 .collect(Collectors.joining(" --> ")), 359 cycleEntryPoint.asType()); 360 } 361 isKotlinClass(TypeElement typeElement)362 private static boolean isKotlinClass(TypeElement typeElement) { 363 return typeElement.getAnnotationMirrors().stream() 364 .map(mirror -> mirror.getAnnotationType()) 365 .anyMatch(type -> ClassName.get(type).equals(ClassNames.KOTLIN_METADATA)); 366 } 367 368 /** 369 * The Android type of the Android Entry Point element. Component splits (like with fragment 370 * bindings) are coalesced. 371 */ 372 public enum AndroidType { 373 APPLICATION, 374 ACTIVITY, 375 BROADCAST_RECEIVER, 376 FRAGMENT, 377 SERVICE, 378 VIEW 379 } 380 381 /** The type of Android Entry Point element. This includes splits for different components. */ 382 private static final class Type { 383 private static final Type APPLICATION = 384 new Type( 385 AndroidClassNames.SINGLETON_COMPONENT, 386 AndroidType.APPLICATION, 387 AndroidClassNames.APPLICATION_COMPONENT_MANAGER, 388 null); 389 private static final Type SERVICE = 390 new Type( 391 AndroidClassNames.SERVICE_COMPONENT, 392 AndroidType.SERVICE, 393 AndroidClassNames.SERVICE_COMPONENT_MANAGER, 394 CodeBlock.of("this")); 395 private static final Type BROADCAST_RECEIVER = 396 new Type( 397 AndroidClassNames.SINGLETON_COMPONENT, 398 AndroidType.BROADCAST_RECEIVER, 399 AndroidClassNames.BROADCAST_RECEIVER_COMPONENT_MANAGER, 400 null); 401 private static final Type ACTIVITY = 402 new Type( 403 AndroidClassNames.ACTIVITY_COMPONENT, 404 AndroidType.ACTIVITY, 405 AndroidClassNames.ACTIVITY_COMPONENT_MANAGER, 406 CodeBlock.of("this")); 407 private static final Type FRAGMENT = 408 new Type( 409 AndroidClassNames.FRAGMENT_COMPONENT, 410 AndroidType.FRAGMENT, 411 AndroidClassNames.FRAGMENT_COMPONENT_MANAGER, 412 CodeBlock.of("this")); 413 private static final Type VIEW = 414 new Type( 415 AndroidClassNames.VIEW_WITH_FRAGMENT_COMPONENT, 416 AndroidType.VIEW, 417 AndroidClassNames.VIEW_COMPONENT_MANAGER, 418 CodeBlock.of("this, true /* hasFragmentBindings */")); 419 private static final Type VIEW_NO_FRAGMENT = 420 new Type( 421 AndroidClassNames.VIEW_COMPONENT, 422 AndroidType.VIEW, 423 AndroidClassNames.VIEW_COMPONENT_MANAGER, 424 CodeBlock.of("this, false /* hasFragmentBindings */")); 425 426 final ClassName component; 427 final AndroidType androidType; 428 final ClassName manager; 429 final CodeBlock componentManagerInitArgs; 430 Type( ClassName component, AndroidType androidType, ClassName manager, CodeBlock componentManagerInitArgs)431 Type( 432 ClassName component, 433 AndroidType androidType, 434 ClassName manager, 435 CodeBlock componentManagerInitArgs) { 436 this.component = component; 437 this.androidType = androidType; 438 this.manager = manager; 439 this.componentManagerInitArgs = componentManagerInitArgs; 440 } 441 androidType()442 AndroidType androidType() { 443 return androidType; 444 } 445 of(TypeElement element, TypeElement baseElement)446 private static Type of(TypeElement element, TypeElement baseElement) { 447 if (Processors.hasAnnotation(element, AndroidClassNames.HILT_ANDROID_APP)) { 448 return forHiltAndroidApp(element, baseElement); 449 } 450 return forAndroidEntryPoint(element, baseElement); 451 } 452 forHiltAndroidApp(TypeElement element, TypeElement baseElement)453 private static Type forHiltAndroidApp(TypeElement element, TypeElement baseElement) { 454 ProcessorErrors.checkState( 455 Processors.isAssignableFrom(baseElement, AndroidClassNames.APPLICATION), 456 element, 457 "@HiltAndroidApp base class must extend Application. Found: %s", 458 baseElement); 459 return Type.APPLICATION; 460 } 461 forAndroidEntryPoint(TypeElement element, TypeElement baseElement)462 private static Type forAndroidEntryPoint(TypeElement element, TypeElement baseElement) { 463 if (Processors.isAssignableFrom(baseElement, AndroidClassNames.ACTIVITY)) { 464 ProcessorErrors.checkState( 465 Processors.isAssignableFrom(baseElement, AndroidClassNames.COMPONENT_ACTIVITY), 466 element, 467 "Activities annotated with @AndroidEntryPoint must be a subclass of " 468 + "androidx.activity.ComponentActivity. (e.g. FragmentActivity, " 469 + "AppCompatActivity, etc.)" 470 ); 471 return Type.ACTIVITY; 472 } else if (Processors.isAssignableFrom(baseElement, AndroidClassNames.SERVICE)) { 473 return Type.SERVICE; 474 } else if (Processors.isAssignableFrom(baseElement, AndroidClassNames.BROADCAST_RECEIVER)) { 475 return Type.BROADCAST_RECEIVER; 476 } else if (Processors.isAssignableFrom(baseElement, AndroidClassNames.FRAGMENT)) { 477 return Type.FRAGMENT; 478 } else if (Processors.isAssignableFrom(baseElement, AndroidClassNames.VIEW)) { 479 boolean withFragmentBindings = 480 Processors.hasAnnotation(element, AndroidClassNames.WITH_FRAGMENT_BINDINGS); 481 return withFragmentBindings ? Type.VIEW : Type.VIEW_NO_FRAGMENT; 482 } else if (Processors.isAssignableFrom(baseElement, AndroidClassNames.APPLICATION)) { 483 throw new BadInputException( 484 "@AndroidEntryPoint cannot be used on an Application. Use @HiltAndroidApp instead.", 485 element); 486 } 487 throw new BadInputException( 488 "@AndroidEntryPoint base class must extend ComponentActivity, (support) Fragment, " 489 + "View, Service, or BroadcastReceiver.", 490 element); 491 } 492 } 493 checkConsistentAnnotations( TypeElement element, AndroidEntryPointMetadata baseMetadata)494 private static void checkConsistentAnnotations( 495 TypeElement element, AndroidEntryPointMetadata baseMetadata) { 496 TypeElement baseElement = baseMetadata.element(); 497 checkAnnotationsMatch(element, baseElement, AndroidClassNames.WITH_FRAGMENT_BINDINGS); 498 499 ProcessorErrors.checkState( 500 baseMetadata.allowsOptionalInjection() 501 || !Processors.hasAnnotation(element, AndroidClassNames.OPTIONAL_INJECT), 502 element, 503 "@OptionalInject Hilt class cannot extend from a non-optional @AndroidEntryPoint " 504 + "base: %s", 505 element); 506 } 507 checkAnnotationsMatch( TypeElement element, TypeElement baseElement, ClassName annotationName)508 private static void checkAnnotationsMatch( 509 TypeElement element, TypeElement baseElement, ClassName annotationName) { 510 boolean isAnnotated = Processors.hasAnnotation(element, annotationName); 511 boolean isBaseAnnotated = Processors.hasAnnotation(baseElement, annotationName); 512 ProcessorErrors.checkState( 513 isAnnotated == isBaseAnnotated, 514 element, 515 isBaseAnnotated 516 ? "Classes that extend an @%1$s base class must also be annotated @%1$s" 517 : "Classes that extend a @AndroidEntryPoint base class must not use @%1$s when the " 518 + "base class " 519 + "does not use @%1$s", 520 annotationName.simpleName()); 521 } 522 } 523