All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dagger.internal.codegen.binding.BindingFactory Maven / Gradle / Ivy

The newest version!
/*
 * 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 androidx.room.compiler.processing.XElementKt.isMethod;
import static androidx.room.compiler.processing.XElementKt.isVariableElement;
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.xprocessing.XElements.asMethod;
import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
import static dagger.internal.codegen.xprocessing.XElements.asVariable;
import static dagger.internal.codegen.xprocessing.XTypes.erasedTypeName;
import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;

import androidx.room.compiler.processing.XConstructorElement;
import androidx.room.compiler.processing.XConstructorType;
import androidx.room.compiler.processing.XElement;
import androidx.room.compiler.processing.XExecutableParameterElement;
import androidx.room.compiler.processing.XMethodElement;
import androidx.room.compiler.processing.XMethodType;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XTypeElement;
import androidx.room.compiler.processing.XVariableElement;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import dagger.Module;
import dagger.internal.codegen.base.MapType;
import dagger.internal.codegen.base.SetType;
import dagger.internal.codegen.javapoet.TypeNames;
import dagger.internal.codegen.model.BindingKind;
import dagger.internal.codegen.model.DependencyRequest;
import dagger.internal.codegen.model.Key;
import dagger.internal.codegen.model.RequestKind;
import java.util.Optional;
import javax.inject.Inject;

/** A factory for {@link Binding} objects. */
public final class BindingFactory {
  private final KeyFactory keyFactory;
  private final DependencyRequestFactory dependencyRequestFactory;
  private final InjectionSiteFactory injectionSiteFactory;
  private final InjectionAnnotations injectionAnnotations;

  @Inject
  BindingFactory(
      KeyFactory keyFactory,
      DependencyRequestFactory dependencyRequestFactory,
      InjectionSiteFactory injectionSiteFactory,
      InjectionAnnotations injectionAnnotations) {
    this.keyFactory = keyFactory;
    this.dependencyRequestFactory = dependencyRequestFactory;
    this.injectionSiteFactory = injectionSiteFactory;
    this.injectionAnnotations = injectionAnnotations;
  }

  /**
   * Returns an {@link BindingKind#INJECTION} binding.
   *
   * @param constructorElement the {@code @Inject}-annotated constructor
   * @param resolvedEnclosingType 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 InjectionBinding injectionBinding(
      XConstructorElement constructorElement, Optional resolvedEnclosingType) {
    checkArgument(InjectionAnnotations.hasInjectAnnotation(constructorElement));

    XConstructorType constructorType = constructorElement.getExecutableType();
    XType enclosingType = constructorElement.getEnclosingElement().getType();
    // If the class this is constructing has some type arguments, resolve everything.
    if (!enclosingType.getTypeArguments().isEmpty() && resolvedEnclosingType.isPresent()) {
      checkIsSameErasedType(resolvedEnclosingType.get(), enclosingType);
      enclosingType = resolvedEnclosingType.get();
      constructorType = constructorElement.asMemberOf(enclosingType);
    }

    // Collect all dependency requests within the provision method.
    ImmutableSet.Builder constructorDependencies = ImmutableSet.builder();
    for (int i = 0; i < constructorElement.getParameters().size(); i++) {
      XExecutableParameterElement parameter = constructorElement.getParameters().get(i);
      XType parameterType = constructorType.getParameterTypes().get(i);
      constructorDependencies.add(
          dependencyRequestFactory.forRequiredResolvedVariable(parameter, parameterType));
    }

    return InjectionBinding.builder()
        .bindingElement(constructorElement)
        .key(keyFactory.forInjectConstructorWithResolvedType(enclosingType))
        .constructorDependencies(constructorDependencies.build())
        .injectionSites(injectionSiteFactory.getInjectionSites(enclosingType))
        .scope(injectionAnnotations.getScope(constructorElement.getEnclosingElement()))
        .unresolved(
            hasNonDefaultTypeParameters(enclosingType)
                ? Optional.of(injectionBinding(constructorElement, Optional.empty()))
                : Optional.empty())
        .build();
  }

  /**
   * Returns an {@link BindingKind#ASSISTED_INJECTION} binding.
   *
   * @param constructorElement the {@code @Inject}-annotated constructor
   * @param resolvedEnclosingType 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 AssistedInjectionBinding assistedInjectionBinding(
      XConstructorElement constructorElement, Optional resolvedEnclosingType) {
    checkArgument(constructorElement.hasAnnotation(TypeNames.ASSISTED_INJECT));

    XConstructorType constructorType = constructorElement.getExecutableType();
    XType enclosingType = constructorElement.getEnclosingElement().getType();
    // If the class this is constructing has some type arguments, resolve everything.
    if (!enclosingType.getTypeArguments().isEmpty() && resolvedEnclosingType.isPresent()) {
      checkIsSameErasedType(resolvedEnclosingType.get(), enclosingType);
      enclosingType = resolvedEnclosingType.get();
      constructorType = constructorElement.asMemberOf(enclosingType);
    }

    // Collect all dependency requests within the provision method.
    ImmutableSet.Builder constructorDependencies = ImmutableSet.builder();
    for (int i = 0; i < constructorElement.getParameters().size(); i++) {
      XExecutableParameterElement parameter = constructorElement.getParameters().get(i);
      XType parameterType = constructorType.getParameterTypes().get(i);
      // Note: we filter out @Assisted parameters since these aren't considered dependency requests.
      if (!AssistedInjectionAnnotations.isAssistedParameter(parameter)) {
        constructorDependencies.add(
            dependencyRequestFactory.forRequiredResolvedVariable(parameter, parameterType));
      }
    }

    return AssistedInjectionBinding.builder()
        .bindingElement(constructorElement)
        .key(keyFactory.forInjectConstructorWithResolvedType(enclosingType))
        .constructorDependencies(constructorDependencies.build())
        .injectionSites(injectionSiteFactory.getInjectionSites(enclosingType))
        .scope(injectionAnnotations.getScope(constructorElement.getEnclosingElement()))
        .unresolved(
            hasNonDefaultTypeParameters(enclosingType)
                ? Optional.of(assistedInjectionBinding(constructorElement, Optional.empty()))
                : Optional.empty())
        .build();
  }

  public AssistedFactoryBinding assistedFactoryBinding(
      XTypeElement factory, Optional resolvedFactoryType) {

    // If the class this is constructing has some type arguments, resolve everything.
    XType factoryType = factory.getType();
    if (!factoryType.getTypeArguments().isEmpty() && resolvedFactoryType.isPresent()) {
      checkIsSameErasedType(resolvedFactoryType.get(), factoryType);
      factoryType = resolvedFactoryType.get();
    }

    XMethodElement factoryMethod = AssistedInjectionAnnotations.assistedFactoryMethod(factory);
    XMethodType factoryMethodType = factoryMethod.asMemberOf(factoryType);
    return AssistedFactoryBinding.builder()
        .key(keyFactory.forType(factoryType))
        .bindingElement(factory)
        .assistedInjectKey(keyFactory.forType(factoryMethodType.getReturnType()))
        .build();
  }

  /**
   * Returns a {@link BindingKind#PROVISION} binding for a {@code @Provides}-annotated method.
   *
   * @param module the installed module that declares or inherits the method
   */
  public ProvisionBinding providesMethodBinding(XMethodElement method, XTypeElement module) {
    XMethodType methodType = method.asMemberOf(module.getType());
    return ProvisionBinding.builder()
        .scope(injectionAnnotations.getScope(method))
        .nullability(Nullability.of(method))
        .bindingElement(method)
        .contributingModule(module)
        .key(keyFactory.forProvidesMethod(method, module))
        .dependencies(
            dependencyRequestFactory.forRequiredResolvedVariables(
                method.getParameters(), methodType.getParameterTypes()))
        .unresolved(
            methodType.isSameType(method.getExecutableType())
                ? Optional.empty()
                : Optional.of(
                    providesMethodBinding(method, asTypeElement(method.getEnclosingElement()))))
        .build();
  }

  /**
   * Returns a {@link BindingKind#PRODUCTION} binding for a {@code @Produces}-annotated method.
   *
   * @param module the installed module that declares or inherits the method
   */
  public ProductionBinding producesMethodBinding(XMethodElement method, XTypeElement module) {
    // TODO(beder): Add nullability checking with Java 8.
    XMethodType methodType = method.asMemberOf(module.getType());
    return ProductionBinding.builder()
        .bindingElement(method)
        .contributingModule(module)
        .key(keyFactory.forProducesMethod(method, module))
        .executorRequest(dependencyRequestFactory.forProductionImplementationExecutor())
        .monitorRequest(dependencyRequestFactory.forProductionComponentMonitor())
        .explicitDependencies(
            dependencyRequestFactory.forRequiredResolvedVariables(
                method.getParameters(), methodType.getParameterTypes()))
        .unresolved(
            methodType.isSameType(method.getExecutableType())
                ? Optional.empty()
                : Optional.of(
                    producesMethodBinding(method, asTypeElement(method.getEnclosingElement()))))
        .build();
  }

  /**
   * Returns a {@link BindingKind#MULTIBOUND_MAP} binding given a set of multibinding contributions.
   *
   * @param key a key that may be satisfied by a multibinding
   */
  public MultiboundMapBinding multiboundMap(
      Key key, Iterable multibindingContributions) {
    return MultiboundMapBinding.builder()
        .bindingType(
            multibindingRequiresProduction(key, multibindingContributions)
                ? BindingType.PRODUCTION
                : BindingType.PROVISION)
        .key(key)
        .dependencies(
            dependencyRequestFactory.forMultibindingContributions(key, multibindingContributions))
        .build();
  }

  /**
   * Returns a {@link BindingKind#MULTIBOUND_SET} binding given a set of multibinding contributions.
   *
   * @param key a key that may be satisfied by a multibinding
   */
  public MultiboundSetBinding multiboundSet(
      Key key, Iterable multibindingContributions) {
    return MultiboundSetBinding.builder()
        .bindingType(
            multibindingRequiresProduction(key, multibindingContributions)
                ? BindingType.PRODUCTION
                : BindingType.PROVISION)
        .key(key)
        .dependencies(
            dependencyRequestFactory.forMultibindingContributions(key, multibindingContributions))
        .build();
  }

  private boolean multibindingRequiresProduction(
      Key key, Iterable multibindingContributions) {
    if (MapType.isMap(key)) {
      MapType mapType = MapType.from(key);
      if (mapType.valuesAreTypeOf(TypeNames.PRODUCER)
          || mapType.valuesAreTypeOf(TypeNames.PRODUCED)) {
        return true;
      }
    } else if (SetType.isSet(key) && SetType.from(key).elementsAreTypeOf(TypeNames.PRODUCED)) {
      return true;
    }
    return Iterables.any(
        multibindingContributions, binding -> binding.bindingType().equals(BindingType.PRODUCTION));
  }

  /**
   * Returns a {@link BindingKind#COMPONENT} binding for the
   * component.
   */
  public ComponentBinding componentBinding(XTypeElement componentDefinitionType) {
    checkNotNull(componentDefinitionType);
    return ComponentBinding.builder()
        .bindingElement(componentDefinitionType)
        .key(keyFactory.forType(componentDefinitionType.getType()))
        .build();
  }

  /**
   * Returns a {@link BindingKind#COMPONENT_DEPENDENCY} binding for a
   * component's dependency.
   */
  public ComponentDependencyBinding componentDependencyBinding(ComponentRequirement dependency) {
    checkNotNull(dependency);
    return ComponentDependencyBinding.builder()
        .bindingElement(dependency.typeElement())
        .key(keyFactory.forType(dependency.type()))
        .build();
  }

  /**
   * Returns a {@link BindingKind#COMPONENT_PROVISION} binding for a
   * method on a component's dependency.
   */
  public ComponentDependencyProvisionBinding componentDependencyProvisionMethodBinding(
      XMethodElement dependencyMethod) {
    checkArgument(dependencyMethod.getParameters().isEmpty());
    return ComponentDependencyProvisionBinding.builder()
        .key(keyFactory.forComponentMethod(dependencyMethod))
        .nullability(Nullability.of(dependencyMethod))
        .scope(injectionAnnotations.getScope(dependencyMethod))
        .bindingElement(dependencyMethod)
        .build();
  }

  /**
   * Returns a {@link BindingKind#COMPONENT_PRODUCTION} binding for a
   * method on a component's dependency.
   */
  public ComponentDependencyProductionBinding componentDependencyProductionMethodBinding(
      XMethodElement dependencyMethod) {
    checkArgument(dependencyMethod.getParameters().isEmpty());
    return ComponentDependencyProductionBinding.builder()
        .key(keyFactory.forProductionComponentMethod(dependencyMethod))
        .bindingElement(dependencyMethod)
        .build();
  }

  /**
   * Returns a {@link BindingKind#BOUND_INSTANCE} binding for a
   * {@code @BindsInstance}-annotated builder setter method or factory method parameter.
   */
  BoundInstanceBinding boundInstanceBinding(ComponentRequirement requirement, XElement element) {
    checkArgument(isVariableElement(element) || isMethod(element));
    XVariableElement parameterElement =
        isVariableElement(element)
            ? asVariable(element)
            : getOnlyElement(asMethod(element).getParameters());
    return BoundInstanceBinding.builder()
        .bindingElement(element)
        .key(requirement.key().get())
        .nullability(Nullability.of(parameterElement))
        .build();
  }

  /**
   * Returns a {@link 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
   */
  SubcomponentCreatorBinding subcomponentCreatorBinding(
      XMethodElement subcomponentCreatorMethod, XTypeElement component) {
    checkArgument(subcomponentCreatorMethod.getParameters().isEmpty());
    Key key =
        keyFactory.forSubcomponentCreatorMethod(subcomponentCreatorMethod, component.getType());
    return SubcomponentCreatorBinding.builder()
        .bindingElement(subcomponentCreatorMethod)
        .key(key)
        .build();
  }

  /**
   * Returns a {@link BindingKind#SUBCOMPONENT_CREATOR} binding
   * declared using {@link Module#subcomponents()}.
   */
  SubcomponentCreatorBinding subcomponentCreatorBinding(
      ImmutableSet subcomponentDeclarations) {
    SubcomponentDeclaration subcomponentDeclaration = subcomponentDeclarations.iterator().next();
    return SubcomponentCreatorBinding.builder().key(subcomponentDeclaration.key()).build();
  }

  /**
   * Returns a {@link BindingKind#DELEGATE} binding.
   *
   * @param delegateDeclaration the {@code @Binds}-annotated declaration
   * @param actualBinding the binding that satisfies the {@code @Binds} declaration
   */
  DelegateBinding delegateBinding(
      DelegateDeclaration delegateDeclaration, ContributionBinding actualBinding) {
    return delegateBinding(delegateDeclaration, Optional.of(actualBinding));
  }

  private DelegateBinding delegateBinding(
      DelegateDeclaration delegateDeclaration, Optional actualBinding) {
    BindingType bindingType = delegateBindingType(actualBinding);
    return DelegateBinding.builder()
        .contributionType(delegateDeclaration.contributionType())
        .bindingElement(delegateDeclaration.bindingElement().get())
        .contributingModule(delegateDeclaration.contributingModule().get())
        .delegateRequest(delegateDeclaration.delegateRequest())
        .nullability(
            actualBinding.map(ContributionBinding::nullability).orElse(Nullability.NOT_NULLABLE))
        .bindingType(bindingType)
        .key(
            bindingType == BindingType.PRODUCTION
                ? keyFactory.forDelegateBinding(delegateDeclaration, TypeNames.PRODUCER)
                : keyFactory.forDelegateBinding(delegateDeclaration, TypeNames.PROVIDER))
        .scope(
            bindingType == BindingType.PRODUCTION
                ? Optional.empty()
                : injectionAnnotations.getScope(delegateDeclaration.bindingElement().get()))
        .build();
  }

  /**
   * Returns a {@link BindingKind#DELEGATE} binding used when there is
   * no binding that satisfies the {@code @Binds} declaration.
   */
  public DelegateBinding unresolvedDelegateBinding(DelegateDeclaration delegateDeclaration) {
    return delegateBinding(delegateDeclaration, Optional.empty());
  }

  private BindingType delegateBindingType(Optional actualBinding) {
    if (actualBinding.isEmpty()) {
      return BindingType.PROVISION;
    }
    checkArgument(actualBinding.get().bindingType() != BindingType.MEMBERS_INJECTION);
    return actualBinding.get().bindingType();
  }

  /**
   * Returns an {@link 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
   */
  OptionalBinding syntheticOptionalBinding(
      Key key,
      RequestKind requestKind,
      ImmutableCollection underlyingKeyBindings) {
    if (underlyingKeyBindings.isEmpty()) {
      return OptionalBinding.builder()
          .bindingType(BindingType.PROVISION)
          .key(key)
          .build();
    }
    boolean isProduction =
        underlyingKeyBindings.stream()
                .anyMatch(binding -> binding.bindingType() == BindingType.PRODUCTION)
            || requestKind.equals(RequestKind.PRODUCER) // handles producerFromProvider cases
            || requestKind.equals(RequestKind.PRODUCED); // handles producerFromProvider cases
    return OptionalBinding.builder()
        .bindingType(isProduction ? BindingType.PRODUCTION : BindingType.PROVISION)
        .key(key)
        .delegateRequest(
            dependencyRequestFactory.forSyntheticPresentOptionalBinding(key, requestKind))
        .build();
  }

  /** Returns a {@link BindingKind#MEMBERS_INJECTOR} binding. */
  public MembersInjectorBinding membersInjectorBinding(
      Key key, MembersInjectionBinding membersInjectionBinding) {
    return MembersInjectorBinding.builder()
        .key(key)
        .bindingElement(membersInjectionBinding.key().type().xprocessing().getTypeElement())
        .injectionSites(membersInjectionBinding.injectionSites())
        .build();
  }

  /**
   * Returns a {@link 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(XType type, Optional resolvedType) {
    // If the class this is injecting has some type arguments, resolve everything.
    if (!type.getTypeArguments().isEmpty() && resolvedType.isPresent()) {
      checkIsSameErasedType(resolvedType.get(), type);
      type = resolvedType.get();
    }
    return MembersInjectionBinding.builder()
        .key(keyFactory.forMembersInjectedType(type))
        .injectionSites(injectionSiteFactory.getInjectionSites(type))
        .unresolved(
            hasNonDefaultTypeParameters(type)
                ? Optional.of(
                    membersInjectionBinding(type.getTypeElement().getType(), Optional.empty()))
                : Optional.empty())
        .build();
  }

  private void checkIsSameErasedType(XType type1, XType type2) {
    checkState(
        erasedTypeName(type1).equals(erasedTypeName(type2)),
        "erased expected type: %s, erased actual type: %s",
        erasedTypeName(type1),
        erasedTypeName(type2));
  }

  private static boolean hasNonDefaultTypeParameters(XType type) {
    // If the type is not declared, then it can't have type parameters.
    if (!isDeclared(type)) {
      return false;
    }

    // If the element has no type parameters, none can be non-default.
    XType defaultType = type.getTypeElement().getType();
    if (defaultType.getTypeArguments().isEmpty()) {
      return false;
    }

    // The actual type parameter size can be different if the user is using a raw type.
    if (defaultType.getTypeArguments().size() != type.getTypeArguments().size()) {
      return true;
    }

    for (int i = 0; i < defaultType.getTypeArguments().size(); i++) {
      if (!defaultType.getTypeArguments().get(i).isSameType(type.getTypeArguments().get(i))) {
        return true;
      }
    }
    return false;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy