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

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

The 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.binding;

import static androidx.room.compiler.processing.XElementKt.isConstructor;
import static androidx.room.compiler.processing.XElementKt.isField;
import static androidx.room.compiler.processing.XElementKt.isMethod;
import static androidx.room.compiler.processing.XElementKt.isMethodParameter;
import static androidx.room.compiler.processing.XElementKt.isTypeElement;
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.binding.SourceFiles.factoryNameForElement;
import static dagger.internal.codegen.binding.SourceFiles.memberInjectedFieldSignatureForVariable;
import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType;
import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.xprocessing.XElements.asField;
import static dagger.internal.codegen.xprocessing.XElements.asMethod;
import static dagger.internal.codegen.xprocessing.XElements.asMethodParameter;
import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement;

import androidx.room.compiler.processing.XAnnotation;
import androidx.room.compiler.processing.XConstructorElement;
import androidx.room.compiler.processing.XElement;
import androidx.room.compiler.processing.XExecutableElement;
import androidx.room.compiler.processing.XFieldElement;
import androidx.room.compiler.processing.XProcessingEnv;
import androidx.room.compiler.processing.XTypeElement;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.ClassName;
import dagger.internal.codegen.base.DaggerSuperficialValidation;
import dagger.internal.codegen.base.ElementFormatter;
import dagger.internal.codegen.compileroption.CompilerOptions;
import dagger.internal.codegen.javapoet.TypeNames;
import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
import dagger.internal.codegen.model.DaggerAnnotation;
import dagger.internal.codegen.model.Scope;
import dagger.internal.codegen.xprocessing.XAnnotations;
import java.util.Optional;
import java.util.stream.Stream;
import javax.inject.Inject;

/** Utilities relating to annotations defined in the {@code javax.inject} package. */
public final class InjectionAnnotations {
  private final XProcessingEnv processingEnv;
  private final KotlinMetadataUtil kotlinMetadataUtil;
  private final DaggerSuperficialValidation superficialValidation;
  private final CompilerOptions compilerOptions;

  @Inject
  InjectionAnnotations(
      XProcessingEnv processingEnv,
      KotlinMetadataUtil kotlinMetadataUtil,
      DaggerSuperficialValidation superficialValidation,
      CompilerOptions compilerOptions) {
    this.processingEnv = processingEnv;
    this.kotlinMetadataUtil = kotlinMetadataUtil;
    this.superficialValidation = superficialValidation;
    this.compilerOptions = compilerOptions;
  }

  /**
   * Returns the scope on the given element if it exists.
   *
   * 

The {@code ScopeMetadata} is used to avoid superficial validation on unnecessary * annotations. If the {@code ScopeMetadata} does not exist, then all annotations must be * superficially validated before we can determine if they are scopes or not. * * @throws IllegalArgumentException if the given element has more than one scope. */ public Optional getScope(XElement element) { return getScopes(element).stream().collect(toOptional()); } /** * Returns the scopes on the given element, or an empty set if none exist. * *

Note: Use {@link #getScope(XElement)} if the usage of the scope on the given element has * already been validated and known to be unique. This method should typically only be used in the * process of such validation. * *

The {@code ScopeMetadata} is used to avoid superficial validation on unnecessary * annotations. If the {@code ScopeMetadata} does not exist, then all annotations must be * superficially validated before we can determine if they are scopes or not. */ public ImmutableSet getScopes(XElement element) { superficialValidation.validateTypeOf(element); ImmutableSet scopes = getScopesWithMetadata(element).orElseGet(() -> getScopesWithoutMetadata(element)); // Fully validate each scope to ensure its values are also valid. scopes.stream() .map(scope -> scope.scopeAnnotation().xprocessing()) .forEach(scope -> superficialValidation.validateAnnotationOf(element, scope)); return scopes; } private ImmutableSet getScopesWithoutMetadata(XElement element) { // Validate the annotation types before we check for @Scope, otherwise the @Scope // annotation may appear to be missing (b/213880825). superficialValidation.validateAnnotationTypesOf(element); return element.getAllAnnotations().stream() .filter(InjectionAnnotations::hasScopeAnnotation) .map(DaggerAnnotation::from) .map(Scope::scope) .collect(toImmutableSet()); } private Optional> getScopesWithMetadata(XElement element) { Optional scopeMetadata = getScopeMetadata(element); if (!scopeMetadata.isPresent()) { return Optional.empty(); } String scopeName = scopeMetadata.get().getAsString("value"); if (scopeName.isEmpty()) { return Optional.of(ImmutableSet.of()); } ImmutableList scopeAnnotations = element.getAllAnnotations().stream() .filter( annotation -> scopeName.contentEquals( annotation.getType().getTypeElement().getQualifiedName())) .collect(toImmutableList()); checkState( scopeAnnotations.size() == 1, "Expected %s to have a scope annotation for %s but found: %s", ElementFormatter.elementToString(element), scopeName, scopeAnnotations.stream().map(XAnnotations::toStableString).collect(toImmutableList())); XAnnotation scopeAnnotation = getOnlyElement(scopeAnnotations); // Do superficial validation before we convert to a Scope, otherwise the @Scope annotation may // appear to be missing from the annotation if it's no longer on the classpath. superficialValidation.validateAnnotationTypeOf(element, scopeAnnotation); // If strictSuperficialValidation is disabled, then we fall back to the old behavior where // we may potentially miss a scope rather than report an exception. if (compilerOptions.strictSuperficialValidation()) { return Optional.of(ImmutableSet.of(Scope.scope(DaggerAnnotation.from(scopeAnnotation)))); } else { return Scope.isScope(DaggerAnnotation.from(scopeAnnotation)) ? Optional.of(ImmutableSet.of(Scope.scope(DaggerAnnotation.from(scopeAnnotation)))) : Optional.empty(); } } private Optional getScopeMetadata(XElement element) { return getGeneratedNameForScopeMetadata(element) .flatMap(factoryName -> Optional.ofNullable(processingEnv.findTypeElement(factoryName))) .flatMap(factory -> Optional.ofNullable(factory.getAnnotation(TypeNames.SCOPE_METADATA))); } private Optional getGeneratedNameForScopeMetadata(XElement element) { // Currently, we only support ScopeMetadata for inject-constructor types and provides methods. if (isTypeElement(element)) { return asTypeElement(element).getConstructors().stream() .filter(InjectionAnnotations::hasInjectOrAssistedInjectAnnotation) .findFirst() .map(SourceFiles::factoryNameForElement); } else if (isMethod(element) && element.hasAnnotation(TypeNames.PROVIDES)) { return Optional.of(factoryNameForElement(asMethod(element))); } return Optional.empty(); } /** * Returns the qualifier on the given element if it exists. * *

The {@code QualifierMetadata} is used to avoid superficial validation on unnecessary * annotations. If the {@code QualifierMetadata} does not exist, then all annotations must be * superficially validated before we can determine if they are qualifiers or not. * * @throws IllegalArgumentException if the given element has more than one qualifier. */ public Optional getQualifier(XElement element) { checkNotNull(element); ImmutableSet qualifierAnnotations = getQualifiers(element); switch (qualifierAnnotations.size()) { case 0: return Optional.empty(); case 1: return Optional.of(getOnlyElement(qualifierAnnotations)); default: throw new IllegalArgumentException( element + " was annotated with more than one @Qualifier annotation"); } } /* * Returns the qualifiers on the given element, or an empty set if none exist. * *

The {@code QualifierMetadata} is used to avoid superficial validation on unnecessary * annotations. If the {@code QualifierMetadata} does not exist, then all annotations must be * superficially validated before we can determine if they are qualifiers or not. */ public ImmutableSet getQualifiers(XElement element) { superficialValidation.validateTypeOf(element); ImmutableSet qualifiers = getQualifiersWithMetadata(element) .orElseGet(() -> getQualifiersWithoutMetadata(element)); if (isField(element)) { XFieldElement field = asField(element); // static/top-level injected fields are not supported, // no need to get qualifier from kotlin metadata if (!field.isStatic() && isTypeElement(field.getEnclosingElement()) && hasInjectAnnotation(field) && kotlinMetadataUtil.hasMetadata(field)) { qualifiers = Stream.concat(qualifiers.stream(), getQualifiersForKotlinProperty(field).stream()) .map(DaggerAnnotation::from) // Wrap in DaggerAnnotation to deduplicate .distinct() .map(DaggerAnnotation::xprocessing) .collect(toImmutableSet()); } } // Fully validate each qualifier to ensure its values are also valid. qualifiers.forEach(qualifier -> superficialValidation.validateAnnotationOf(element, qualifier)); return qualifiers; } private ImmutableSet getQualifiersWithoutMetadata(XElement element) { // Validate the annotation types before we check for @Qualifier, otherwise the // @Qualifier annotation may appear to be missing (b/213880825). superficialValidation.validateAnnotationTypesOf(element); return element.getAllAnnotations().stream() .filter(InjectionAnnotations::hasQualifierAnnotation) .collect(toImmutableSet()); } private Optional> getQualifiersWithMetadata(XElement element) { Optional qualifierMetadata = getQualifierMetadata(element); if (!qualifierMetadata.isPresent()) { return Optional.empty(); } ImmutableSet qualifierNames = ImmutableSet.copyOf(qualifierMetadata.get().getAsStringList("value")); if (qualifierNames.isEmpty()) { return Optional.of(ImmutableSet.of()); } ImmutableSet qualifierAnnotations = element.getAllAnnotations().stream() .filter( annotation -> qualifierNames.contains( annotation.getType().getTypeElement().getQualifiedName())) .collect(toImmutableSet()); if (qualifierAnnotations.isEmpty()) { return Optional.of(ImmutableSet.of()); } // We should be guaranteed that there's exactly one qualifier since the existance of // @QualifierMetadata means that this element has already been processed and multiple // qualifiers would have been caught already. XAnnotation qualifierAnnotation = getOnlyElement(qualifierAnnotations); // Ensure the annotation type is superficially valid before we check for @Qualifier, otherwise // the @Qualifier marker may appear to be missing from the annotation (b/213880825). superficialValidation.validateAnnotationTypeOf(element, qualifierAnnotation); if (compilerOptions.strictSuperficialValidation()) { return Optional.of(ImmutableSet.of(qualifierAnnotation)); } else { // If strictSuperficialValidation is disabled, then we fall back to the old behavior where // we may potentially miss a qualifier rather than report an exception. return hasQualifierAnnotation(qualifierAnnotation) ? Optional.of(ImmutableSet.of(qualifierAnnotation)) : Optional.empty(); } } /** * Returns {@code QualifierMetadata} annotation. * *

Currently, {@code QualifierMetadata} is only associated with inject constructor parameters, * inject fields, inject method parameters, provide methods, and provide method parameters. */ private Optional getQualifierMetadata(XElement element) { return getGeneratedNameForQualifierMetadata(element) .flatMap(name -> Optional.ofNullable(processingEnv.findTypeElement(name))) .flatMap(type -> Optional.ofNullable(type.getAnnotation(TypeNames.QUALIFIER_METADATA))); } private Optional getGeneratedNameForQualifierMetadata(XElement element) { // Currently we only support @QualifierMetadata for @Inject fields, @Inject method parameters, // @Inject constructor parameters, @Provides methods, and @Provides method parameters. if (isField(element) && hasInjectAnnotation(element)) { return Optional.of(membersInjectorNameForType(closestEnclosingTypeElement(element))); } else if (isMethod(element) && element.hasAnnotation(TypeNames.PROVIDES)) { return Optional.of(factoryNameForElement(asMethod(element))); } else if (isMethodParameter(element)) { XExecutableElement executableElement = asMethodParameter(element).getEnclosingElement(); if (isConstructor(executableElement) && hasInjectOrAssistedInjectAnnotation(executableElement)) { return Optional.of(factoryNameForElement(executableElement)); } if (isMethod(executableElement) && hasInjectAnnotation(executableElement)) { return Optional.of(membersInjectorNameForType(closestEnclosingTypeElement(element))); } if (isMethod(executableElement) && executableElement.hasAnnotation(TypeNames.PROVIDES)) { return Optional.of(factoryNameForElement(executableElement)); } } return Optional.empty(); } /** Returns the constructors in {@code type} that are annotated with {@link Inject}. */ public static ImmutableSet injectedConstructors(XTypeElement type) { return type.getConstructors().stream() .filter(InjectionAnnotations::hasInjectAnnotation) .collect(toImmutableSet()); } private static boolean hasQualifierAnnotation(XAnnotation annotation) { return annotation .getType() .getTypeElement() .hasAnyAnnotation(TypeNames.QUALIFIER, TypeNames.QUALIFIER_JAVAX); } private static boolean hasScopeAnnotation(XAnnotation annotation) { return annotation .getType() .getTypeElement() .hasAnyAnnotation(TypeNames.SCOPE, TypeNames.SCOPE_JAVAX); } /** Returns true if the given element is annotated with {@link Inject}. */ public static boolean hasInjectAnnotation(XElement element) { return element.hasAnyAnnotation(TypeNames.INJECT, TypeNames.INJECT_JAVAX); } private static boolean hasInjectOrAssistedInjectAnnotation(XElement element) { return element.hasAnyAnnotation( TypeNames.INJECT, TypeNames.INJECT_JAVAX, TypeNames.ASSISTED_INJECT); } /** * Gets the qualifiers annotation of a Kotlin Property. Finding these annotations involve finding * the synthetic method for annotations as described by the Kotlin metadata or finding the * corresponding MembersInjector method for the field, which also contains the qualifier * annotation. */ private ImmutableSet getQualifiersForKotlinProperty(XFieldElement field) { // TODO(bcorso): Consider moving this to KotlinMetadataUtil if (kotlinMetadataUtil.isMissingSyntheticPropertyForAnnotations(field)) { // If we detect that the synthetic method for annotations is missing, possibly due to the // element being from a compiled class, then find the MembersInjector that was generated // for the enclosing class and extract the qualifier information from it. XTypeElement membersInjector = processingEnv.findTypeElement( membersInjectorNameForType(asTypeElement(field.getEnclosingElement()))); if (membersInjector != null) { String memberInjectedFieldSignature = memberInjectedFieldSignatureForVariable(field); // TODO(danysantiago): We have to iterate over all the injection methods for every qualifier // look up. Making this N^2 when looking through all the injected fields. :( return membersInjector.getDeclaredMethods().stream() .filter( method -> Optional.ofNullable(method.getAnnotation(TypeNames.INJECTED_FIELD_SIGNATURE)) .map(annotation -> annotation.getAsString("value")) .map(memberInjectedFieldSignature::equals) // If a method is not an @InjectedFieldSignature method then filter it out .orElse(false)) .collect(toOptional()) .map(this::getQualifiers) .orElseThrow( () -> new IllegalStateException( String.format( "No matching InjectedFieldSignature for %1$s. This likely means that " + "%1$s was compiled with an older, incompatible version of " + "Dagger. Please update all Dagger dependencies to the same " + "version.", memberInjectedFieldSignature))); } else { throw new IllegalStateException( "No MembersInjector found for " + field.getEnclosingElement()); } } else { return Stream.concat( kotlinMetadataUtil .getSyntheticPropertyAnnotations(field, TypeNames.QUALIFIER) .stream(), kotlinMetadataUtil .getSyntheticPropertyAnnotations(field, TypeNames.QUALIFIER_JAVAX) .stream()) .collect(toImmutableSet()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy