Is it possible to get a hold of values associated with an enum from inside an annotation processor?

58 Views Asked by At

In the case below, I want to get the value of MyString.class since the @RendererType is of value TEXT on the field inside the annotation processor's execution.

This is my custom annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Renderer {

  RendererType value() default RendererType.TEXT;
}

which contains a RendererType enum whos definition is:

public enum RendererType {
  TEXT(new Class[] {MyString.class}),
  TEXTAREA(new Class[] {MyString.class}),
  CHECKBOX(new Class[] {MyBoolean.class});

  private final Class<?>[] supportedTypes;

  RendererType(Class<?>[] classes) {
    this.supportedTypes = classes;
  }

  public Class<?>[] getSupportedTypes() {
    return this.supportedTypes;
  }
}

Example usage of @Renderer on a field in a POJO would look like:

@Renderer(RendererType.CHECKBOX)
boolean old = true;

This is the annotation processor StringProcessor, for @Renderer annotation:

import com.company.annotation.metadata.Renderer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;

public class StringProcessor extends AbstractProcessor {

  private Messager messager;

  @Override
  public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    messager = processingEnv.getMessager();
  }

  @Override
  public boolean process(
      final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {

    for (TypeElement typeElement : annotations) {
        for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) {

            TypeMirror typeMirror = element.asType();

            ElementKind elementKind = element.getKind();

            if (elementKind.equals(ElementKind.FIELD)) {
                System.out.println("FIELD");
                // Do something to get `supportedTypes` associated with the enum value
            }
        }
    }

    return false;
  }

  @Override
  public Set<String> getSupportedAnnotationTypes() {
    final Set<String> annotations = new HashSet<>(Arrays.asList(Renderer.class.getName()));
    return annotations;
  }
}
1

There are 1 best solutions below

0
Slaw On BEST ANSWER

If your annotation processor has access to Renderer at compile-time, which I assume it does given your implementation of getSupportedAnnotationTypes, then the easiest approach is to use a class literal with AnnotatedConstruct#getAnnotation(Class). That will give you an instance of Renderer which you can then use like normal.

Example

Renderer.java
package com.example;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Renderer {

  RendererType value() default RendererType.TEXT;
}
RendererType.java
package com.example;

import java.util.List;

public enum RendererType {
  TEXT(String.class),
  TEXTAREA(String.class),
  CHECKBOX(Boolean.class, boolean.class);

  // using unmodifiable list because lists are generally better than
  // arrays and you no longer have to worry about returning a defensive
  // copy to avoid globally-scoped modifications
  private final List<Class<?>> supportedTypes;

  // vararg parameter makes declaring the constants a little cleaner
  RendererType(Class<?>... supportedTypes) {
    // List::of returns an unmodifiable list
    this.supportedTypes = List.of(supportedTypes);
  }

  public List<Class<?>> getSupportedTypes() {
    return supportedTypes;
  }
}

Note: If you still want to use Class<?>[] then make sure that getSupportedTypes() returns a copy of the array.

RendererProcessor.java
package com.example.processing;

import com.example.Renderer;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;

// replace source version as needed
@SupportedSourceVersion(SourceVersion.RELEASE_21)
// or override 'getSupportedAnnotationTypes'
@SupportedAnnotationTypes("com.example.Renderer")
public class RendererProcessor extends AbstractProcessor {

  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    for (Element element : roundEnv.getElementsAnnotatedWith(Renderer.class)) {
      // no need to verify element is field due to @Target meta-annotation
      Renderer renderer = element.getAnnotation(Renderer.class);
      List<Class<?>> supportedTypes = renderer.value().getSupportedTypes();
      // TODO: do something with 'supportedTypes'
    }
    return false;
  }
}

Note: If you override getSupportedAnnotationTypes then return Set.of(Renderer.class.getName()) can be the implementation.