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

io.avaje.jsonb.generator.APContext Maven / Gradle / Ivy

package io.avaje.jsonb.generator;

import static java.util.function.Predicate.not;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import javax.annotation.processing.Filer;
import javax.annotation.processing.Generated;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;

/**
 * Utiliy Class that stores the {@link ProcessingEnvironment} and provides various helper methods
 */
@Generated("avaje-prism-generator")
public final class APContext {

  private static int jdkVersion;
  private static boolean previewEnabled;
  private static final ThreadLocal CTX = new ThreadLocal<>();

  private APContext() {}

  public static final class Ctx {
    private final ProcessingEnvironment processingEnv;
    private final Messager messager;
    private final Filer filer;
    private final Elements elementUtils;
    private final Types typeUtils;
    private ModuleElement module;

    public Ctx(ProcessingEnvironment processingEnv) {

      this.processingEnv = processingEnv;
      messager = processingEnv.getMessager();
      filer = processingEnv.getFiler();
      elementUtils = processingEnv.getElementUtils();
      typeUtils = processingEnv.getTypeUtils();
    }

    public Ctx(Messager messager, Filer filer, Elements elementUtils, Types typeUtils) {

      this.processingEnv = null;
      this.messager = messager;
      this.filer = filer;
      this.elementUtils = elementUtils;
      this.typeUtils = typeUtils;
    }
  }

  /**
   * Initialize the ThreadLocal containing the Processing Enviroment. this typically should be
   * called during the init phase of processing. Be sure to run the clear method at the last round
   * of processing
   *
   * @param processingEnv the current annotation processing enviroment
   */
  public static void init(ProcessingEnvironment processingEnv) {
    CTX.set(new Ctx(processingEnv));
    jdkVersion = processingEnv.getSourceVersion().ordinal();
    previewEnabled = jdkVersion >= 13 && initPreviewEnabled(processingEnv);
  }

  private static boolean initPreviewEnabled(ProcessingEnvironment processingEnv) {
    try {
      return (boolean)
          ProcessingEnvironment.class.getDeclaredMethod("isPreviewEnabled").invoke(processingEnv);
    } catch (final Throwable e) {
      return false;
    }
  }

  /**
   * Initialize the ThreadLocal containing the {@link ProcessingEnvironment}. Be sure to run the
   * clear method at the last round of processing
   *
   * @param context the current annotation processing enviroment
   * @param jdkVersion the JDK version number
   * @param preview whether preview features are enabled
   */
  public static void init(Ctx context, int jdkVersion, boolean preview) {
    CTX.set(context);
    jdkVersion = jdkVersion;
    previewEnabled = preview;
  }
  /** Clears the ThreadLocal containing the {@link ProcessingEnvironment}. */
  public static void clear() {
    CTX.remove();
  }

  /**
   * Returns the source version that any generated source and class files should conform to
   *
   * @return the source version as an int
   */
  public static int jdkVersion() {
    return jdkVersion;
  }

  /**
   * Returns whether {@code --preview-enabled} has been added to compiler flags.
   *
   * @return true if preview features are enabled
   */
  public static boolean previewEnabled() {
    return previewEnabled;
  }

  /**
   * Prints an error at the location of the element.
   *
   * @param e the element to use as a position hint
   * @param msg the message, or an empty string if none
   * @param args {@code String#format} arguments
   */
  public static void logError(Element e, String msg, Object... args) {
    messager().printMessage(Diagnostic.Kind.ERROR, String.format(msg, args), e);
  }

  /**
   * Prints an error.
   *
   * @param msg the message, or an empty string if none
   * @param args {@code String#format} arguments
   */
  public static void logError(String msg, Object... args) {
    messager().printMessage(Diagnostic.Kind.ERROR, String.format(msg, args));
  }

  /**
   * Prints an warning at the location of the element.
   *
   * @param e the element to use as a position hint
   * @param msg the message, or an empty string if none
   * @param args {@code String#format} arguments
   */
  public static void logWarn(Element e, String msg, Object... args) {
    messager().printMessage(Diagnostic.Kind.WARNING, String.format(msg, args), e);
  }

  /**
   * Prints a warning.
   *
   * @param msg the message, or an empty string if none
   * @param args {@code String#format} arguments
   */
  public static void logWarn(String msg, Object... args) {
    messager().printMessage(Diagnostic.Kind.WARNING, String.format(msg, args));
  }

  /**
   * Prints a note.
   *
   * @param msg the message, or an empty string if none
   * @param args {@code String#format} arguments
   */
  public static void logNote(Element e, String msg, Object... args) {
    messager().printMessage(Diagnostic.Kind.NOTE, String.format(msg, args), e);
  }

  /**
   * Prints a note at the location of the element.
   *
   * @param e the element to use as a position hint
   * @param msg the message, or an empty string if none
   * @param args {@code String#format} arguments
   */
  public static void logNote(String msg, Object... args) {
    messager().printMessage(Diagnostic.Kind.NOTE, String.format(msg, args));
  }

  /**
   * Returns the elements annotated with the given annotation interface.
   *
   * @param round RoundEnviroment to extract the elements
   * @param annotationFQN the fqn of the annotation
   * @return the elements annotated with the given annotation interface,or an empty set if there are
   *     none
   */
  public static Set elementsAnnotatedWith(
      RoundEnvironment round, String annotationFQN) {

    return Optional.ofNullable(typeElement(annotationFQN))
        .map(round::getElementsAnnotatedWith)
        .orElse(Set.of());
  }

  /**
   * Create a file writer for the given class name.
   *
   * @param name canonical (fully qualified) name of the principal class or interface being declared
   *     in this file or a package name followed by {@code ".package-info"} for a package
   *     information file
   * @param originatingElements class, interface, package, or module elements causally associated
   *     with the creation of this file, may be elided or {@code null}
   * @return a JavaFileObject to write the new source file
   */
  public static JavaFileObject createSourceFile(CharSequence name, Element... originatingElements)
      throws IOException {
    return filer().createSourceFile(name, originatingElements);
  }

  /**
   * Returns a type element given its canonical name.
   *
   * @param name the canonical name
   * @return the named type element, or null if no type element can be uniquely determined
   */
  public static TypeElement typeElement(String name) {
    return elements().getTypeElement(name);
  }

  /**
   * Returns the element corresponding to a type.The type may be a DeclaredType or
   * TypeVariable.Returns null if the type is not one with a corresponding element.
   *
   * @param t the type to map to an element
   * @return the element corresponding to the given type
   */
  public static TypeElement asTypeElement(TypeMirror t) {

    return (TypeElement) types().asElement(t);
  }

  /**
   * Get current {@link ProcessingEnvironment}
   *
   * @return the enviroment
   */
  public static ProcessingEnvironment processingEnv() {
    return CTX.get().processingEnv;
  }

  /**
   * Get current {@link Filer} from the {@link ProcessingEnvironment}
   *
   * @return the filer
   */
  public static Filer filer() {
    return CTX.get().filer;
  }

  /**
   * Get current {@link Elements} from the {@link ProcessingEnvironment}
   *
   * @return the filer
   */
  public static Elements elements() {
    return CTX.get().elementUtils;
  }

  /**
   * Get current {@link Messager} from the {@link ProcessingEnvironment}
   *
   * @return the messager
   */
  public static Messager messager() {
    return CTX.get().messager;
  }

  /**
   * Get current {@link Types} from the {@link ProcessingEnvironment}
   *
   * @return the types
   */
  public static Types types() {
    return CTX.get().typeUtils;
  }

  /**
   * Determine whether the first type can be assigned to the second
   *
   * @param type string type to check
   * @param superType the type that should be assignable to.
   * @return true if type can be assinged to supertype
   */
  public static boolean isAssignable(String type, String superType) {
    return type.equals(superType) || isAssignable(typeElement(type), superType);
  }

  /**
   * Determine whether the first type can be assigned to the second
   *
   * @param type type to check
   * @param superType the type that should be assignable to.
   * @return true if type can be assinged to supertype
   */
  public static boolean isAssignable(TypeElement type, String superType) {
    return Optional.ofNullable(type).stream()
        .flatMap(APContext::superTypes)
        .anyMatch(superType::equals);
  }

  private static Stream superTypes(TypeElement element) {
    final var types = types();
    return types.directSupertypes(element.asType()).stream()
        .filter(type -> !type.toString().contains("java.lang.Object"))
        .map(superType -> (TypeElement) types.asElement(superType))
        .flatMap(e -> Stream.concat(superTypes(e), Stream.of(e)))
        .map(Object::toString);
  }

  /**
   * Discover the {@link ModuleElement} for the project being processed and set in the context.
   *
   * @param annotations the annotation interfaces requested to be processed
   * @param roundEnv environment for information about the current and prior round
   */
  public static void setProjectModuleElement(
      Set annotations, RoundEnvironment roundEnv) {
    if (CTX.get().module == null) {
      CTX.get().module =
          annotations.stream()
              .map(roundEnv::getElementsAnnotatedWith)
              .filter(not(Collection::isEmpty))
              .findAny()
              .map(s -> s.iterator().next())
              .map(elements()::getModuleOf)
              .orElse(null);
    }
  }

  /**
   * Retrieve the project's {@link ModuleElement}. {@code setProjectModuleElement} must be called
   * before this.
   *
   * @return the {@link ModuleElement} associated with the current project
   */
  public static ModuleElement getProjectModuleElement() {
    return CTX.get().module;
  }

  /**
   * Gets a {@link BufferedReader} for the project's {@code module-info.java} source file.
   *
   * 

Calling {@link ModuleElement}'s {@code getDirectives()} method has a chance of making * compilation fail in certain situations. Therefore, manually parsing {@code module-info.java} * seems to be the safest way to get module information. * * @return * @throws IOException if unable to read the module-info */ public static BufferedReader getModuleInfoReader() throws IOException { var inputStream = filer() .getResource(StandardLocation.SOURCE_PATH, "", "module-info.java") .toUri() .toURL() .openStream(); return new BufferedReader(new InputStreamReader(inputStream)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy