1

Even though my question origins from annotation processing, my question is more Java annotation related.

I was writing some code until I realised I didn't know a good way to implement something.

The program uses annotation-processing, I'm trying to get the value of multiple JAX-RS annotations, let's take @PathParam and @QueryParam as example. Both annotations have the abstract method called value()


The following piece of code is an example of how I do not want to write it. I'd have to do this for each JAX-RS annotation.

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

    for(Element element : roundEnv.getElementsAnnotatedWith(PathParam.class)) {
        PathParam parameter = element.getAnnotation(PathParam.class);
        String value = parameter.value();

        // Process data & more program code.
    }

    for(Element element : roundEnv.getElementsAnnotatedWith(QueryParam.class)) {
        QueryParam parameter = element.getAnnotation(QueryParam.class);
        String value = parameter.value();

        // Process data & more program code.
    }

    // Etc... do the same for other JAX-RS annotations.

    return true;
}

I know with abstract classes you can do the following:

abstract class Animal { 
    abstract String name();
}

class Dog extends Animal {
    public String name() {
        return "Dog";
    }
}

class Cat extends Animal {
    public String name() {
        return "Cat";
    }
}

Animal animal = new Cat();
System.out.println(animal.name()); // Prints 'Cat'

animal = new Dog();
System.out.println(animal.name()); // Prints 'Dog'

But I'm not sure how to accomplish a similar thing using annotation since there is no superclass which it can be cast to. I'm imagining it should be something along the lines of this:

ArrayList<Class<? extends Annotation>> annotationsToCheck = 
        new ArrayList<>(Arrays.asList(PathParam.class, QueryParam.class));

for(Class<? extends Annotation> annotationToCheck : annotationsToCheck) {
    for(Element element : roundEnv.getElementsAnnotatedWith(annotationToCheck)) {

        // Somehow cast it to something so that the value() method can be accessed

        // Process data & more program code.

    }

}

I feel like I'm close but I just can't quite put my finger on it. Is there a good way to solve my problem?

Mark
  • 5,089
  • 2
  • 20
  • 31
  • Sorry, but I don't see any Java `interface` declarations in that code. *Hint:* [`@Override`](https://docs.oracle.com/javase/8/docs/api/java/lang/Override.html) is an **annotation**, not an interface. [`List`](https://docs.oracle.com/javase/8/docs/api/java/util/List.html) would be an example of an interface. – Andreas Oct 12 '18 at 20:23
  • The `@PathParam` and `@QueryParam` of `javax.ws.rs` are interfaces, if I misunderstood please let me know. – Mark Oct 12 '18 at 20:26
  • [What's the difference between interface and @interface in java?](https://stackoverflow.com/q/918393/5221149) – Andreas Oct 12 '18 at 20:28
  • Oops, I'll update my question. Thanks. – Mark Oct 12 '18 at 20:30
  • Did you try [`getElementsAnnotatedWithAny​(Set.of(PathParam.class, QueryParam.class))`](https://docs.oracle.com/javase/9/docs/api/javax/annotation/processing/RoundEnvironment.html#getElementsAnnotatedWithAny-java.util.Set-)? – Andreas Oct 12 '18 at 20:42
  • Good suggestion! However even if I were to upgrade to Java 9 I'd still be stuck with the `Set extends Element>` which that function returns. I still cannot access the `value()` method without casting/checking if it's `@PathParam` or `@QueryParam`. – Mark Oct 12 '18 at 20:55
  • Of course you cannot access `value()` without casting first, because as far as Java is concerned, the `Element` may have both annotations at the same time, with different value, and those values have different meaning depending on which annotation it's on, so you first have to identify the annotation. Without know the particular annotation, how can you interpret the `value()` result correctly? – Andreas Oct 12 '18 at 20:59
  • Yes, however if you look at the last code block I'm looping through all elements annotated with `@PathParam` first, then `@QueryParam`. Since it's getting all `Element` objects with annotation `annotationToCheck` you know which annotation is currently being used to get the elements. My question was if there was any way to write it in a way that's sort of generic so I would be able to call the `value()` method of the `element` variable of either annotation without casting them. – Mark Oct 12 '18 at 21:11

1 Answers1

0

In Java 9+, no casting is needed:

for (Element element : roundEnv.getElementsAnnotatedWithAny​(Set.of(PathParam.class,
                                                                   QueryParam.class))) {
    PathParam pathParam = element.getAnnotation(PathParam.class);
    if (pathParam != null) {
        String value = pathParam.value();
        // Process @PathParam value
    }
    QueryParam queryParam = element.getAnnotation(QueryParam.class);
    if (queryParam != null) {
        String value = queryParam.value();
        // Process @QueryParam value
    }
}

Or, if you only expect one of them:

for (Element element : roundEnv.getElementsAnnotatedWithAny​(Set.of(PathParam.class,
                                                                   QueryParam.class))) {
    String value = null;
    PathParam pathParam = null;
    QueryParam queryParam = null;
    if ((pathParam = element.getAnnotation(PathParam.class)) != null) {
        value = pathParam.value();
    } else if ((queryParam = element.getAnnotation(QueryParam.class)) != null) {
        value = queryParam.value();
    }
    // Process value. Exactly one of pathParam and queryParam will be non-null
}
Andreas
  • 154,647
  • 11
  • 152
  • 247