3

我正在用 Kotlin 编写 HTML 模板语言。

我的模板引擎将需要解析属性表达式,例如,不仅要在 's 的类和超类中定义的成员中obj.myProperty查找,还要在用户指定的 Kotlin 包列表中定义的扩展属性中查找。"myProperty"obj

例如,如果我的解释器正在评估x.absoluteValue并且x结果是一个 Int,我有以下信息:

  • 对象 KClass:Int::class
  • 物业名称:"absoluteValue"
  • 用户要求搜索的包列表:kotlin.math等。

我可以使用什么 API 来获取给定包中定义的所有顶级扩展属性kotlin.math的列表,例如,作为反映项目的列表,例如List<KProperty<*>>?在模板编译时(即 Kotlin 运行时),我将浏览该扩展列表并寻找一个"absoluteValue"与 Int 接收器兼容的名称。

我知道我可以手动定义扩展属性列表,例如listOf(Int::absoluteValue, ...)在导入它们之后,但我希望我的用户指定一个包列表,而不是单个属性。


更新:我决定将我的模板引擎基于 Kotlin 的 JSR-223 支持,javax.script.ScriptEngineManager因此使用稳定的 API 并让 Kotlin 编译器在它认为合适的时候解析扩展属性。

4

1 回答 1

3

关于 kotlin 扩展函数的一些知识:

  • 从 java 的角度来看,kotlin 扩展函数是静态方法,它们将它们扩展的类作为参数

这使得它们很难与常规的旧静态函数区分开来。最初我什至不确定是否有区别。

所以让我们弄清楚是否有区别。

ExtensionFunctions.kt 中的声明:

class Test

fun bar(test : Test){}

fun Test.bar2(){}

fun Test.foo45(bar : Test, i :Int): Int = i

一些命令行:


francis@debian:~/test76/target/classes/io/github/pirocks$  javap -p -c -s -l ExtensionFunctionsKt.class 
Compiled from "ExtensionFunctions.kt"
public final class io.github.pirocks.ExtensionFunctionsKt {
  public static final void bar(io.github.pirocks.Test);
    descriptor: (Lio/github/pirocks/Test;)V
    Code:
       0: aload_0
       1: ldc           #9                  // String test
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
       6: return
    LineNumberTable:
      line 6: 6
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       7     0  test   Lio/github/pirocks/Test;

  public static final void bar2(io.github.pirocks.Test);
    descriptor: (Lio/github/pirocks/Test;)V
    Code:
       0: aload_0
       1: ldc           #19                 // String $this$bar2
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
       6: return
    LineNumberTable:
      line 8: 6
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       7     0 $this$bar2   Lio/github/pirocks/Test;

  public static final int foo45(io.github.pirocks.Test, io.github.pirocks.Test, int);
    descriptor: (Lio/github/pirocks/Test;Lio/github/pirocks/Test;I)I
    Code:
       0: aload_0
       1: ldc           #23                 // String $this$foo45
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_1
       7: ldc           #24                 // String bar
       9: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
      12: iload_2
      13: ireturn
    LineNumberTable:
      line 10: 12
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      14     0 $this$foo45   Lio/github/pirocks/Test;
          0      14     1   bar   Lio/github/pirocks/Test;
          0      14     2     i   I

  <further output omitted>
francis@debian:~/test76/target/classes/io/github/pirocks$ 


如您所见,除了第一个参数名称外,常规静态函数和扩展函数之间没有太大区别。扩展函数参数被命名为$this$functionName. 我们可以通过解析字节码并检查参数名称来使用它来确定函数是否具有扩展变量。值得一提的是,这有点 hacky,如果有问题的类已经通过字节码混淆器运行,则可能无法工作。因为自己编写字节码解析器需要做很多工作,所以我使用 commons-bcel 为我完成所有工作。

扩展功能.kt:

package io.github.pirocks
import org.apache.bcel.classfile.ClassParser

class Test

fun bar(test : Test){}

fun Test.bar2(){}

fun Test.foo45(bar : Test, i :Int): Int = i


fun main(args: Array<String>) {
    val classFileInQuestionStream = "Just wanted an object instance".javaClass.getResourceAsStream("/io/github/pirocks/ExtensionFunctionsKt.class")!!
    val parsedClass = ClassParser(classFileInQuestionStream, "ExtensionFunctionsKt.class").parse()
    parsedClass.methods.forEach { method ->
        if(method.localVariableTable.localVariableTable.any {
            it.name == ("\$this$${method.name}")
        }){
            println("Is an extension function:")
            println(method)
        }
    }

}

以上应输出:

Is an extension function:
public static final void bar2(io.github.pirocks.Test $this$bar2) [RuntimeInvisibleParameterAnnotations]
Is an extension function:
public static final int foo45(io.github.pirocks.Test $this$foo45, io.github.pirocks.Test bar, int i) [RuntimeInvisibleParameterAnnotations]

Commons-bcel 还可以为您提供每个扩展功能的类型/名称/属性信息。

您在问题中提到使用 Int 上的扩展函数执行此操作。这更棘手,因为absoluteValue被声明了,谁知道在哪里(Intellij Ctrl+B 告诉我它位于这个名为 MathH.kt 的巨大文件中,它实际上是 MathKt.class,在包 kotlin.math 中,包含在一些随机 jar 中来自行家)。由于不是每个人都会从 maven 获得相同的随机 jar,因此最好的做法是在System.getProperty("java.class.path"). AnnoyinglyabsoluteValue被声明为内联函数,因此在 stdlib jar 中没有它的踪迹。这不适用于所有 kotlin 标准库扩展函数。因此,您可以使用以下内容获取 stdlib 中的所有扩展函数(更正:有两个 stdlib jar,因此仅获取在 中声明的扩展函数kotlin-stdlib-version-number)。

package io.github.pirocks

import org.apache.bcel.classfile.ClassParser
import java.nio.file.Paths
import java.util.jar.JarFile


class Test

fun bar(test: Test) {}

fun Test.bar2() {}

fun Test.foo45(bar: Test, i: Int): Int = i


fun main(args: Array<String>) {
    val jarPath = System.getProperty("java.class.path").split(":").filter {
        it.contains(Regex("kotlin-stdlib-[0-9]\\.[0-9]+\\.[0-9]+\\.jar"))
    }.map {
        Paths.get(it)
    }.single()//if theres more than one kotlin-stdlib we're in trouble

    val theJar = JarFile(jarPath.toFile())
    val jarEntries = theJar.entries()

    while (jarEntries.hasMoreElements()) {
        val entry = jarEntries.nextElement()
        if (entry.name.endsWith(".class")) {
            val cp = ClassParser(theJar.getInputStream(entry), entry.getName())
            val javaClass = cp.parse()
            javaClass.methods.forEach { method ->
                if (method.localVariableTable?.localVariableTable?.any {
                        it.name == ("\$this$${method.name}")
                    } == true) {
                    println("Is an extension function:")
                    println(method)
                }

            }
        }


    }
}


编辑:

至于实际回答有关如何在包中获取扩展功能的问题:

您需要遍历类路径中的每个条目,包括类和 jar,并检查与所需包匹配的任何类。至于确定一个类的包,你可以使用 commons-bcel 函数JavaClass::getPackageName

于 2019-08-30T21:48:42.103 回答