如何在Kotlin注释处理期间访问方法体? 概述
我想知道是否有一种方法可以将注释应用于函数,并在注释处理期间访问这些函数的主体。如果无法通过检查注释处理器中的如何在Kotlin注释处理期间访问方法体? 概述,kotlin,annotation-processing,Kotlin,Annotation Processing,我想知道是否有一种方法可以将注释应用于函数,并在注释处理期间访问这些函数的主体。如果无法通过检查注释处理器中的元素对象直接获取方法体,那么除了访问应用这些注释的函数的源代码之外,还有其他方法吗 细节 作为我正在进行的一个项目的一部分,我尝试使用kapt检查带有特定类型注释的Kotlin函数,并基于它们生成类。例如,给定如下注释函数: @ElementaryNode fun addTwoNumbers(x: Int, y: Int) = x + y My annotation processor
元素
对象直接获取方法体,那么除了访问应用这些注释的函数的源代码之外,还有其他方法吗
细节
作为我正在进行的一个项目的一部分,我尝试使用kapt检查带有特定类型注释的Kotlin函数,并基于它们生成类。例如,给定如下注释函数:
@ElementaryNode
fun addTwoNumbers(x: Int, y: Int) = x + y
My annotation processor当前生成以下内容:
class AddTwoNumbers : Node {
val x: InputPort<Int> = TODO("implement node port property")
val y: InputPort<Int> = TODO("implement node port property")
val output: OutputPort<Int> = TODO("implement node port property")
}
然而,kapt只生成方法体的存根,所以我从每个方法体得到的都是这样的东西:
@ElementaryNode
fun addTwoNumbers(x: Int, y: Int) = x + y
$gradle干净构建
...
>任务:kaptGenerateStubsKotlin
w:警告:正在处理AddTwoNumber:{
返回0;
}
w:警告:正在处理两个数字:{
返回0.0;
}
w:警告:正在处理转换:{
返回null;
}
w:警告:正在处理最小值和最大值:{
返回null;
}
w:警告:正在处理的虚拟对象:{
}
更新
访问表示每个函数的ExecutableElement
上的Element.enclosuringelement
为我提供了定义函数的包/模块的限定名称。例如,addtwowerumbers
在Main.kt
中声明为顶级函数,在注释处理过程中,我得到以下输出:processing addtworumbers:com.mycompany.testmaster.playder.MainKt
根据这些信息,有什么方法可以访问原始源文件(
Main.kt
)吗?这并不容易,但我最终设法找到了一个(相当粗糙的)解决方案
我发现在注释处理过程中,Kotlin在临时构建输出目录下生成元数据文件。这些元数据文件包含序列化信息,其中包括指向包含我正在处理的注释的原始源文件的路径:
通过查看Kapt插件的源代码,我发现这使我能够找出如何反序列化这些文件中的信息,从而提取原始源代码的位置
我创建了一个Kotlin对象SourceCodeLocator
,它将所有这些放在一起,这样我可以向它传递一个表示函数的元素
,它将返回包含它的源代码的字符串表示:
package com.mycompany.testmaster.nodegen.parsers
import com.mycompany.testmaster.nodegen.KAPT_KOTLIN_GENERATED_OPTION_NAME
import com.mycompany.testmaster.nodegen.KAPT_METADATA_EXTENSION
import java.io.ByteArrayInputStream
import java.io.File
import java.io.ObjectInputStream
import javax.annotation.processing.ProcessingEnvironment
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.ExecutableElement
internal object SourceCodeLocator {
fun sourceOf(function: Element, environment: ProcessingEnvironment): String {
if (function !is ExecutableElement)
error("Cannot extract source code from non-executable element")
return getSourceCodeContainingFunction(function, environment)
}
private fun getSourceCodeContainingFunction(function: Element, environment: ProcessingEnvironment): String {
val metadataFile = getMetadataForFunction(function, environment)
val path = deserializeMetadata(metadataFile.readBytes()).entries
.single { it.key.contains(function.simpleName) }
.value
val sourceFile = File(path)
assert(sourceFile.isFile) { "Source file does not exist at stated position within metadata" }
return sourceFile.readText()
}
private fun getMetadataForFunction(element: Element, environment: ProcessingEnvironment): File {
val enclosingClass = element.enclosingElement
assert(enclosingClass.kind == ElementKind.CLASS)
val stubDirectory = locateStubDirectory(environment)
val metadataPath = enclosingClass.toString().replace(".", "/")
val metadataFile = File("$stubDirectory/$metadataPath.$KAPT_METADATA_EXTENSION")
if (!metadataFile.isFile) error("Cannot locate kapt metadata for function")
return metadataFile
}
private fun deserializeMetadata(data: ByteArray): Map<String, String> {
val metadata = mutableMapOf<String, String>()
val ois = ObjectInputStream(ByteArrayInputStream(data))
ois.readInt() // Discard version information
val lineInfoCount = ois.readInt()
repeat(lineInfoCount) {
val fqName = ois.readUTF()
val path = ois.readUTF()
val isRelative = ois.readBoolean()
ois.readInt() // Discard position information
assert(!isRelative)
metadata[fqName] = path
}
return metadata
}
private fun locateStubDirectory(environment: ProcessingEnvironment): File {
val kaptKotlinGeneratedDir = environment.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
val buildDirectory = File(kaptKotlinGeneratedDir).ancestors.firstOrNull { it.name == "build" }
val stubDirectory = buildDirectory?.let { File("${buildDirectory.path}/tmp/kapt3/stubs/main") }
if (stubDirectory == null || !stubDirectory.isDirectory)
error("Could not locate kapt stub directory")
return stubDirectory
}
// TODO: convert into generator for Kotlin 1.3
private val File.ancestors: Iterable<File>
get() {
val ancestors = mutableListOf<File>()
var currentAncestor: File? = this
while (currentAncestor != null) {
ancestors.add(currentAncestor)
currentAncestor = currentAncestor.parentFile
}
return ancestors
}
}
并生成如下源代码,其中包含原始源代码的修改版本:
米南德马克斯总吨
//此代码由测试主节点生成工具在2018-10-29T08:31:35.847生成。
//
//不要修改此文件。以后可能会覆盖任何更改。
包com.mycompany.testmaster.playway.nodes.gen
导入com.mycompany.testmaster.domain.ElementaryNode
导入com.mycompany.testmaster.domain.InputPort
导入com.mycompany.testmaster.domain.OutputPort
导入com.mycompany.testmaster.domain.Port
导入kotlin.collections.Set
导入kotlinx.coroutines.async
导入kotlinx.coroutines.coroutineScope
类MinAndMax:ElementaryNode(){
private val_值:Port=Port()
val值:InputPort=\u值
private val_min:Port=Port()
最小值:输出端口=\u最小值
private val_max:Port=Port()
val max:OutputPort=\u max
覆盖val端口:集合=集合(_值、_最小值、_最大值)
重写suspend fun executeOnce(){
共线镜{
val values=async{u values.receive()}
val输出=_节点库(values.await())
_最小正向(输出最小值)
_最大正向(输出最大值)
}
}
}
私人娱乐节点库(价值观:Iterable)=
输出(values.min()、values.max())
私有数据类输出(最小值:T?,最大值:T?)
package com.mycompany.testmaster.nodegen.parsers
import com.mycompany.testmaster.nodegen.KAPT_KOTLIN_GENERATED_OPTION_NAME
import com.mycompany.testmaster.nodegen.KAPT_METADATA_EXTENSION
import java.io.ByteArrayInputStream
import java.io.File
import java.io.ObjectInputStream
import javax.annotation.processing.ProcessingEnvironment
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.ExecutableElement
internal object SourceCodeLocator {
fun sourceOf(function: Element, environment: ProcessingEnvironment): String {
if (function !is ExecutableElement)
error("Cannot extract source code from non-executable element")
return getSourceCodeContainingFunction(function, environment)
}
private fun getSourceCodeContainingFunction(function: Element, environment: ProcessingEnvironment): String {
val metadataFile = getMetadataForFunction(function, environment)
val path = deserializeMetadata(metadataFile.readBytes()).entries
.single { it.key.contains(function.simpleName) }
.value
val sourceFile = File(path)
assert(sourceFile.isFile) { "Source file does not exist at stated position within metadata" }
return sourceFile.readText()
}
private fun getMetadataForFunction(element: Element, environment: ProcessingEnvironment): File {
val enclosingClass = element.enclosingElement
assert(enclosingClass.kind == ElementKind.CLASS)
val stubDirectory = locateStubDirectory(environment)
val metadataPath = enclosingClass.toString().replace(".", "/")
val metadataFile = File("$stubDirectory/$metadataPath.$KAPT_METADATA_EXTENSION")
if (!metadataFile.isFile) error("Cannot locate kapt metadata for function")
return metadataFile
}
private fun deserializeMetadata(data: ByteArray): Map<String, String> {
val metadata = mutableMapOf<String, String>()
val ois = ObjectInputStream(ByteArrayInputStream(data))
ois.readInt() // Discard version information
val lineInfoCount = ois.readInt()
repeat(lineInfoCount) {
val fqName = ois.readUTF()
val path = ois.readUTF()
val isRelative = ois.readBoolean()
ois.readInt() // Discard position information
assert(!isRelative)
metadata[fqName] = path
}
return metadata
}
private fun locateStubDirectory(environment: ProcessingEnvironment): File {
val kaptKotlinGeneratedDir = environment.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
val buildDirectory = File(kaptKotlinGeneratedDir).ancestors.firstOrNull { it.name == "build" }
val stubDirectory = buildDirectory?.let { File("${buildDirectory.path}/tmp/kapt3/stubs/main") }
if (stubDirectory == null || !stubDirectory.isDirectory)
error("Could not locate kapt stub directory")
return stubDirectory
}
// TODO: convert into generator for Kotlin 1.3
private val File.ancestors: Iterable<File>
get() {
val ancestors = mutableListOf<File>()
var currentAncestor: File? = this
while (currentAncestor != null) {
ancestors.add(currentAncestor)
currentAncestor = currentAncestor.parentFile
}
return ancestors
}
}
package com.mycompany.testmaster.playground.nodes
import com.mycompany.testmaster.nodegen.annotations.ElementaryNode
@ElementaryNode
private fun <T: Comparable<T>> minAndMax(values: Iterable<T>) =
Output(values.min(), values.max())
private data class Output<T : Comparable<T>>(val min: T?, val max: T?)
// This code was generated by the <Company> Test Master node generation tool at 2018-10-29T08:31:35.847.
//
// Do not modify this file. Any changes may be overwritten at a later time.
package com.mycompany.testmaster.playground.nodes.gen
import com.mycompany.testmaster.domain.ElementaryNode
import com.mycompany.testmaster.domain.InputPort
import com.mycompany.testmaster.domain.OutputPort
import com.mycompany.testmaster.domain.Port
import kotlin.collections.Set
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
class MinAndMax<T : Comparable<in T>> : ElementaryNode() {
private val _values: Port<Iterable<out T>> = Port<Iterable<out T>>()
val values: InputPort<Iterable<out T>> = _values
private val _min: Port<T?> = Port<T?>()
val min: OutputPort<T?> = _min
private val _max: Port<T?> = Port<T?>()
val max: OutputPort<T?> = _max
override val ports: Set<Port<*>> = setOf(_values, _min, _max)
override suspend fun executeOnce() {
coroutineScope {
val values = async { _values.receive() }
val output = _nodeBody(values.await())
_min.forward(output.min)
_max.forward(output.max)
}
}
}
private fun <T: Comparable<T>> _nodeBody(values: Iterable<T>) =
Output(values.min(), values.max())
private data class Output<T : Comparable<T>>(val min: T?, val max: T?)