| # 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: |
| |
| ```xml |
| <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: |
| |
| ```gradle |
| repositories { |
| mavenCentral() |
| } |
| |
| dependencies { |
| implementation "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 the [`kotlin.Metadata`](../../stdlib/jvm/runtime/kotlin/Metadata.kt) 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. |
| |
| ```kotlin |
| 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'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 for each Node property which has type `Flags`. |
| For example, functions have 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 corresponding `KotlinClassMetadata.write` function. |
| Resulting `KotlinClassMetadata.annotationData` can be used to write `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") |
| } |
| |
| ... |
| } |
| |
| val annotation = KotlinClassMetadata.writeClass(klass).annotationData |
| |
| // Write annotation directly or use annotation.kind, annotation.data1, annotation.data2, etc. |
| ``` |
| |
| 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.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`: |
| |
| ```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.write(module).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 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!") |
| } |
| ``` |
| |
| |
| |