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

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

There is a newer version: 2.54
Show newest version
/*
 * Copyright (C) 2014 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 com.google.auto.common.MoreElements.asType;
import static com.google.auto.common.MoreElements.isAnnotationPresent;
import static dagger.internal.codegen.base.Scopes.scopesOf;
import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors;
import static dagger.internal.codegen.binding.InjectionAnnotations.injectedConstructors;
import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.STATIC;
import static javax.lang.model.type.TypeKind.DECLARED;

import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.common.collect.ImmutableSet;
import dagger.assisted.AssistedInject;
import dagger.internal.codegen.base.ClearableCache;
import dagger.internal.codegen.binding.InjectionAnnotations;
import dagger.internal.codegen.compileroption.CompilerOptions;
import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
import dagger.internal.codegen.langmodel.Accessibility;
import dagger.internal.codegen.langmodel.DaggerElements;
import dagger.internal.codegen.langmodel.DaggerTypes;
import dagger.spi.model.Scope;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind;

/**
 * A {@linkplain ValidationReport validator} for {@link Inject}-annotated elements and the types
 * that contain them.
 */
@Singleton
public final class InjectValidator implements ClearableCache {
  private final DaggerTypes types;
  private final DaggerElements elements;
  private final CompilerOptions compilerOptions;
  private final DependencyRequestValidator dependencyRequestValidator;
  private final Optional privateAndStaticInjectionDiagnosticKind;
  private final InjectionAnnotations injectionAnnotations;
  private final KotlinMetadataUtil metadataUtil;
  private final Map> reports = new HashMap<>();

  @Inject
  InjectValidator(
      DaggerTypes types,
      DaggerElements elements,
      DependencyRequestValidator dependencyRequestValidator,
      CompilerOptions compilerOptions,
      InjectionAnnotations injectionAnnotations,
      KotlinMetadataUtil metadataUtil) {
    this(
        types,
        elements,
        compilerOptions,
        dependencyRequestValidator,
        Optional.empty(),
        injectionAnnotations,
        metadataUtil);
  }

  private InjectValidator(
      DaggerTypes types,
      DaggerElements elements,
      CompilerOptions compilerOptions,
      DependencyRequestValidator dependencyRequestValidator,
      Optional privateAndStaticInjectionDiagnosticKind,
      InjectionAnnotations injectionAnnotations,
      KotlinMetadataUtil metadataUtil) {
    this.types = types;
    this.elements = elements;
    this.compilerOptions = compilerOptions;
    this.dependencyRequestValidator = dependencyRequestValidator;
    this.privateAndStaticInjectionDiagnosticKind = privateAndStaticInjectionDiagnosticKind;
    this.injectionAnnotations = injectionAnnotations;
    this.metadataUtil = metadataUtil;
  }

  @Override
  public void clearCache() {
    reports.clear();
  }

  /**
   * Returns a new validator that performs the same validation as this one, but is strict about
   * rejecting optionally-specified JSR 330 behavior that Dagger doesn't support (unless {@code
   * -Adagger.ignorePrivateAndStaticInjectionForComponent=enabled} was set in the javac options).
   */
  public InjectValidator whenGeneratingCode() {
    return compilerOptions.ignorePrivateAndStaticInjectionForComponent()
        ? this
        : new InjectValidator(
            types,
            elements,
            compilerOptions,
            dependencyRequestValidator,
            Optional.of(Diagnostic.Kind.ERROR),
            injectionAnnotations,
            metadataUtil);
  }

  public ValidationReport validateConstructor(ExecutableElement constructorElement) {
    return reentrantComputeIfAbsent(reports, constructorElement, this::validateConstructorUncached);
  }

  private ValidationReport validateConstructorUncached(
      ExecutableElement constructorElement) {
    ValidationReport.Builder builder =
        ValidationReport.about(asType(constructorElement.getEnclosingElement()));

    if (isAnnotationPresent(constructorElement, Inject.class)
        && isAnnotationPresent(constructorElement, AssistedInject.class)) {
      builder.addError("Constructors cannot be annotated with both @Inject and @AssistedInject");
    }

    Class injectAnnotation =
        isAnnotationPresent(constructorElement, Inject.class) ? Inject.class : AssistedInject.class;

    if (constructorElement.getModifiers().contains(PRIVATE)) {
      builder.addError(
          "Dagger does not support injection into private constructors", constructorElement);
    }

    for (AnnotationMirror qualifier : injectionAnnotations.getQualifiers(constructorElement)) {
      builder.addError(
          String.format(
              "@Qualifier annotations are not allowed on @%s constructors",
              injectAnnotation.getSimpleName()),
          constructorElement,
          qualifier);
    }

    String scopeErrorMsg =
        String.format(
            "@Scope annotations are not allowed on @%s constructors",
            injectAnnotation.getSimpleName());

    if (injectAnnotation == Inject.class) {
      scopeErrorMsg += "; annotate the class instead";
    }

    for (Scope scope : scopesOf(constructorElement)) {
      builder.addError(scopeErrorMsg, constructorElement, scope.scopeAnnotation().java());
    }

    for (VariableElement parameter : constructorElement.getParameters()) {
      validateDependencyRequest(builder, parameter);
    }

    if (throwsCheckedExceptions(constructorElement)) {
      builder.addItem(
          String.format(
              "Dagger does not support checked exceptions on @%s constructors",
              injectAnnotation.getSimpleName()),
          privateMemberDiagnosticKind(),
          constructorElement);
    }

    checkInjectIntoPrivateClass(constructorElement, builder);

    TypeElement enclosingElement =
        MoreElements.asType(constructorElement.getEnclosingElement());

    Set typeModifiers = enclosingElement.getModifiers();
    if (typeModifiers.contains(ABSTRACT)) {
      builder.addError(
          String.format(
              "@%s is nonsense on the constructor of an abstract class",
              injectAnnotation.getSimpleName()),
          constructorElement);
    }

    if (enclosingElement.getNestingKind().isNested()
        && !typeModifiers.contains(STATIC)) {
      builder.addError(
          String.format(
              "@%s constructors are invalid on inner classes. "
                  + "Did you mean to make the class static?",
              injectAnnotation.getSimpleName()),
          constructorElement);
    }

    // This is computationally expensive, but probably preferable to a giant index
    ImmutableSet injectConstructors =
        ImmutableSet.builder()
            .addAll(injectedConstructors(enclosingElement))
            .addAll(assistedInjectedConstructors(enclosingElement))
            .build();

    if (injectConstructors.size() > 1) {
      builder.addError("Types may only contain one injected constructor", constructorElement);
    }

    ImmutableSet scopes = scopesOf(enclosingElement);
    if (injectAnnotation == AssistedInject.class) {
      for (Scope scope : scopes) {
        builder.addError(
            "A type with an @AssistedInject-annotated constructor cannot be scoped",
            enclosingElement,
            scope.scopeAnnotation().java());
      }
    } else if (scopes.size() > 1) {
      for (Scope scope : scopes) {
        builder.addError(
            "A single binding may not declare more than one @Scope",
            enclosingElement,
            scope.scopeAnnotation().java());
      }
    }

    return builder.build();
  }

  private ValidationReport validateField(VariableElement fieldElement) {
    ValidationReport.Builder builder = ValidationReport.about(fieldElement);
    Set modifiers = fieldElement.getModifiers();
    if (modifiers.contains(FINAL)) {
      builder.addError("@Inject fields may not be final", fieldElement);
    }

    if (modifiers.contains(PRIVATE)) {
      builder.addItem(
          "Dagger does not support injection into private fields",
          privateMemberDiagnosticKind(),
          fieldElement);
    }

    if (modifiers.contains(STATIC)) {
      builder.addItem(
          "Dagger does not support injection into static fields",
          staticMemberDiagnosticKind(),
          fieldElement);
    }

    validateDependencyRequest(builder, fieldElement);

    return builder.build();
  }

  private ValidationReport validateMethod(ExecutableElement methodElement) {
    ValidationReport.Builder builder = ValidationReport.about(methodElement);
    Set modifiers = methodElement.getModifiers();
    if (modifiers.contains(ABSTRACT)) {
      builder.addError("Methods with @Inject may not be abstract", methodElement);
    }

    if (modifiers.contains(PRIVATE)) {
      builder.addItem(
          "Dagger does not support injection into private methods",
          privateMemberDiagnosticKind(),
          methodElement);
    }

    if (modifiers.contains(STATIC)) {
      builder.addItem(
          "Dagger does not support injection into static methods",
          staticMemberDiagnosticKind(),
          methodElement);
    }

    if (!methodElement.getTypeParameters().isEmpty()) {
      builder.addError("Methods with @Inject may not declare type parameters", methodElement);
    }

    if (!methodElement.getThrownTypes().isEmpty()) {
      builder.addError("Methods with @Inject may not throw checked exceptions. "
          + "Please wrap your exceptions in a RuntimeException instead.", methodElement);
    }

    for (VariableElement parameter : methodElement.getParameters()) {
      validateDependencyRequest(builder, parameter);
    }

    return builder.build();
  }

  private void validateDependencyRequest(
      ValidationReport.Builder builder, VariableElement parameter) {
    dependencyRequestValidator.validateDependencyRequest(builder, parameter, parameter.asType());
    dependencyRequestValidator.checkNotProducer(builder, parameter);
  }

  public ValidationReport validateMembersInjectionType(TypeElement typeElement) {
    // TODO(beder): This element might not be currently compiled, so this error message could be
    // left in limbo. Find an appropriate way to display the error message in that case.
    ValidationReport.Builder builder = ValidationReport.about(typeElement);
    boolean hasInjectedMembers = false;
    for (VariableElement element : ElementFilter.fieldsIn(typeElement.getEnclosedElements())) {
      if (MoreElements.isAnnotationPresent(element, Inject.class)) {
        hasInjectedMembers = true;
        ValidationReport report = validateField(element);
        if (!report.isClean()) {
          builder.addSubreport(report);
        }
      }
    }
    for (ExecutableElement element : ElementFilter.methodsIn(typeElement.getEnclosedElements())) {
      if (MoreElements.isAnnotationPresent(element, Inject.class)) {
        hasInjectedMembers = true;
        ValidationReport report = validateMethod(element);
        if (!report.isClean()) {
          builder.addSubreport(report);
        }
      }
    }

    if (hasInjectedMembers) {
      checkInjectIntoPrivateClass(typeElement, builder);
      checkInjectIntoKotlinObject(typeElement, builder);
    }
    TypeMirror superclass = typeElement.getSuperclass();
    if (!superclass.getKind().equals(TypeKind.NONE)) {
      ValidationReport report = validateType(MoreTypes.asTypeElement(superclass));
      if (!report.isClean()) {
        builder.addSubreport(report);
      }
    }
    return builder.build();
  }

  public ValidationReport validateType(TypeElement typeElement) {
    ValidationReport.Builder builder = ValidationReport.about(typeElement);
    ValidationReport membersInjectionReport =
        validateMembersInjectionType(typeElement);
    if (!membersInjectionReport.isClean()) {
      builder.addSubreport(membersInjectionReport);
    }
    for (ExecutableElement element :
        ElementFilter.constructorsIn(typeElement.getEnclosedElements())) {
      if (isAnnotationPresent(element, Inject.class)
          || isAnnotationPresent(element, AssistedInject.class)) {
        ValidationReport report = validateConstructor(element);
        if (!report.isClean()) {
          builder.addSubreport(report);
        }
      }
    }
    return builder.build();
  }

  public boolean isValidType(TypeMirror type) {
    if (!type.getKind().equals(DECLARED)) {
      return true;
    }
    return validateType(MoreTypes.asTypeElement(type)).isClean();
  }

  /** Returns true if the given method element declares a checked exception. */
  private boolean throwsCheckedExceptions(ExecutableElement methodElement) {
    TypeMirror runtimeExceptionType = elements.getTypeElement(RuntimeException.class).asType();
    TypeMirror errorType = elements.getTypeElement(Error.class).asType();
    for (TypeMirror thrownType : methodElement.getThrownTypes()) {
      if (!types.isSubtype(thrownType, runtimeExceptionType)
          && !types.isSubtype(thrownType, errorType)) {
        return true;
      }
    }
    return false;
  }

  private void checkInjectIntoPrivateClass(
      Element element, ValidationReport.Builder builder) {
    if (!Accessibility.isElementAccessibleFromOwnPackage(
        DaggerElements.closestEnclosingTypeElement(element))) {
      builder.addItem(
          "Dagger does not support injection into private classes",
          privateMemberDiagnosticKind(),
          element);
    }
  }

  private void checkInjectIntoKotlinObject(
      TypeElement element, ValidationReport.Builder builder) {
    if (metadataUtil.isObjectClass(element) || metadataUtil.isCompanionObjectClass(element)) {
      builder.addError("Dagger does not support injection into Kotlin objects", element);
    }
  }

  private Diagnostic.Kind privateMemberDiagnosticKind() {
    return privateAndStaticInjectionDiagnosticKind.orElse(
        compilerOptions.privateMemberValidationKind());
  }

  private Diagnostic.Kind staticMemberDiagnosticKind() {
    return privateAndStaticInjectionDiagnosticKind.orElse(
        compilerOptions.staticMemberValidationKind());
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy