I'm trying to write an annotation processor to insert methods and fields on a class... and the documentation is so sparse. I'm not getting far and I don't know if I'm approaching it correctly.
The processing environment provides a Filer
object which has handy methods for creating new source and class files. Those work fine but then I tried to figure out how read the existing source files, and all it provides is "getResource". So in my Processor implementation I've done this:
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { try { for (TypeElement te : annotations) { for (Element element : roundEnv.getElementsAnnotatedWith(te)) { FileObject in_file = processingEnv.getFiler().getResource( StandardLocation.SOURCE_PATH, "", element.asType().toString().replace(".", "/") + ".java"); FileObject out_file = processingEnv.getFiler().getResource( StandardLocation.SOURCE_OUTPUT, "", element.asType().toString().replace(".", "/") + ".java"); //if (out_file.getLastModified() >= in_file.getLastModified()) continue; CharSequence data = in_file.getCharContent(false); data = transform(data); // run the macro processor JavaFileObject out_file2 = processingEnv.getFiler().createSourceFile( element.asType().toString(), element); Writer w = out_file2.openWriter(); w.append(data); w.close(); } } } catch (Exception e) { e.printStackTrace(); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage()); } return true; }
My first quandary is I can't help feeling that element.asType().toString().replace(".", "/") + ".java"
(to get the qualified type name and convert it into a package and source file path) is not a nice way to approach the problem. The rest of the API is so over-engineered but there doesn't seem to be a handy method for retrieving the original source code.
The real problem is that then the compiler gets spontaneously upset by the second source file in the output directory ("error: duplicate class") and now I'm stuck.
I've already written the rest of this -- a macro lexer and parser and whatnot for calculating some data and inserting the field values and methods -- but it operates as a initial step outside the compiler. Except for the fact that the original files cannot have a .java extension (to prevent the compiler seeing them), this works nicely. Then I heard that annotations can do code generation, which I assume will be more proper and convenient, but I can't find much guidance on it.
Annotation processing is a tool built into javac for scanning and processing annotations at compile time. It can create new source files; however, it can't modify existing ones. It's done in rounds. The first round starts when the compilation reaches the pre-compile phase.
Annotations provide information to a program at compile time or at runtime based on which the program can take further action. An annotation processor processes these annotations at compile time or runtime to provide functionality such as code generation, error checking, etc.
In the Java computer programming language, an annotation is a form of syntactic metadata that can be added to Java source code. Classes, methods, variables, parameters and Java packages may be annotated. Like Javadoc tags, Java annotations can be read from source files.
Annotation processing takes place at compile time (compile time = the time when the java compiler compiles your java source code). Annotation processing is a tool build in javac for scanning and processing annotations at compile time. You can register your own annotation processor for certain annotations.
The intention behind the annotation processor is to allow a developer to add new classes, not replace existing classes. That being said, there is a bug that allows you to add code to existing classes. Project Lombok has leveraged this to add getter and setter (among other things) to your compiled java classes.
The approach I have taken to 'replace' methods/fields is either extend from or delegate to the input class. This allows you to override/divert calls to the target class.
So if this is your input class:
InputImpl.java:
public class InputImpl implements Input{ public void foo(){ System.out.println("foo"); } public void bar(){ System.out.println("bar"); } }
You could generate the following to "replace" it:
InputReplacementImpl.java:
public class InputReplacementImpl implements Input{ private Input delegate; //setup delegate.... public void foo(){ System.out.println("foo replacement"); } public void bar(){ delegate.bar(); } }
This begs the question, how do you reference InputReplacementImpl
instead of InputImpl
. You can either generate some more code to perform the wrapping or simply call the constructor of the code expected to be generated.
I'm not really sure what your question is, but I hope this sheds some light on your issues.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With