This library provides an API to read and modify metadata of binary files generated by the Kotlin/JVM compiler, namely .class
and .kotlin_module
files.
To use this library in your project, add a dependency on org.jetbrains.kotlinx:kotlinx-metadata-jvm:$kotlinx_metadata_version
(where kotlinx_metadata_version
is the version of the library).
Example usage in Maven:
<project> <dependencies> <dependency> <groupId>org.jetbrains.kotlinx</groupId> <artifactId>kotlinx-metadata-jvm</artifactId> <version>${kotlinx_metadata_version}</version> </dependency> </dependencies> ... </project>
Example usage in Gradle:
repositories { mavenCentral() } dependencies { implementation "org.jetbrains.kotlinx:kotlinx-metadata-jvm:$kotlinx_metadata_version" }
The entry point for reading the Kotlin metadata of a .class
file is KotlinClassMetadata.read
. The data it takes is the kotlin.Metadata
annotation on the class file generated by the Kotlin compiler. Obtain the kotlin.Metadata
annotation reflectively or construct it from binary representation (e.g. by reading classfile with org.objectweb.asm.ClassReader
), and then use KotlinClassMetadata.read
to obtain the correct instance of the class metadata.
val metadataAnnotation = Metadata( // pass arguments here ) val metadata = KotlinClassMetadata.read(metadataAnnotation)
KotlinClassMetadata
is a sealed class, with subclasses representing all the different kinds of classes generated by the Kotlin compiler. Unless you are sure that you are reading a class of a specific kind and can do a simple cast, a when
is a good choice to handle all the possibilities:
when (metadata) { is KotlinClassMetadata.Class -> ... is KotlinClassMetadata.FileFacade -> ... is KotlinClassMetadata.SyntheticClass -> ... is KotlinClassMetadata.MultiFileClassFacade -> ... is KotlinClassMetadata.MultiFileClassPart -> ... is KotlinClassMetadata.Unknown -> ... }
Let us assume we have obtained an instance of KotlinClassMetadata.Class
; other kinds of classes are handled similarly, except some of them have metadata in a slightly different form. The main way to make sense of the underlying metadata is to access the kmClass
property, which returns an instance of KmClass
(Km
is a shorthand for “Kotlin metadata”):
val klass = metadata.kmClass println(klass.functions.map { it.name }) println(klass.properties.map { it.name })
Please refer to MetadataSmokeTest.listInlineFunctions
for an example where all inline functions are read from the class metadata along with their JVM signatures.
Most of the Km nodes (KmClass
, KmFunction
, KmType
, and so on) have a set of extension properties that allow to get and set various attributes. Most of these attributes are boolean values, but some of them, such as visibility, are represented by enum classes. For example, you can check function visibility and presence of suspend
modifier with corresponding extension properties:
val function: KmFunction = ... if (function.visibility == Visibility.PUBLIC) { println("function ${function.name} is public") } if (function.isSuspend) { println("function ${function.name} has the 'suspend' modifier") }
To create metadata of a Kotlin class file from scratch, construct an instance of KmClass
/KmPackage
/KmLambda
, fill it with the data, and call corresponding KotlinClassMetadata.write
function. Resulting kotlin.Metadata
annotation can be written to a class file.
When using metadata writers from Kotlin source code, it is very convenient to use Kotlin scoping functions such as apply
to reduce boilerplate:
// Writing metadata of a class val klass = KmClass().apply { // Setting the name and the modifiers of the class. name = "MyClass" visibility = Visibility.PUBLIC // Adding one public primary constructor constructors += KmConstructor().apply { visibility = Visibility.PUBLIC isSecondary = false // Setting the JVM signature (for example, to be used by kotlin-reflect) signature = JvmMethodSignature("<init>", "()V") } ... } val annotation = KotlinClassMetadata.writeClass(klass) // Write annotation directly or use annotation.kind, annotation.data1, annotation.data2, etc.
Please refer to MetadataSmokeTest.produceKotlinClassFile
for an example where metadata of a simple Kotlin class is created, and then the class file is produced with ASM and loaded by Kotlin reflection.
Similarly to how KotlinClassMetadata
is used to read/write metadata of Kotlin .class
files, KotlinModuleMetadata
is the entry point for reading/writing .kotlin_module
files. Use KotlinModuleMetadata.read
or KotlinModuleMetadata.write
in very much the same fashion as with the class files. The only difference is that the source for the reader (and the result of the writer) is a simple byte array, not the structured data loaded from kotlin.Metadata
:
// Read the module metadata val bytes = File("META-INF/main.kotlin_module").readBytes() val metadata = KotlinModuleMetadata.read(bytes) val module = metadata.kmModule ... // Write the module metadata val bytes = KotlinModuleMetadata.write(module) File("META-INF/main.kotlin_module").writeBytes(bytes)