I want to use an annotation processor to create an @Internal annotation which throws an error if an internal library class is used outside the library.
However, I'm not sure how to find where a class is referenced and throw an error then.
For example, with these 2 library classes:
//Implementation
@Internal
public class InternalClass implements SomeInterface {
}
//API class
public interface SomeInterface {
static SomeInterface getInstance() {
return new InternalClass();
}
}
public class Test {
public static void main(String[] args) {
InternalClass annotated = new InternalClass(); //ERROR
SomeInterface interface = SomeInterface.getInstance(); //FINE
}
}
How would I do that?
public class InternalProcessor extends AbstractProcessor {
@Override
public boolean process(final Set<? extends TypeElement> annotations,
final RoundEnvironment roundEnv) {
//What goes in here?
}
}
CodePudding user response:
Without hackery, this is rather difficult. You get access to a few things:
- You get 'mirror objects' that let you programmatically query signatures of stuff being compiled. Signatures is the class name, what it extends, the fields, the types of those fields, the constructors and the methods, and the types and names of the parameters, their throws clauses, their return types, and any annotations in any signature. But you do not get anything to inspect the body of that method.
- You get the ability to read arbitrary source files under compilation, as raw bytes.
- You get the ability to write arbitrary files out, including new source files (which then get processed and compiles in a 'rounds' system). Notably you don't get the ability to modify code in existing files.
- You get the ability to emit error and warning nodes on any signature or annotation.
Note how you just can't do it easily then: You want access to method bodies which you don't get. Your only option is to open all the source files, throw them through a java parser (which java doesn't ship with; you have various options but they all have downsides. javac
's is complete and open source but restrictively licensed and not all that easily traversed. They also change that API every other minute. ecj
's is complete, permissively licensed open source, and fast, with a much more stable API, but that API is deplorably designed and badly documented. All other java parsers are incomplete, in the sense that no serious project uses it and thus you should expect that various language constructs that javac and ecj can handle will crash such a parser. They also tend to have bad error recovery so one weird or new construct used anywhere in a source file means that entire file just errors out for you and there's no analysis you can perform.
To make matters even more complicated, there's the notion of incremental compilation. When you edit a file, and save, Just that file gets recompiled, the rest isn't touched. This is a big deal: I can modify a source file and start using '@Internal' marked stuff in it, and then run compilers (IDEs run them as you save), and that @Internal
annotation does not trigger anything because no code with it is being compiled (instead, code that uses a class so marked is being compiled). APs can just trigger on any compilation regardless of annotations, so you're not completely out of luck on this regard, but you need to process everything and scan all used types.
What you want to do is therefore a crazy complicated project that an expert java programmer will need more than a week to put together at least. Your job would entail:
- Immediately accepting massive slowdowns in compilation; parsing is 90% of any compile run and you need to do so at least twice, and you probably need to either accept even more slowdowns or build a complicated caching system.
- Figuring out a java parser (java is a very complicated language, hence so are these parsers), in order to analyse the code the way you want to.
- Then write this project once you've written a 'framework' that parses and caches the ASTs for everything that you see fly on by as an annotation processor.
Taking a step back, just preventing access to certain methods: Module systems such as OSGi and java's module-info
aim to do that too, it sounds like you want to badly reinvent some wheels. Maybe just use an existing module system?