使用TreeTranslator对Kotlin不适用的函数进行重命名

时间:2023-01-25 08:50:59

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.

我正在尝试在Java接口中重命名一个方法,并在根据AST(抽象语法树)重写的过程中在Kotlin接口中调用函数。对于这个问题,我们忽略了重命名方法/函数为调用带来的影响。要找到要重命名的方法/函数,我使用自定义注释和注释处理器。我按照下面的说明让它在Java接口上工作。

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

我创建了一个包含三个模块的新项目。app模块、标注模块、标注处理器模块。

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

该应用模块是一个Android应用,包含两个独立的Java和Kotlin接口文件,每个文件都有一个带注释的方法/函数。

RenameJava.java

RenameJava.java

package nl.peperzaken.renametest;

import nl.peperzaken.renameannotation.Rename;

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

RenameKotlin.kt

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.

annotation模块是一个Java库,它只包含@Rename注释,我们指定只允许它在函数上,我们说它可能只在源代码中可见。

Rename.kt

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.

注释处理程序模块是一个Java库,它只包含处理器,该处理器迭代具有注释的元素并对其进行转换。

RenameProcessor.kt

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

Gradle文件

I added the following to the annotation processor 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:

我在app build中添加了以下内容。

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

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

注释。除了默认生成的依赖项之外,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.

我们拥有不同模块的原因是,我们可以避免将注释和处理器构建到最终的APK中,因为我们只在构建过程中需要它们。

Output

输出

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

日志显示Java接口中的方法被重命名为:

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

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

Kotlin接口没有生成日志。指示批注处理器没有运行。

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

当您阅读生成的APK的class .dex时,您将看到以下内容:

使用TreeTranslator对Kotlin不适用的函数进行重命名

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.

您可以看到Java接口的方法已被正确地重命名。而Kotlin接口的功能则没有。即使它出现在日志中。

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'".

app: 'annotationProcessor'依赖项不会被识别为kapt注释处理器。请将这些工件的配置名改为“kapt”:“RenameTest:rename-processor: unknown”,并应用kapt插件:“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:

所以让我们按照警告的建议去做。添加应用插件:“kotlin-kapt”到应用程序构建中。将注释处理器升级为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:

Java和Kotlin文件的日志都出现了。成功,你认为呢?看看新生成的APK的class .dex会让你产生不同的想法,因为它们都是原始的形式:

使用TreeTranslator对Kotlin不适用的函数进行重命名

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.

是否有办法在最终的APK中获得所需的输出?输出为Java接口中的方法和Kotlin接口中的函数都被重命名。

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

链接到示例项目:https://github.com/peperzaken/kotlin-annots-rename -test

1 个解决方案

#1


2  

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.

Kapt并不直接处理Kotlin文件,而是通过Java文件存根运行注释处理。因此,对Kotlin文件的AST树的更改只对其他注释处理器可见,不影响编译。

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.

注意,Java AST API不是注释处理API (JSR 269)的一部分——它实际上是一个内部的Javac API,而且很明显,Kotlinc不是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).

解决问题的更可靠的方法是类文件后处理(或Kotlin编译器插件,但它不能用于Java)。

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

另外,在Kotlin中还有一个@JvmName()注释,用于更改JVM声明名。

#1


2  

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.

Kapt并不直接处理Kotlin文件,而是通过Java文件存根运行注释处理。因此,对Kotlin文件的AST树的更改只对其他注释处理器可见,不影响编译。

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.

注意,Java AST API不是注释处理API (JSR 269)的一部分——它实际上是一个内部的Javac API,而且很明显,Kotlinc不是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).

解决问题的更可靠的方法是类文件后处理(或Kotlin编译器插件,但它不能用于Java)。

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

另外,在Kotlin中还有一个@JvmName()注释,用于更改JVM声明名。