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

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

The newest version!
/*
 * 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 dagger.internal.codegen.xprocessing.XElements.hasAnyAnnotation;
import static dagger.internal.codegen.xprocessing.XMethodElements.getEnclosingTypeElement;
import static dagger.internal.codegen.xprocessing.XMethodElements.hasTypeParameters;
import static dagger.internal.codegen.xprocessing.XTypes.isSubtype;
import static java.util.stream.Collectors.joining;

import androidx.room.compiler.processing.XExecutableElement;
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 androidx.room.compiler.processing.XVariableElement;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.FormatMethod;
import com.squareup.javapoet.ClassName;
import dagger.internal.codegen.binding.InjectionAnnotations;
import dagger.internal.codegen.javapoet.TypeNames;
import java.util.Optional;

/** A validator for methods that represent binding declarations. */
abstract class BindingMethodValidator extends BindingElementValidator {
  private final ClassName methodAnnotation;
  private final ImmutableSet enclosingElementAnnotations;
  private final Abstractness abstractness;
  private final ExceptionSuperclass exceptionSuperclass;
  private final XProcessingEnv processingEnv;
  private final DependencyRequestValidator dependencyRequestValidator;

  /**
   * Creates a validator object.
   *
   * @param methodAnnotation the annotation on a method that identifies it as a binding method
   * @param enclosingElementAnnotation the method must be declared in a class or interface annotated
   *     with this annotation
   */
  protected BindingMethodValidator(
      ClassName methodAnnotation,
      ClassName enclosingElementAnnotation,
      Abstractness abstractness,
      ExceptionSuperclass exceptionSuperclass,
      AllowsMultibindings allowsMultibindings,
      AllowsScoping allowsScoping,
      XProcessingEnv processingEnv,
      DependencyRequestValidator dependencyRequestValidator,
      InjectionAnnotations injectionAnnotations) {
    this(
        methodAnnotation,
        ImmutableSet.of(enclosingElementAnnotation),
        abstractness,
        exceptionSuperclass,
        allowsMultibindings,
        allowsScoping,
        processingEnv,
        dependencyRequestValidator,
        injectionAnnotations);
  }

  /**
   * Creates a validator object.
   *
   * @param methodAnnotation the annotation on a method that identifies it as a binding method
   * @param enclosingElementAnnotations the method must be declared in a class or interface
   *     annotated with one of these annotations
   */
  protected BindingMethodValidator(
      ClassName methodAnnotation,
      Iterable enclosingElementAnnotations,
      Abstractness abstractness,
      ExceptionSuperclass exceptionSuperclass,
      AllowsMultibindings allowsMultibindings,
      AllowsScoping allowsScoping,
      XProcessingEnv processingEnv,
      DependencyRequestValidator dependencyRequestValidator,
      InjectionAnnotations injectionAnnotations) {
    super(allowsMultibindings, allowsScoping, injectionAnnotations);
    this.methodAnnotation = methodAnnotation;
    this.enclosingElementAnnotations = ImmutableSet.copyOf(enclosingElementAnnotations);
    this.abstractness = abstractness;
    this.exceptionSuperclass = exceptionSuperclass;
    this.processingEnv = processingEnv;
    this.dependencyRequestValidator = dependencyRequestValidator;
  }

  /** The annotation that identifies binding methods validated by this object. */
  final ClassName methodAnnotation() {
    return methodAnnotation;
  }

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

  @Override
  protected final String bindingElements() {
    return String.format("@%s methods", methodAnnotation.simpleName());
  }

  @Override
  protected final String bindingElementTypeVerb() {
    return "return";
  }

  /** Abstract validator for individual binding method elements. */
  protected abstract class MethodValidator extends ElementValidator {
    private final XMethodElement method;

    protected MethodValidator(XMethodElement method) {
      super(method);
      this.method = method;
    }

    @Override
    protected final Optional bindingElementType() {
      return Optional.of(method.getReturnType());
    }

    @Override
    protected final void checkAdditionalProperties() {
      checkNotExtensionFunction();
      checkEnclosingElement();
      checkTypeParameters();
      checkNotPrivate();
      checkAbstractness();
      checkThrows();
      checkParameters();
      checkAdditionalMethodProperties();
    }

    /** Checks additional properties of the binding method. */
    protected void checkAdditionalMethodProperties() {}

    private void checkNotExtensionFunction() {
      if (method.isExtensionFunction()) {
        report.addError(bindingMethods("can not be an extension function"));
      }
    }

    /**
     * Adds an error if the method is not declared in a class or interface annotated with one of the
     * {@link #enclosingElementAnnotations}.
     */
    private void checkEnclosingElement() {
      XTypeElement enclosingTypeElement = getEnclosingTypeElement(method);
      if (enclosingTypeElement.isCompanionObject()) {
        // Binding method is in companion object, use companion object's enclosing class instead.
        enclosingTypeElement = enclosingTypeElement.getEnclosingTypeElement();
      }
      if (!hasAnyAnnotation(enclosingTypeElement, enclosingElementAnnotations)) {
        report.addError(
            bindingMethods(
                "can only be present within a @%s",
                enclosingElementAnnotations.stream()
                    .map(ClassName::simpleName)
                    .collect(joining(" or @"))));
      }
    }

    /** Adds an error if the method is generic. */
    private void checkTypeParameters() {
      if (hasTypeParameters(method)) {
        report.addError(bindingMethods("may not have type parameters"));
      }
    }

    /** Adds an error if the method is private. */
    private void checkNotPrivate() {
      if (method.isPrivate()) {
        report.addError(bindingMethods("cannot be private"));
      }
    }

    /** Adds an error if the method is abstract but must not be, or is not and must be. */
    private void checkAbstractness() {
      boolean isAbstract = method.isAbstract();
      switch (abstractness) {
        case MUST_BE_ABSTRACT:
          if (!isAbstract) {
            report.addError(bindingMethods("must be abstract"));
          }
          break;

        case MUST_BE_CONCRETE:
          if (isAbstract) {
            report.addError(bindingMethods("cannot be abstract"));
          }
      }
    }

    /**
     * Adds an error if the method declares throws anything but an {@link Error} or an appropriate
     * subtype of {@link Exception}.
     */
    private void checkThrows() {
      exceptionSuperclass.checkThrows(BindingMethodValidator.this, method, report);
    }

    /** Adds errors for the method parameters. */
    protected void checkParameters() {
      for (XVariableElement parameter : method.getParameters()) {
        checkParameter(parameter);
      }
    }

    /**
     * Adds errors for a method parameter. This implementation reports an error if the parameter has
     * more than one qualifier.
     */
    protected void checkParameter(XVariableElement parameter) {
      dependencyRequestValidator.validateDependencyRequest(report, parameter, parameter.getType());
    }
  }

  /** An abstract/concrete restriction on methods. */
  protected enum Abstractness {
    MUST_BE_ABSTRACT,
    MUST_BE_CONCRETE
  }

  /**
   * The exception class that all {@code throws}-declared throwables must extend, other than {@link
   * Error}.
   */
  protected enum ExceptionSuperclass {
    /** Methods may not declare any throwable types. */
    NO_EXCEPTIONS {
      @Override
      protected String errorMessage(BindingMethodValidator validator) {
        return validator.bindingMethods("may not throw");
      }

      @Override
      protected void checkThrows(
          BindingMethodValidator validator,
          XExecutableElement element,
          ValidationReport.Builder report) {
        if (!element.getThrownTypes().isEmpty()) {
          report.addError(validator.bindingMethods("may not throw"));
          return;
        }
      }
    },

    /** Methods may throw checked or unchecked exceptions or errors. */
    EXCEPTION(TypeNames.EXCEPTION) {
      @Override
      protected String errorMessage(BindingMethodValidator validator) {
        return validator.bindingMethods(
            "may only throw unchecked exceptions or exceptions subclassing Exception");
      }
    },

    /** Methods may throw unchecked exceptions or errors. */
    RUNTIME_EXCEPTION(TypeNames.RUNTIME_EXCEPTION) {
      @Override
      protected String errorMessage(BindingMethodValidator validator) {
        return validator.bindingMethods("may only throw unchecked exceptions");
      }
    },
    ;

    @SuppressWarnings("Immutable")
    private final ClassName superclass;

    ExceptionSuperclass() {
      this(null);
    }

    ExceptionSuperclass(ClassName superclass) {
      this.superclass = superclass;
    }

    /**
     * Adds an error if the method declares throws anything but an {@link Error} or an appropriate
     * subtype of {@link Exception}.
     *
     * 

This method is overridden in {@link #NO_EXCEPTIONS}. */ protected void checkThrows( BindingMethodValidator validator, XExecutableElement element, ValidationReport.Builder report) { XType exceptionSupertype = validator.processingEnv.findType(superclass); XType errorType = validator.processingEnv.findType(TypeNames.ERROR); for (XType thrownType : element.getThrownTypes()) { if (!isSubtype(thrownType, exceptionSupertype) && !isSubtype(thrownType, errorType)) { report.addError(errorMessage(validator)); break; } } } protected abstract String errorMessage(BindingMethodValidator validator); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy