1

I'd like to write an annotation processor that scans for annotations on the classpath.

The idea is something like this:

  • Main library
    • Processor implementation which looks up @Foo annotations from Dependency A and Dependency B and generates a class based on both of them.
  • Dependency A
    • Depends on Base
    • Declares @Foo(someParam=Bar.class) public class A {...}
  • Dependency B
    • Depends on Base
    • Declares annotation @Foo(someParam=Baz.class) public class B {...}
  • Base
    • Declares public @interface Foo{...}.

Is this possible? Is there maybe a better way to do it?

  • 1
    Do you mean: Dep A and dep B both declare `public @interface Foo {}` in different packages? In the same package? Neither declared a new `@interface` named `Foo`; instead both have some class or field or whatnot annotated with `@Foo`, which comes from a common source? Your question is unclear. – rzwitserloot Aug 20 '21 at 12:38
  • 1
    I'm saying that Dep A and Dep B both declare a class annotated with `@Foo`. The declaration of `Foo` is in another module. I've updated the original question, PTAL. – user1143968 Aug 20 '21 at 13:34

1 Answers1

1

Yes, that is in fact the most basic and simple mode that you'd use an annotation processor in.

Any given annotation processor registers which annotations it is interested in. You can choose to say "*", in which case your annotation processor is handed every generated class model (that's the object representing a class in flight; after it has been parsed, but before it has been written to disk). More usually, you choose one or a few annotations, in which case you get notified only with those models that were annotated with one of these annotations:

Base project

MyAnno.java

package com.foo;

@Target(ElementType.TYPE) //[1]
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnno {}

MyProcessor.java

@SupportedAnnotationTypes("com.foo.MyAnno") // [2]
class MyProcessor extends AbstractProcessor {

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

    for (TypeElement annoType : annos) {
      Set<? extends Element> annotatedElems = round.getElementsAnnotatedWith(annoType);
      for (Element elem : annotatedElems) hello(elem);
    }
    return false;
  }

  private void hello(Element elem) {
    // 'elem' can represent packages, classes, methods, fields, etc.
    // because you constrainted the anno to only be allowed on types,
    // it'd have to be a type (class or interface), so we can cast..

    TypeElement typeElem = (TypeElement) elem;
    // do whatever you want with it, here.
}

[1] this says that this annotation can only be put on types. So, for example, not on methods (trying to write @MyAnno public String foo() {} would be a compiler error, stating that @MyAnno cannot be placed on methods).

[2] There's a chicken-and-egg thing going on with annotation processors: You're still compiling code, which means the class files that represent the code possibly do not exist yet, nor does the compiler have a good idea on what methods are even available in them. Therefore, you do not get to use the usual reflection library. Instead, you get model/element types, such as TypeElement, and a lot of it is 'stringly typed' where you refer to things in string form, such as here. The fact that you don't write @SupportedAnnotationTypes(MyAnno.class) is an intentional part of the AP design.

Really, just follow your garden variety 'how do I write an annotation processor' tutorial. They should cover this.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • My concern was that Dep A and Dep B will be compiled in separate processes (e.g. say they're separate maven modules). So when the main module is compiled, Dep A and Dep B have already been through annotation processing and these annotations would not be discovered. Is that not correct? – user1143968 Aug 23 '21 at 14:16
  • That's not possible - they can't be compiled unless the class file obtained by compiling the file containing 'public @interface MyAnno' - so ship that class file together with your AP. – rzwitserloot Aug 23 '21 at 21:39
  • That's not what I'm saying either. The AP and the @interface class are a common dependency of all other modules. I'm saying this is what happens (1) build AP and `@interface` (2) build Dep A (3) build Dep B (4) build main, which runs AP on Dep A and Dep B to discover annotations and generate code. I think this isn't possible though, right? The AP will run on step (2) and (3), but won't discover any annotations on step (4). – user1143968 Aug 24 '21 at 10:59
  • But why aren't you running the AP in step (2) and step (3)? That is how the AP system was designed. What you're trying to is not what it was designed for. Which makes 'is it supported' a much more tricky question, and even if the answer is yes (I think so, but I'm not quite sure what it is you are trying to do. For example if the annotation has RetentionPolicy.SOURCE, the answer is actually a simple and final 'no, not possible') - then a good answer would explain how, but also append '... but this doesnt sound like a good idea' with a few issues you're going to run into stapled on. – rzwitserloot Aug 24 '21 at 12:42
  • I want to generate a single class which is based on the annotation in Dep A and Dep B. So there's no point running AP on Dep A or B, it needs to run on main. So yeah, a good answer here is "no this isn't possible, instead you need to use reflection to find the annotations and generate the class that way". – user1143968 Aug 24 '21 at 16:23