Использование 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();
}

ПереименоватьKotlin.kt

package nl.peperzaken.renametest

import nl.peperzaken.renameannotation.Rename

interface RenameKotlin {
    @Rename
    fun functionToRename()
}

Модуль аннотации - это библиотека Java, которая содержит только аннотацию @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
    }
}

Файлы 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 не было создано журнала. Это означает, что обработчик аннотаций не запущен.

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

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

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

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

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

Итак, давайте сделаем то, что предлагает предупреждение. Добавьте 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. Думаете, успех? Глядя на classes.dex только что сгенерированного APK, вы подумаете об обратном, поскольку оба находятся в исходной форме:

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

Вопрос

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

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

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
16
0
613
1

Ответы 1

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

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

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

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

Пост-обработка файла класса кажется подходящим вариантом, но я бы предпочел изменить что-то непосредственно перед компиляцией, чтобы ничего не ломалось во время выполнения. Аннотация @JvmName() действительно переименовывается для Kotlin, но не может использоваться в Java. Также аннотация kotlin.Metadata в выходном байт-коде по-прежнему содержит исходное имя.

stefana 02.07.2018 15:00

Другие вопросы по теме