File: ReadMe.md

package info (click to toggle)
kotlin 1.3.31%2Bds1-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 109,524 kB
  • sloc: java: 454,753; xml: 18,599; javascript: 10,452; sh: 513; python: 97; makefile: 54; ansic: 4
file content (198 lines) | stat: -rw-r--r-- 9,813 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# 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!")
}
```