1 /* 2 * Copyright (C) 2017 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.internal.codegen.writing; 18 19 import static androidx.room.compiler.processing.XElementKt.isConstructor; 20 import static androidx.room.compiler.processing.XElementKt.isMethod; 21 import static androidx.room.compiler.processing.XElementKt.isMethodParameter; 22 import static androidx.room.compiler.processing.XTypeKt.isVoid; 23 import static com.google.common.base.CaseFormat.LOWER_CAMEL; 24 import static com.google.common.base.CaseFormat.UPPER_CAMEL; 25 import static com.google.common.base.Preconditions.checkArgument; 26 import static com.squareup.javapoet.MethodSpec.methodBuilder; 27 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedParameter; 28 import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; 29 import static dagger.internal.codegen.binding.SourceFiles.memberInjectedFieldSignatureForVariable; 30 import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; 31 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 32 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; 33 import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock; 34 import static dagger.internal.codegen.javapoet.CodeBlocks.toConcatenatedCodeBlock; 35 import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; 36 import static dagger.internal.codegen.javapoet.TypeNames.rawTypeName; 37 import static dagger.internal.codegen.langmodel.Accessibility.isElementAccessibleFrom; 38 import static dagger.internal.codegen.langmodel.Accessibility.isRawTypeAccessible; 39 import static dagger.internal.codegen.langmodel.Accessibility.isRawTypePubliclyAccessible; 40 import static dagger.internal.codegen.xprocessing.XElements.asConstructor; 41 import static dagger.internal.codegen.xprocessing.XElements.asExecutable; 42 import static dagger.internal.codegen.xprocessing.XElements.asField; 43 import static dagger.internal.codegen.xprocessing.XElements.asMethod; 44 import static dagger.internal.codegen.xprocessing.XElements.asMethodParameter; 45 import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; 46 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 47 import static dagger.internal.codegen.xprocessing.XTypeElements.typeVariableNames; 48 import static dagger.internal.codegen.xprocessing.XTypes.erasedTypeName; 49 import static javax.lang.model.element.Modifier.PUBLIC; 50 import static javax.lang.model.element.Modifier.STATIC; 51 52 import androidx.room.compiler.processing.XAnnotation; 53 import androidx.room.compiler.processing.XConstructorElement; 54 import androidx.room.compiler.processing.XExecutableElement; 55 import androidx.room.compiler.processing.XExecutableParameterElement; 56 import androidx.room.compiler.processing.XFieldElement; 57 import androidx.room.compiler.processing.XMethodElement; 58 import androidx.room.compiler.processing.XType; 59 import androidx.room.compiler.processing.XTypeElement; 60 import androidx.room.compiler.processing.XVariableElement; 61 import com.google.common.collect.ImmutableList; 62 import com.google.common.collect.ImmutableMap; 63 import com.google.common.collect.ImmutableSet; 64 import com.squareup.javapoet.AnnotationSpec; 65 import com.squareup.javapoet.ClassName; 66 import com.squareup.javapoet.CodeBlock; 67 import com.squareup.javapoet.MethodSpec; 68 import com.squareup.javapoet.ParameterSpec; 69 import com.squareup.javapoet.TypeName; 70 import dagger.internal.Preconditions; 71 import dagger.internal.codegen.base.UniqueNameSet; 72 import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite; 73 import dagger.internal.codegen.binding.Nullability; 74 import dagger.internal.codegen.binding.ProvisionBinding; 75 import dagger.internal.codegen.compileroption.CompilerOptions; 76 import dagger.internal.codegen.extension.DaggerCollectors; 77 import dagger.internal.codegen.javapoet.TypeNames; 78 import dagger.internal.codegen.model.DaggerAnnotation; 79 import dagger.internal.codegen.model.DependencyRequest; 80 import dagger.internal.codegen.xprocessing.XAnnotations; 81 import java.util.List; 82 import java.util.Optional; 83 import java.util.function.Function; 84 85 /** Convenience methods for creating and invoking {@link InjectionMethod}s. */ 86 final class InjectionMethods { 87 88 /** 89 * A method that returns an object from a {@code @Provides} method or an {@code @Inject}ed 90 * constructor. Its parameters match the dependency requests for constructor and members 91 * injection. 92 * 93 * <p>For {@code @Provides} methods named "foo", the method name is "proxyFoo". For example: 94 * 95 * <pre><code> 96 * abstract class FooModule { 97 * {@literal @Provides} static Foo provideFoo(Bar bar, Baz baz) { … } 98 * } 99 * 100 * public static proxyProvideFoo(Bar bar, Baz baz) { … } 101 * </code></pre> 102 * 103 * <p>For {@code @Inject}ed constructors, the method name is "newFoo". For example: 104 * 105 * <pre><code> 106 * class Foo { 107 * {@literal @Inject} Foo(Bar bar) {} 108 * } 109 * 110 * public static Foo newFoo(Bar bar) { … } 111 * </code></pre> 112 */ 113 static final class ProvisionMethod { 114 // These names are already defined in factories and shouldn't be used for the proxy method name. 115 private static final ImmutableSet<String> BANNED_PROXY_NAMES = ImmutableSet.of("get", "create"); 116 117 /** 118 * Returns a method that invokes the binding's {@linkplain ProvisionBinding#bindingElement() 119 * constructor} and injects the instance's members. 120 */ create(ProvisionBinding binding, CompilerOptions compilerOptions)121 static MethodSpec create(ProvisionBinding binding, CompilerOptions compilerOptions) { 122 XExecutableElement executableElement = asExecutable(binding.bindingElement().get()); 123 if (isConstructor(executableElement)) { 124 return constructorProxy(asConstructor(executableElement)); 125 } else if (isMethod(executableElement)) { 126 XMethodElement method = asMethod(executableElement); 127 String methodName = 128 BANNED_PROXY_NAMES.contains(getSimpleName(method)) 129 ? "proxy" + LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(method)) 130 : getSimpleName(method); 131 return methodProxy( 132 method, 133 methodName, 134 InstanceCastPolicy.IGNORE, 135 CheckNotNullPolicy.get(binding, compilerOptions)); 136 } 137 throw new AssertionError(executableElement); 138 } 139 140 /** 141 * Invokes the injection method for {@code binding}, with the dependencies transformed with the 142 * {@code dependencyUsage} function. 143 */ invoke( ProvisionBinding binding, Function<DependencyRequest, CodeBlock> dependencyUsage, Function<XExecutableParameterElement, String> uniqueAssistedParameterName, ClassName requestingClass, Optional<CodeBlock> moduleReference, CompilerOptions compilerOptions)144 static CodeBlock invoke( 145 ProvisionBinding binding, 146 Function<DependencyRequest, CodeBlock> dependencyUsage, 147 Function<XExecutableParameterElement, String> uniqueAssistedParameterName, 148 ClassName requestingClass, 149 Optional<CodeBlock> moduleReference, 150 CompilerOptions compilerOptions) { 151 ImmutableList.Builder<CodeBlock> arguments = ImmutableList.builder(); 152 moduleReference.ifPresent(arguments::add); 153 invokeArguments(binding, dependencyUsage, uniqueAssistedParameterName) 154 .forEach(arguments::add); 155 156 ClassName enclosingClass = generatedClassNameForBinding(binding); 157 MethodSpec methodSpec = create(binding, compilerOptions); 158 return invokeMethod(methodSpec, arguments.build(), enclosingClass, requestingClass); 159 } 160 invokeArguments( ProvisionBinding binding, Function<DependencyRequest, CodeBlock> dependencyUsage, Function<XExecutableParameterElement, String> uniqueAssistedParameterName)161 static ImmutableList<CodeBlock> invokeArguments( 162 ProvisionBinding binding, 163 Function<DependencyRequest, CodeBlock> dependencyUsage, 164 Function<XExecutableParameterElement, String> uniqueAssistedParameterName) { 165 ImmutableMap<XExecutableParameterElement, DependencyRequest> dependencyRequestMap = 166 binding.provisionDependencies().stream() 167 .collect( 168 toImmutableMap( 169 request -> asMethodParameter(request.requestElement().get().xprocessing()), 170 request -> request)); 171 172 ImmutableList.Builder<CodeBlock> arguments = ImmutableList.builder(); 173 XExecutableElement method = asExecutable(binding.bindingElement().get()); 174 for (XExecutableParameterElement parameter : method.getParameters()) { 175 if (isAssistedParameter(parameter)) { 176 arguments.add(CodeBlock.of("$L", uniqueAssistedParameterName.apply(parameter))); 177 } else if (dependencyRequestMap.containsKey(parameter)) { 178 DependencyRequest request = dependencyRequestMap.get(parameter); 179 arguments.add(dependencyUsage.apply(request)); 180 } else { 181 throw new AssertionError("Unexpected parameter: " + parameter); 182 } 183 } 184 185 return arguments.build(); 186 } 187 constructorProxy(XConstructorElement constructor)188 private static MethodSpec constructorProxy(XConstructorElement constructor) { 189 XTypeElement enclosingType = constructor.getEnclosingElement(); 190 MethodSpec.Builder builder = 191 methodBuilder("newInstance") 192 .addModifiers(PUBLIC, STATIC) 193 .varargs(constructor.isVarArgs()) 194 .returns(enclosingType.getType().getTypeName()) 195 .addTypeVariables(typeVariableNames(enclosingType)); 196 197 copyThrows(builder, constructor); 198 199 CodeBlock arguments = 200 copyParameters(builder, new UniqueNameSet(), constructor.getParameters()); 201 return builder 202 .addStatement("return new $T($L)", enclosingType.getType().getTypeName(), arguments) 203 .build(); 204 } 205 206 /** 207 * Returns {@code true} if injecting an instance of {@code binding} from {@code callingPackage} 208 * requires the use of an injection method. 209 */ requiresInjectionMethod( ProvisionBinding binding, CompilerOptions compilerOptions, ClassName requestingClass)210 static boolean requiresInjectionMethod( 211 ProvisionBinding binding, CompilerOptions compilerOptions, ClassName requestingClass) { 212 XExecutableElement executableElement = asExecutable(binding.bindingElement().get()); 213 return !binding.injectionSites().isEmpty() 214 || binding.shouldCheckForNull(compilerOptions) 215 || !isElementAccessibleFrom(executableElement, requestingClass.packageName()) 216 // This check should be removable once we drop support for -source 7 217 || executableElement.getParameters().stream() 218 .map(XExecutableParameterElement::getType) 219 .anyMatch(type -> !isRawTypeAccessible(type, requestingClass.packageName())); 220 } 221 } 222 223 /** 224 * A static method that injects one member of an instance of a type. Its first parameter is an 225 * instance of the type to be injected. The remaining parameters match the dependency requests for 226 * the injection site. 227 * 228 * <p>Example: 229 * 230 * <pre><code> 231 * class Foo { 232 * {@literal @Inject} Bar bar; 233 * {@literal @Inject} void setThings(Baz baz, Qux qux) {} 234 * } 235 * 236 * public static injectBar(Foo instance, Bar bar) { … } 237 * public static injectSetThings(Foo instance, Baz baz, Qux qux) { … } 238 * </code></pre> 239 */ 240 static final class InjectionSiteMethod { 241 /** 242 * When a type has an inaccessible member from a supertype (e.g. an @Inject field in a parent 243 * that's in a different package), a method in the supertype's package must be generated to give 244 * the subclass's members injector a way to inject it. Each potentially inaccessible member 245 * receives its own method, as the subclass may need to inject them in a different order from 246 * the parent class. 247 */ create(InjectionSite injectionSite)248 static MethodSpec create(InjectionSite injectionSite) { 249 String methodName = methodName(injectionSite); 250 switch (injectionSite.kind()) { 251 case METHOD: 252 return methodProxy( 253 asMethod(injectionSite.element()), 254 methodName, 255 InstanceCastPolicy.CAST_IF_NOT_PUBLIC, 256 CheckNotNullPolicy.IGNORE); 257 case FIELD: 258 Optional<XAnnotation> qualifier = 259 injectionSite.dependencies().stream() 260 // methods for fields have a single dependency request 261 .collect(DaggerCollectors.onlyElement()) 262 .key() 263 .qualifier() 264 .map(DaggerAnnotation::xprocessing); 265 return fieldProxy(asField(injectionSite.element()), methodName, qualifier); 266 } 267 throw new AssertionError(injectionSite); 268 } 269 270 /** 271 * Invokes each of the injection methods for {@code injectionSites}, with the dependencies 272 * transformed using the {@code dependencyUsage} function. 273 * 274 * @param instanceType the type of the {@code instance} parameter 275 */ invokeAll( ImmutableSet<InjectionSite> injectionSites, ClassName generatedTypeName, CodeBlock instanceCodeBlock, XType instanceType, Function<DependencyRequest, CodeBlock> dependencyUsage)276 static CodeBlock invokeAll( 277 ImmutableSet<InjectionSite> injectionSites, 278 ClassName generatedTypeName, 279 CodeBlock instanceCodeBlock, 280 XType instanceType, 281 Function<DependencyRequest, CodeBlock> dependencyUsage) { 282 return injectionSites.stream() 283 .map( 284 injectionSite -> { 285 XType injectSiteType = injectionSite.enclosingTypeElement().getType(); 286 287 // If instance has been declared as Object because it is not accessible from the 288 // component, but the injectionSite is in a supertype of instanceType that is 289 // publicly accessible, the InjectionSiteMethod will request the actual type and not 290 // Object as the first parameter. If so, cast to the supertype which is accessible 291 // from within generatedTypeName 292 CodeBlock maybeCastedInstance = 293 instanceType.getTypeName().equals(TypeName.OBJECT) 294 && isRawTypeAccessible(injectSiteType, generatedTypeName.packageName()) 295 ? CodeBlock.of("($T) $L", erasedTypeName(injectSiteType), instanceCodeBlock) 296 : instanceCodeBlock; 297 return CodeBlock.of( 298 "$L;", 299 invoke(injectionSite, generatedTypeName, maybeCastedInstance, dependencyUsage)); 300 }) 301 .collect(toConcatenatedCodeBlock()); 302 } 303 304 /** 305 * Invokes the injection method for {@code injectionSite}, with the dependencies transformed 306 * using the {@code dependencyUsage} function. 307 */ invoke( InjectionSite injectionSite, ClassName generatedTypeName, CodeBlock instanceCodeBlock, Function<DependencyRequest, CodeBlock> dependencyUsage)308 private static CodeBlock invoke( 309 InjectionSite injectionSite, 310 ClassName generatedTypeName, 311 CodeBlock instanceCodeBlock, 312 Function<DependencyRequest, CodeBlock> dependencyUsage) { 313 ImmutableList<CodeBlock> arguments = 314 ImmutableList.<CodeBlock>builder() 315 .add(instanceCodeBlock) 316 .addAll( 317 injectionSite.dependencies().stream() 318 .map(dependencyUsage) 319 .collect(toImmutableList())) 320 .build(); 321 ClassName enclosingClass = membersInjectorNameForType(injectionSite.enclosingTypeElement()); 322 MethodSpec methodSpec = create(injectionSite); 323 return invokeMethod(methodSpec, arguments, enclosingClass, generatedTypeName); 324 } 325 326 /* 327 * TODO(ronshapiro): this isn't perfect, as collisions could still exist. Some examples: 328 * 329 * - @Inject void members() {} will generate a method that conflicts with the instance 330 * method `injectMembers(T)` 331 * - Adding the index could conflict with another member: 332 * @Inject void a(Object o) {} 333 * @Inject void a(String s) {} 334 * @Inject void a1(String s) {} 335 * 336 * Here, Method a(String) will add the suffix "1", which will conflict with the method 337 * generated for a1(String) 338 * - Members named "members" or "methods" could also conflict with the {@code static} injection 339 * method. 340 */ methodName(InjectionSite injectionSite)341 private static String methodName(InjectionSite injectionSite) { 342 int index = injectionSite.indexAmongAtInjectMembersWithSameSimpleName(); 343 String indexString = index == 0 ? "" : String.valueOf(index + 1); 344 return "inject" 345 + LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(injectionSite.element())) 346 + indexString; 347 } 348 } 349 350 private enum InstanceCastPolicy { 351 CAST_IF_NOT_PUBLIC, IGNORE; 352 useObjectType(XType instanceType)353 boolean useObjectType(XType instanceType) { 354 return this == CAST_IF_NOT_PUBLIC && !isRawTypePubliclyAccessible(instanceType); 355 } 356 } 357 358 private enum CheckNotNullPolicy { 359 IGNORE, CHECK_FOR_NULL; 360 checkForNull(CodeBlock maybeNull)361 CodeBlock checkForNull(CodeBlock maybeNull) { 362 return this.equals(IGNORE) 363 ? maybeNull 364 : CodeBlock.of("$T.checkNotNullFromProvides($L)", Preconditions.class, maybeNull); 365 } 366 get(ProvisionBinding binding, CompilerOptions compilerOptions)367 static CheckNotNullPolicy get(ProvisionBinding binding, CompilerOptions compilerOptions) { 368 return binding.shouldCheckForNull(compilerOptions) ? CHECK_FOR_NULL : IGNORE; 369 } 370 } 371 methodProxy( XMethodElement method, String methodName, InstanceCastPolicy instanceCastPolicy, CheckNotNullPolicy checkNotNullPolicy)372 private static MethodSpec methodProxy( 373 XMethodElement method, 374 String methodName, 375 InstanceCastPolicy instanceCastPolicy, 376 CheckNotNullPolicy checkNotNullPolicy) { 377 XTypeElement enclosingType = asTypeElement(method.getEnclosingElement()); 378 379 MethodSpec.Builder builder = 380 methodBuilder(methodName) 381 .addModifiers(PUBLIC, STATIC) 382 .varargs(method.isVarArgs()) 383 .addTypeVariables(method.getExecutableType().getTypeVariableNames()); 384 385 UniqueNameSet parameterNameSet = new UniqueNameSet(); 386 CodeBlock instance; 387 if (method.isStatic() || enclosingType.isCompanionObject()) { 388 instance = CodeBlock.of("$T", rawTypeName(enclosingType.getType().getTypeName())); 389 } else if (enclosingType.isKotlinObject()) { 390 // Call through the singleton instance. 391 // See: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#static-methods 392 instance = CodeBlock.of("$T.INSTANCE", rawTypeName(enclosingType.getType().getTypeName())); 393 } else { 394 builder.addTypeVariables(typeVariableNames(enclosingType)); 395 boolean useObject = instanceCastPolicy.useObjectType(enclosingType.getType()); 396 instance = copyInstance(builder, parameterNameSet, enclosingType.getType(), useObject); 397 } 398 CodeBlock arguments = copyParameters(builder, parameterNameSet, method.getParameters()); 399 CodeBlock invocation = 400 checkNotNullPolicy.checkForNull( 401 CodeBlock.of("$L.$L($L)", instance, method.getJvmName(), arguments)); 402 403 copyThrows(builder, method); 404 405 if (isVoid(method.getReturnType())) { 406 return builder.addStatement("$L", invocation).build(); 407 } else { 408 Nullability nullability = Nullability.of(method); 409 nullability 410 .nullableAnnotations() 411 .forEach(builder::addAnnotation); 412 return builder 413 .returns(method.getReturnType().getTypeName()) 414 .addStatement("return $L", invocation) 415 .build(); 416 } 417 } 418 fieldProxy( XFieldElement field, String methodName, Optional<XAnnotation> qualifier)419 private static MethodSpec fieldProxy( 420 XFieldElement field, String methodName, Optional<XAnnotation> qualifier) { 421 XTypeElement enclosingType = asTypeElement(field.getEnclosingElement()); 422 423 MethodSpec.Builder builder = 424 methodBuilder(methodName) 425 .addModifiers(PUBLIC, STATIC) 426 .addAnnotation( 427 AnnotationSpec.builder(TypeNames.INJECTED_FIELD_SIGNATURE) 428 .addMember("value", "$S", memberInjectedFieldSignatureForVariable(field)) 429 .build()) 430 .addTypeVariables(typeVariableNames(enclosingType)); 431 432 qualifier.map(XAnnotations::getAnnotationSpec).ifPresent(builder::addAnnotation); 433 434 boolean useObject = !isRawTypePubliclyAccessible(enclosingType.getType()); 435 UniqueNameSet parameterNameSet = new UniqueNameSet(); 436 CodeBlock instance = 437 copyInstance(builder, parameterNameSet, enclosingType.getType(), useObject); 438 CodeBlock argument = copyParameters(builder, parameterNameSet, ImmutableList.of(field)); 439 return builder.addStatement("$L.$L = $L", instance, getSimpleName(field), argument).build(); 440 } 441 invokeMethod( MethodSpec methodSpec, ImmutableList<CodeBlock> parameters, ClassName enclosingClass, ClassName requestingClass)442 private static CodeBlock invokeMethod( 443 MethodSpec methodSpec, 444 ImmutableList<CodeBlock> parameters, 445 ClassName enclosingClass, 446 ClassName requestingClass) { 447 checkArgument(methodSpec.parameters.size() == parameters.size()); 448 CodeBlock parameterBlock = makeParametersCodeBlock(parameters); 449 return enclosingClass.equals(requestingClass) 450 ? CodeBlock.of("$L($L)", methodSpec.name, parameterBlock) 451 : CodeBlock.of("$T.$L($L)", enclosingClass, methodSpec.name, parameterBlock); 452 } 453 copyThrows(MethodSpec.Builder methodBuilder, XExecutableElement method)454 private static void copyThrows(MethodSpec.Builder methodBuilder, XExecutableElement method) { 455 method.getThrownTypes().stream().map(XType::getTypeName).forEach(methodBuilder::addException); 456 } 457 copyParameters( MethodSpec.Builder methodBuilder, UniqueNameSet parameterNameSet, List<? extends XVariableElement> parameters)458 private static CodeBlock copyParameters( 459 MethodSpec.Builder methodBuilder, 460 UniqueNameSet parameterNameSet, 461 List<? extends XVariableElement> parameters) { 462 return parameters.stream() 463 .map( 464 parameter -> { 465 String name = 466 parameterNameSet.getUniqueName( 467 isMethodParameter(parameter) 468 ? asMethodParameter(parameter).getJvmName() 469 : getSimpleName(parameter)); 470 boolean useObject = !isRawTypePubliclyAccessible(parameter.getType()); 471 return copyParameter(methodBuilder, parameter.getType(), name, useObject); 472 }) 473 .collect(toParametersCodeBlock()); 474 } 475 476 private static CodeBlock copyParameter( 477 MethodSpec.Builder methodBuilder, XType type, String name, boolean useObject) { 478 TypeName typeName = useObject ? TypeName.OBJECT : type.getTypeName(); 479 methodBuilder.addParameter(ParameterSpec.builder(typeName, name).build()); 480 return useObject ? CodeBlock.of("($T) $L", type.getTypeName(), name) : CodeBlock.of("$L", name); 481 } 482 483 private static CodeBlock copyInstance( 484 MethodSpec.Builder methodBuilder, 485 UniqueNameSet parameterNameSet, 486 XType type, 487 boolean useObject) { 488 CodeBlock instance = 489 copyParameter(methodBuilder, type, parameterNameSet.getUniqueName("instance"), useObject); 490 // If we had to cast the instance add an extra parenthesis incase we're calling a method on it. 491 return useObject ? CodeBlock.of("($L)", instance) : instance; 492 } 493 } 494