Использование TreeTranslator для переименования функций, не работающих в Kotlin - программирование
Подтвердить что ты не робот

Использование TreeTranslator для переименования функций, не работающих в Kotlin

Я пытаюсь переименовать метод в интерфейсе Java и функцию в интерфейсе Kotlin во время построения в соответствии с переписанием AST (абстрактное синтаксическое дерево). По этому вопросу мы игнорируем последствия, связанные с переименованием метода/функции для вызовов. Чтобы найти метод/функцию для переименования, я использую специальный обработчик аннотации и аннотации. Я работаю над интерфейсом Java, следуя этим инструкциям.

Я создал новый проект с тремя модулями. Модуль приложения, модуль аннотации и модуль обработки аннотаций.

Модуль приложения - приложение для Android и содержит два отдельных файла интерфейса Java и Kotlin с одним аннотированным методом/функцией.

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()
}

Модуль аннотации представляет собой библиотеку Java, которая содержит @Rename аннотацию @Rename и мы указываем ее только на функции, и мы говорим, что она может быть видна только в исходном коде.

Rename.kt

package nl.peperzaken.renameannotation

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

Модуль обработчика аннотаций представляет собой библиотеку 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
    }
}

Грейд файлы

Я добавил в обработчик аннотации 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 не было журнала. Указание обработчика аннотации не выполнялось.

Когда вы посмотрите на classes.dex сгенерированного APK, вы увидите следующее:

Output annotationProcessor

Вы можете видеть, что метод интерфейса Java был переименован правильно. Хотя функции интерфейса Kotlin нет. Даже если он отображается в журнале.

Вы также заметите это предупреждение в журнале:

app: зависимостей 'annotationProcessor' не будут распознаваться как kapt-аннотационные процессоры. Измените имя конфигурации на "kapt" для этих артефактов: "RenameTest: rename-processor: unspecified" и примените плагин kapt: "применить плагин:" kotlin-kapt "".

Поэтому давайте сделаем то, что предлагает предупреждение. Добавьте apply plugin: 'kotlin-kapt' в app 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. Успех, который вы думаете? Если посмотреть на classes.dex вновь созданного APK, вы подумаете иначе, поскольку оба находятся в их первоначальной форме:

Output kapt

Вопрос

Есть ли способ получить желаемый результат в финальной APK? Поскольку вывод состоит в том, что и метод в интерфейсе Java, и функция в интерфейсе Kotlin переименованы.

Ссылка на образец проекта: https://github.com/peperzaken/kotlin-annotation-rename-test

4b9b3361

Ответ 1

Kapt не обрабатывает файлы Kotlin напрямую - вместо этого он обрабатывает обработку аннотаций поверх окон файлов Java. Таким образом, изменения в дереве AST для файлов Kotlin видны только для других обработчиков аннотаций и не влияют на компиляцию.

Обратите внимание, что API Java API не является частью API обработки аннотаций (JSR 269) - он фактически является внутренним Javac API, и, очевидно, Kotlinc не является Javac.

Более надежный подход для решения вашей проблемы - это пост-обработка файла класса (или плагин компилятора Kotlin, но тогда он не будет работать для Java).

Кроме того, в Kotlin у вас есть аннотация @JvmName() которая изменяет имя декларации JVM.