Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to append a method to existing class using annotation processing in java / kotlin?

I'm new to annotation processing and code generation. I want to find out how can I perform such operation like appending new method to existing class. Here is an example of what I want to do:

Assume that we have a class with with custom annotations like this one:

class SourceClass {
    @CustomAnnotation
    fun annotatedFun1(vararg argument: Any) {
        //Do something
    }

    @CustomAnnotation
    fun annotatedFun2(vararg argument: Any) {
        //Do something
    }

    fun someOtherFun() {
        //Do something
    }
}

And the result I want to get - extended copy of that class:

class ResultClass {
    fun hasFunWithName(name: String): Boolean {
        return (name in arrayOf("annotatedFun1", "annotatedFun2"))
    }

    fun callFunByName(name: String, vararg arguments: Any) {
        when (name) {
            "annotatedFun1" -> annotatedFun1(*arguments)
            "annotatedFun2" -> annotatedFun2(*arguments)
        }
    }

    fun annotatedFun1(vararg argument: Any) {
        //Do something
    }

    fun annotatedFun2(vararg argument: Any) {
        //Do something
    }

    fun someOtherFun() {
        //Do something
    }
}

I've already found out how to create annotation processor. I'm looking for a method to save all existing fields, properties and methods in source class and to append a few more methods to it.

If it is possible to modify class without creating new one - it would be perfect, but in all tutorials only new classes are created and I didn't find any example where all contents of source class are being copied to another one.

Please, do not advise to use reflection. I need this for android and so reflection is not the option cause of resources cost. I'm looking for compile-time solution.

It is required for custom script language implemented in app and should be used to simplify wrapper classes structure. When this job is done directly in code - it looks awful when such method count exceeds 20 per class.

like image 621
Andrei Vinogradov Avatar asked May 10 '18 13:05

Andrei Vinogradov


People also ask

How do you add an annotation to a class in Java?

Select the Java element to which you want to add an annotation. Click Add annotation. You see in the Java class the @Stateless annotation is added to your code. You see in the Java class that your annotation is added to the code.

What is annotation processing Kotlin?

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.

Can we apply two annotation together?

It is also possible to use multiple annotations on the same declaration: @Author(name = "Jane Doe") @EBook class MyClass { ... } If the annotations have the same type, then this is called a repeating annotation: @Author(name = "Jane Doe") @Author(name = "John Smith") class MyClass { ... }

Do Java annotations work in Kotlin?

It is the preferred way of declaring source code metadata as it standardize format and function, and is closely coupled. Kotlin supports Java-like annotations and in this tutorial, we will discuss custom annotations and how they can be used through reflection. Defining an annotation can be as simple as this.


3 Answers

Here is a good example of Java Annotation Processing I recently worked with. It's an implementation of @Immutable annotation.

Check out ByteBuddy or Kotlin Poet to understand how additional code generation works.

For Kotlin you do almost the same, check this manual for Kotlin-specific steps.

like image 177
Sergei Rybalkin Avatar answered Nov 15 '22 21:11

Sergei Rybalkin


With Kotlin, you can use extension functions and that is the recommended way of adding new functionality to existing classes that you don't control. https://kotlinlang.org/docs/reference/extensions.html

like image 20
Chirdeep Tomar Avatar answered Nov 15 '22 21:11

Chirdeep Tomar


You may be abel to follow the pattern used by Project Lombok. See How does lombok work? or the source code for details.

Another option would be to write a new class that extends your source class:

class ResultClass : SourceClass {
    fun hasFunWithName(name: String): Boolean {
        return (name in arrayOf("annotatedFun1", "annotatedFun2"))
    }

    fun callFunByName(name: String, vararg arguments: Any) {
        when (name) {
            "annotatedFun1" -> annotatedFun1(*arguments)
            "annotatedFun2" -> annotatedFun2(*arguments)
        }
    }
}

Or perhaps use composition instead and implemnent cover methods for all the public methods in SourceClass.

If you are not tied to doing this using annotation processing, you could use a separate piece of custom code to process the source code files before compiling. Maybe use a regular expression like /@CustomAnnotation\s+.*fun (\w+)\s*\(([^)]*)\)/gm (Test on Regex101) to find the annotated methods.

like image 40
Rangi Keen Avatar answered Nov 15 '22 20:11

Rangi Keen