Search code examples
javaannotation-processing

JSR269 annotation processing getElementsAnnotatedWith() return all the annotated elements each loop and cannot distinguish it belongs to which type


Recently, I have been intersted with the JSR-269 annotation processing and I want to write a lib to eliminate some boilerplate code with it, such as json processing. I really generate the code, then, however, I encounter a fatal problem and spent a lot of time but could not slove it.

The problem is, the RoundEnvironment.getElementsAnnotatedWith() method alway return all the elements annotated with the annotation, I cannot tistinguish which one comes from a specific class. Maybe the problem isn' t clear. I show you the code below.

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface JsonObject {
}



@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface JsonField {
  String value() default "";
}



@JsonObject
public class Name {
  @JsonField("last") public  String last;
  @JsonField("first") public String first;
}



@JsonObject
public class User {
  @JsonField("age") public int age;
  @JsonField("name") public String name;
  @JsonField("sex") public boolean sex;
}

the fisrt 2 are the annotation, JsonObject indicates that the annotated type is an JsonObject and JsonField indicates that the annotated field is an json field.

the latter 2 are the sample POJO class that I want to gennerate json-parse code.

in the Processor class, the AbstractProcessor.process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) method, when I call roundEnv.roundEnv.getElementsAnnotatedWith(JsonField.class) each type(Name, User) in a loop, the return result is all the json field, in the sample above, the result is ["last", "first", "age", "name", "sex"]. In this situatin, I cannot distinguish which field belongs to which POJO.

May be the words cannot explain what I mean. Here is what I do in the process method.

// the set[Name, User]
Set<? extends Element> jsonObjects = roundEnv.getElementsAnnotatedWith(JsonObject.class);
for (Element jsonObject : jsonObjects) {
  // the set[last, first, age, name, sex], **THIS IS THE PROBLEM**
  Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(JsonField.class);
  // other stuff...
}

Feel free to ask me anything I didn' t mention or not clear. Any suggestion is appreciated, Thanks in advance!


Solution

  • You can build a collection of your json object elements by calling the getElementsAnnotatedWith(JsonField.class) method and filering the result based on the annotation of the enclosing element.

    Here is a complete example (using runtime annotation processing for simplicity):

    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    @SupportedAnnotationTypes("*")
    public class ElementFilterProcessor extends AbstractProcessor {
    
        @Retention(RetentionPolicy.RUNTIME)
        public static @interface JsonObject {}
    
        @Retention(RetentionPolicy.RUNTIME)
        public static @interface JsonField { String value(); }
    
        @JsonObject
        public class Name {
            @JsonField("last") public  String last;
            @JsonField("first") public String first;
        }
    
        @JsonObject
        public class User {
            @JsonField("age") public int age;
            @JsonField("name") public String name;
            @JsonField("sex") public boolean sex;
        }
    
        public static void main(String[] args) {
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            final JavaCompiler.CompilationTask task = compiler.getTask(
                    null,
                    null,
                    null,
                    null,
                    Collections.singleton(ElementFilterProcessor.class.getName()),
                    Collections.EMPTY_SET);
            task.setProcessors(Collections.singleton(new ElementFilterProcessor()));
            task.call();
        }
    
        @Override
        public boolean process(
                final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
            if (roundEnv.processingOver()) {
                return true;
            }
            final Set<? extends Element> jsonFields =
                    roundEnv.getElementsAnnotatedWith(JsonField.class);
            final Map<Element, List<Element>> jsonObjects = new HashMap<>();
            for (final Element element : jsonFields) {
                final Element classElement = element.getEnclosingElement();
                if (classElement.getAnnotation(JsonObject.class) != null) {
                    List<Element> list = jsonObjects.get(classElement);
                    if (list == null) {
                        list = new ArrayList<>();
                        jsonObjects.put(classElement, list);
                    }
                    list.add(element);
                }
            }
            System.out.println(jsonObjects);
            return false;
        }
    }
    

    Output:

    {stackoverflow.annotation.ElementFilterProcessor.User=[age, name, sex], stackoverflow.annotation.ElementFilterProcessor.Name=[last, first]}
    

    I'd also recommend to rather consider using a third-party library for mapping Java objects to JSON. For example, the Jackson processor