枚举包中定义的扩展属性

时间:2019-08-08 21:44:34

标签: kotlin package kotlin-reflect

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

我的模板引擎需要通过不仅在obj.myProperty的类和超类中定义的成员中,而且还在"myProperty"中定义的成员中查找obj来解析属性表达式,例如x.absoluteValue。在用户指定的Kotlin软件包列表中定义了扩展属性

例如,如果我的口译员正在评估x,而Int::class确实是一个Int,则我具有以下信息:

  • 对象KClass:"absoluteValue"
  • 属性名称:kotlin.math
  • 用户要求搜索的软件包列表:kotlin.math

我可以使用哪种API来获取给定包中定义的所有顶级扩展属性(例如List<KProperty<*>>)的列表,作为诸如"absoluteValue"之类的反映项的列表?在模板编译时(即Kotlin运行时),我将遍历该扩展列表,并查找与Int接收器兼容的名为listOf(Int::absoluteValue, ...)的扩展。

我知道我可以在导入扩展属性后手动定义它们的列表,例如javax.script.ScriptEngineManager,但是我希望用户指定软件包列表,而不是单个属性。


更新:我决定以{{1}}为模板模板引擎,以Kotlin的JSR-223支持为基础,因此使用稳定的API,并让Kotlin编译器根据需要解析扩展属性。

1 个答案:

答案 0 :(得分: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。我们可以通过解析字节码并检查参数名称来确定函数是否具有扩展名。值得一提的是,如果所讨论的类已通过字节码混淆器运行,则此方法有些黑,可能无法正常工作。因为自己编写字节码解析器需要做很多工作,所以我使用commons-bcel为我完成所有工作。

ExtensionFunctions.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中,在一些来自Maven的随机jar中)。由于并非所有人都会从maven中获得相同的随机jar,因此最好的做法是在System.getProperty("java.class.path")中查找kotlin标准库。烦人的absoluteValue被声明为一个内联函数,因此在stdlib jar中没有任何痕迹。对于所有kotlin stdlib扩展功能,情况并非如此。因此,您可以使用以下代码在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