Something to know about kotlin extension functions:
- From the point of view of java, kotlin extension functions are static methods which take the class they are extending as a parameter
This makes them hard to distinguish from regular old static functions. Initially I wasn't even sure of there was a difference.
So lets figure out if there is a difference.
Declarations in ExtensionFunctions.kt:
class Test
fun bar(test : Test){}
fun Test.bar2(){}
fun Test.foo45(bar : Test, i :Int): Int = i
Some command line:
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$
As you can see there isn't much difference between a regular static function and an extension function except for the first parameter name. Extension function parameters are named $this$functionName
. We can use this to figure out whether a function is of the extension varity by parsing the bytecode, and checking parameter names. It is worth mentioning this is somewhat hacky and probably won't work if the classes in question has been run through a bytecode obfuscator. Because writing bytecode parsers on your own is a lot of work I'm using commons-bcel to do all the work for me.
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)
}
}
}
The above should output:
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 can also provide you with type/name/attribute information for each extension function.
You mentioned in your question doing this with extension functions on Int. This is trickier, because absoluteValue
is declared, who knows where(Intellij Ctrl+B tells me that it is located in this massive file called MathH.kt, which is actually MathKt.class, in the package kotlin.math, in some random jar included from maven). Since not everyone will have the same random jar from maven, the best course of action is to look for the kotlin standard library in System.getProperty("java.class.path")
. Annoyingly absoluteValue
is declared as an inline function and so there is no trace of it in the stdlib jars. This isn't true for all kotlin stdlib extension functions. So you can use the below to get all extension functions in the stdlib (correction: there are two stdlib jars, so this only gets extension functions declared in 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)
}
}
}
}
}
Edit:
As to actually answering the question about how to get extension functions in a package:
You would need to iterate over each entry in the classpath, both classes and jar, and check for any classes matching the desired package. As for determining the package of a class you can use the commons-bcel function JavaClass::getPackageName
.