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

dagger.internal.codegen.validation.BindingElementValidator Maven / Gradle / Ivy

/*
 * Copyright (C) 2016 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.validation;

import static androidx.room.compiler.processing.XTypeKt.isArray;
import static androidx.room.compiler.processing.XTypeKt.isVoid;
import static com.google.common.base.Verify.verifyNotNull;
import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedFactoryType;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedInjectionType;
import static dagger.internal.codegen.binding.MapKeys.getMapKeys;
import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive;
import static dagger.internal.codegen.xprocessing.XTypes.isTypeVariable;

import androidx.room.compiler.processing.XAnnotation;
import androidx.room.compiler.processing.XElement;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XTypeElement;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.FormatMethod;
import com.squareup.javapoet.ClassName;
import dagger.internal.codegen.base.ContributionType;
import dagger.internal.codegen.base.FrameworkTypes;
import dagger.internal.codegen.base.SetType;
import dagger.internal.codegen.binding.InjectionAnnotations;
import dagger.internal.codegen.javapoet.TypeNames;
import dagger.internal.codegen.model.Key;
import dagger.internal.codegen.model.Scope;
import dagger.internal.codegen.xprocessing.XElements;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.inject.Qualifier;

/** A validator for elements that represent binding declarations. */
public abstract class BindingElementValidator {
  private static final ImmutableSet MULTIBINDING_ANNOTATIONS =
      ImmutableSet.of(TypeNames.INTO_SET, TypeNames.ELEMENTS_INTO_SET, TypeNames.INTO_MAP);

  private final AllowsMultibindings allowsMultibindings;
  private final AllowsScoping allowsScoping;
  private final Map cache = new HashMap<>();
  private final InjectionAnnotations injectionAnnotations;

  /** Creates a validator object. */
  // TODO(bcorso): Consider reworking BindingElementValidator and all subclasses to use composition
  // rather than inheritance. The web of inheritance makes it difficult to track what implementation
  // of a method is actually being used.
  protected BindingElementValidator(
      AllowsMultibindings allowsMultibindings,
      AllowsScoping allowsScoping,
      InjectionAnnotations injectionAnnotations) {
    this.allowsMultibindings = allowsMultibindings;
    this.allowsScoping = allowsScoping;
    this.injectionAnnotations = injectionAnnotations;
  }

  /** Returns a {@link ValidationReport} for {@code element}. */
  public final ValidationReport validate(E element) {
    return reentrantComputeIfAbsent(cache, element, this::validateUncached);
  }

  private ValidationReport validateUncached(E element) {
    return elementValidator(element).validate();
  }

  /**
   * Returns an error message of the form "<{@link #bindingElements()}> rule", where
   * rule comes from calling {@link String#format(String, Object...)} on {@code ruleFormat}
   * and the other arguments.
   */
  @FormatMethod
  protected final String bindingElements(String ruleFormat, Object... args) {
    return new Formatter().format("%s ", bindingElements()).format(ruleFormat, args).toString();
  }

  /**
   * The kind of elements that this validator validates. Should be plural. Used for error reporting.
   */
  protected abstract String bindingElements();

  /** The verb describing the {@link ElementValidator#bindingElementType()} in error messages. */
  // TODO(ronshapiro,dpb): improve the name of this method and it's documentation.
  protected abstract String bindingElementTypeVerb();

  /** The error message when a binding element has a bad type. */
  protected String badTypeMessage() {
    return bindingElements(
        "must %s a primitive, an array, a type variable, or a declared type",
        bindingElementTypeVerb());
  }

  /**
   * The error message when a the type for a binding element with {@link
   * dagger.multibindings.ElementsIntoSet @ElementsIntoSet} or {@code SET_VALUES} is a not set type.
   */
  protected String elementsIntoSetNotASetMessage() {
    return bindingElements(
        "annotated with @ElementsIntoSet must %s a Set", bindingElementTypeVerb());
  }

  /**
   * The error message when a the type for a binding element with {@link
   * dagger.multibindings.ElementsIntoSet @ElementsIntoSet} or {@code SET_VALUES} is a raw set.
   */
  protected String elementsIntoSetRawSetMessage() {
    return bindingElements(
        "annotated with @ElementsIntoSet cannot %s a raw Set", bindingElementTypeVerb());
  }

  /*** Returns an {@link ElementValidator} for validating the given {@code element}. */
  protected abstract ElementValidator elementValidator(E element);

  /** Validator for a single binding element. */
  protected abstract class ElementValidator {
    private final E element;
    protected final ValidationReport.Builder report;
    private final ImmutableSet qualifiers;

    protected ElementValidator(E element) {
      this.element = element;
      this.report = ValidationReport.about(element);
      qualifiers = injectionAnnotations.getQualifiers(element);
    }

    /** Checks the element for validity. */
    private ValidationReport validate() {
      checkType();
      checkQualifiers();
      checkMapKeys();
      checkMultibindings();
      checkScopes();
      checkAdditionalProperties();
      return report.build();
    }

    /** Check any additional properties of the element. Does nothing by default. */
    protected void checkAdditionalProperties() {}

    /**
     * The type declared by this binding element. This may differ from a binding's {@link
     * Key#type()}, for example in multibindings. An {@link Optional#empty()} return value indicates
     * that the contributed type is ambiguous or missing, i.e. a {@code @BindsInstance} method with
     * zero or many parameters.
     */
    // TODO(dpb): should this be an ImmutableList, with this class checking the size?
    protected abstract Optional bindingElementType();

    /**
     * Adds an error if the {@link #bindingElementType() binding element type} is not appropriate.
     *
     * 

Adds an error if the type is not a primitive, array, declared type, or type variable. * *

If the binding is not a multibinding contribution, adds an error if the type is a * framework type. * *

If the element has {@link dagger.multibindings.ElementsIntoSet @ElementsIntoSet} or {@code * SET_VALUES}, adds an error if the type is not a {@code Set} for some {@code T} */ protected void checkType() { switch (ContributionType.fromBindingElement(element)) { case UNIQUE: // Validate that a unique binding is not attempting to bind a framework type. This // validation is only appropriate for unique bindings because multibindings may collect // framework types. E.g. Set> is perfectly reasonable. checkFrameworkType(); // Validate that a unique binding is not attempting to bind an unqualified assisted type. // This validation is only appropriate for unique bindings because multibindings may // collect assisted types. checkAssistedType(); // fall through case SET: case MAP: bindingElementType().ifPresent(this::checkKeyType); break; case SET_VALUES: checkSetValuesType(); } } /** * Adds an error if {@code keyType} is not a primitive, declared type, array, or type variable. */ protected void checkKeyType(XType keyType) { if (isVoid(keyType)) { report.addError(bindingElements("must %s a value (not void)", bindingElementTypeVerb())); } else if (!(isPrimitive(keyType) || isDeclared(keyType) || isArray(keyType) || isTypeVariable(keyType))) { report.addError(badTypeMessage()); } } /** Adds errors for unqualified assisted types. */ private void checkAssistedType() { if (qualifiers.isEmpty() && bindingElementType().isPresent() && isDeclared(bindingElementType().get())) { XTypeElement keyElement = bindingElementType().get().getTypeElement(); if (isAssistedInjectionType(keyElement)) { report.addError("Dagger does not support providing @AssistedInject types.", keyElement); } if (isAssistedFactoryType(keyElement)) { report.addError("Dagger does not support providing @AssistedFactory types.", keyElement); } } } /** * Adds an error if the type for an element with {@link * dagger.multibindings.ElementsIntoSet @ElementsIntoSet} or {@code SET_VALUES} is not a a * {@code Set} for a reasonable {@code T}. */ // TODO(gak): should we allow "covariant return" for set values? protected void checkSetValuesType() { bindingElementType().ifPresent(this::checkSetValuesType); } /** Adds an error if {@code type} is not a {@code Set} for a reasonable {@code T}. */ protected final void checkSetValuesType(XType type) { if (!SetType.isSet(type)) { report.addError(elementsIntoSetNotASetMessage()); } else { SetType setType = SetType.from(type); if (setType.isRawType()) { report.addError(elementsIntoSetRawSetMessage()); } else { checkKeyType(setType.elementType()); } } } /** * Adds an error if the element has more than one {@linkplain Qualifier qualifier} annotation. */ private void checkQualifiers() { if (qualifiers.size() > 1) { for (XAnnotation qualifier : qualifiers) { report.addError( bindingElements("may not use more than one @Qualifier"), element, qualifier); } } } /** * Adds an error if an {@link dagger.multibindings.IntoMap @IntoMap} element doesn't have * exactly one {@link dagger.MapKey @MapKey} annotation, or if an element that is {@link * dagger.multibindings.IntoMap @IntoMap} has any. */ private void checkMapKeys() { if (!allowsMultibindings.allowsMultibindings()) { return; } ImmutableSet mapKeys = getMapKeys(element); if (ContributionType.fromBindingElement(element).equals(ContributionType.MAP)) { switch (mapKeys.size()) { case 0: report.addError(bindingElements("of type map must declare a map key")); break; case 1: break; default: report.addError(bindingElements("may not have more than one map key")); break; } } else if (!mapKeys.isEmpty()) { report.addError(bindingElements("of non map type cannot declare a map key")); } } /** * Adds errors if: * *

    *
  • the element doesn't allow {@linkplain MultibindingAnnotations multibinding annotations} * and has any *
  • the element does allow them but has more than one *
  • the element has a multibinding annotation and its {@link dagger.Provides} or {@link * dagger.producers.Produces} annotation has a {@code type} parameter. *
*/ private void checkMultibindings() { ImmutableSet multibindingAnnotations = XElements.getAllAnnotations(element, MULTIBINDING_ANNOTATIONS); switch (allowsMultibindings) { case NO_MULTIBINDINGS: for (XAnnotation annotation : multibindingAnnotations) { report.addError( bindingElements("cannot have multibinding annotations"), element, annotation); } break; case ALLOWS_MULTIBINDINGS: if (multibindingAnnotations.size() > 1) { for (XAnnotation annotation : multibindingAnnotations) { report.addError( bindingElements("cannot have more than one multibinding annotation"), element, annotation); } } break; } } /** * Adds an error if the element has a scope but doesn't allow scoping, or if it has more than * one {@linkplain Scope scope} annotation. */ private void checkScopes() { ImmutableSet scopes = injectionAnnotations.getScopes(element); String error = null; switch (allowsScoping) { case ALLOWS_SCOPING: if (scopes.size() <= 1) { return; } error = bindingElements("cannot use more than one @Scope"); break; case NO_SCOPING: error = bindingElements("cannot be scoped"); break; } verifyNotNull(error); for (Scope scope : scopes) { report.addError(error, element, scope.scopeAnnotation().xprocessing()); } } /** * Adds an error if the {@link #bindingElementType() type} is a {@linkplain FrameworkTypes * framework type}. */ private void checkFrameworkType() { if (bindingElementType().filter(FrameworkTypes::isFrameworkType).isPresent()) { report.addError(bindingElements("must not %s framework types", bindingElementTypeVerb())); } } } /** Whether to check multibinding annotations. */ enum AllowsMultibindings { /** * This element disallows multibinding annotations, so don't bother checking for their validity. * {@link MultibindingAnnotationsProcessingStep} will add errors if the element has any * multibinding annotations. */ NO_MULTIBINDINGS, /** This element allows multibinding annotations, so validate them. */ ALLOWS_MULTIBINDINGS, ; private boolean allowsMultibindings() { return this == ALLOWS_MULTIBINDINGS; } } /** How to check scoping annotations. */ enum AllowsScoping { /** This element disallows scoping, so check that no scope annotations are present. */ NO_SCOPING, /** This element allows scoping, so validate that there's at most one scope annotation. */ ALLOWS_SCOPING, ; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy