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