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

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

/*
 * 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.common.collect.Iterables.getOnlyElement;
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 dagger.internal.codegen.binding.SourceFiles.factoryNameForElement;
import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement;
import static dagger.internal.codegen.xprocessing.XElements.getAnyAnnotation;
import static dagger.internal.codegen.xprocessing.XMethodElements.hasTypeParameters;
import static dagger.internal.codegen.xprocessing.XTypes.isSubtype;

import androidx.room.compiler.processing.XAnnotation;
import androidx.room.compiler.processing.XConstructorElement;
import androidx.room.compiler.processing.XElement;
import androidx.room.compiler.processing.XExecutableParameterElement;
import androidx.room.compiler.processing.XFieldElement;
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.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;
import dagger.internal.codegen.base.ClearableCache;
import dagger.internal.codegen.base.DaggerSuperficialValidation;
import dagger.internal.codegen.binding.InjectionAnnotations;
import dagger.internal.codegen.binding.MethodSignatureFormatter;
import dagger.internal.codegen.compileroption.CompilerOptions;
import dagger.internal.codegen.javapoet.TypeNames;
import dagger.internal.codegen.langmodel.Accessibility;
import dagger.internal.codegen.model.Scope;
import dagger.internal.codegen.xprocessing.XAnnotations;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.tools.Diagnostic;

/**
 * A {@linkplain ValidationReport validator} for {@link Inject}-annotated elements and the types
 * that contain them.
 */
@Singleton
public final class InjectValidator implements ClearableCache {

  private final XProcessingEnv processingEnv;
  private final DependencyRequestValidator dependencyRequestValidator;
  private final InjectionAnnotations injectionAnnotations;
  private final DaggerSuperficialValidation superficialValidation;
  private final MethodSignatureFormatter methodSignatureFormatter;
  private final InternalValidator validator;
  private final InternalValidator validatorWhenGeneratingCode;

  @Inject
  InjectValidator(
      XProcessingEnv processingEnv,
      DependencyRequestValidator dependencyRequestValidator,
      CompilerOptions compilerOptions,
      InjectionAnnotations injectionAnnotations,
      DaggerSuperficialValidation superficialValidation,
      MethodSignatureFormatter methodSignatureFormatter) {
    this.processingEnv = processingEnv;
    this.dependencyRequestValidator = dependencyRequestValidator;
    this.injectionAnnotations = injectionAnnotations;
    this.superficialValidation = superficialValidation;
    this.methodSignatureFormatter = methodSignatureFormatter;

    // When validating types that require a generated factory class we need to error on private and
    // static inject members even if the compiler options are set to not error.
    this.validatorWhenGeneratingCode =
        new InternalValidator(Diagnostic.Kind.ERROR, Diagnostic.Kind.ERROR);

    // When validating types that might not require a generated factory we can take the user flags
    // for private and static inject members into account, but try to reuse the existing one if the
    // diagnostic kinds are the same.
    this.validator =
        (compilerOptions.privateMemberValidationKind() == Diagnostic.Kind.ERROR
                && compilerOptions.staticMemberValidationKind() == Diagnostic.Kind.ERROR)
            ? validatorWhenGeneratingCode
            : new InternalValidator(
                compilerOptions.privateMemberValidationKind(),
                compilerOptions.staticMemberValidationKind());
  }

  @Override
  public void clearCache() {
    validator.clearCache();
    validatorWhenGeneratingCode.clearCache();
  }

  public ValidationReport validate(XTypeElement typeElement) {
    return validator.validate(typeElement);
  }

  public ValidationReport validateForMembersInjection(XTypeElement typeElement) {
    return validator.validateForMembersInjection(typeElement);
  }

  /**
   * Validates {@code typeElement} that requires a factory to be generated.
   *
   * 

In this case, the validator will have stricter validation for private and static injection * since the generated factory doesn't support those types. */ public ValidationReport validateWhenGeneratingCode(XTypeElement typeElement) { if (typeElement.getPackageName().startsWith("org.atinject.tck")) { // The Technology Compatibility Kit (TCK) package is a special package for testing the JSR330 // spec, which includes optional features like supporting static/private inject members. Even // though Dagger doesn't support this, we allow it for this one case for the test coverage // purposes. Use the normal validator which takes the user's compiler flags into account. return validator.validate(typeElement); } return validatorWhenGeneratingCode.validate(typeElement); } private final class InternalValidator { private final Diagnostic.Kind privateMemberDiagnosticKind; private final Diagnostic.Kind staticMemberDiagnosticKind; private final Map provisionReports = new HashMap<>(); private final Map membersInjectionReports = new HashMap<>(); InternalValidator( Diagnostic.Kind privateMemberDiagnosticKind, Diagnostic.Kind staticMemberDiagnosticKind) { this.privateMemberDiagnosticKind = privateMemberDiagnosticKind; this.staticMemberDiagnosticKind = staticMemberDiagnosticKind; } void clearCache() { provisionReports.clear(); membersInjectionReports.clear(); } ValidationReport validate(XTypeElement typeElement) { return reentrantComputeIfAbsent(provisionReports, typeElement, this::validateUncached); } private ValidationReport validateUncached(XTypeElement typeElement) { ValidationReport.Builder builder = ValidationReport.about(typeElement); builder.addSubreport(validateForMembersInjectionInternal(typeElement)); ImmutableSet injectConstructors = ImmutableSet.builder() .addAll(injectedConstructors(typeElement)) .addAll(assistedInjectedConstructors(typeElement)) .build(); switch (injectConstructors.size()) { case 0: break; // Nothing to validate. case 1: builder.addSubreport(validateConstructor(getOnlyElement(injectConstructors))); break; default: builder.addError( String.format( "Type %s may only contain one injected constructor. Found: %s", typeElement.getQualifiedName(), injectConstructors.stream() .map(methodSignatureFormatter::format) .collect(toImmutableList())), typeElement); } return builder.build(); } private ValidationReport validateConstructor(XConstructorElement constructorElement) { superficialValidation.validateTypeOf(constructorElement); ValidationReport.Builder builder = ValidationReport.about(constructorElement.getEnclosingElement()); if (InjectionAnnotations.hasInjectAnnotation(constructorElement) && constructorElement.hasAnnotation(TypeNames.ASSISTED_INJECT)) { builder.addError("Constructors cannot be annotated with both @Inject and @AssistedInject"); } ClassName injectAnnotation = getAnyAnnotation( constructorElement, TypeNames.INJECT, TypeNames.INJECT_JAVAX, TypeNames.ASSISTED_INJECT) .map(XAnnotations::getClassName) .get(); if (constructorElement.isPrivate()) { builder.addError( "Dagger does not support injection into private constructors", constructorElement); } // If this type has already been processed in a previous round or compilation unit then there // is no reason to recheck for invalid scope annotations since it's already been checked. // This allows us to skip superficial validation of constructor annotations in subsequent // compilations where the annotation types may no longer be on the classpath. if (!processedInPreviousRoundOrCompilationUnit(constructorElement)) { superficialValidation.validateAnnotationsOf(constructorElement); for (XAnnotation qualifier : injectionAnnotations.getQualifiers(constructorElement)) { builder.addError( String.format( "@Qualifier annotations are not allowed on @%s constructors", injectAnnotation.simpleName()), constructorElement, qualifier); } String scopeErrorMsg = String.format( "@Scope annotations are not allowed on @%s constructors", injectAnnotation.simpleName()); if (injectAnnotation.equals(TypeNames.INJECT) || injectAnnotation.equals(TypeNames.INJECT_JAVAX)) { scopeErrorMsg += "; annotate the class instead"; } for (Scope scope : injectionAnnotations.getScopes(constructorElement)) { builder.addError( scopeErrorMsg, constructorElement, scope.scopeAnnotation().xprocessing()); } } for (XExecutableParameterElement parameter : constructorElement.getParameters()) { superficialValidation.validateTypeOf(parameter); validateDependencyRequest(builder, parameter); } if (throwsCheckedExceptions(constructorElement)) { builder.addItem( String.format( "Dagger does not support checked exceptions on @%s constructors", injectAnnotation.simpleName()), privateMemberDiagnosticKind, constructorElement); } checkInjectIntoPrivateClass(constructorElement, builder); XTypeElement enclosingElement = constructorElement.getEnclosingElement(); if (enclosingElement.isAbstract()) { builder.addError( String.format( "@%s is nonsense on the constructor of an abstract class", injectAnnotation.simpleName()), constructorElement); } if (enclosingElement.isNested() && !enclosingElement.isStatic()) { builder.addError( String.format( "@%s constructors are invalid on inner classes. " + "Did you mean to make the class static?", injectAnnotation.simpleName()), constructorElement); } // Note: superficial validation of the annotations is done as part of getting the scopes. ImmutableSet scopes = injectionAnnotations.getScopes(constructorElement.getEnclosingElement()); if (injectAnnotation.equals(TypeNames.ASSISTED_INJECT)) { for (Scope scope : scopes) { builder.addError( "A type with an @AssistedInject-annotated constructor cannot be scoped", enclosingElement, scope.scopeAnnotation().xprocessing()); } } else if (scopes.size() > 1) { for (Scope scope : scopes) { builder.addError( "A single binding may not declare more than one @Scope", enclosingElement, scope.scopeAnnotation().xprocessing()); } } return builder.build(); } private ValidationReport validateField(XFieldElement fieldElement) { superficialValidation.validateTypeOf(fieldElement); ValidationReport.Builder builder = ValidationReport.about(fieldElement); if (fieldElement.isFinal()) { builder.addError("@Inject fields may not be final", fieldElement); } if (fieldElement.isPrivate()) { builder.addItem( "Dagger does not support injection into private fields", privateMemberDiagnosticKind, fieldElement); } if (fieldElement.isStatic()) { builder.addItem( "Dagger does not support injection into static fields", staticMemberDiagnosticKind, fieldElement); } if (fieldElement.isProtected() && fieldElement.getEnclosingElement().isFromKotlin() ) { builder.addError( "Dagger injector does not have access to kotlin protected fields", fieldElement); } validateDependencyRequest(builder, fieldElement); return builder.build(); } private ValidationReport validateMethod(XMethodElement methodElement) { superficialValidation.validateTypeOf(methodElement); ValidationReport.Builder builder = ValidationReport.about(methodElement); if (methodElement.isAbstract()) { builder.addError("Methods with @Inject may not be abstract", methodElement); } if (methodElement.isPrivate()) { builder.addItem( "Dagger does not support injection into private methods", privateMemberDiagnosticKind, methodElement); } if (methodElement.isStatic()) { builder.addItem( "Dagger does not support injection into static methods", staticMemberDiagnosticKind, methodElement); } // No need to resolve type parameters since we're only checking existence. if (hasTypeParameters(methodElement)) { builder.addError("Methods with @Inject may not declare type parameters", methodElement); } // No need to resolve thrown types since we're only checking existence. if (!methodElement.getThrownTypes().isEmpty()) { builder.addError( "Methods with @Inject may not throw checked exceptions. " + "Please wrap your exceptions in a RuntimeException instead.", methodElement); } for (XExecutableParameterElement parameter : methodElement.getParameters()) { superficialValidation.validateTypeOf(parameter); validateDependencyRequest(builder, parameter); } return builder.build(); } private void validateDependencyRequest( ValidationReport.Builder builder, XVariableElement parameter) { dependencyRequestValidator.validateDependencyRequest(builder, parameter, parameter.getType()); dependencyRequestValidator.checkNotProducer(builder, parameter); } public ValidationReport validateForMembersInjection(XTypeElement typeElement) { return !processedInPreviousRoundOrCompilationUnit(typeElement) ? validate(typeElement) // validate everything : validateForMembersInjectionInternal(typeElement); // validate only inject members } private ValidationReport validateForMembersInjectionInternal(XTypeElement typeElement) { return reentrantComputeIfAbsent( membersInjectionReports, typeElement, this::validateForMembersInjectionInternalUncached); } private ValidationReport validateForMembersInjectionInternalUncached(XTypeElement typeElement) { superficialValidation.validateTypeOf(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 (XFieldElement field : typeElement.getDeclaredFields()) { if (InjectionAnnotations.hasInjectAnnotation(field)) { hasInjectedMembers = true; ValidationReport report = validateField(field); if (!report.isClean()) { builder.addSubreport(report); } } } for (XMethodElement method : typeElement.getDeclaredMethods()) { if (InjectionAnnotations.hasInjectAnnotation(method)) { hasInjectedMembers = true; ValidationReport report = validateMethod(method); if (!report.isClean()) { builder.addSubreport(report); } } } if (hasInjectedMembers) { checkInjectIntoPrivateClass(typeElement, builder); checkInjectIntoKotlinObject(typeElement, builder); } Optional.ofNullable(typeElement.getSuperType()) .filter(supertype -> !supertype.getTypeName().equals(TypeName.OBJECT)) .ifPresent( supertype -> { superficialValidation.validateSuperTypeOf(typeElement); ValidationReport report = validateForMembersInjection(supertype.getTypeElement()); if (!report.isClean()) { builder.addSubreport(report); } }); return builder.build(); } /** Returns true if the given method element declares a checked exception. */ private boolean throwsCheckedExceptions(XConstructorElement constructorElement) { XType runtimeException = processingEnv.findType(TypeNames.RUNTIME_EXCEPTION); XType error = processingEnv.findType(TypeNames.ERROR); superficialValidation.validateThrownTypesOf(constructorElement); return !constructorElement.getThrownTypes().stream() .allMatch(type -> isSubtype(type, runtimeException) || isSubtype(type, error)); } private void checkInjectIntoPrivateClass(XElement element, ValidationReport.Builder builder) { if (!Accessibility.isElementAccessibleFromOwnPackage(closestEnclosingTypeElement(element))) { builder.addItem( "Dagger does not support injection into private classes", privateMemberDiagnosticKind, element); } } private void checkInjectIntoKotlinObject( XTypeElement element, ValidationReport.Builder builder) { if (element.isKotlinObject() || element.isCompanionObject()) { builder.addError("Dagger does not support injection into Kotlin objects", element); } } private boolean processedInPreviousRoundOrCompilationUnit( XConstructorElement injectConstructor) { return processingEnv.findTypeElement(factoryNameForElement(injectConstructor)) != null; } private boolean processedInPreviousRoundOrCompilationUnit(XTypeElement membersInjectedType) { return processingEnv.findTypeElement(membersInjectorNameForType(membersInjectedType)) != null; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy