Using TreeTranslator to rename functions not working for Kotlin

Multi tool use
Multi tool use


Using TreeTranslator to rename functions not working for Kotlin



I am trying to rename a method in a Java interface and a function in a Kotlin interface during building according to AST (Abstract Syntax Tree) rewriting. For this question we ignore the implications that renaming a method/function brings for invocations. To find the method/function to rename I am using a custom annotation and annotation processor. I have it working for the Java interface by following these instructions.



I created a new project with three modules. The app module, annotation module and annotation processor module.



The app module is an Android App and contains two separate Java and Kotlin interface files with one annotated method/function each.



RenameJava.java


package nl.peperzaken.renametest;

import nl.peperzaken.renameannotation.Rename;

public interface RenameJava {
@Rename
void methodToRename();
}



RenameKotlin.kt


package nl.peperzaken.renametest

import nl.peperzaken.renameannotation.Rename

interface RenameKotlin {
@Rename
fun functionToRename()
}



The annotation module is a Java Library that only contains the @Rename annotation and we specify to only allow it on functions and we say it may only be visible in the source code.


@Rename



Rename.kt


package nl.peperzaken.renameannotation

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
annotation class Rename



The annotation processor module is a Java Library that only contains the processor that iterates the elements that have the annotation and do transformations on them.



RenameProcessor.kt


package nl.peperzaken.renameprocessor

import com.google.auto.service.AutoService
import com.sun.source.util.Trees
import com.sun.tools.javac.processing.JavacProcessingEnvironment
import com.sun.tools.javac.tree.JCTree
import com.sun.tools.javac.tree.TreeTranslator
import com.sun.tools.javac.util.Names
import nl.peperzaken.renameannotation.Rename
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.TypeElement
import javax.tools.Diagnostic

@AutoService(Processor::class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("nl.peperzaken.renameannotation.Rename")
class RenameProcessor : AbstractProcessor() {

private lateinit var trees: Trees
private lateinit var names: Names

private val visitor = object : TreeTranslator() {
override fun visitMethodDef(jcMethodDecl: JCTree.JCMethodDecl) {
super.visitMethodDef(jcMethodDecl)

// print original declaration
processingEnv.messager.printMessage(
Diagnostic.Kind.NOTE,
jcMethodDecl.toString()
)

// Rename declaration
jcMethodDecl.name = names.fromString("renamed")

// print renamed declaration
processingEnv.messager.printMessage(
Diagnostic.Kind.NOTE,
jcMethodDecl.toString()
)

// commit changes
result = jcMethodDecl
}
}

@Synchronized
override fun init(processingEnvironment: ProcessingEnvironment) {
super.init(processingEnvironment)
trees = Trees.instance(processingEnvironment)
val context = (processingEnvironment as JavacProcessingEnvironment).context
names = Names.instance(context)
}

override fun process(set: Set<TypeElement>, roundEnvironment: RoundEnvironment): Boolean {
// Find elements that are annotated with @Rename
for (element in roundEnvironment.getElementsAnnotatedWith(Rename::class.java)) {
val tree = trees.getTree(element) as JCTree
tree.accept(visitor)
}
return true
}
}



Gradle files



I added the following to the annotation processor build.gradle:


build.gradle


// Add annotation dependency
implementation project(':rename-annotation')
// Used to generate META-INF so the processor can run
compile 'com.google.auto.service:auto-service:1.0-rc4'
kapt "com.google.auto.service:auto-service:1.0-rc4"
// To prevent unresolved references during building. You might not need this dependency.
implementation files("${System.getProperty('java.home')}/../lib/tools.jar")



I added the following to the app build.gradle:


build.gradle


compileOnly project(':rename-annotation')
annotationProcessor project(':rename-processor')



The annotation build.gradle doesn't have dependencies besides the default generated ones.


build.gradle



The reason we have different modules is so we can prevent the annotation and processor being build into the final APK since we only need those during building.



Output



The log shows the method in the Java interface was renamed:


Note:
@Rename()
void methodToRename();
Note:
@Rename()
void renamed();



There was no log generated for the Kotlin interface. Indicating the annotation processor did not run.



When you take a look at classes.dex of the generated APK then you will see the following:


classes.dex



Output annotationProcessor



You can see that the method of the Java interface has been renamed properly. While the function of the Kotlin interface has not. Even though it shows up in the log.



You will also notice this warning in the log:



app: 'annotationProcessor' dependencies won't be recognized as kapt annotation processors. Please change the configuration name to 'kapt' for these artifacts: 'RenameTest:rename-processor:unspecified' and apply the kapt plugin: "apply plugin: 'kotlin-kapt'".



So lets do what the warning suggests. Add apply plugin: 'kotlin-kapt' to app build.gradle and change annotationProcessor to kapt. After sync and rebuild the output is:


apply plugin: 'kotlin-kapt'


build.gradle


annotationProcessor


kapt


Note:
@Rename()
void methodToRename();
Note:
@Rename()
void renamed();
Note:
@nl.peperzaken.renameannotation.Rename()
public abstract void functionToRename();
Note:
@nl.peperzaken.renameannotation.Rename()
public abstract void renamed();



Logs for the Java and Kotlin file are both appearing. Success you think? Looking at classes.dex of the newly generated APK will make you think otherwise since both are in their original form:


classes.dex



Output kapt



Question



Is there a way to get the desired output in the final APK? With the output being that both the method in the Java interface and the function in the Kotlin interface are renamed.



Link to sample project: https://github.com/peperzaken/kotlin-annotation-rename-test



This question has not received enough attention.




1 Answer
1



Kapt does not process Kotlin files directly – it runs annotation processing over Java file stubs instead. So the changes in AST tree for Kotlin files are only visible to other annotation processors and do not affect compilation.



Note that Java AST API is not a part of the annotation processing API (JSR 269) - it's actually an internal Javac API, and, obviously, Kotlinc is not Javac.



The more reliable approach to solve your problem would be a class file post-processing (or a Kotlin compiler plugin, but then it won't work for Java).



Also, in Kotlin you have the @JvmName() annotation that changes the JVM declaration name.


@JvmName()





The class file post processing seems the way to go, but I would rather change stuff just before compiling so nothing breaks during runtime. The @JvmName() annotation does rename for Kotlin, but can't be used in Java. Also the kotlin.Metadata annotation in the output bytecode still contains the original name.
– stefana
Jul 2 at 13:00


@JvmName()






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

C4j6hQurCp3IywlNTI,tNKpWlL8V,HWhroS4mD8G,5 hmSbcOFIsFpy jsGJ rRJ,7uqjinSgryN3BI7y i
x ICU7tRVmc

Popular posts from this blog

Rothschild family

Cinema of Italy