# 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 the kotlinx repository at https://kotlin.bintray.com/kotlinx, and 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>
    <repositories>
        <repository>
            <id>bintray-kotlin-kotlinx</id>
            <name>bintray</name>
            <url>https://kotlin.bintray.com/kotlinx</url>
        </repository>
    </repositories>
    ...
</project>
```

Example usage in Gradle:

```
repositories {
    mavenCentral()
    maven { url "https://kotlin.bintray.com/kotlinx/" }
}

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. (Note that loading values of `kotlin.Metadata` reflectively is only possible *from Java sources* until Kotlin 1.3, because this annotation is internal in the standard library, see [KT-23602](https://youtrack.jetbrains.com/issue/KT-23602).)

```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 `accept`, passing an instance of [`KmClassVisitor`](../src/kotlinx/metadata/visitors.kt) to handle the incoming information (`Km` is a shorthand for “Kotlin metadata”):

```kotlin
metadata.accept(object : KmClassVisitor() {
    override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor? {
        // This will be called for each function in the class. "name" is the
        // function name, and "flags" represent modifier flags (see below)

        ...

        // Return an instance of KmFunctionVisitor for more details,
        // or null if this function is of no interest
    }
})
```

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 `visit*` methods take the parameter named `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 in each `visit*` method. For example, for functions, this is common declaration flags (visibility, modality) plus `Flag.Function` flags:

```kotlin
override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor? {
    if (Flag.IS_PUBLIC(flags)) {
        println("function $name is public")
    }
    if (Flag.Function.IS_SUSPEND(flags)) {
        println("function $name has the 'suspend' modifier")
    }
    ...
}
```

## Extensions

Certain information in the metadata of Kotlin `.class` files is JVM-only, just like certain information in the `.meta.js` files on Kotlin/JS is JS-only. To retain the possibility to release Kotlin/JS- (and later, Kotlin/Native-) reading metadata library, we've extracted most of the API of this library to a platform-independent [`kotlinx-metadata`](../) (here platform-independent means not that it's agnostic to the platform it's compiled to, but that it's agnostic to the platform it allows to read Kotlin metadata from), and `kotlinx-metadata-jvm` is a small addition with JVM-only data.

To read platform-specific (in this case, JVM-specific) data, each visitor that has that data declares a `visitExtensions` method, taking the [*extension type*](../src/kotlinx/metadata/extensions.kt) and returning the visitor of that type, capable of reading platform-specific data. The intended way to implement `visitExtensions` for JVM is to check if the given extension type is of the needed JVM extension visitor and return a new instance of that visitor, or return null otherwise. Each JVM extension visitor has its type declared in the `TYPE` variable in the companion object. For example, to read JVM extensions on the property:

```kotlin
override fun visitExtensions(type: KmExtensionType): KmPropertyExtensionVisitor? {
    // If these are JVM property extensions, read them by returning a visitor
    if (type == JvmPropertyExtensionVisitor.TYPE) {
        return object : JvmPropertyExtensionVisitor() {
            // Read JVM property extensions
            ...
        }
    }
    
    // If these are extensions of some other type, ignore them
    return null
}
```

## Writing metadata

To create metadata of a Kotlin class file from scratch, use one of the `Writer` classes declared in `KotlinClassMetadata`'s subclasses. Writers of relevant classes inherit from the corresponding `Km*Visitor` classes. Invoke the corresponding `visit*` methods successively on the writer to add declarations (do not forget to call `visitEnd` where applicable!), and call `write` in the end to produce the metadata. 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 `run` to reduce boilerplate:

```kotlin
// Writing metadata of a class
val header = KotlinClassMetadata.Class.Writer().run {
    // Visiting the name and the modifiers on the class.
    // Flags are constructed by invoking "flagsOf(...)"
    visit(flagsOf(Flag.IS_PUBLIC), "MyClass")
    
    // Adding one public primary constructor
    visitConstructor(flagsOf(Flag.IS_PUBLIC, Flag.Constructor.IS_PRIMARY))!!.run {
        // Visiting JVM signature (for example, to be used by kotlin-reflect)
        (visitExtensions(JvmConstructorExtensionVisitor.TYPE) as JvmConstructorExtensionVisitor).run {
            visit(JvmMethodSignature("<init>", "()V"))
        }
        
        // Not forgetting to call visitEnd at the end of visit of the declaration
        visitEnd()
    }
    
    ...
    ...
    
    // Finally writing everything to arrays of bytes
    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)
metadata.accept(object : KmModuleVisitor() {
    ...
}

// Write the module metadata
val bytes = KotlinModuleMetadata.Writer().run {
    visitPackageParts(...)
    
    write().bytes
}
File("META-INF/main.kotlin_module").writeBytes(bytes)
```

## Laziness

Note that until you invoke `accept` on a `KotlinClassMetadata` or `KotlinModuleMetadata` instance, the data is not completely parsed and verified. If you need to check if the data is not horribly corrupted before proceeding, make sure to call `accept`, even with an empty visitor:

```kotlin
val metadata: KotlinClassMetadata.Class = ...

try {
    // Guarantees eager parsing of the underlying data
    metadata.accept(object : KmClassVisitor() {})
} catch (e: Exception) {
    System.err.println("Metadata is corrupted!")
}
```



