Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java static metaprogramming

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

like image 438
Victor Nazarov Avatar asked Dec 05 '11 15:12

Victor Nazarov


People also ask

What is static metaprogramming?

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.

Does Java have metaprogramming?

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.

What is metaprogramming in Java?

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.

What is metaprogramming example?

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.


2 Answers

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.

like image 67
AlexR Avatar answered Oct 06 '22 11:10

AlexR


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

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!

Using the Annotation Processor

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 :)

like image 34
Thorben Kuck Avatar answered Oct 06 '22 13:10

Thorben Kuck