blob: 360adf1bfd498ad9af50145ade804f3dee470995 [file] [log] [blame]
/*
* Copyright (C) 2017 The Dagger Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dagger.internal.codegen.writing;
import static androidx.room.compiler.codegen.XTypeNameKt.toJavaPoet;
import static androidx.room.compiler.processing.XElementKt.isMethodParameter;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedParameter;
import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding;
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.langmodel.Accessibility.isRawTypeAccessible;
import static dagger.internal.codegen.langmodel.Accessibility.isRawTypePubliclyAccessible;
import static dagger.internal.codegen.xprocessing.XElements.asExecutable;
import static dagger.internal.codegen.xprocessing.XElements.asMethodParameter;
import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
import static dagger.internal.codegen.xprocessing.XTypes.erasedTypeName;
import androidx.room.compiler.processing.XExecutableElement;
import androidx.room.compiler.processing.XExecutableParameterElement;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XVariableElement;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import dagger.internal.codegen.base.UniqueNameSet;
import dagger.internal.codegen.binding.AssistedInjectionBinding;
import dagger.internal.codegen.binding.ContributionBinding;
import dagger.internal.codegen.binding.InjectionBinding;
import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite;
import dagger.internal.codegen.binding.ProvisionBinding;
import dagger.internal.codegen.compileroption.CompilerOptions;
import dagger.internal.codegen.model.DependencyRequest;
import dagger.internal.codegen.xprocessing.Nullability;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
/** Convenience methods for creating and invoking {@link InjectionMethod}s. */
final class InjectionMethods {
/**
* A method that returns an object from a {@code @Provides} method or an {@code @Inject}ed
* constructor. Its parameters match the dependency requests for constructor and members
* injection.
*
* <p>For {@code @Provides} methods named "foo", the method name is "proxyFoo". For example:
*
* <pre><code>
* abstract class FooModule {
* {@literal @Provides} static Foo provideFoo(Bar bar, Baz baz) { … }
* }
*
* public static proxyProvideFoo(Bar bar, Baz baz) { … }
* </code></pre>
*
* <p>For {@code @Inject}ed constructors, the method name is "newFoo". For example:
*
* <pre><code>
* class Foo {
* {@literal @Inject} Foo(Bar bar) {}
* }
*
* public static Foo newFoo(Bar bar) { … }
* </code></pre>
*/
static final class ProvisionMethod {
/**
* Invokes the injection method for {@code binding}, with the dependencies transformed with the
* {@code dependencyUsage} function.
*/
static CodeBlock invoke(
ContributionBinding binding,
Function<DependencyRequest, CodeBlock> dependencyUsage,
Function<XExecutableParameterElement, String> uniqueAssistedParameterName,
ClassName requestingClass,
Optional<CodeBlock> moduleReference,
CompilerOptions compilerOptions) {
ImmutableList.Builder<CodeBlock> arguments = ImmutableList.builder();
moduleReference.ifPresent(arguments::add);
invokeArguments(binding, dependencyUsage, uniqueAssistedParameterName)
.forEach(arguments::add);
ClassName enclosingClass = toJavaPoet(generatedClassNameForBinding(binding));
String methodName = generatedProxyMethodName(binding);
return invokeMethod(methodName, arguments.build(), enclosingClass, requestingClass);
}
static ImmutableList<CodeBlock> invokeArguments(
ContributionBinding binding,
Function<DependencyRequest, CodeBlock> dependencyUsage,
Function<XExecutableParameterElement, String> uniqueAssistedParameterName) {
ImmutableMap<XExecutableParameterElement, DependencyRequest> dependencyRequestMap =
provisionDependencies(binding).stream()
.collect(
toImmutableMap(
request -> asMethodParameter(request.requestElement().get().xprocessing()),
request -> request));
ImmutableList.Builder<CodeBlock> arguments = ImmutableList.builder();
XExecutableElement method = asExecutable(binding.bindingElement().get());
for (XExecutableParameterElement parameter : method.getParameters()) {
if (isAssistedParameter(parameter)) {
arguments.add(CodeBlock.of("$L", uniqueAssistedParameterName.apply(parameter)));
} else if (dependencyRequestMap.containsKey(parameter)) {
DependencyRequest request = dependencyRequestMap.get(parameter);
arguments.add(dependencyUsage.apply(request));
} else {
throw new AssertionError("Unexpected parameter: " + parameter);
}
}
return arguments.build();
}
private static ImmutableSet<DependencyRequest> provisionDependencies(
ContributionBinding binding) {
switch (binding.kind()) {
case INJECTION:
return ((InjectionBinding) binding).constructorDependencies();
case ASSISTED_INJECTION:
return ((AssistedInjectionBinding) binding).constructorDependencies();
case PROVISION:
return ((ProvisionBinding) binding).dependencies();
default:
throw new AssertionError("Unexpected binding kind: " + binding.kind());
}
}
}
/**
* A static method that injects one member of an instance of a type. Its first parameter is an
* instance of the type to be injected. The remaining parameters match the dependency requests for
* the injection site.
*
* <p>Example:
*
* <pre><code>
* class Foo {
* {@literal @Inject} Bar bar;
* {@literal @Inject} void setThings(Baz baz, Qux qux) {}
* }
*
* public static injectBar(Foo instance, Bar bar) { … }
* public static injectSetThings(Foo instance, Baz baz, Qux qux) { … }
* </code></pre>
*/
static final class InjectionSiteMethod {
/**
* Invokes each of the injection methods for {@code injectionSites}, with the dependencies
* transformed using the {@code dependencyUsage} function.
*
* @param instanceType the type of the {@code instance} parameter
*/
static CodeBlock invokeAll(
ImmutableSet<InjectionSite> injectionSites,
ClassName generatedTypeName,
CodeBlock instanceCodeBlock,
XType instanceType,
Function<DependencyRequest, CodeBlock> dependencyUsage) {
return injectionSites.stream()
.map(
injectionSite -> {
XType injectSiteType = injectionSite.enclosingTypeElement().getType();
// If instance has been declared as Object because it is not accessible from the
// component, but the injectionSite is in a supertype of instanceType that is
// publicly accessible, the InjectionSiteMethod will request the actual type and not
// Object as the first parameter. If so, cast to the supertype which is accessible
// from within generatedTypeName
CodeBlock maybeCastedInstance =
instanceType.getTypeName().equals(TypeName.OBJECT)
&& isRawTypeAccessible(injectSiteType, generatedTypeName.packageName())
? CodeBlock.of("($T) $L", erasedTypeName(injectSiteType), instanceCodeBlock)
: instanceCodeBlock;
return CodeBlock.of(
"$L;",
invoke(injectionSite, generatedTypeName, maybeCastedInstance, dependencyUsage));
})
.collect(toConcatenatedCodeBlock());
}
/**
* Invokes the injection method for {@code injectionSite}, with the dependencies transformed
* using the {@code dependencyUsage} function.
*/
private static CodeBlock invoke(
InjectionSite injectionSite,
ClassName generatedTypeName,
CodeBlock instanceCodeBlock,
Function<DependencyRequest, CodeBlock> dependencyUsage) {
ImmutableList<CodeBlock> arguments =
ImmutableList.<CodeBlock>builder()
.add(instanceCodeBlock)
.addAll(
injectionSite.dependencies().stream()
.map(dependencyUsage)
.collect(toImmutableList()))
.build();
ClassName enclosingClass =
toJavaPoet(membersInjectorNameForType(injectionSite.enclosingTypeElement()));
String methodName = membersInjectorMethodName(injectionSite);
return invokeMethod(methodName, arguments, enclosingClass, generatedTypeName);
}
}
private static CodeBlock invokeMethod(
String methodName,
ImmutableList<CodeBlock> parameters,
ClassName enclosingClass,
ClassName requestingClass) {
CodeBlock parameterBlock = makeParametersCodeBlock(parameters);
return enclosingClass.equals(requestingClass)
? CodeBlock.of("$L($L)", methodName, parameterBlock)
: CodeBlock.of("$T.$L($L)", enclosingClass, methodName, parameterBlock);
}
static CodeBlock copyParameters(
MethodSpec.Builder methodBuilder,
UniqueNameSet parameterNameSet,
List<? extends XVariableElement> parameters) {
return parameters.stream()
.map(
parameter -> {
String name =
parameterNameSet.getUniqueName(
isMethodParameter(parameter)
? asMethodParameter(parameter).getJvmName()
: getSimpleName(parameter));
boolean useObject = !isRawTypePubliclyAccessible(parameter.getType());
return copyParameter(
methodBuilder, parameter.getType(), name, useObject, Nullability.of(parameter));
})
.collect(toParametersCodeBlock());
}
static CodeBlock copyParameter(
MethodSpec.Builder methodBuilder,
XType type,
String name,
boolean useObject,
Nullability nullability) {
TypeName typeName = useObject ? TypeName.OBJECT : type.getTypeName();
nullability.typeUseNullableAnnotations().stream()
.map(it -> AnnotationSpec.builder(it).build())
.forEach(typeName::annotated);
methodBuilder.addParameter(
ParameterSpec.builder(typeName, name)
.addAnnotations(
nullability.nonTypeUseNullableAnnotations().stream()
.map(it -> AnnotationSpec.builder(it).build())
.collect(toImmutableList()))
.build());
return useObject ? CodeBlock.of("($T) $L", type.getTypeName(), name) : CodeBlock.of("$L", name);
}
}