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

dagger.hilt.processor.internal.Processors Maven / Gradle / Ivy

There is a newer version: 2.52
Show newest version
/*
 * Copyright (C) 2019 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.hilt.processor.internal;

import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
import static com.google.common.base.Preconditions.checkNotNull;
import static dagger.hilt.processor.internal.kotlin.KotlinMetadataUtils.getMetadataUtil;
import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static javax.lang.model.element.Modifier.PUBLIC;

import androidx.room.compiler.processing.JavaPoetExtKt;
import androidx.room.compiler.processing.XAnnotation;
import androidx.room.compiler.processing.XAnnotationValue;
import androidx.room.compiler.processing.XConstructorElement;
import androidx.room.compiler.processing.XElement;
import androidx.room.compiler.processing.XExecutableElement;
import androidx.room.compiler.processing.XFiler.Mode;
import androidx.room.compiler.processing.XHasModifiers;
import androidx.room.compiler.processing.XProcessingEnv;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XTypeElement;
import com.google.common.base.CaseFormat;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import dagger.internal.codegen.xprocessing.XAnnotations;
import dagger.internal.codegen.xprocessing.XElements;
import dagger.internal.codegen.xprocessing.XTypes;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;

/** Static helper methods for writing a processor. */
public final class Processors {

  public static final String CONSTRUCTOR_NAME = "";

  public static final String STATIC_INITIALIZER_NAME = "";

  /** Generates the aggregating metadata class for an aggregating annotation. */
  public static void generateAggregatingClass(
      String aggregatingPackage,
      AnnotationSpec aggregatingAnnotation,
      XTypeElement originatingElement,
      Class generatorClass) {
    generateAggregatingClass(
        aggregatingPackage,
        aggregatingAnnotation,
        originatingElement,
        generatorClass,
        Mode.Isolating);
  }

  /** Generates the aggregating metadata class for an aggregating annotation. */
  public static void generateAggregatingClass(
      String aggregatingPackage,
      AnnotationSpec aggregatingAnnotation,
      XTypeElement originatingElement,
      Class generatorClass,
      Mode mode) {
    ClassName name =
        ClassName.get(aggregatingPackage, "_" + getFullEnclosedName(originatingElement));
    XProcessingEnv env = getProcessingEnv(originatingElement);
    TypeSpec.Builder builder =
        TypeSpec.classBuilder(name)
            .addModifiers(PUBLIC)
            .addAnnotation(aggregatingAnnotation)
            .addJavadoc("This class should only be referenced by generated code! ")
            .addJavadoc("This class aggregates information across multiple compilations.\n");
    JavaPoetExtKt.addOriginatingElement(builder, originatingElement);
    addGeneratedAnnotation(builder, env, generatorClass);

    env.getFiler().write(JavaFile.builder(name.packageName(), builder.build()).build(), mode);
  }

  /** Returns a map from {@link XAnnotation} attribute name to {@link XAnnotationValue}s */
  public static ImmutableMap getAnnotationValues(XAnnotation annotation) {
    ImmutableMap.Builder annotationMembers = ImmutableMap.builder();
    for (XAnnotationValue value : annotation.getAnnotationValues()) {
      annotationMembers.put(value.getName(), value);
    }
    return annotationMembers.build();
  }

  /** Returns a list of {@link XTypeElement}s for a class attribute on an annotation. */
  public static ImmutableList getAnnotationClassValues(
      XAnnotation annotation, String key) {
    ImmutableList values = XAnnotations.getAsTypeElementList(annotation, key);

    ProcessorErrors.checkState(
        values.size() >= 1,
        annotation.getTypeElement(),
        "@%s, '%s' class is invalid or missing: %s",
        annotation.getName(),
        key,
        XAnnotations.toStableString(annotation));

    return values;
  }

  public static ImmutableList getOptionalAnnotationClassValues(
      XAnnotation annotation, String key) {
    return getOptionalAnnotationValues(annotation, key).stream()
        .filter(XAnnotationValue::hasTypeValue)
        .map(
            annotationValue -> {
              try {
                return annotationValue.asType();
              } catch (TypeNotPresentException e) {
                // TODO(b/277367118): we may need a way to ignore error types in XProcessing.
                // TODO(b/278560196): we should throw ErrorTypeException and clean up broken tests.
                return null;
              }
            })
        .filter(Objects::nonNull)
        .map(XType::getTypeElement)
        .collect(toImmutableList());
  }

  private static ImmutableList getOptionalAnnotationValues(
      XAnnotation annotation, String key) {
    return annotation.getAnnotationValues().stream()
        .filter(annotationValue -> annotationValue.getName().equals(key))
        .collect(toOptional())
        .map(
            annotationValue ->
                (annotationValue.hasListValue()
                    ? ImmutableList.copyOf(annotationValue.asAnnotationValueList())
                    : ImmutableList.of(annotationValue)))
        .orElse(ImmutableList.of());
  }

  public static XTypeElement getTopLevelType(XElement originalElement) {
    checkNotNull(originalElement);
    for (XElement e = originalElement; e != null; e = e.getEnclosingElement()) {
      if (isTopLevel(e)) {
        return XElements.asTypeElement(e);
      }
    }
    throw new IllegalStateException(
        "Cannot find a top-level type for " + XElements.toStableString(originalElement));
  }

  public static boolean isTopLevel(XElement element) {
    return element.getEnclosingElement() == null;
  }

  /** Returns true if the given element has an annotation with the given class name. */
  public static boolean hasAnnotation(Element element, ClassName className) {
    return getAnnotationMirrorOptional(element, className).isPresent();
  }

  /** Returns true if the given element has an annotation that is an error kind. */
  public static boolean hasErrorTypeAnnotation(XElement element) {
    for (XAnnotation annotation : element.getAllAnnotations()) {
      if (annotation.getType().isError()) {
        return true;
      }
    }
    return false;
  }

  /**
   * Returns the annotation mirror from the given element that corresponds to the given class.
   *
   * @throws IllegalArgumentException if 2 or more annotations are found.
   * @return {@link Optional#empty()} if no annotation is found on the element.
   */
  static Optional getAnnotationMirrorOptional(
      Element element, ClassName className) {
    return element.getAnnotationMirrors().stream()
        .filter(mirror -> ClassName.get(mirror.getAnnotationType()).equals(className))
        .collect(toOptional());
  }

  /**
   * Returns the name of a class, including prefixing with enclosing class names. i.e. for inner
   * class Foo enclosed by Bar, returns Bar_Foo instead of just Foo
   */
  public static String getEnclosedName(ClassName name) {
    return Joiner.on('_').join(name.simpleNames());
  }

  /**
   * Returns an equivalent class name with the {@code .} (dots) used for inner classes replaced with
   * {@code _}.
   */
  public static ClassName getEnclosedClassName(ClassName className) {
    return ClassName.get(className.packageName(), getEnclosedName(className));
  }

  /**
   * Returns an equivalent class name with the {@code .} (dots) used for inner classes replaced with
   * {@code _}.
   */
  public static ClassName getEnclosedClassName(XTypeElement typeElement) {
    return getEnclosedClassName(typeElement.getClassName());
  }

  /**
   * Returns the fully qualified class name, with _ instead of . For elements that are not type
   * elements, this continues to append the simple name of elements. For example,
   * foo_bar_Outer_Inner_fooMethod.
   */
  public static String getFullEnclosedName(XElement element) {
    Preconditions.checkNotNull(element);
    String qualifiedName = "";
    while (element != null) {
      if (element.getEnclosingElement() == null) {
        qualifiedName =
            element.getClosestMemberContainer().asClassName().getCanonicalName() + qualifiedName;
      } else {
        // This check is needed to keep the name stable when compiled with jdk8 vs jdk11. jdk11
        // contains newly added "module" enclosing elements of packages, which adds an additional
        // "_" prefix to the name due to an empty module element compared with jdk8.
        if (!XElements.getSimpleName(element).isEmpty()) {
          qualifiedName = "." + XElements.getSimpleName(element) + qualifiedName;
        }
      }
      element = element.getEnclosingElement();
    }
    return qualifiedName.replace('.', '_');
  }

  /** Appends the given string to the end of the class name. */
  public static ClassName append(ClassName name, String suffix) {
    return name.peerClass(name.simpleName() + suffix);
  }

  /** Prepends the given string to the beginning of the class name. */
  public static ClassName prepend(ClassName name, String prefix) {
    return name.peerClass(prefix + name.simpleName());
  }

  /**
   * Removes the string {@code suffix} from the simple name of {@code type} and returns it.
   *
   * @throws BadInputException if the simple name of {@code type} does not end with {@code suffix}
   */
  public static ClassName removeNameSuffix(XTypeElement type, String suffix) {
    ClassName originalName = type.getClassName();
    String originalSimpleName = originalName.simpleName();
    ProcessorErrors.checkState(
        originalSimpleName.endsWith(suffix),
        type,
        "Name of type %s must end with '%s'",
        originalName,
        suffix);
    String withoutSuffix =
        originalSimpleName.substring(0, originalSimpleName.length() - suffix.length());
    return originalName.peerClass(withoutSuffix);
  }

  /** Returns {@code true} if element inherits directly or indirectly from the className. */
  public static boolean isAssignableFrom(XTypeElement element, ClassName className) {
    return isAssignableFromAnyOf(element, ImmutableSet.of(className));
  }

  /** Returns {@code true} if element inherits directly or indirectly from any of the classNames. */
  public static boolean isAssignableFromAnyOf(
      XTypeElement element, ImmutableSet classNames) {
    for (ClassName className : classNames) {
      if (element.getClassName().equals(className)) {
        return true;
      }
    }

    XType superClass = element.getSuperClass();
    // None type is returned if this is an interface or Object
    // Error type is returned for classes that are generated by this processor
    if (superClass != null && !superClass.isNone() && !superClass.isError()) {
      Preconditions.checkState(XTypes.isDeclared(superClass));
      if (isAssignableFromAnyOf(superClass.getTypeElement(), classNames)) {
        return true;
      }
    }

    for (XType iface : element.getSuperInterfaces()) {
      // Skip errors and keep looking. This is especially needed for classes generated by this
      // processor.
      if (iface.isError()) {
        continue;
      }
      Preconditions.checkState(
          XTypes.isDeclared(iface), "Interface type is %s", XTypes.getKindName(iface));
      if (isAssignableFromAnyOf(iface.getTypeElement(), classNames)) {
        return true;
      }
    }

    return false;
  }

  /** Returns MapKey annotated annotations found on an element. */
  public static ImmutableList getMapKeyAnnotations(XElement element) {
    // Normally, we wouldn't need to handle Kotlin metadata because map keys are typically used
    // only on methods. However, with @BindValueIntoMap, this can be used on fields so we need
    // to check annotations on the property as well, just like with qualifiers.
    return getMetadataUtil().getAnnotationsAnnotatedWith(element, ClassNames.MAP_KEY);
  }

  /** Returns Qualifier annotated annotations found on an element. */
  public static ImmutableList getQualifierAnnotations(XElement element) {
    return getMetadataUtil().getAnnotationsAnnotatedWith(element, ClassNames.QUALIFIER);
  }

  /** Returns Scope annotated annotations found on an element. */
  public static ImmutableList getScopeAnnotations(XElement element) {
    return ImmutableList.copyOf(
        element.getAnnotationsAnnotatedWith(ClassNames.SCOPE));
  }

  /**
   * Shortcut for converting from upper camel to lower camel case
   *
   * 

Example: "SomeString" => "someString" */ public static String upperToLowerCamel(String upperCamel) { return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, upperCamel); } /** @return copy of the given MethodSpec as {@link MethodSpec.Builder} with method body removed */ public static MethodSpec.Builder copyMethodSpecWithoutBody(MethodSpec methodSpec) { MethodSpec.Builder builder; if (methodSpec.isConstructor()) { // Constructors cannot have return types builder = MethodSpec.constructorBuilder(); } else { builder = MethodSpec.methodBuilder(methodSpec.name) .returns(methodSpec.returnType); } return builder .addAnnotations(methodSpec.annotations) .addModifiers(methodSpec.modifiers) .addParameters(methodSpec.parameters) .addExceptions(methodSpec.exceptions) .addJavadoc(methodSpec.javadoc.toString()) .addTypeVariables(methodSpec.typeVariables); } /** * Returns true if the given method is annotated with one of the annotations Dagger recognizes for * abstract methods (e.g. @Binds). */ public static boolean hasDaggerAbstractMethodAnnotation(XExecutableElement method) { return method.hasAnnotation(ClassNames.BINDS) || method.hasAnnotation(ClassNames.BINDS_OPTIONAL_OF) || method.hasAnnotation(ClassNames.MULTIBINDS) || method.hasAnnotation(ClassNames.CONTRIBUTES_ANDROID_INJECTOR); } public static boolean requiresModuleInstance(XTypeElement module) { // Binding methods that lack ABSTRACT or STATIC require module instantiation. // Required by Dagger. See b/31489617. return module.getDeclaredMethods().stream() .filter(Processors::isBindingMethod) .anyMatch(method -> !method.isAbstract() && !method.isStatic()) && !module.isKotlinObject(); } public static boolean hasVisibleEmptyConstructor(XTypeElement type) { List constructors = type.getConstructors(); return constructors.isEmpty() || constructors.stream() .filter(constructor -> constructor.getParameters().isEmpty()) .anyMatch( constructor -> !constructor.isPrivate() ); } private static boolean isBindingMethod(XExecutableElement method) { return method.hasAnnotation(ClassNames.PROVIDES) || method.hasAnnotation(ClassNames.BINDS) || method.hasAnnotation(ClassNames.BINDS_OPTIONAL_OF) || method.hasAnnotation(ClassNames.MULTIBINDS); } public static void addGeneratedAnnotation( TypeSpec.Builder typeSpecBuilder, XProcessingEnv env, Class generatorClass) { addGeneratedAnnotation(typeSpecBuilder, env, generatorClass.getName()); } public static void addGeneratedAnnotation( TypeSpec.Builder typeSpecBuilder, XProcessingEnv env, String generatorClass) { XTypeElement annotation = env.findGeneratedAnnotation(); if (annotation != null) { typeSpecBuilder.addAnnotation( AnnotationSpec.builder(annotation.getClassName()) .addMember("value", "$S", generatorClass) .build()); } } public static AnnotationSpec getOriginatingElementAnnotation(XTypeElement element) { TypeName rawType = rawTypeName(getTopLevelType(element).getClassName()); return AnnotationSpec.builder(ClassNames.ORIGINATING_ELEMENT) .addMember("topLevelClass", "$T.class", rawType) .build(); } /** * Returns the {@link TypeName} for the raw type of the given type name. If the argument isn't a * parameterized type, it returns the argument unchanged. */ public static TypeName rawTypeName(TypeName typeName) { return (typeName instanceof ParameterizedTypeName) ? ((ParameterizedTypeName) typeName).rawType : typeName; } public static Optional getOriginatingTestElement(XElement element) { XTypeElement topLevelType = getOriginatingTopLevelType(element); return topLevelType.hasAnnotation(ClassNames.HILT_ANDROID_TEST) ? Optional.of(topLevelType) : Optional.empty(); } private static XTypeElement getOriginatingTopLevelType(XElement element) { XTypeElement topLevelType = getTopLevelType(element); if (topLevelType.hasAnnotation(ClassNames.ORIGINATING_ELEMENT)) { return getOriginatingTopLevelType( XAnnotations.getAsTypeElement( topLevelType.getAnnotation(ClassNames.ORIGINATING_ELEMENT), "topLevelClass")); } return topLevelType; } public static boolean hasJavaPackagePrivateVisibility(XHasModifiers element) { return !element.isPrivate() && !element.isProtected() && !element.isInternal() && !element.isPublic(); } private Processors() {} }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy