I'd like to implement annotation processor that will generate new class based on existing "prototype" class.
import java.util.List
@MyAnnotation
class MySuperClassPrototype {
static MySuperClassPrototype createInstance() {
return new MySuperClassPrototype();
}
}
As a result of code below. The following new source file (compilation unit) will be generated:
import java.util.List
class MySuperClass {
static MySuperClass createInstance() {
return new MySuperClass();
}
public void specialAddedMethod() {
/*...*/
}
}
I'd like to copy all top-level import statements and static members and not static members of prototype-class. I've moved pretty far with Compiler Tree API (com.sun.source.tree). I can print out Tree data-type while substituting new class name for old. But there are problems that seems pretty hard.
If I get Tree.Kind.IDENTIFIER in the tree, how can I find what actual class it references. I need to replace all occurrences of MySuperClassPrototype identifier with MySuperClass identifier, and than print out whole tree.
Is it feasible?
Similarly I need to filter out @MyAnnotation annotation, and again it is represented with Tree.Kind.IDENTIFIER or Tree.Kind.MEMBER_SELECT.
How can I find out actual annotation class that is referenced by this identifier?
And another problem is printing out tree. If I use toString method I got decent result, but constructors are printed as methods with "<init>" name instead of methods with the same name as it's class, so I need to manually print every kind of Tree node.
You can see code I've come with here
Static metaprogramming means doing that work at compile-time, and typically modifying or adding to the program based on that work. Static Metaprogramming would allow developers to enhance the features of a class, a method, or a type by adding any kind of code at static time.
You might think that Java, because it is so static and so verbose, couldn't have too many metaprogramming features. But that is so not the case. It has annotations. It has a reflection package.
annotation package, just tell the Java compiler how the annotations are to be treated, including the element types to be annotated, retention policies, and default values. Metaprogramming is writing programs that manipulate other programs or themselves based on metadata.
MetaProgramming gives Ruby the ability to open and modify classes, create methods on the fly and much more. A few examples of metaprogramming in Ruby are: Adding a new method to Ruby's native classes or to classes that have been declared beforehand. Using send to invoke a method by name programmatically.
Yes, it is possible and I know at least 2 ways.
First, "traditional" way is to write ant task/maven plugin/just command line java utility that scans given file path and calls for each class something like Class.forName(className).getAnnotations(MyAnnotation.class)
. If this is not null discover class using reflection and do what you need.
Other way is a little bit more difficult but more powerful.
You can implement your own Processor
(that implements javax.annotation.processing.Processor
or even better extends javax.annotation.processing.AbstractProcessor
.
Your processor will just have to be placed to the compiler
classpath and it will run automatically when compiler runs. You can even configure your IDE (e.g. Eclipse) to run your processor. It is a kind of extension to java compiler. So, every time eclipse builds your project it runs the processor and creates all new classes according to new annotations you have added.
Please take a look on this project as a reference.
8 Years and not yet answered. Because of that, i will try to answer it, to your satisfaction.
I fill furthermore concentrate on the static part of the question.
TL;DR:
You will not find copy and paste code in this answer.
Is it feasible?
Yes, absolutely.
How can I find out actual annotation class that is referenced by this identifier?
You will have to use the RoundEnvironment within an Annotation Processor to get the TypeElement.
Static metaprogramming (which you asked for) is metaprogramming done at compile time. By Kontrast: Dynamic metaprogramming is metaprogramming done at run time. And metaprogramming it self is the design of programs, that handle other programs as data.
Pfeh, a lot to take in. If you are interested in this topic, a more or less good source for that is wikipedia.
Your target would be, to generate a class at compile time. For run time, this would be done with something like cglib. But, since you choose static (and for all the right reasons), i will not explain this.
The concept you are looking for is the annotation processor. The link is a link to Baeldung, where they do exactly, what you are looking for, only with the builder pattern in mind. You will love to hear, that this scenario is highly encouraged and easy to do with the annotation processor API. It even allows you, to generate code, which again is passed to the same or another annotation processor, without you doing anything.
Before jumping right in, try to google yourself about "Java Annotation Processing". There are a lot of good sources out there, which will help you. To much, to list here. Just note, that coding in an annotation processor is different than coding normally. Not a huge difference, but the classes you are working on are not yet created. So keep this in mind and don't get discouraged!
Your basic annotation processor would look something like this:
@SupportedAnnotationTypes("package.of.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class BuilderProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
// First let's find all annotated elements
Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(MyAnnotation.class);
// Handle all the annotated classes
return false;
}
}
The AutoService Annotation is used, to dynamically register your annotation processor. It comes from an external source, just so you don't wonder, why this code won't compile.
In the handle all annotated classes
part, you have the annotated Elements (which are the annotated classes). You now would have to verify, that they are classes and not interfaces or other annotations. This is because @Target(ElementType.Type)
aims at any type, which includes interfaces and annotations. Furthermore, you would want to verify, that anything you require is present, or print an error to the compiler using the Messager.
If you print an error here (for example), you will stop compiling and the error will be seen in most modern IDEs. It can be reached by calling roundEnv.getMessager()
Afterwards you can generate a new class and write it to the input of the compiler, as a .java file. This can be done by using the Filer.
An answer in StackOverflow really does no justice to this topic. I highly recommend looking at the Baeldung example and trying to uncover things from there. This API is as old as Java 6, but still not that greatly used. I encourage you, the reader, to try it out for yourself :)
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