【问题标题】:Using TreeTranslator to rename functions not working for Kotlin使用 TreeTranslator 重命名不适用于 Kotlin 的函数
【发布时间】:2018-06-29 11:41:12
【问题描述】:

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

我创建了一个包含三个模块的新项目。应用模块、注解模块和注解处理器模块。

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

重命名Java.java

package nl.peperzaken.renametest;

import nl.peperzaken.renameannotation.Rename;

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

重命名Kotlin.kt

package nl.peperzaken.renametest

import nl.peperzaken.renameannotation.Rename

interface RenameKotlin {
    @Rename
    fun functionToRename()
}

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

重命名.kt

package nl.peperzaken.renameannotation

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

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

重命名处理器.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 文件

我在注释处理器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")

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

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

注解build.gradle除了默认生成的之外没有依赖。

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

输出

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

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

没有为 Kotlin 界面生成日志。表示注解处理器没有运行。

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

可以看到Java接口的方法已经正确重命名了。而 Kotlin 接口的功能却没有。即使它显示在日志中。

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

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

所以让我们按照警告的建议去做吧。将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();

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

问题

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

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

【问题讨论】:

    标签: java android gradle kotlin abstract-syntax-tree


    【解决方案1】:

    Kapt 不直接处理 Kotlin 文件——而是在 Java 文件存根上运行注解处理。所以 Kotlin 文件的 AST 树的变化只对其他注释处理器可见,不影响编译。

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

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

    此外,在 Kotlin 中,@JvmName() 注释会更改 JVM 声明名称。

    【讨论】:

    • 类文件后处理似乎是要走的路,但我宁愿在编译之前更改一些东西,这样在运行时不会中断。 @JvmName() 注释确实为 Kotlin 重命名,但不能在 Java 中使用。此外,输出字节码中的 kotlin.Metadata 注释仍然包含原始名称。
    猜你喜欢
    • 2015-11-06
    • 2013-11-15
    • 1970-01-01
    • 2020-01-13
    • 2019-10-13
    • 1970-01-01
    • 2016-12-22
    • 1970-01-01
    • 2018-06-30
    相关资源
    最近更新 更多