使用TreeTranslator重命名不适用于Kotlin的函数

ste*_*ana 15 java android abstract-syntax-tree gradle kotlin

我正在尝试根据AST(抽象语法树)重写在构建期间重命名Java接口中的方法和Kotlin接口中的函数.对于这个问题,我们忽略了重命名方法/函数给调用带来的影响.要找到要重命名的方法/函数,我正在使用自定义注释和注释处理器.我按照这些说明操作Java接口.

我用三个模块创建了一个新项目.app模块,注释模块和注释处理器模块.

该应用模块是一个Android应用程序,并包含与一个注释的方法/函数每两个独立的Java和科特林接口文件.

RenameJava.java

package nl.peperzaken.renametest;

import nl.peperzaken.renameannotation.Rename;

public interface RenameJava {
    @Rename
    void methodToRename();
}
Run Code Online (Sandbox Code Playgroud)

RenameKotlin.kt

package nl.peperzaken.renametest

import nl.peperzaken.renameannotation.Rename

interface RenameKotlin {
    @Rename
    fun functionToRename()
}
Run Code Online (Sandbox Code Playgroud)

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

Rename.kt

package nl.peperzaken.renameannotation

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
annotation class Rename
Run Code Online (Sandbox Code Playgroud)

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

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
    }
}
Run Code Online (Sandbox Code Playgroud)

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")
Run Code Online (Sandbox Code Playgroud)

我在应用程序中添加了以下内容build.gradle:

compileOnly project(':rename-annotation')
annotationProcessor project(':rename-processor')
Run Code Online (Sandbox Code Playgroud)

build.gradle除了默认生成的注释之外,注释没有依赖项.

我们有不同模块的原因是我们可以阻止注释和处理器构建到最终APK,因为我们只需要在构建期间.

产量

日志显示Java界面中的方法已重命名:

Note: 
  @Rename()
  void methodToRename();
Note: 
  @Rename()
  void renamed();
Run Code Online (Sandbox Code Playgroud)

没有为Kotlin界面生成日志.指示注释处理器未运行.

当您查看classes.dex生成的APK时,您将看到以下内容:

输出annotationProcessor

您可以看到Java接口的方法已正确重命名.虽然Kotlin界面的功能还没有.即使它出现在日志中.

您还会在日志中注意到此警告:

app:'annotationProcessor'依赖项不会被识别为kapt注释处理器.请将配置名称更改为'kapt'以获取这些工件:'RenameTest:rename-processor:unspecified'并应用kapt插件:"apply plugin:'kotlin-kapt'".

所以,让我们做警告的建议.添加apply plugin: 'kotlin-kapt'到应用build.gradle并更改annotationProcessorkapt.同步并重建后输出为:

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();
Run Code Online (Sandbox Code Playgroud)

Java和Kotlin文件的日志都出现了.你认为成功吗?查看classes.dex新生成的APK会让您反思,因为两者都是原始形式:

输出kapt

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

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

yan*_*nex 3

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

\n\n

请注意,Java AST API 不是注释处理 API (JSR 269) 的一部分 - 它实际上是内部 Javac API,而且显然,Kotlinc 不是 Javac。

\n\n

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

\n\n

此外,在 Kotlin 中,您还可以使用@JvmName()更改 JVM 声明名称的注释。

\n