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 com.google.common.collect.Iterables.getOnlyElement; 20 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; 21 import static javax.lang.model.element.Modifier.PRIVATE; 22 23 import com.google.common.base.Preconditions; 24 import com.squareup.javapoet.AnnotationSpec; 25 import com.squareup.javapoet.ClassName; 26 import com.squareup.javapoet.CodeBlock; 27 import com.squareup.javapoet.FieldSpec; 28 import com.squareup.javapoet.MethodSpec; 29 import com.squareup.javapoet.ParameterSpec; 30 import com.squareup.javapoet.TypeName; 31 import com.squareup.javapoet.TypeSpec; 32 import dagger.hilt.android.processor.internal.AndroidClassNames; 33 import dagger.hilt.processor.internal.ClassNames; 34 import dagger.hilt.processor.internal.Processors; 35 import java.util.List; 36 import java.util.Optional; 37 import java.util.stream.Collectors; 38 import javax.lang.model.element.AnnotationMirror; 39 import javax.lang.model.element.Element; 40 import javax.lang.model.element.ExecutableElement; 41 import javax.lang.model.element.Modifier; 42 import javax.lang.model.element.TypeElement; 43 import javax.lang.model.element.VariableElement; 44 import javax.lang.model.util.ElementFilter; 45 46 /** Helper class for writing Hilt generators. */ 47 final class Generators { 48 addGeneratedBaseClassJavadoc(TypeSpec.Builder builder, ClassName annotation)49 static void addGeneratedBaseClassJavadoc(TypeSpec.Builder builder, ClassName annotation) { 50 builder.addJavadoc("A generated base class to be extended by the @$T annotated class. If using" 51 + " the Gradle plugin, this is swapped as the base class via bytecode transformation.", 52 annotation); 53 } 54 55 /** 56 * Copies all constructors with arguments to the builder. 57 */ copyConstructors(TypeElement baseClass, TypeSpec.Builder builder)58 static void copyConstructors(TypeElement baseClass, TypeSpec.Builder builder) { 59 copyConstructors(baseClass, CodeBlock.builder().build(), builder); 60 } 61 62 /** 63 * Copies all constructors with arguments along with an appended body to the builder. 64 */ copyConstructors(TypeElement baseClass, CodeBlock body, TypeSpec.Builder builder)65 static void copyConstructors(TypeElement baseClass, CodeBlock body, TypeSpec.Builder builder) { 66 List<ExecutableElement> constructors = 67 ElementFilter.constructorsIn(baseClass.getEnclosedElements()) 68 .stream() 69 .filter(constructor -> !constructor.getModifiers().contains(PRIVATE)) 70 .collect(Collectors.toList()); 71 72 if (constructors.size() == 1 73 && getOnlyElement(constructors).getParameters().isEmpty() 74 && body.isEmpty()) { 75 // No need to copy the constructor if the default constructor will handle it. 76 return; 77 } 78 79 constructors.forEach(constructor -> builder.addMethod(copyConstructor(constructor, body))); 80 } 81 82 /** Returns Optional with AnnotationSpec for Nullable if found on element, empty otherwise. */ getNullableAnnotationSpec(Element element)83 private static Optional<AnnotationSpec> getNullableAnnotationSpec(Element element) { 84 for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { 85 if (annotationMirror 86 .getAnnotationType() 87 .asElement() 88 .getSimpleName() 89 .contentEquals("Nullable")) { 90 AnnotationSpec annotationSpec = AnnotationSpec.get(annotationMirror); 91 // If using the android internal Nullable, convert it to the externally-visible version. 92 return AndroidClassNames.NULLABLE_INTERNAL.equals(annotationSpec.type) 93 ? Optional.of(AnnotationSpec.builder(AndroidClassNames.NULLABLE).build()) 94 : Optional.of(annotationSpec); 95 } 96 } 97 return Optional.empty(); 98 } 99 100 /** 101 * Returns a ParameterSpec of the input parameter, @Nullable annotated if existing in original 102 * (this does not handle Nullable type annotations). 103 */ getParameterSpecWithNullable(VariableElement parameter)104 private static ParameterSpec getParameterSpecWithNullable(VariableElement parameter) { 105 ParameterSpec.Builder builder = ParameterSpec.get(parameter).toBuilder(); 106 getNullableAnnotationSpec(parameter).ifPresent(builder::addAnnotation); 107 return builder.build(); 108 } 109 110 /** 111 * Returns a {@link MethodSpec} for a constructor matching the given {@link ExecutableElement} 112 * constructor signature, and just calls super. If the constructor is 113 * {@link android.annotation.TargetApi} guarded, adds the TargetApi as well. 114 */ 115 // Example: 116 // Foo(Param1 param1, Param2 param2) { 117 // super(param1, param2); 118 // } copyConstructor(ExecutableElement constructor)119 static MethodSpec copyConstructor(ExecutableElement constructor) { 120 return copyConstructor(constructor, CodeBlock.builder().build()); 121 } 122 copyConstructor(ExecutableElement constructor, CodeBlock body)123 private static MethodSpec copyConstructor(ExecutableElement constructor, CodeBlock body) { 124 List<ParameterSpec> params = 125 constructor.getParameters().stream() 126 .map(parameter -> getParameterSpecWithNullable(parameter)) 127 .collect(Collectors.toList()); 128 129 final MethodSpec.Builder builder = 130 MethodSpec.constructorBuilder() 131 .addParameters(params) 132 .addStatement( 133 "super($L)", 134 params.stream().map(param -> param.name).collect(Collectors.joining(", "))) 135 .addCode(body); 136 137 constructor.getAnnotationMirrors().stream() 138 .filter(a -> Processors.hasAnnotation(a, AndroidClassNames.TARGET_API)) 139 .collect(toOptional()) 140 .map(AnnotationSpec::get) 141 .ifPresent(builder::addAnnotation); 142 143 return builder.build(); 144 } 145 146 /** 147 * Copies the Android lint annotations from the annotated element to the generated element. 148 * 149 * <p>Note: For now we only copy over {@link android.annotation.TargetApi}. 150 */ copyLintAnnotations(Element element, TypeSpec.Builder builder)151 static void copyLintAnnotations(Element element, TypeSpec.Builder builder) { 152 if (Processors.hasAnnotation(element, AndroidClassNames.TARGET_API)) { 153 builder.addAnnotation( 154 AnnotationSpec.get( 155 Processors.getAnnotationMirror(element, AndroidClassNames.TARGET_API))); 156 } 157 } 158 159 // @Override 160 // public CompT generatedComponent() { 161 // return componentManager().generatedComponent(); 162 // } addComponentOverride(AndroidEntryPointMetadata metadata, TypeSpec.Builder builder)163 static void addComponentOverride(AndroidEntryPointMetadata metadata, TypeSpec.Builder builder) { 164 if (metadata.overridesAndroidEntryPointClass()) { 165 // We don't need to override this method if we are extending a Hilt type. 166 return; 167 } 168 builder 169 .addSuperinterface(ClassNames.GENERATED_COMPONENT_MANAGER_HOLDER) 170 .addMethod( 171 MethodSpec.methodBuilder("generatedComponent") 172 .addAnnotation(Override.class) 173 .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 174 .returns(TypeName.OBJECT) 175 .addStatement("return $L.generatedComponent()", componentManagerCallBlock(metadata)) 176 .build()); 177 } 178 179 /** Adds the inject() and optionally the componentManager() methods to allow for injection. */ addInjectionMethods(AndroidEntryPointMetadata metadata, TypeSpec.Builder builder)180 static void addInjectionMethods(AndroidEntryPointMetadata metadata, TypeSpec.Builder builder) { 181 switch (metadata.androidType()) { 182 case ACTIVITY: 183 case FRAGMENT: 184 case VIEW: 185 case SERVICE: 186 addComponentManagerMethods(metadata, builder); 187 // fall through 188 case BROADCAST_RECEIVER: 189 addInjectMethod(metadata, builder); 190 break; 191 default: 192 throw new AssertionError(); 193 } 194 } 195 196 // @Override 197 // public FragmentComponentManager componentManager() { 198 // if (componentManager == null) { 199 // synchronize (componentManagerLock) { 200 // if (componentManager == null) { 201 // componentManager = createComponentManager(); 202 // } 203 // } 204 // } 205 // return componentManager; 206 // } addComponentManagerMethods( AndroidEntryPointMetadata metadata, TypeSpec.Builder typeSpecBuilder)207 private static void addComponentManagerMethods( 208 AndroidEntryPointMetadata metadata, TypeSpec.Builder typeSpecBuilder) { 209 if (metadata.overridesAndroidEntryPointClass()) { 210 // We don't need to override this method if we are extending a Hilt type. 211 return; 212 } 213 214 ParameterSpec managerParam = metadata.componentManagerParam(); 215 typeSpecBuilder.addField(componentManagerField(metadata)); 216 217 typeSpecBuilder.addMethod(createComponentManagerMethod(metadata)); 218 219 MethodSpec.Builder methodSpecBuilder = 220 MethodSpec.methodBuilder("componentManager") 221 .addAnnotation(Override.class) 222 .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 223 .returns(managerParam.type) 224 .beginControlFlow("if ($N == null)", managerParam); 225 226 // Views do not do double-checked locking because this is called from the constructor 227 if (metadata.androidType() != AndroidEntryPointMetadata.AndroidType.VIEW) { 228 typeSpecBuilder.addField(componentManagerLockField()); 229 230 methodSpecBuilder 231 .beginControlFlow("synchronized (componentManagerLock)") 232 .beginControlFlow("if ($N == null)", managerParam); 233 } 234 235 methodSpecBuilder 236 .addStatement("$N = createComponentManager()", managerParam) 237 .endControlFlow(); 238 239 if (metadata.androidType() != AndroidEntryPointMetadata.AndroidType.VIEW) { 240 methodSpecBuilder 241 .endControlFlow() 242 .endControlFlow(); 243 } 244 245 methodSpecBuilder.addStatement("return $N", managerParam); 246 247 typeSpecBuilder.addMethod(methodSpecBuilder.build()); 248 } 249 250 // protected FragmentComponentManager createComponentManager() { 251 // return new FragmentComponentManager(initArgs); 252 // } createComponentManagerMethod(AndroidEntryPointMetadata metadata)253 private static MethodSpec createComponentManagerMethod(AndroidEntryPointMetadata metadata) { 254 Preconditions.checkState( 255 metadata.componentManagerInitArgs().isPresent(), 256 "This method should not have been called for metadata where the init args are not" 257 + " present."); 258 return MethodSpec.methodBuilder("createComponentManager") 259 .addModifiers(Modifier.PROTECTED) 260 .returns(metadata.componentManager()) 261 .addStatement( 262 "return new $T($L)", 263 metadata.componentManager(), 264 metadata.componentManagerInitArgs().get()) 265 .build(); 266 } 267 268 // private volatile ComponentManager componentManager; componentManagerField(AndroidEntryPointMetadata metadata)269 private static FieldSpec componentManagerField(AndroidEntryPointMetadata metadata) { 270 ParameterSpec managerParam = metadata.componentManagerParam(); 271 FieldSpec.Builder builder = FieldSpec.builder(managerParam.type, managerParam.name) 272 .addModifiers(Modifier.PRIVATE); 273 274 // Views do not need volatile since these are set in the constructor if ever set. 275 if (metadata.androidType() != AndroidEntryPointMetadata.AndroidType.VIEW) { 276 builder.addModifiers(Modifier.VOLATILE); 277 } 278 279 return builder.build(); 280 } 281 282 // private final Object componentManagerLock = new Object(); componentManagerLockField()283 private static FieldSpec componentManagerLockField() { 284 return FieldSpec.builder(TypeName.get(Object.class), "componentManagerLock") 285 .addModifiers(Modifier.PRIVATE, Modifier.FINAL) 286 .initializer("new Object()") 287 .build(); 288 } 289 290 // protected void inject() { 291 // if (!injected) { 292 // generatedComponent().inject$CLASS(($CLASS) this); 293 // injected = true; 294 // } 295 // } addInjectMethod( AndroidEntryPointMetadata metadata, TypeSpec.Builder typeSpecBuilder)296 private static void addInjectMethod( 297 AndroidEntryPointMetadata metadata, TypeSpec.Builder typeSpecBuilder) { 298 MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("inject") 299 .addModifiers(Modifier.PROTECTED); 300 301 // Check if the parent is a Hilt type. If it isn't or if it is but it 302 // wasn't injected by hilt, then return. 303 // Object parent = ...depends on type... 304 // if (!(parent instanceof GeneratedComponentManager) 305 // || ((parent instanceof InjectedByHilt) && 306 // !((InjectedByHilt) parent).wasInjectedByHilt())) { 307 // return; 308 // 309 if (metadata.allowsOptionalInjection()) { 310 methodSpecBuilder 311 .addStatement("$T parent = $L", ClassNames.OBJECT, getParentCodeBlock(metadata)) 312 .beginControlFlow( 313 "if (!(parent instanceof $T) " 314 + "|| ((parent instanceof $T) && !(($T) parent).wasInjectedByHilt()))", 315 ClassNames.GENERATED_COMPONENT_MANAGER, 316 AndroidClassNames.INJECTED_BY_HILT, 317 AndroidClassNames.INJECTED_BY_HILT) 318 .addStatement("return") 319 .endControlFlow(); 320 } 321 322 typeSpecBuilder.addField(injectedField(metadata)); 323 324 switch (metadata.androidType()) { 325 case ACTIVITY: 326 case FRAGMENT: 327 case VIEW: 328 case SERVICE: 329 // Only add @Override if an ancestor extends a generated Hilt class. 330 // When using bytecode injection, this isn't always guaranteed. 331 if (metadata.overridesAndroidEntryPointClass() 332 && ancestorExtendsGeneratedHiltClass(metadata)) { 333 methodSpecBuilder.addAnnotation(Override.class); 334 } 335 methodSpecBuilder 336 .beginControlFlow("if (!injected)") 337 .addStatement("injected = true") 338 .addStatement( 339 "(($T) $L).$L($L)", 340 metadata.injectorClassName(), 341 generatedComponentCallBlock(metadata), 342 metadata.injectMethodName(), 343 unsafeCastThisTo(metadata.elementClassName())) 344 .endControlFlow(); 345 break; 346 case BROADCAST_RECEIVER: 347 typeSpecBuilder.addField(injectedLockField()); 348 349 methodSpecBuilder 350 .addParameter(ParameterSpec.builder(AndroidClassNames.CONTEXT, "context").build()) 351 .beginControlFlow("if (!injected)") 352 .beginControlFlow("synchronized (injectedLock)") 353 .beginControlFlow("if (!injected)") 354 .addStatement( 355 "(($T) $T.generatedComponent(context)).$L($L)", 356 metadata.injectorClassName(), 357 metadata.componentManager(), 358 metadata.injectMethodName(), 359 unsafeCastThisTo(metadata.elementClassName())) 360 .addStatement("injected = true") 361 .endControlFlow() 362 .endControlFlow() 363 .endControlFlow(); 364 break; 365 default: 366 throw new AssertionError(); 367 } 368 369 // Also add a wasInjectedByHilt method if needed. 370 // Even if we aren't optionally injected, if we override an optionally injected Hilt class 371 // we also need to override the wasInjectedByHilt method. 372 if (metadata.allowsOptionalInjection() || metadata.baseAllowsOptionalInjection()) { 373 typeSpecBuilder.addMethod( 374 MethodSpec.methodBuilder("wasInjectedByHilt") 375 .addAnnotation(Override.class) 376 .addModifiers(Modifier.PUBLIC) 377 .returns(boolean.class) 378 .addStatement("return injected") 379 .build()); 380 // Only add the interface though if this class allows optional injection (not that it 381 // really matters since if the base allows optional injection the class implements the 382 // interface anyway). But it is probably better to be consistent about only optionally 383 // injected classes extend the interface. 384 if (metadata.allowsOptionalInjection()) { 385 typeSpecBuilder.addSuperinterface(AndroidClassNames.INJECTED_BY_HILT); 386 } 387 } 388 389 typeSpecBuilder.addMethod(methodSpecBuilder.build()); 390 } 391 getParentCodeBlock(AndroidEntryPointMetadata metadata)392 private static CodeBlock getParentCodeBlock(AndroidEntryPointMetadata metadata) { 393 switch (metadata.androidType()) { 394 case ACTIVITY: 395 case SERVICE: 396 return CodeBlock.of("getApplicationContext()"); 397 case FRAGMENT: 398 return CodeBlock.of("getHost()"); 399 case VIEW: 400 return CodeBlock.of( 401 "$L.maybeGetParentComponentManager()", componentManagerCallBlock(metadata)); 402 case BROADCAST_RECEIVER: 403 // Broadcast receivers receive a "context" parameter 404 return CodeBlock.of("context.getApplicationContext()"); 405 default: 406 throw new AssertionError(); 407 } 408 } 409 410 /** 411 * Returns the call to {@code generatedComponent()} with casts if needed. 412 * 413 * <p>A cast is required when the root generated Hilt class uses bytecode injection because 414 * subclasses won't have access to the {@code generatedComponent()} method in that case. 415 */ generatedComponentCallBlock(AndroidEntryPointMetadata metadata)416 private static CodeBlock generatedComponentCallBlock(AndroidEntryPointMetadata metadata) { 417 return CodeBlock.of( 418 "$L.generatedComponent()", 419 !metadata.isRootMetadata() && metadata.rootMetadata().requiresBytecodeInjection() 420 ? unsafeCastThisTo(ClassNames.GENERATED_COMPONENT_MANAGER_HOLDER) 421 : "this"); 422 } 423 424 /** 425 * Returns the call to {@code componentManager()} with casts if needed. 426 * 427 * <p>A cast is required when the root generated Hilt class uses bytecode injection because 428 * subclasses won't have access to the {@code componentManager()} method in that case. 429 */ componentManagerCallBlock(AndroidEntryPointMetadata metadata)430 private static CodeBlock componentManagerCallBlock(AndroidEntryPointMetadata metadata) { 431 return CodeBlock.of( 432 "$L.componentManager()", 433 !metadata.isRootMetadata() && metadata.rootMetadata().requiresBytecodeInjection() 434 ? unsafeCastThisTo(ClassNames.GENERATED_COMPONENT_MANAGER_HOLDER) 435 : "this"); 436 } 437 unsafeCastThisTo(ClassName castType)438 static CodeBlock unsafeCastThisTo(ClassName castType) { 439 return CodeBlock.of("$T.<$T>unsafeCast(this)", ClassNames.UNSAFE_CASTS, castType); 440 } 441 442 /** Returns {@code true} if the an ancestor annotated class extends the generated class */ ancestorExtendsGeneratedHiltClass(AndroidEntryPointMetadata metadata)443 private static boolean ancestorExtendsGeneratedHiltClass(AndroidEntryPointMetadata metadata) { 444 while (metadata.baseMetadata().isPresent()) { 445 metadata = metadata.baseMetadata().get(); 446 if (!metadata.requiresBytecodeInjection()) { 447 return true; 448 } 449 } 450 return false; 451 } 452 453 // private boolean injected = false; injectedField(AndroidEntryPointMetadata metadata)454 private static FieldSpec injectedField(AndroidEntryPointMetadata metadata) { 455 FieldSpec.Builder builder = FieldSpec.builder(TypeName.BOOLEAN, "injected") 456 .addModifiers(Modifier.PRIVATE); 457 458 // Broadcast receivers do double-checked locking so this needs to be volatile 459 if (metadata.androidType() == AndroidEntryPointMetadata.AndroidType.BROADCAST_RECEIVER) { 460 builder.addModifiers(Modifier.VOLATILE); 461 } 462 463 // Views should not add an initializer here as this runs after the super constructor 464 // and may reset state set during the super constructor call. 465 if (metadata.androidType() != AndroidEntryPointMetadata.AndroidType.VIEW) { 466 builder.initializer("false"); 467 } 468 return builder.build(); 469 } 470 471 // private final Object injectedLock = new Object(); injectedLockField()472 private static FieldSpec injectedLockField() { 473 return FieldSpec.builder(TypeName.OBJECT, "injectedLock") 474 .addModifiers(Modifier.PRIVATE, Modifier.FINAL) 475 .initializer("new $T()", TypeName.OBJECT) 476 .build(); 477 } 478 479 /** 480 * Adds the SupressWarnings to supress a warning in the generated code. 481 * 482 * @param keys the string keys of the warnings to suppress, e.g. 'deprecation', 'unchecked', etc. 483 */ addSuppressAnnotation(TypeSpec.Builder builder, String... keys)484 public static void addSuppressAnnotation(TypeSpec.Builder builder, String... keys) { 485 AnnotationSpec.Builder annotationBuilder = AnnotationSpec.builder(SuppressWarnings.class); 486 for (String key : keys) { 487 annotationBuilder.addMember("value", "$S", key); 488 } 489 builder.addAnnotation(annotationBuilder.build()); 490 } 491 Generators()492 private Generators() {} 493 } 494