blob: 34cc3412d7515f42aa1c352d1f9d348654909216 [file] [view]
# kotlinx-metadata-jvm
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.
## Usage
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 {
compile "org.jetbrains.kotlinx:kotlinx-metadata-jvm:$kotlinx_metadata_version"
}
```
## Overview
The entry point for reading the Kotlin metadata of a `.class` file is [`KotlinClassMetadata.read`](src/kotlinx/metadata/jvm/KotlinClassMetadata.kt). The data it takes is encapsulated in [`KotlinClassHeader`](src/kotlinx/metadata/jvm/KotlinClassHeader.kt) which is basically what is written in the [`kotlin.Metadata`](../../stdlib/jvm/runtime/kotlin/Metadata.kt) annotation on the class file generated by the Kotlin compiler. Construct `KotlinClassHeader` by reading the values from `kotlin.Metadata` reflectively or from some other resource, and then use `KotlinClassMetadata.read` to obtain the correct instance of the class metadata.
```kotlin
val header = KotlinClassHeader(
...
/* pass Metadata.k, Metadata.d1, Metadata.d2, etc as arguments ... */
)
val metadata = KotlinClassMetadata.read(header)
```
`KotlinClassMetadata` is a sealed class, with subclasses representing all the different kinds of classes generated by the Kotlin compiler. Unless you're sure that you're reading a class of a specific kind and can do a simple cast, a `when` is a good choice to handle all the possibilities:
```kotlin
when (metadata) {
is KotlinClassMetadata.Class -> ...
is KotlinClassMetadata.FileFacade -> ...
is KotlinClassMetadata.SyntheticClass -> ...
is KotlinClassMetadata.MultiFileClassFacade -> ...
is KotlinClassMetadata.MultiFileClassPart -> ...
is KotlinClassMetadata.Unknown -> ...
}
```
Let's assume we've 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 invoke `toKmClass`, which returns an instance of `KmClass` (`Km` is a shorthand for “Kotlin metadata”):
```kotlin
val klass = metadata.toKmClass()
println(klass.functions.map { it.name })
println(klass.properties.map { it.name })
```
Please refer to [`MetadataSmokeTest.listInlineFunctions`](test/kotlinx/metadata/test/MetadataSmokeTest.kt) for an example where all inline functions are read from the class metadata along with their JVM signatures.
## Flags
Numerous objects have a property named `flags` of type `Flags`. These flags represent modifiers or other boolean attributes of a declaration or a type. To check if a certain flag is present, call one of the flags in [`Flag`](../src/kotlinx/metadata/Flag.kt) on the given integer value. The set of applicable flags is documented on each property or the corresponding `visit*` method. For example, for functions, this is common declaration flags (visibility, modality) plus `Flag.Function` flags:
```kotlin
val function: KmFunction = ...
if (Flag.IS_PUBLIC(function.flags)) {
println("function ${function.name} is public")
}
if (Flag.Function.IS_SUSPEND(function.flags)) {
println("function ${function.name} has the 'suspend' modifier")
}
```
## Writing metadata
To create metadata of a Kotlin class file from scratch, construct an instance of `KmClass`/`KmPackage`/`KmLambda`, fill it with the data and call `accept` with the `Writer` class declared in the corresponding `KotlinClassMetadata` subclass. Finally, use `KotlinClassMetadata.header` to obtain the raw data and write it to the `kotlin.Metadata` annotation on a class file.
When using metadata writers from Kotlin source code, it's very convenient to use Kotlin scoping functions such as `apply` to reduce boilerplate:
```kotlin
// Writing metadata of a class
val klass = KmClass().apply {
// Setting the name and the modifiers of the class.
// Flags are constructed by invoking "flagsOf(...)"
name = "MyClass"
flags = flagsOf(Flag.IS_PUBLIC)
// Adding one public primary constructor
constructors += KmConstructor(flagsOf(Flag.IS_PUBLIC, Flag.Constructor.IS_PRIMARY)).apply {
// Setting the JVM signature (for example, to be used by kotlin-reflect)
signature = JvmMethodSignature("<init>", "()V")
}
...
}
// Finally writing everything to arrays of bytes
val header = KotlinClassMetadata.Class.Writer().apply(klass::accept).write().header
// Use header.kind, header.data1, header.data2, etc. to write values to kotlin.Metadata
...
```
Please refer to [`MetadataSmokeTest.produceKotlinClassFile`](test/kotlinx/metadata/test/MetadataSmokeTest.kt) 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.
## Module metadata
Similarly to how `KotlinClassMetadata` is used to read/write metadata of Kotlin `.class` files, [`KotlinModuleMetadata`](src/kotlinx/metadata/jvm/KotlinModuleMetadata.kt) is the entry point for reading/writing `.kotlin_module` files. Use `KotlinModuleMetadata.read` or `KotlinModuleMetadata.Writer` 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`:
```kotlin
// Read the module metadata
val bytes = File("META-INF/main.kotlin_module").readBytes()
val metadata = KotlinModuleMetadata.read(bytes)
val module = metadata.toKmModule()
...
// Write the module metadata
val bytes = KotlinModuleMetadata.Writer().apply(module::accept).write().bytes
File("META-INF/main.kotlin_module").writeBytes(bytes)
```
## Laziness
Note that until you load the actual underlying data of a `KotlinClassMetadata` or `KotlinModuleMetadata` instance by invoking `accept` or one of the `toKm...` methods, the data is not completely parsed and verified. If you need to check if the data is not horribly corrupted before proceeding, ensure that either of those is called:
```kotlin
val metadata: KotlinClassMetadata.Class = ...
try {
// Guarantees eager parsing of the underlying data
metadata.toKmClass()
} catch (e: Exception) {
System.err.println("Metadata is corrupted!")
}
```