blob: 6f2fc805e01492b48790fd50fbcc642875b21fdd [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.binding;
import static com.google.auto.common.MoreElements.isAnnotationPresent;
import static com.google.auto.common.MoreTypes.asDeclared;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.getOnlyElement;
import static dagger.internal.codegen.base.MoreAnnotationMirrors.wrapOptionalInEquivalence;
import static dagger.internal.codegen.base.Scopes.uniqueScopeOf;
import static dagger.internal.codegen.binding.Binding.hasNonDefaultTypeParameters;
import static dagger.internal.codegen.binding.ComponentDescriptor.isComponentProductionMethod;
import static dagger.internal.codegen.binding.ConfigurationAnnotations.getNullableType;
import static dagger.internal.codegen.binding.ContributionBinding.bindingKindForMultibindingKey;
import static dagger.internal.codegen.binding.MapKeys.getMapKey;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static dagger.model.BindingKind.ASSISTED_FACTORY;
import static dagger.model.BindingKind.ASSISTED_INJECTION;
import static dagger.model.BindingKind.BOUND_INSTANCE;
import static dagger.model.BindingKind.COMPONENT;
import static dagger.model.BindingKind.COMPONENT_DEPENDENCY;
import static dagger.model.BindingKind.COMPONENT_PRODUCTION;
import static dagger.model.BindingKind.COMPONENT_PROVISION;
import static dagger.model.BindingKind.DELEGATE;
import static dagger.model.BindingKind.INJECTION;
import static dagger.model.BindingKind.MEMBERS_INJECTOR;
import static dagger.model.BindingKind.OPTIONAL;
import static dagger.model.BindingKind.PRODUCTION;
import static dagger.model.BindingKind.PROVISION;
import static dagger.model.BindingKind.SUBCOMPONENT_CREATOR;
import static javax.lang.model.element.ElementKind.CONSTRUCTOR;
import static javax.lang.model.element.ElementKind.METHOD;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import dagger.Module;
import dagger.assisted.AssistedInject;
import dagger.internal.codegen.base.ContributionType;
import dagger.internal.codegen.base.MapType;
import dagger.internal.codegen.base.SetType;
import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite;
import dagger.internal.codegen.binding.ProductionBinding.ProductionKind;
import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
import dagger.internal.codegen.langmodel.DaggerElements;
import dagger.internal.codegen.langmodel.DaggerTypes;
import dagger.model.DependencyRequest;
import dagger.model.Key;
import dagger.model.RequestKind;
import dagger.producers.Produced;
import dagger.producers.Producer;
import java.util.Optional;
import java.util.function.BiFunction;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeMirror;
/** A factory for {@link Binding} objects. */
public final class BindingFactory {
private final DaggerTypes types;
private final KeyFactory keyFactory;
private final DependencyRequestFactory dependencyRequestFactory;
private final InjectionSiteFactory injectionSiteFactory;
private final DaggerElements elements;
private final InjectionAnnotations injectionAnnotations;
private final KotlinMetadataUtil metadataUtil;
@Inject
BindingFactory(
DaggerTypes types,
DaggerElements elements,
KeyFactory keyFactory,
DependencyRequestFactory dependencyRequestFactory,
InjectionSiteFactory injectionSiteFactory,
InjectionAnnotations injectionAnnotations,
KotlinMetadataUtil metadataUtil) {
this.types = types;
this.elements = elements;
this.keyFactory = keyFactory;
this.dependencyRequestFactory = dependencyRequestFactory;
this.injectionSiteFactory = injectionSiteFactory;
this.injectionAnnotations = injectionAnnotations;
this.metadataUtil = metadataUtil;
}
/**
* Returns an {@link dagger.model.BindingKind#INJECTION} binding.
*
* @param constructorElement the {@code @Inject}-annotated constructor
* @param resolvedType the parameterized type if the constructor is for a generic class and the
* binding should be for the parameterized type
*/
// TODO(dpb): See if we can just pass the parameterized type and not also the constructor.
public ProvisionBinding injectionBinding(
ExecutableElement constructorElement, Optional<TypeMirror> resolvedType) {
checkArgument(constructorElement.getKind().equals(CONSTRUCTOR));
checkArgument(
isAnnotationPresent(constructorElement, Inject.class)
|| isAnnotationPresent(constructorElement, AssistedInject.class));
checkArgument(!injectionAnnotations.getQualifier(constructorElement).isPresent());
ExecutableType constructorType = MoreTypes.asExecutable(constructorElement.asType());
DeclaredType constructedType =
MoreTypes.asDeclared(constructorElement.getEnclosingElement().asType());
// If the class this is constructing has some type arguments, resolve everything.
if (!constructedType.getTypeArguments().isEmpty() && resolvedType.isPresent()) {
DeclaredType resolved = MoreTypes.asDeclared(resolvedType.get());
// Validate that we're resolving from the correct type.
checkState(
types.isSameType(types.erasure(resolved), types.erasure(constructedType)),
"erased expected type: %s, erased actual type: %s",
types.erasure(resolved),
types.erasure(constructedType));
constructorType = MoreTypes.asExecutable(types.asMemberOf(resolved, constructorElement));
constructedType = resolved;
}
// Collect all dependency requests within the provision method.
// Note: we filter out @Assisted parameters since these aren't considered dependency requests.
ImmutableSet.Builder<DependencyRequest> provisionDependencies = ImmutableSet.builder();
for (int i = 0; i < constructorElement.getParameters().size(); i++) {
VariableElement parameter = constructorElement.getParameters().get(i);
TypeMirror parameterType = constructorType.getParameterTypes().get(i);
if (!AssistedInjectionAnnotations.isAssistedParameter(parameter)) {
provisionDependencies.add(
dependencyRequestFactory.forRequiredResolvedVariable(parameter, parameterType));
}
}
Key key = keyFactory.forInjectConstructorWithResolvedType(constructedType);
ProvisionBinding.Builder builder =
ProvisionBinding.builder()
.contributionType(ContributionType.UNIQUE)
.bindingElement(constructorElement)
.key(key)
.provisionDependencies(provisionDependencies.build())
.injectionSites(injectionSiteFactory.getInjectionSites(constructedType))
.kind(
isAnnotationPresent(constructorElement, AssistedInject.class)
? ASSISTED_INJECTION
: INJECTION)
.scope(uniqueScopeOf(constructorElement.getEnclosingElement()));
TypeElement bindingTypeElement = MoreElements.asType(constructorElement.getEnclosingElement());
if (hasNonDefaultTypeParameters(bindingTypeElement, key.type(), types)) {
builder.unresolved(injectionBinding(constructorElement, Optional.empty()));
}
return builder.build();
}
public ProvisionBinding assistedFactoryBinding(
TypeElement factory, Optional<TypeMirror> resolvedType) {
// If the class this is constructing has some type arguments, resolve everything.
DeclaredType factoryType = MoreTypes.asDeclared(factory.asType());
if (!factoryType.getTypeArguments().isEmpty() && resolvedType.isPresent()) {
DeclaredType resolved = MoreTypes.asDeclared(resolvedType.get());
// Validate that we're resolving from the correct type by checking that the erasure of the
// resolvedType is the same as the erasure of the factoryType.
checkState(
types.isSameType(types.erasure(resolved), types.erasure(factoryType)),
"erased expected type: %s, erased actual type: %s",
types.erasure(resolved),
types.erasure(factoryType));
factoryType = resolved;
}
ExecutableElement factoryMethod =
AssistedInjectionAnnotations.assistedFactoryMethod(factory, elements, types);
ExecutableType factoryMethodType =
MoreTypes.asExecutable(types.asMemberOf(factoryType, factoryMethod));
return ProvisionBinding.builder()
.contributionType(ContributionType.UNIQUE)
.key(Key.builder(factoryType).build())
.bindingElement(factory)
.provisionDependencies(
ImmutableSet.of(
DependencyRequest.builder()
.key(Key.builder(factoryMethodType.getReturnType()).build())
.kind(RequestKind.PROVIDER)
.build()))
.kind(ASSISTED_FACTORY)
.build();
}
/**
* Returns a {@link dagger.model.BindingKind#PROVISION} binding for a {@code @Provides}-annotated
* method.
*
* @param contributedBy the installed module that declares or inherits the method
*/
public ProvisionBinding providesMethodBinding(
ExecutableElement providesMethod, TypeElement contributedBy) {
return setMethodBindingProperties(
ProvisionBinding.builder(),
providesMethod,
contributedBy,
keyFactory.forProvidesMethod(providesMethod, contributedBy),
this::providesMethodBinding)
.kind(PROVISION)
.scope(uniqueScopeOf(providesMethod))
.nullableType(getNullableType(providesMethod))
.build();
}
/**
* Returns a {@link dagger.model.BindingKind#PRODUCTION} binding for a {@code @Produces}-annotated
* method.
*
* @param contributedBy the installed module that declares or inherits the method
*/
public ProductionBinding producesMethodBinding(
ExecutableElement producesMethod, TypeElement contributedBy) {
// TODO(beder): Add nullability checking with Java 8.
ProductionBinding.Builder builder =
setMethodBindingProperties(
ProductionBinding.builder(),
producesMethod,
contributedBy,
keyFactory.forProducesMethod(producesMethod, contributedBy),
this::producesMethodBinding)
.kind(PRODUCTION)
.productionKind(ProductionKind.fromProducesMethod(producesMethod))
.thrownTypes(producesMethod.getThrownTypes())
.executorRequest(dependencyRequestFactory.forProductionImplementationExecutor())
.monitorRequest(dependencyRequestFactory.forProductionComponentMonitor());
return builder.build();
}
private <C extends ContributionBinding, B extends ContributionBinding.Builder<C, B>>
B setMethodBindingProperties(
B builder,
ExecutableElement method,
TypeElement contributedBy,
Key key,
BiFunction<ExecutableElement, TypeElement, C> create) {
checkArgument(method.getKind().equals(METHOD));
ExecutableType methodType =
MoreTypes.asExecutable(
types.asMemberOf(MoreTypes.asDeclared(contributedBy.asType()), method));
if (!types.isSameType(methodType, method.asType())) {
builder.unresolved(create.apply(method, MoreElements.asType(method.getEnclosingElement())));
}
boolean isKotlinObject =
metadataUtil.isObjectClass(contributedBy)
|| metadataUtil.isCompanionObjectClass(contributedBy);
return builder
.contributionType(ContributionType.fromBindingElement(method))
.bindingElement(method)
.contributingModule(contributedBy)
.isContributingModuleKotlinObject(isKotlinObject)
.key(key)
.dependencies(
dependencyRequestFactory.forRequiredResolvedVariables(
method.getParameters(), methodType.getParameterTypes()))
.wrappedMapKeyAnnotation(wrapOptionalInEquivalence(getMapKey(method)));
}
/**
* Returns a {@link dagger.model.BindingKind#MULTIBOUND_MAP} or {@link
* dagger.model.BindingKind#MULTIBOUND_SET} binding given a set of multibinding contribution
* bindings.
*
* @param key a key that may be satisfied by a multibinding
*/
public ContributionBinding syntheticMultibinding(
Key key, Iterable<ContributionBinding> multibindingContributions) {
ContributionBinding.Builder<?, ?> builder =
multibindingRequiresProduction(key, multibindingContributions)
? ProductionBinding.builder()
: ProvisionBinding.builder();
return builder
.contributionType(ContributionType.UNIQUE)
.key(key)
.dependencies(
dependencyRequestFactory.forMultibindingContributions(key, multibindingContributions))
.kind(bindingKindForMultibindingKey(key))
.build();
}
private boolean multibindingRequiresProduction(
Key key, Iterable<ContributionBinding> multibindingContributions) {
if (MapType.isMap(key)) {
MapType mapType = MapType.from(key);
if (mapType.valuesAreTypeOf(Producer.class) || mapType.valuesAreTypeOf(Produced.class)) {
return true;
}
} else if (SetType.isSet(key) && SetType.from(key).elementsAreTypeOf(Produced.class)) {
return true;
}
return Iterables.any(
multibindingContributions, binding -> binding.bindingType().equals(BindingType.PRODUCTION));
}
/** Returns a {@link dagger.model.BindingKind#COMPONENT} binding for the component. */
public ProvisionBinding componentBinding(TypeElement componentDefinitionType) {
checkNotNull(componentDefinitionType);
return ProvisionBinding.builder()
.contributionType(ContributionType.UNIQUE)
.bindingElement(componentDefinitionType)
.key(keyFactory.forType(componentDefinitionType.asType()))
.kind(COMPONENT)
.build();
}
/**
* Returns a {@link dagger.model.BindingKind#COMPONENT_DEPENDENCY} binding for a component's
* dependency.
*/
public ProvisionBinding componentDependencyBinding(ComponentRequirement dependency) {
checkNotNull(dependency);
return ProvisionBinding.builder()
.contributionType(ContributionType.UNIQUE)
.bindingElement(dependency.typeElement())
.key(keyFactory.forType(dependency.type()))
.kind(COMPONENT_DEPENDENCY)
.build();
}
/**
* Returns a {@link dagger.model.BindingKind#COMPONENT_PROVISION} or {@link
* dagger.model.BindingKind#COMPONENT_PRODUCTION} binding for a method on a component's
* dependency.
*
* @param componentDescriptor the component with the dependency, not the dependency that has the
* method
*/
public ContributionBinding componentDependencyMethodBinding(
ComponentDescriptor componentDescriptor, ExecutableElement dependencyMethod) {
checkArgument(dependencyMethod.getKind().equals(METHOD));
checkArgument(dependencyMethod.getParameters().isEmpty());
ContributionBinding.Builder<?, ?> builder;
if (componentDescriptor.isProduction()
&& isComponentProductionMethod(elements, dependencyMethod)) {
builder =
ProductionBinding.builder()
.key(keyFactory.forProductionComponentMethod(dependencyMethod))
.kind(COMPONENT_PRODUCTION)
.thrownTypes(dependencyMethod.getThrownTypes());
} else {
builder =
ProvisionBinding.builder()
.key(keyFactory.forComponentMethod(dependencyMethod))
.nullableType(getNullableType(dependencyMethod))
.kind(COMPONENT_PROVISION)
.scope(uniqueScopeOf(dependencyMethod));
}
return builder
.contributionType(ContributionType.UNIQUE)
.bindingElement(dependencyMethod)
.build();
}
/**
* Returns a {@link dagger.model.BindingKind#BOUND_INSTANCE} binding for a
* {@code @BindsInstance}-annotated builder setter method or factory method parameter.
*/
ProvisionBinding boundInstanceBinding(ComponentRequirement requirement, Element element) {
checkArgument(element instanceof VariableElement || element instanceof ExecutableElement);
VariableElement parameterElement =
element instanceof VariableElement
? MoreElements.asVariable(element)
: getOnlyElement(MoreElements.asExecutable(element).getParameters());
return ProvisionBinding.builder()
.contributionType(ContributionType.UNIQUE)
.bindingElement(element)
.key(requirement.key().get())
.nullableType(getNullableType(parameterElement))
.kind(BOUND_INSTANCE)
.build();
}
/**
* Returns a {@link dagger.model.BindingKind#SUBCOMPONENT_CREATOR} binding declared by a component
* method that returns a subcomponent builder. Use {{@link
* #subcomponentCreatorBinding(ImmutableSet)}} for bindings declared using {@link
* Module#subcomponents()}.
*
* @param component the component that declares or inherits the method
*/
ProvisionBinding subcomponentCreatorBinding(
ExecutableElement subcomponentCreatorMethod, TypeElement component) {
checkArgument(subcomponentCreatorMethod.getKind().equals(METHOD));
checkArgument(subcomponentCreatorMethod.getParameters().isEmpty());
Key key =
keyFactory.forSubcomponentCreatorMethod(
subcomponentCreatorMethod, asDeclared(component.asType()));
return ProvisionBinding.builder()
.contributionType(ContributionType.UNIQUE)
.bindingElement(subcomponentCreatorMethod)
.key(key)
.kind(SUBCOMPONENT_CREATOR)
.build();
}
/**
* Returns a {@link dagger.model.BindingKind#SUBCOMPONENT_CREATOR} binding declared using {@link
* Module#subcomponents()}.
*/
ProvisionBinding subcomponentCreatorBinding(
ImmutableSet<SubcomponentDeclaration> subcomponentDeclarations) {
SubcomponentDeclaration subcomponentDeclaration = subcomponentDeclarations.iterator().next();
return ProvisionBinding.builder()
.contributionType(ContributionType.UNIQUE)
.key(subcomponentDeclaration.key())
.kind(SUBCOMPONENT_CREATOR)
.build();
}
/**
* Returns a {@link dagger.model.BindingKind#DELEGATE} binding.
*
* @param delegateDeclaration the {@code @Binds}-annotated declaration
* @param actualBinding the binding that satisfies the {@code @Binds} declaration
*/
ContributionBinding delegateBinding(
DelegateDeclaration delegateDeclaration, ContributionBinding actualBinding) {
switch (actualBinding.bindingType()) {
case PRODUCTION:
return buildDelegateBinding(
ProductionBinding.builder().nullableType(actualBinding.nullableType()),
delegateDeclaration,
Producer.class);
case PROVISION:
return buildDelegateBinding(
ProvisionBinding.builder()
.scope(uniqueScopeOf(delegateDeclaration.bindingElement().get()))
.nullableType(actualBinding.nullableType()),
delegateDeclaration,
Provider.class);
case MEMBERS_INJECTION: // fall-through to throw
}
throw new AssertionError("bindingType: " + actualBinding);
}
/**
* Returns a {@link dagger.model.BindingKind#DELEGATE} binding used when there is no binding that
* satisfies the {@code @Binds} declaration.
*/
public ContributionBinding unresolvedDelegateBinding(DelegateDeclaration delegateDeclaration) {
return buildDelegateBinding(
ProvisionBinding.builder().scope(uniqueScopeOf(delegateDeclaration.bindingElement().get())),
delegateDeclaration,
Provider.class);
}
private ContributionBinding buildDelegateBinding(
ContributionBinding.Builder<?, ?> builder,
DelegateDeclaration delegateDeclaration,
Class<?> frameworkType) {
boolean isKotlinObject =
metadataUtil.isObjectClass(delegateDeclaration.contributingModule().get())
|| metadataUtil.isCompanionObjectClass(delegateDeclaration.contributingModule().get());
return builder
.contributionType(delegateDeclaration.contributionType())
.bindingElement(delegateDeclaration.bindingElement().get())
.contributingModule(delegateDeclaration.contributingModule().get())
.isContributingModuleKotlinObject(isKotlinObject)
.key(keyFactory.forDelegateBinding(delegateDeclaration, frameworkType))
.dependencies(delegateDeclaration.delegateRequest())
.wrappedMapKeyAnnotation(delegateDeclaration.wrappedMapKey())
.kind(DELEGATE)
.build();
}
/**
* Returns an {@link dagger.model.BindingKind#OPTIONAL} binding for {@code key}.
*
* @param requestKind the kind of request for the optional binding
* @param underlyingKeyBindings the possibly empty set of bindings that exist in the component for
* the underlying (non-optional) key
*/
ContributionBinding syntheticOptionalBinding(
Key key,
RequestKind requestKind,
ImmutableCollection<? extends Binding> underlyingKeyBindings) {
if (underlyingKeyBindings.isEmpty()) {
return ProvisionBinding.builder()
.contributionType(ContributionType.UNIQUE)
.key(key)
.kind(OPTIONAL)
.build();
}
boolean requiresProduction =
underlyingKeyBindings.stream()
.anyMatch(binding -> binding.bindingType() == BindingType.PRODUCTION)
|| requestKind.equals(RequestKind.PRODUCER) // handles producerFromProvider cases
|| requestKind.equals(RequestKind.PRODUCED); // handles producerFromProvider cases
return (requiresProduction ? ProductionBinding.builder() : ProvisionBinding.builder())
.contributionType(ContributionType.UNIQUE)
.key(key)
.kind(OPTIONAL)
.dependencies(dependencyRequestFactory.forSyntheticPresentOptionalBinding(key, requestKind))
.build();
}
/** Returns a {@link dagger.model.BindingKind#MEMBERS_INJECTOR} binding. */
public ProvisionBinding membersInjectorBinding(
Key key, MembersInjectionBinding membersInjectionBinding) {
return ProvisionBinding.builder()
.key(key)
.contributionType(ContributionType.UNIQUE)
.kind(MEMBERS_INJECTOR)
.bindingElement(MoreTypes.asTypeElement(membersInjectionBinding.key().type()))
.provisionDependencies(membersInjectionBinding.dependencies())
.injectionSites(membersInjectionBinding.injectionSites())
.build();
}
/**
* Returns a {@link dagger.model.BindingKind#MEMBERS_INJECTION} binding.
*
* @param resolvedType if {@code declaredType} is a generic class and {@code resolvedType} is a
* parameterization of that type, the returned binding will be for the resolved type
*/
// TODO(dpb): See if we can just pass one nongeneric/parameterized type.
public MembersInjectionBinding membersInjectionBinding(
DeclaredType declaredType, Optional<TypeMirror> resolvedType) {
// If the class this is injecting has some type arguments, resolve everything.
if (!declaredType.getTypeArguments().isEmpty() && resolvedType.isPresent()) {
DeclaredType resolved = asDeclared(resolvedType.get());
// Validate that we're resolving from the correct type.
checkState(
types.isSameType(types.erasure(resolved), types.erasure(declaredType)),
"erased expected type: %s, erased actual type: %s",
types.erasure(resolved),
types.erasure(declaredType));
declaredType = resolved;
}
ImmutableSortedSet<InjectionSite> injectionSites =
injectionSiteFactory.getInjectionSites(declaredType);
ImmutableSet<DependencyRequest> dependencies =
injectionSites.stream()
.flatMap(injectionSite -> injectionSite.dependencies().stream())
.collect(toImmutableSet());
Key key = keyFactory.forMembersInjectedType(declaredType);
TypeElement typeElement = MoreElements.asType(declaredType.asElement());
return new AutoValue_MembersInjectionBinding(
key,
dependencies,
typeElement,
hasNonDefaultTypeParameters(typeElement, key.type(), types)
? Optional.of(
membersInjectionBinding(asDeclared(typeElement.asType()), Optional.empty()))
: Optional.empty(),
injectionSites);
}
}