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