diff --git a/java/dagger/internal/codegen/binding/SourceFiles.java b/java/dagger/internal/codegen/binding/SourceFiles.java index 350051522f4..d776eb4a9a7 100644 --- a/java/dagger/internal/codegen/binding/SourceFiles.java +++ b/java/dagger/internal/codegen/binding/SourceFiles.java @@ -36,6 +36,7 @@ import static dagger.internal.codegen.model.BindingKind.ASSISTED_INJECTION; import static dagger.internal.codegen.model.BindingKind.INJECTION; import static dagger.internal.codegen.xprocessing.XElements.asExecutable; +import static dagger.internal.codegen.xprocessing.XElements.asMethod; import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; import static dagger.internal.codegen.xprocessing.XTypeElements.typeVariableNames; @@ -43,6 +44,7 @@ import androidx.room.compiler.processing.XExecutableElement; import androidx.room.compiler.processing.XFieldElement; +import androidx.room.compiler.processing.XMethodElement; import androidx.room.compiler.processing.XTypeElement; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; @@ -58,6 +60,7 @@ import com.squareup.javapoet.TypeVariableName; import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.base.SetType; +import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.model.DependencyRequest; import dagger.internal.codegen.model.RequestKind; @@ -144,6 +147,23 @@ public ImmutableMap frameworkFieldUsages( dep -> frameworkTypeUsageStatement(CodeBlock.of("$N", fields.get(dep)), dep.kind())); } + public static String generatedProxyMethodName(ContributionBinding binding) { + switch (binding.kind()) { + case INJECTION: + case ASSISTED_INJECTION: + return "newInstance"; + case PROVISION: + XMethodElement method = asMethod(binding.bindingElement().get()); + String simpleName = getSimpleName(method); + // If the simple name is already defined in the factory, prepend "proxy" to the name. + return simpleName.contentEquals("get") || simpleName.contentEquals("create") + ? "proxy" + LOWER_CAMEL.to(UPPER_CAMEL, simpleName) + : simpleName; + default: + throw new AssertionError("Unexpected binding kind: " + binding); + } + } + /** Returns the generated factory or members injector name for a binding. */ public static ClassName generatedClassNameForBinding(Binding binding) { switch (binding.kind()) { @@ -206,6 +226,29 @@ public static String memberInjectedFieldSignatureForVariable(XFieldElement field return field.getEnclosingElement().getClassName().canonicalName() + "." + getSimpleName(field); } + /* + * TODO(ronshapiro): this isn't perfect, as collisions could still exist. Some examples: + * + * - @Inject void members() {} will generate a method that conflicts with the instance + * method `injectMembers(T)` + * - Adding the index could conflict with another member: + * @Inject void a(Object o) {} + * @Inject void a(String s) {} + * @Inject void a1(String s) {} + * + * Here, Method a(String) will add the suffix "1", which will conflict with the method + * generated for a1(String) + * - Members named "members" or "methods" could also conflict with the {@code static} injection + * method. + */ + public static String membersInjectorMethodName(InjectionSite injectionSite) { + int index = injectionSite.indexAmongAtInjectMembersWithSameSimpleName(); + String indexString = index == 0 ? "" : String.valueOf(index + 1); + return "inject" + + LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(injectionSite.element())) + + indexString; + } + public static String classFileName(ClassName className) { return CLASS_FILE_NAME_JOINER.join(className.simpleNames()); } diff --git a/java/dagger/internal/codegen/writing/FactoryGenerator.java b/java/dagger/internal/codegen/writing/FactoryGenerator.java index 7fee1b394b6..3b78b969c90 100644 --- a/java/dagger/internal/codegen/writing/FactoryGenerator.java +++ b/java/dagger/internal/codegen/writing/FactoryGenerator.java @@ -24,6 +24,7 @@ import static dagger.internal.codegen.binding.SourceFiles.bindingTypeElementTypeVariableNames; import static dagger.internal.codegen.binding.SourceFiles.generateBindingFieldsForDependencies; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; +import static dagger.internal.codegen.binding.SourceFiles.generatedProxyMethodName; import static dagger.internal.codegen.binding.SourceFiles.parameterizedGeneratedTypeNameForBinding; import static dagger.internal.codegen.extension.DaggerStreams.presentValues; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; @@ -36,15 +37,26 @@ import static dagger.internal.codegen.model.BindingKind.INJECTION; import static dagger.internal.codegen.model.BindingKind.PROVISION; import static dagger.internal.codegen.writing.GwtCompatibility.gwtIncompatibleAnnotation; +import static dagger.internal.codegen.writing.InjectionMethods.copyParameter; +import static dagger.internal.codegen.writing.InjectionMethods.copyParameters; +import static dagger.internal.codegen.xprocessing.XElements.asConstructor; +import static dagger.internal.codegen.xprocessing.XElements.asMethod; +import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; +import static dagger.internal.codegen.xprocessing.XTypeElements.typeVariableNames; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; +import androidx.room.compiler.processing.XConstructorElement; import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableElement; import androidx.room.compiler.processing.XExecutableParameterElement; import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XMethodElement; import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -56,6 +68,7 @@ import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; +import dagger.internal.Preconditions; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.base.UniqueNameSet; import dagger.internal.codegen.binding.AssistedInjectionBinding; @@ -73,6 +86,7 @@ import dagger.internal.codegen.model.Scope; import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod; import dagger.internal.codegen.writing.InjectionMethods.ProvisionMethod; +import dagger.internal.codegen.xprocessing.Nullability; import java.util.Optional; import java.util.stream.Stream; import javax.inject.Inject; @@ -136,7 +150,7 @@ private TypeSpec.Builder factoryBuilder(ContributionBinding binding) { return factoryBuilder .addMethod(getMethod(binding, factoryFields)) .addMethod(staticCreateMethod(binding, factoryFields)) - .addMethod(staticProvisionMethod(binding)); + .addMethod(staticProxyMethod(binding)); } // private static final class InstanceHolder { @@ -298,17 +312,101 @@ private MethodSpec getMethod(ContributionBinding binding, FactoryFields factoryF return getMethod.build(); } - // Example 1: Provision binding - // public static Foo provideFoo(FooModule module, Bar bar, Baz baz) { - // return Preconditions.checkNotNullFromProvides(module.provideFoo(bar, baz)); - // } + private MethodSpec staticProxyMethod(ContributionBinding binding) { + switch (binding.kind()) { + case INJECTION: + case ASSISTED_INJECTION: + return staticProxyMethodForInjection(binding); + case PROVISION: + return staticProxyMethodForProvision((ProvisionBinding) binding); + default: + throw new AssertionError("Unexpected binding kind: " + binding); + } + } + + // Example: // - // Example 2: Injection binding // public static Foo newInstance(Bar bar, Baz baz) { // return new Foo(bar, baz); // } - private MethodSpec staticProvisionMethod(ContributionBinding binding) { - return ProvisionMethod.create(binding, compilerOptions); + private static MethodSpec staticProxyMethodForInjection(ContributionBinding binding) { + XConstructorElement constructor = asConstructor(binding.bindingElement().get()); + XTypeElement enclosingType = constructor.getEnclosingElement(); + MethodSpec.Builder builder = + methodBuilder(generatedProxyMethodName(binding)) + .addModifiers(PUBLIC, STATIC) + .varargs(constructor.isVarArgs()) + .returns(enclosingType.getType().getTypeName()) + .addTypeVariables(typeVariableNames(enclosingType)) + .addExceptions(getThrownTypes(constructor)); + CodeBlock arguments = copyParameters(builder, new UniqueNameSet(), constructor.getParameters()); + return builder + .addStatement("return new $T($L)", enclosingType.getType().getTypeName(), arguments) + .build(); + } + + // Example: + // + // public static Foo provideFoo(FooModule module, Bar bar, Baz baz) { + // return Preconditions.checkNotNullFromProvides(module.provideFoo(bar, baz)); + // } + private MethodSpec staticProxyMethodForProvision(ProvisionBinding binding) { + XMethodElement method = asMethod(binding.bindingElement().get()); + MethodSpec.Builder builder = + methodBuilder(generatedProxyMethodName(binding)) + .addModifiers(PUBLIC, STATIC) + .varargs(method.isVarArgs()) + .addExceptions(getThrownTypes(method)); + + XTypeElement enclosingType = asTypeElement(method.getEnclosingElement()); + UniqueNameSet parameterNameSet = new UniqueNameSet(); + CodeBlock module; + if (method.isStatic() || enclosingType.isCompanionObject()) { + module = CodeBlock.of("$T", enclosingType.getClassName()); + } else if (enclosingType.isKotlinObject()) { + // Call through the singleton instance. + // See: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#static-methods + module = CodeBlock.of("$T.INSTANCE", enclosingType.getClassName()); + } else { + builder.addTypeVariables(typeVariableNames(enclosingType)); + module = copyInstance(builder, parameterNameSet, enclosingType.getType()); + } + CodeBlock arguments = copyParameters(builder, parameterNameSet, method.getParameters()); + CodeBlock invocation = CodeBlock.of("$L.$L($L)", module, method.getJvmName(), arguments); + + Nullability nullability = Nullability.of(method); + nullability + .nonTypeUseNullableAnnotations() + .forEach(builder::addAnnotation); + return builder + .returns( + method.getReturnType().getTypeName() + .annotated( + nullability.typeUseNullableAnnotations().stream() + .map(annotation -> AnnotationSpec.builder(annotation).build()) + .collect(toImmutableList()))) + .addStatement("return $L", maybeWrapInCheckForNull(binding, invocation)) + .build(); + } + + private CodeBlock maybeWrapInCheckForNull(ProvisionBinding binding, CodeBlock codeBlock) { + return binding.shouldCheckForNull(compilerOptions) + ? CodeBlock.of("$T.checkNotNullFromProvides($L)", Preconditions.class, codeBlock) + : codeBlock; + } + + private static CodeBlock copyInstance( + MethodSpec.Builder methodBuilder, UniqueNameSet parameterNameSet, XType type) { + return copyParameter( + methodBuilder, + type, + parameterNameSet.getUniqueName("instance"), + /* useObject= */ false, + Nullability.NOT_NULLABLE); + } + + private static ImmutableList getThrownTypes(XExecutableElement executable) { + return executable.getThrownTypes().stream().map(XType::getTypeName).collect(toImmutableList()); } private AnnotationSpec scopeMetadataAnnotation(ContributionBinding binding) { diff --git a/java/dagger/internal/codegen/writing/InjectionMethods.java b/java/dagger/internal/codegen/writing/InjectionMethods.java index f1d5e32c4b1..721cc8e983d 100644 --- a/java/dagger/internal/codegen/writing/InjectionMethods.java +++ b/java/dagger/internal/codegen/writing/InjectionMethods.java @@ -16,46 +16,27 @@ package dagger.internal.codegen.writing; -import static androidx.room.compiler.processing.XElementKt.isConstructor; -import static androidx.room.compiler.processing.XElementKt.isMethod; import static androidx.room.compiler.processing.XElementKt.isMethodParameter; -import static androidx.room.compiler.processing.XTypeKt.isVoid; -import static com.google.common.base.CaseFormat.LOWER_CAMEL; -import static com.google.common.base.CaseFormat.UPPER_CAMEL; -import static com.google.common.base.Preconditions.checkArgument; -import static com.squareup.javapoet.MethodSpec.methodBuilder; import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedParameter; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; -import static dagger.internal.codegen.binding.SourceFiles.memberInjectedFieldSignatureForVariable; +import static dagger.internal.codegen.binding.SourceFiles.generatedProxyMethodName; +import static dagger.internal.codegen.binding.SourceFiles.membersInjectorMethodName; import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock; import static dagger.internal.codegen.javapoet.CodeBlocks.toConcatenatedCodeBlock; import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; -import static dagger.internal.codegen.javapoet.TypeNames.rawTypeName; import static dagger.internal.codegen.langmodel.Accessibility.isRawTypeAccessible; import static dagger.internal.codegen.langmodel.Accessibility.isRawTypePubliclyAccessible; -import static dagger.internal.codegen.xprocessing.XElements.asConstructor; import static dagger.internal.codegen.xprocessing.XElements.asExecutable; -import static dagger.internal.codegen.xprocessing.XElements.asField; -import static dagger.internal.codegen.xprocessing.XElements.asMethod; import static dagger.internal.codegen.xprocessing.XElements.asMethodParameter; -import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; -import static dagger.internal.codegen.xprocessing.XTypeElements.typeVariableNames; import static dagger.internal.codegen.xprocessing.XTypes.erasedTypeName; -import static javax.lang.model.element.Modifier.PUBLIC; -import static javax.lang.model.element.Modifier.STATIC; -import androidx.room.compiler.processing.XAnnotation; -import androidx.room.compiler.processing.XConstructorElement; import androidx.room.compiler.processing.XExecutableElement; import androidx.room.compiler.processing.XExecutableParameterElement; -import androidx.room.compiler.processing.XFieldElement; -import androidx.room.compiler.processing.XMethodElement; import androidx.room.compiler.processing.XType; -import androidx.room.compiler.processing.XTypeElement; import androidx.room.compiler.processing.XVariableElement; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -66,7 +47,6 @@ import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeName; -import dagger.internal.Preconditions; import dagger.internal.codegen.base.UniqueNameSet; import dagger.internal.codegen.binding.AssistedInjectionBinding; import dagger.internal.codegen.binding.ContributionBinding; @@ -74,12 +54,8 @@ import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite; import dagger.internal.codegen.binding.ProvisionBinding; import dagger.internal.codegen.compileroption.CompilerOptions; -import dagger.internal.codegen.extension.DaggerCollectors; -import dagger.internal.codegen.javapoet.TypeNames; -import dagger.internal.codegen.model.DaggerAnnotation; import dagger.internal.codegen.model.DependencyRequest; import dagger.internal.codegen.xprocessing.Nullability; -import dagger.internal.codegen.xprocessing.XAnnotations; import java.util.List; import java.util.Optional; import java.util.function.Function; @@ -113,30 +89,6 @@ final class InjectionMethods { * */ static final class ProvisionMethod { - // These names are already defined in factories and shouldn't be used for the proxy method name. - private static final ImmutableSet BANNED_PROXY_NAMES = ImmutableSet.of("get", "create"); - - /** - * Returns a method that invokes the binding's constructor and injects the instance's members. - */ - static MethodSpec create(ContributionBinding binding, CompilerOptions compilerOptions) { - XExecutableElement executableElement = asExecutable(binding.bindingElement().get()); - if (isConstructor(executableElement)) { - return constructorProxy(asConstructor(executableElement)); - } else if (isMethod(executableElement)) { - XMethodElement method = asMethod(executableElement); - String methodName = - BANNED_PROXY_NAMES.contains(getSimpleName(method)) - ? "proxy" + LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(method)) - : getSimpleName(method); - return methodProxy( - method, - methodName, - InstanceCastPolicy.IGNORE, - CheckNotNullPolicy.get(binding, compilerOptions)); - } - throw new AssertionError(executableElement); - } /** * Invokes the injection method for {@code binding}, with the dependencies transformed with the @@ -155,8 +107,8 @@ static CodeBlock invoke( .forEach(arguments::add); ClassName enclosingClass = generatedClassNameForBinding(binding); - MethodSpec methodSpec = create(binding, compilerOptions); - return invokeMethod(methodSpec, arguments.build(), enclosingClass, requestingClass); + String methodName = generatedProxyMethodName(binding); + return invokeMethod(methodName, arguments.build(), enclosingClass, requestingClass); } static ImmutableList invokeArguments( @@ -199,24 +151,6 @@ private static ImmutableSet provisionDependencies( throw new AssertionError("Unexpected binding kind: " + binding.kind()); } } - - private static MethodSpec constructorProxy(XConstructorElement constructor) { - XTypeElement enclosingType = constructor.getEnclosingElement(); - MethodSpec.Builder builder = - methodBuilder("newInstance") - .addModifiers(PUBLIC, STATIC) - .varargs(constructor.isVarArgs()) - .returns(enclosingType.getType().getTypeName()) - .addTypeVariables(typeVariableNames(enclosingType)); - - copyThrows(builder, constructor); - - CodeBlock arguments = - copyParameters(builder, new UniqueNameSet(), constructor.getParameters()); - return builder - .addStatement("return new $T($L)", enclosingType.getType().getTypeName(), arguments) - .build(); - } } /** @@ -237,35 +171,6 @@ private static MethodSpec constructorProxy(XConstructorElement constructor) { * */ static final class InjectionSiteMethod { - /** - * When a type has an inaccessible member from a supertype (e.g. an @Inject field in a parent - * that's in a different package), a method in the supertype's package must be generated to give - * the subclass's members injector a way to inject it. Each potentially inaccessible member - * receives its own method, as the subclass may need to inject them in a different order from - * the parent class. - */ - static MethodSpec create(InjectionSite injectionSite) { - String methodName = methodName(injectionSite); - switch (injectionSite.kind()) { - case METHOD: - return methodProxy( - asMethod(injectionSite.element()), - methodName, - InstanceCastPolicy.CAST_IF_NOT_PUBLIC, - CheckNotNullPolicy.IGNORE); - case FIELD: - Optional qualifier = - injectionSite.dependencies().stream() - // methods for fields have a single dependency request - .collect(DaggerCollectors.onlyElement()) - .key() - .qualifier() - .map(DaggerAnnotation::xprocessing); - return fieldProxy(asField(injectionSite.element()), methodName, qualifier); - } - throw new AssertionError(injectionSite); - } - /** * Invokes each of the injection methods for {@code injectionSites}, with the dependencies * transformed using the {@code dependencyUsage} function. @@ -318,148 +223,23 @@ private static CodeBlock invoke( .collect(toImmutableList())) .build(); ClassName enclosingClass = membersInjectorNameForType(injectionSite.enclosingTypeElement()); - MethodSpec methodSpec = create(injectionSite); - return invokeMethod(methodSpec, arguments, enclosingClass, generatedTypeName); - } - - /* - * TODO(ronshapiro): this isn't perfect, as collisions could still exist. Some examples: - * - * - @Inject void members() {} will generate a method that conflicts with the instance - * method `injectMembers(T)` - * - Adding the index could conflict with another member: - * @Inject void a(Object o) {} - * @Inject void a(String s) {} - * @Inject void a1(String s) {} - * - * Here, Method a(String) will add the suffix "1", which will conflict with the method - * generated for a1(String) - * - Members named "members" or "methods" could also conflict with the {@code static} injection - * method. - */ - private static String methodName(InjectionSite injectionSite) { - int index = injectionSite.indexAmongAtInjectMembersWithSameSimpleName(); - String indexString = index == 0 ? "" : String.valueOf(index + 1); - return "inject" - + LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(injectionSite.element())) - + indexString; + String methodName = membersInjectorMethodName(injectionSite); + return invokeMethod(methodName, arguments, enclosingClass, generatedTypeName); } } - private enum InstanceCastPolicy { - CAST_IF_NOT_PUBLIC, IGNORE; - - boolean useObjectType(XType instanceType) { - return this == CAST_IF_NOT_PUBLIC && !isRawTypePubliclyAccessible(instanceType); - } - } - - private enum CheckNotNullPolicy { - IGNORE, CHECK_FOR_NULL; - - CodeBlock checkForNull(CodeBlock maybeNull) { - return this.equals(IGNORE) - ? maybeNull - : CodeBlock.of("$T.checkNotNullFromProvides($L)", Preconditions.class, maybeNull); - } - - static CheckNotNullPolicy get(ContributionBinding binding, CompilerOptions compilerOptions) { - return binding.shouldCheckForNull(compilerOptions) ? CHECK_FOR_NULL : IGNORE; - } - } - - private static MethodSpec methodProxy( - XMethodElement method, - String methodName, - InstanceCastPolicy instanceCastPolicy, - CheckNotNullPolicy checkNotNullPolicy) { - XTypeElement enclosingType = asTypeElement(method.getEnclosingElement()); - - MethodSpec.Builder builder = - methodBuilder(methodName) - .addModifiers(PUBLIC, STATIC) - .varargs(method.isVarArgs()) - .addTypeVariables(method.getExecutableType().getTypeVariableNames()); - - UniqueNameSet parameterNameSet = new UniqueNameSet(); - CodeBlock instance; - if (method.isStatic() || enclosingType.isCompanionObject()) { - instance = CodeBlock.of("$T", rawTypeName(enclosingType.getType().getTypeName())); - } else if (enclosingType.isKotlinObject()) { - // Call through the singleton instance. - // See: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#static-methods - instance = CodeBlock.of("$T.INSTANCE", rawTypeName(enclosingType.getType().getTypeName())); - } else { - builder.addTypeVariables(typeVariableNames(enclosingType)); - boolean useObject = instanceCastPolicy.useObjectType(enclosingType.getType()); - instance = copyInstance(builder, parameterNameSet, enclosingType.getType(), useObject); - } - CodeBlock arguments = copyParameters(builder, parameterNameSet, method.getParameters()); - CodeBlock invocation = - checkNotNullPolicy.checkForNull( - CodeBlock.of("$L.$L($L)", instance, method.getJvmName(), arguments)); - - copyThrows(builder, method); - - if (isVoid(method.getReturnType())) { - return builder.addStatement("$L", invocation).build(); - } else { - Nullability nullability = Nullability.of(method); - nullability - .nonTypeUseNullableAnnotations() - .forEach(builder::addAnnotation); - return builder - .returns( - method.getReturnType().getTypeName() - .annotated( - nullability.typeUseNullableAnnotations().stream() - .map(annotation -> AnnotationSpec.builder(annotation).build()) - .collect(toImmutableList()))) - .addStatement("return $L", invocation) - .build(); - } - } - - private static MethodSpec fieldProxy( - XFieldElement field, String methodName, Optional qualifier) { - XTypeElement enclosingType = asTypeElement(field.getEnclosingElement()); - - MethodSpec.Builder builder = - methodBuilder(methodName) - .addModifiers(PUBLIC, STATIC) - .addAnnotation( - AnnotationSpec.builder(TypeNames.INJECTED_FIELD_SIGNATURE) - .addMember("value", "$S", memberInjectedFieldSignatureForVariable(field)) - .build()) - .addTypeVariables(typeVariableNames(enclosingType)); - - qualifier.map(XAnnotations::getAnnotationSpec).ifPresent(builder::addAnnotation); - - boolean useObject = !isRawTypePubliclyAccessible(enclosingType.getType()); - UniqueNameSet parameterNameSet = new UniqueNameSet(); - CodeBlock instance = - copyInstance(builder, parameterNameSet, enclosingType.getType(), useObject); - CodeBlock argument = copyParameters(builder, parameterNameSet, ImmutableList.of(field)); - return builder.addStatement("$L.$L = $L", instance, getSimpleName(field), argument).build(); - } - private static CodeBlock invokeMethod( - MethodSpec methodSpec, + String methodName, ImmutableList parameters, ClassName enclosingClass, ClassName requestingClass) { - checkArgument(methodSpec.parameters.size() == parameters.size()); CodeBlock parameterBlock = makeParametersCodeBlock(parameters); return enclosingClass.equals(requestingClass) - ? CodeBlock.of("$L($L)", methodSpec.name, parameterBlock) - : CodeBlock.of("$T.$L($L)", enclosingClass, methodSpec.name, parameterBlock); - } - - private static void copyThrows(MethodSpec.Builder methodBuilder, XExecutableElement method) { - method.getThrownTypes().stream().map(XType::getTypeName).forEach(methodBuilder::addException); + ? CodeBlock.of("$L($L)", methodName, parameterBlock) + : CodeBlock.of("$T.$L($L)", enclosingClass, methodName, parameterBlock); } - private static CodeBlock copyParameters( + static CodeBlock copyParameters( MethodSpec.Builder methodBuilder, UniqueNameSet parameterNameSet, List parameters) { @@ -478,7 +258,7 @@ private static CodeBlock copyParameters( .collect(toParametersCodeBlock()); } - private static CodeBlock copyParameter( + static CodeBlock copyParameter( MethodSpec.Builder methodBuilder, XType type, String name, @@ -497,20 +277,4 @@ private static CodeBlock copyParameter( .build()); return useObject ? CodeBlock.of("($T) $L", type.getTypeName(), name) : CodeBlock.of("$L", name); } - - private static CodeBlock copyInstance( - MethodSpec.Builder methodBuilder, - UniqueNameSet parameterNameSet, - XType type, - boolean useObject) { - CodeBlock instance = - copyParameter( - methodBuilder, - type, - parameterNameSet.getUniqueName("instance"), - useObject, - Nullability.NOT_NULLABLE); - // If we had to cast the instance add an extra parenthesis incase we're calling a method on it. - return useObject ? CodeBlock.of("($L)", instance) : instance; - } } diff --git a/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java b/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java index d019ba43f8d..81665ae4c12 100644 --- a/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java +++ b/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java @@ -17,11 +17,14 @@ package dagger.internal.codegen.writing; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.squareup.javapoet.MethodSpec.constructorBuilder; import static com.squareup.javapoet.MethodSpec.methodBuilder; import static com.squareup.javapoet.TypeSpec.classBuilder; import static dagger.internal.codegen.binding.SourceFiles.bindingTypeElementTypeVariableNames; import static dagger.internal.codegen.binding.SourceFiles.generateBindingFieldsForDependencies; +import static dagger.internal.codegen.binding.SourceFiles.memberInjectedFieldSignatureForVariable; +import static dagger.internal.codegen.binding.SourceFiles.membersInjectorMethodName; import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; import static dagger.internal.codegen.binding.SourceFiles.parameterizedGeneratedTypeNameForBinding; import static dagger.internal.codegen.extension.DaggerStreams.presentValues; @@ -32,17 +35,30 @@ import static dagger.internal.codegen.javapoet.CodeBlocks.toConcatenatedCodeBlock; import static dagger.internal.codegen.javapoet.TypeNames.membersInjectorOf; import static dagger.internal.codegen.javapoet.TypeNames.rawTypeName; +import static dagger.internal.codegen.langmodel.Accessibility.isRawTypePubliclyAccessible; import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; import static dagger.internal.codegen.writing.GwtCompatibility.gwtIncompatibleAnnotation; +import static dagger.internal.codegen.writing.InjectionMethods.copyParameter; +import static dagger.internal.codegen.writing.InjectionMethods.copyParameters; +import static dagger.internal.codegen.xprocessing.XElements.asField; +import static dagger.internal.codegen.xprocessing.XElements.asMethod; +import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.internal.codegen.xprocessing.XTypeElements.typeVariableNames; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; +import androidx.room.compiler.processing.XAnnotation; import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XFieldElement; import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XMethodElement; import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.squareup.javapoet.AnnotationSpec; @@ -58,12 +74,16 @@ import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.base.UniqueNameSet; import dagger.internal.codegen.binding.MembersInjectionBinding; +import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite; import dagger.internal.codegen.binding.SourceFiles; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.model.DaggerAnnotation; import dagger.internal.codegen.model.DependencyRequest; import dagger.internal.codegen.model.Key; import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod; +import dagger.internal.codegen.xprocessing.Nullability; +import dagger.internal.codegen.xprocessing.XAnnotations; +import java.util.Optional; import javax.inject.Inject; /** @@ -112,7 +132,7 @@ public ImmutableList topLevelTypes(MembersInjectionBinding bin binding.injectionSites().stream() .filter( site -> site.enclosingTypeElement().equals(binding.membersInjectedType())) - .map(InjectionSiteMethod::create) + .map(MembersInjectorGenerator::membersInjectionMethod) .collect(toImmutableList())); gwtIncompatibleAnnotation(binding).ifPresent(injectorTypeBuilder::addAnnotation); @@ -120,6 +140,87 @@ public ImmutableList topLevelTypes(MembersInjectionBinding bin return ImmutableList.of(injectorTypeBuilder); } + private static MethodSpec membersInjectionMethod(InjectionSite injectionSite) { + String methodName = membersInjectorMethodName(injectionSite); + switch (injectionSite.kind()) { + case METHOD: + return methodInjectionMethod(asMethod(injectionSite.element()), methodName); + case FIELD: + Optional qualifier = + // methods for fields have a single dependency request + getOnlyElement(injectionSite.dependencies()) + .key() + .qualifier() + .map(DaggerAnnotation::xprocessing); + return fieldInjectionMethod(asField(injectionSite.element()), methodName, qualifier); + } + throw new AssertionError(injectionSite); + } + + // Example: + // + // public static void injectMethod(Instance instance, Foo foo, Bar bar) { + // instance.injectMethod(foo, bar); + // } + private static MethodSpec methodInjectionMethod(XMethodElement method, String methodName) { + XTypeElement enclosingType = asTypeElement(method.getEnclosingElement()); + MethodSpec.Builder builder = + methodBuilder(methodName) + .addModifiers(PUBLIC, STATIC) + .varargs(method.isVarArgs()) + .addTypeVariables(typeVariableNames(enclosingType)) + .addExceptions(getThrownTypes(method)); + + UniqueNameSet parameterNameSet = new UniqueNameSet(); + CodeBlock instance = copyInstance(builder, parameterNameSet, enclosingType.getType()); + CodeBlock arguments = copyParameters(builder, parameterNameSet, method.getParameters()); + return builder.addStatement("$L.$L($L)", instance, method.getJvmName(), arguments).build(); + } + + // Example: + // + // public static void injectFoo(Instance instance, Foo foo) { + // instance.foo = foo; + // } + private static MethodSpec fieldInjectionMethod( + XFieldElement field, String methodName, Optional qualifier) { + XTypeElement enclosingType = asTypeElement(field.getEnclosingElement()); + + MethodSpec.Builder builder = + methodBuilder(methodName) + .addModifiers(PUBLIC, STATIC) + .addAnnotation( + AnnotationSpec.builder(TypeNames.INJECTED_FIELD_SIGNATURE) + .addMember("value", "$S", memberInjectedFieldSignatureForVariable(field)) + .build()) + .addTypeVariables(typeVariableNames(enclosingType)); + + qualifier.map(XAnnotations::getAnnotationSpec).ifPresent(builder::addAnnotation); + + UniqueNameSet parameterNameSet = new UniqueNameSet(); + CodeBlock instance = copyInstance(builder, parameterNameSet, enclosingType.getType()); + CodeBlock argument = copyParameters(builder, parameterNameSet, ImmutableList.of(field)); + return builder.addStatement("$L.$L = $L", instance, getSimpleName(field), argument).build(); + } + + private static ImmutableList getThrownTypes(XExecutableElement executable) { + return executable.getThrownTypes().stream().map(XType::getTypeName).collect(toImmutableList()); + } + + private static CodeBlock copyInstance( + MethodSpec.Builder methodBuilder, UniqueNameSet parameterNameSet, XType type) { + boolean useObject = !isRawTypePubliclyAccessible(type); + CodeBlock instance = + copyParameter( + methodBuilder, + type, + parameterNameSet.getUniqueName("instance"), + useObject, + Nullability.NOT_NULLABLE); + // If we had to cast the instance add an extra parenthesis incase we're calling a method on it. + return useObject ? CodeBlock.of("($L)", instance) : instance; + } + // MyClass( // Provider dep1Provider, // Provider dep2Provider,