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;
}
}
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.
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;
}
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.
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.