├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ ├── ProjectDeclarations.kt │ ├── ProjectUtils.kt │ ├── Properties.kt │ ├── Publications.kt │ └── PublicationsUtils.kt ├── data-class-with ├── api │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── me │ │ └── eugeniomarletti │ │ └── Api.kt ├── app │ ├── build.gradle.kts │ ├── generated-kotlin-sources.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── com │ │ └── example │ │ └── Test.kt └── processor │ ├── build.gradle.kts │ └── src │ └── main │ └── kotlin │ └── me │ └── eugeniomarletti │ ├── DataClassWithProcessor.kt │ └── Generator.kt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lib ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── me │ └── eugeniomarletti │ └── kotlin │ ├── metadata │ ├── Constants.kt │ ├── Flags.kt │ ├── KotlinClassHeader.kt │ ├── KotlinMetadata.kt │ ├── KotlinMetadataUtils.kt │ └── jvm │ │ ├── JvmDescriptorUtils.kt │ │ ├── JvmProtoBuf.kt │ │ ├── JvmProtoBufUtil.kt │ │ └── package-info.kt │ └── processing │ ├── KotlinAbstractProcessor.kt │ └── KotlinProcessingEnvironment.kt └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | **/build 2 | **/out 3 | local.properties 4 | .gradle 5 | .idea 6 | *.iml 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2018 Eugenio Marletti. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Bintray](https://api.bintray.com/packages/takhion/kotlin-metadata/kotlin-metadata/images/download.svg) ](https://bintray.com/takhion/kotlin-metadata/kotlin-metadata/_latestVersion) 2 | [![License](https://img.shields.io/badge/License-MIT-blue.svg)](/LICENSE) 3 | 4 | # Kotlin Metadata 5 | 6 | This library is a wrapper around Kotlin's `@Metadata` annotation, exposing its data in a typesafe manner. 7 | 8 | The `@Metadata` annotation contains information about Kotlin specific features that would otherwise be lost when compiling Kotlin to Java, and are especially useful when developing annotation processors. 9 | 10 | ## Usage 11 | 12 | Gradle: 13 | ```gradle 14 | compile("me.eugeniomarletti.kotlin.metadata:kotlin-metadata:") 15 | ``` 16 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { kotlin("jvm") version "1.2.40" apply false } 2 | 3 | subprojects { 4 | repositories { 5 | jcenter() 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { `kotlin-dsl` } 2 | 3 | repositories { jcenter() } 4 | 5 | dependencies { 6 | compileOnly(kotlin("gradle-plugin")) 7 | compile("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.0") 8 | } 9 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/ProjectDeclarations.kt: -------------------------------------------------------------------------------- 1 | import com.jfrog.bintray.gradle.BintrayExtension 2 | import org.gradle.api.Project 3 | import org.gradle.api.plugins.JavaPluginConvention 4 | import org.gradle.api.publish.PublishingExtension 5 | import org.gradle.api.tasks.SourceSet 6 | import org.gradle.api.tasks.SourceSetContainer 7 | import org.gradle.kotlin.dsl.get 8 | 9 | val publishing = configuration() 10 | val bintray = configuration() 11 | 12 | val Project.java by extension() 13 | 14 | val SourceSetContainer.main: SourceSet get() = this["main"] 15 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/ProjectUtils.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.Plugin 2 | import org.gradle.api.Project 3 | import org.gradle.kotlin.dsl.configure 4 | import org.gradle.kotlin.dsl.the 5 | import kotlin.properties.ReadOnlyProperty 6 | import kotlin.reflect.KProperty 7 | 8 | typealias ProjectExt0 = Project.() -> R 9 | typealias ProjectExt1 = Project.(T) -> R 10 | 11 | inline fun extension(): ProjectExt0 = { the() } 12 | inline fun configuration(): ProjectExt1 Unit, Unit> = { configure(it) } 13 | 14 | inline fun > Project.applyPlugin() = apply { plugin(T::class.java) } 15 | 16 | operator fun (This.() -> R).getValue(thisRef: This, property: KProperty<*>) = invoke(thisRef) 17 | 18 | fun extraOrEnv(envName: String) = 19 | findProjectProperty { it as? String ?: System.getenv(envName) } 20 | 21 | fun extraOrDefault(defaultValue: Boolean) = 22 | findProjectProperty { 23 | when { 24 | it is Boolean -> it 25 | it is String && it.equals("true", ignoreCase = true) -> true 26 | it is String && it.equals("false", ignoreCase = true) -> false 27 | else -> defaultValue 28 | } 29 | } 30 | 31 | inline fun findProjectProperty(crossinline transformValue: (Any?) -> T) = 32 | readOnlyProperty { findProperty(it.name).let(transformValue) } 33 | 34 | inline fun readOnlyProperty(crossinline getValue: This.(property: KProperty<*>) -> T) = 35 | object : ReadOnlyProperty { 36 | override fun getValue(thisRef: This, property: KProperty<*>): T = getValue(thisRef, property) 37 | } 38 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Properties.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("MayBeConstant") 2 | 3 | import org.gradle.api.Project 4 | 5 | val codeVersion = "1.4.0" 6 | 7 | val gitHubUser = "Takhion" 8 | val gitHubRepo = "kotlin-metadata" 9 | val gitHubRepoDomain = "github.com/$gitHubUser/$gitHubRepo" 10 | 11 | val gitTag = "v$codeVersion" 12 | val gitRepo = "$gitHubRepoDomain.git" 13 | 14 | val mainRepoUrl = "https://$gitHubRepoDomain" 15 | val taggedRepoUrl = "$mainRepoUrl/tree/$gitTag" 16 | 17 | val libName = "kotlin-metadata" 18 | val libDescription = "Analysis of Kotlin type metadata during annotation processing." 19 | val libUrl = mainRepoUrl 20 | 21 | val libGroupId = "me.eugeniomarletti.kotlin.metadata" 22 | val libArtifactId = "kotlin-metadata" 23 | val libVersion = codeVersion 24 | 25 | val publicationName = libArtifactId.split("-").joinToString("") { it.capitalize() }.decapitalize() 26 | val publicationTaskName = "publish${publicationName.capitalize()}PublicationToMavenRepository" 27 | 28 | val authorName = "Eugenio Marletti" 29 | 30 | val licenseName = "MIT" 31 | val Project.licenseFile get() = rootDir.resolve("LICENSE") 32 | val Project.licenseUrl get() = "$mainRepoUrl/blob/$gitTag/${licenseFile.toRelativeString(rootDir)}" 33 | 34 | val issuesSystem = "GitHub" 35 | val issuesUrl = "$mainRepoUrl/issues" 36 | 37 | val bintrayRepo = "kotlin-metadata" 38 | val bintrayTags = arrayOf("kotlin", "kotlin-metadata") 39 | 40 | val Project.bintrayPublish by extraOrDefault(true) 41 | val Project.bintrayOverride by extraOrDefault(false) 42 | val Project.bintrayDryRun by extraOrDefault(false) 43 | val Project.bintrayGpgSign by extraOrDefault(true) 44 | val Project.bintrayMavenCentralSync by extraOrDefault(true) 45 | val Project.bintrayMavenCentralClose by extraOrDefault(true) 46 | 47 | val Project.bintrayUser by extraOrEnv("BINTRAY_USER") 48 | val Project.bintrayKey by extraOrEnv("BINTRAY_KEY") 49 | 50 | val Project.sonatypeUser by extraOrEnv("SONATYPE_USER") 51 | val Project.sonatypePassword by extraOrEnv("SONATYPE_PASSWORD") 52 | 53 | val Project.outputDir get() = buildDir.resolve("out") 54 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Publications.kt: -------------------------------------------------------------------------------- 1 | import com.jfrog.bintray.gradle.Artifact 2 | import com.jfrog.bintray.gradle.BintrayPlugin 3 | import org.gradle.api.Project 4 | import org.gradle.api.Task 5 | import org.gradle.api.plugins.JavaLibraryPlugin 6 | import org.gradle.api.publish.maven.MavenPublication 7 | import org.gradle.api.publish.maven.plugins.MavenPublishPlugin 8 | import org.gradle.api.tasks.bundling.Jar 9 | import org.gradle.kotlin.dsl.get 10 | import org.gradle.kotlin.dsl.invoke 11 | import org.gradle.kotlin.dsl.task 12 | 13 | fun Project.configurePublications(uploadTaskName: String = "upload"): Task { 14 | 15 | applyPlugin() 16 | applyPlugin() 17 | applyPlugin() 18 | 19 | val sourcesJar = task("sourcesJar") { 20 | from(java.sourceSets.main.allSource) 21 | classifier = "sources" 22 | } 23 | 24 | val javadocJar = task("javadocJar") { 25 | classifier = "javadoc" 26 | } 27 | 28 | publishing { 29 | repositories.maven { url = uri(outputDir) } 30 | (publications) { 31 | publicationName(MavenPublication::class) { 32 | from(components["java"]) 33 | artifact(sourcesJar) 34 | artifact(javadocJar) 35 | 36 | groupId = libGroupId 37 | artifactId = libArtifactId 38 | version = libVersion 39 | pom.buildXml { 40 | "name"..libName 41 | "description"..libDescription 42 | "url"..libUrl 43 | "licenses" { 44 | "license" { 45 | "name"..licenseName 46 | "url"..licenseUrl 47 | } 48 | } 49 | "issueManagement" { 50 | "system"..issuesSystem 51 | "url"..issuesUrl 52 | } 53 | "developers" { 54 | "developer" { 55 | "name"..authorName 56 | } 57 | } 58 | "scm" { 59 | "connection".."scm:git:git://$gitRepo" 60 | "developerConnection".."scm:git:ssh://$gitRepo" 61 | "tag"..gitTag 62 | "url"..taggedRepoUrl 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | bintray { 70 | publish = bintrayPublish 71 | override = bintrayOverride 72 | dryRun = bintrayDryRun 73 | user = bintrayUser 74 | key = bintrayKey 75 | filesSpec { 76 | fileUploads = fileTree(outputDir).map { 77 | Artifact().apply { 78 | file = it 79 | setPath(it.toRelativeString(outputDir)) 80 | } 81 | } 82 | } 83 | pkg { 84 | repo = bintrayRepo 85 | name = libName 86 | desc = libDescription 87 | websiteUrl = libUrl 88 | issueTrackerUrl = issuesUrl 89 | githubRepo = "$gitHubUser/$gitHubRepo" 90 | vcsUrl = "https://$gitRepo" 91 | setLabels(*bintrayTags) 92 | setLicenses(licenseName) 93 | version { 94 | name = libVersion 95 | vcsTag = gitTag 96 | gpg.sign = bintrayGpgSign 97 | mavenCentralSync { 98 | sync = bintrayMavenCentralSync 99 | close = if (bintrayMavenCentralClose) "1" else "0" 100 | user = sonatypeUser 101 | password = sonatypePassword 102 | } 103 | } 104 | } 105 | } 106 | 107 | val uploadTask = task(uploadTaskName) 108 | val bintrayUploadTask = tasks["bintrayUpload"] 109 | 110 | uploadTask.dependsOn(bintrayUploadTask) 111 | bintrayUploadTask.dependsOn(publicationTaskName) 112 | 113 | return uploadTask 114 | } 115 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/PublicationsUtils.kt: -------------------------------------------------------------------------------- 1 | import com.jfrog.bintray.gradle.BintrayExtension 2 | import com.jfrog.bintray.gradle.BintrayExtension.MavenCentralSyncConfig 3 | import com.jfrog.bintray.gradle.BintrayExtension.PackageConfig 4 | import com.jfrog.bintray.gradle.BintrayExtension.VersionConfig 5 | import com.jfrog.bintray.gradle.RecordingCopyTask 6 | import org.gradle.api.publish.maven.MavenPom 7 | import org.gradle.kotlin.dsl.closureOf 8 | import org.w3c.dom.Document 9 | import org.w3c.dom.Element 10 | import org.w3c.dom.Node 11 | 12 | fun BintrayExtension.filesSpec(configure: RecordingCopyTask.() -> Unit): Any? = filesSpec(closureOf(configure)) 13 | fun BintrayExtension.pkg(configure: PackageConfig.() -> Unit): Any? = pkg(closureOf(configure)) 14 | fun PackageConfig.version(configure: VersionConfig.() -> Unit): Any? = version(closureOf(configure)) 15 | fun VersionConfig.mavenCentralSync(configure: MavenCentralSyncConfig.() -> Unit): Any? = mavenCentralSync(closureOf(configure)) 16 | 17 | inline fun MavenPom.buildXml(crossinline xml: NodeContext.() -> Unit) { 18 | withXml { 19 | val root = asElement() 20 | NodeContext(root, root.ownerDocument).xml() 21 | } 22 | } 23 | 24 | class NodeContext(val node: T, val doc: Document) { 25 | 26 | inline operator fun String.invoke(nodeContent: NodeContext.() -> Unit): Element = 27 | doc.createElement(this) 28 | .also { NodeContext(it, doc).nodeContent() } 29 | .also { node.appendChild(it) } 30 | 31 | operator fun String.rangeTo(textContent: String) = 32 | invoke { node.textContent = textContent } 33 | } 34 | -------------------------------------------------------------------------------- /data-class-with/api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { kotlin("jvm") } 2 | 3 | dependencies { compile(kotlin("runtime")) } 4 | -------------------------------------------------------------------------------- /data-class-with/api/src/main/kotlin/me/eugeniomarletti/Api.kt: -------------------------------------------------------------------------------- 1 | package me.eugeniomarletti 2 | 3 | import kotlin.annotation.AnnotationTarget.CLASS 4 | 5 | @Target(CLASS) 6 | annotation class WithMethods( 7 | val extensionName: String = "with") 8 | 9 | inline fun checkIfChanged(old: T, new: T, ifChanged: () -> Unit): T = 10 | if (old === new || old == new) old 11 | else { 12 | ifChanged() 13 | new 14 | } 15 | -------------------------------------------------------------------------------- /data-class-with/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | kotlin("kapt") 4 | } 5 | 6 | apply { from("generated-kotlin-sources.gradle.kts") } 7 | 8 | dependencies { 9 | compile(kotlin("stdlib")) 10 | compile(project(":data-class-with:api")) 11 | kapt(project(":data-class-with:processor")) 12 | } 13 | -------------------------------------------------------------------------------- /data-class-with/app/generated-kotlin-sources.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.gradle.plugins.ide.idea.model.IdeaModel 2 | import org.gradle.plugins.ide.idea.IdeaPlugin 3 | 4 | apply { plugin(IdeaPlugin::class.java) } 5 | 6 | configure { 7 | module { 8 | val dirs = listOf( 9 | "generated/source/kapt/main", 10 | "generated/source/kaptKotlin/main", 11 | "tmp/kapt/main/kotlinGenerated") 12 | .map(buildDir::resolve) 13 | 14 | listOf(sourceDirs, generatedSourceDirs).forEach { it.addAll(dirs) } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /data-class-with/app/src/main/kotlin/com/example/Test.kt: -------------------------------------------------------------------------------- 1 | package com.example 2 | 3 | import me.eugeniomarletti.WithMethods 4 | 5 | typealias Foo = TestDataClass2 6 | typealias Bar = TestDataClass2 7 | 8 | class IntArrayList : ArrayList() 9 | 10 | @WithMethods 11 | data class TestDataClass1(val counter: Int, val name: List) { 12 | constructor() : this(0, listOf()) 13 | 14 | fun copy(): TestDataClass1 = TestDataClass1() 15 | fun copy(name: List): TestDataClass1 = TestDataClass1(counter = this.counter, name = name) 16 | } 17 | 18 | @WithMethods 19 | @Suppress("AddVarianceModifier") 20 | data class TestDataClass2(val generic1: T, val generic2: R) 21 | 22 | @WithMethods 23 | data class TestDataClass3(val foo: Foo, val bar: Bar?) 24 | 25 | @WithMethods 26 | data class TestDataClass4, R>(val a: TestDataClass2, val b: TestDataClass2) 27 | 28 | @WithMethods(extensionName = "copyIfNecessary") 29 | data class TestDataClass5(val foo: Foo, val list: IntArrayList?, val mList: MutableList?) 30 | 31 | @WithMethods 32 | data class TestDataClass6(val a: Int) 33 | 34 | class Parent { 35 | @WithMethods 36 | data class TestDataClass6(val foo: Foo, val list: IntArrayList?) where R : Any, R : Runnable 37 | } 38 | 39 | //TODO write proper tests 40 | fun main(vararg args: String) { 41 | TestDataClass2(0, "").let { 42 | check(it !== it.copy(generic1 = 1)) 43 | check(it !== it.with(generic1 = 1)) 44 | check(it !== it.copy(generic1 = 0, generic2 = "")) 45 | check(it === it.with(generic1 = 0, generic2 = "")) 46 | check(it === it.withGeneric1(0)) 47 | check(it === it.withGeneric2("")) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /data-class-with/processor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | kotlin("kapt") 4 | } 5 | 6 | dependencies { 7 | compile(kotlin("stdlib")) 8 | compileOnly("com.google.auto.service:auto-service:1.0-rc4") 9 | kapt("com.google.auto.service:auto-service:1.0-rc4") 10 | compile(project(":data-class-with:api")) 11 | compile(project(":lib")) 12 | } 13 | -------------------------------------------------------------------------------- /data-class-with/processor/src/main/kotlin/me/eugeniomarletti/DataClassWithProcessor.kt: -------------------------------------------------------------------------------- 1 | package me.eugeniomarletti 2 | 3 | import com.google.auto.service.AutoService 4 | import me.eugeniomarletti.Generator.Input 5 | import me.eugeniomarletti.Generator.Parameter 6 | import me.eugeniomarletti.Generator.TypeParameter 7 | import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata 8 | import me.eugeniomarletti.kotlin.metadata.KotlinMetadataUtils 9 | import me.eugeniomarletti.kotlin.metadata.extractFullName 10 | import me.eugeniomarletti.kotlin.metadata.isDataClass 11 | import me.eugeniomarletti.kotlin.metadata.isPrimary 12 | import me.eugeniomarletti.kotlin.metadata.kaptGeneratedOption 13 | import me.eugeniomarletti.kotlin.metadata.kotlinMetadata 14 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf 15 | import me.eugeniomarletti.kotlin.processing.KotlinAbstractProcessor 16 | import java.io.File 17 | import javax.annotation.processing.Processor 18 | import javax.annotation.processing.RoundEnvironment 19 | import javax.lang.model.SourceVersion 20 | import javax.lang.model.element.Element 21 | import javax.lang.model.element.TypeElement 22 | import javax.tools.Diagnostic.Kind.ERROR 23 | 24 | @AutoService(Processor::class) 25 | @Suppress("unused") 26 | class DataClassWithProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils { 27 | 28 | private val annotationName = WithMethods::class.java.canonicalName 29 | 30 | override fun getSupportedAnnotationTypes() = setOf(annotationName) 31 | 32 | override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest() 33 | 34 | override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean { 35 | val annotationElement = elementUtils.getTypeElement(annotationName) 36 | @Suppress("LoopToCallChain") 37 | for (element in roundEnv.getElementsAnnotatedWith(annotationElement)) { 38 | val input = getInputFrom(element) ?: continue 39 | if (!input.generateAndWrite()) return true 40 | } 41 | return true 42 | } 43 | 44 | private fun getInputFrom(element: Element): Input? { 45 | val metadata = element.kotlinMetadata 46 | 47 | if (metadata !is KotlinClassMetadata) { 48 | errorMustBeDataClass(element) 49 | return null 50 | } 51 | 52 | val classData = metadata.data 53 | val (nameResolver, classProto) = classData 54 | 55 | fun ProtoBuf.Type.extractFullName() = extractFullName(classData) 56 | 57 | if (!classProto.isDataClass) { 58 | errorMustBeDataClass(element) 59 | return null 60 | } 61 | 62 | val fqClassName = nameResolver.getString(classProto.fqName).replace('/', '.') 63 | 64 | val `package` = nameResolver.getString(classProto.fqName).substringBeforeLast('/').replace('/', '.') 65 | 66 | val typeArguments = classProto.typeParameterList 67 | .map { typeArgument -> 68 | TypeParameter( 69 | name = nameResolver.getString(typeArgument.name), 70 | upperBoundsFqClassNames = typeArgument.upperBoundList.map { it.extractFullName() }) 71 | } 72 | 73 | val parameters = classProto.constructorList 74 | .single { it.isPrimary } 75 | .valueParameterList 76 | .map { valueParameter -> 77 | Parameter( 78 | name = nameResolver.getString(valueParameter.name), 79 | fqClassName = valueParameter.type.extractFullName()) 80 | } 81 | 82 | val extensionName = element.getAnnotation(WithMethods::class.java).extensionName 83 | 84 | return Input( 85 | fqClassName = fqClassName, 86 | `package` = `package`, 87 | typeArgumentList = typeArguments, 88 | parameterList = parameters, 89 | extensionName = extensionName) 90 | } 91 | 92 | private fun errorMustBeDataClass(element: Element) { 93 | messager.printMessage(ERROR, 94 | "@${WithMethods::class.java.simpleName} can't be applied to $element: must be a Kotlin data class", element) 95 | } 96 | 97 | private fun Input.generateAndWrite(): Boolean { 98 | val generatedDir = generatedDir ?: run { 99 | messager.printMessage(ERROR, "Can't find option '$kaptGeneratedOption'") 100 | return false 101 | } 102 | val dirPath = `package`.replace('.', File.separatorChar) 103 | val filePath = "DataClassWithExtensions_${fqClassName.substringAfter(`package`).replace('.', '_')}.kt" 104 | val dir = File(generatedDir, dirPath).also { it.mkdirs() } 105 | val file = File(dir, filePath) 106 | file.writeText(generate()) 107 | return true 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /data-class-with/processor/src/main/kotlin/me/eugeniomarletti/Generator.kt: -------------------------------------------------------------------------------- 1 | package me.eugeniomarletti 2 | 3 | internal object Generator { 4 | 5 | private const val checkIfChanged = "me.eugeniomarletti.checkIfChanged" 6 | 7 | internal data class Input( 8 | val fqClassName: String, 9 | val `package`: String, 10 | val typeArgumentList: List, 11 | val parameterList: List, 12 | val extensionName: String) { 13 | 14 | fun generate() = Generator.generate(this) 15 | } 16 | 17 | internal data class TypeParameter( 18 | val name: String, 19 | val upperBoundsFqClassNames: List) 20 | 21 | internal data class Parameter( 22 | val name: String, 23 | val fqClassName: String) 24 | 25 | fun generate(input: Input) = run { 26 | val (fqClassName, `package`, typeArgumentList, parameterList, extensionName) = input 27 | val main = main(fqClassName, typeArgumentList, extensionName, parameterList) 28 | val specifics = parameterList.joinToString(separator = "\n") { (parameterName, parameterClassName) -> 29 | specific(fqClassName, typeArgumentList, extensionName, parameterName, parameterClassName) 30 | } 31 | 32 | """ 33 | |package $`package` 34 | | 35 | |$main 36 | | 37 | |$specifics 38 | """.trimMargin() 39 | } 40 | 41 | private fun main( 42 | className: String, 43 | typeArgumentList: List, 44 | extensionName: String, 45 | parameters: List 46 | ) = run { 47 | val typeArguments = typeArguments(typeArgumentList) 48 | val functionArgs = parameters.joinToString { (name, className) -> "$name: $className = this.$name" } 49 | val whereClause = whereClause(typeArgumentList) 50 | val checks = parameters.joinToString(separator = "\n ", transform = { (name) -> check(name) }) 51 | val copyArgs = parameters.joinToString { (name) -> "$name = _$name" } 52 | 53 | """ 54 | |fun $typeArguments $className$typeArguments.$extensionName($functionArgs) $whereClause 55 | | = run { 56 | | var copy = false 57 | | $checks 58 | | if (!copy) this else copy($copyArgs) 59 | |} 60 | """.trimMargin() 61 | } 62 | 63 | private fun typeArguments(typeArgumentList: List) = 64 | typeArgumentList 65 | .takeIf { it.isNotEmpty() } 66 | ?.joinToString(prefix = "<", postfix = ">") { it.name } 67 | ?: "" 68 | 69 | private fun whereClause(typeArguments: List) = 70 | typeArguments 71 | .flatMap { (name, upperBounds) -> upperBounds.map { Pair(name, it) } } 72 | .filterNot { (_, upperBound) -> upperBound.isBlank() } 73 | .takeIf { it.isNotEmpty() } 74 | ?.joinToString(prefix = "\n where ") { (name, upperBound) -> "$name : $upperBound" } 75 | ?: "" 76 | 77 | private fun check(parameterName: String) = 78 | "val _$parameterName = $checkIfChanged(old = this.$parameterName, new = $parameterName, ifChanged = { copy = true })" 79 | 80 | private fun specific( 81 | className: String, 82 | typeArgumentList: List, 83 | extensionName: String, 84 | parameterName: String, 85 | parameterClassName: String 86 | ) = run { 87 | val _methodName = extensionName + parameterName.capitalize() 88 | val typeArguments = typeArguments(typeArgumentList) 89 | val whereClause = whereClause(typeArgumentList) 90 | 91 | """ 92 | |fun $typeArguments $className$typeArguments.$_methodName($parameterName: $parameterClassName = this.$parameterName) $whereClause 93 | | = if (this.$parameterName == $parameterName) this else copy($parameterName = $parameterName) 94 | |""".trimMargin() 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Takhion/kotlin-metadata/756ff8e82499d59481681860cfda5baa79c3af3c/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /lib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { kotlin("jvm") } 4 | 5 | dependencies { 6 | compile(kotlin("stdlib")) 7 | compile("me.eugeniomarletti.kotlin.metadata:kotlin-compiler-lite:1.0.3-k-1.2.40") 8 | } 9 | 10 | tasks.withType { 11 | kotlinOptions { 12 | freeCompilerArgs += listOf("-module-name", "$libGroupId.$libArtifactId") 13 | } 14 | } 15 | 16 | tasks.withType { baseName = libArtifactId } 17 | 18 | val upload = configurePublications() 19 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/me/eugeniomarletti/kotlin/metadata/Constants.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package me.eugeniomarletti.kotlin.metadata 4 | 5 | /** 6 | * Name of the processor option containing the path to the Kotlin generated src dir. 7 | */ 8 | const val kaptGeneratedOption = "kapt.kotlin.generated" 9 | 10 | /** 11 | * Fully qualified name class name of [kotlin.Metadata] (which is internal). 12 | */ 13 | const val kotlinMetadataAnnotation = "kotlin.Metadata" 14 | 15 | /** 16 | * Postfix of the method name containing the [kotlin.Metadata] annotation for the relative property. 17 | * @see [getPropertyOrNull] 18 | */ 19 | const val kotlinPropertyAnnotationsFunPostfix = "\$annotations" 20 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/me/eugeniomarletti/kotlin/metadata/Flags.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package me.eugeniomarletti.kotlin.metadata 4 | 5 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf 6 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Class.Kind 7 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.MemberKind 8 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Modality 9 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf.Visibility 10 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.deserialization.Flags 11 | import me.eugeniomarletti.kotlin.metadata.shadow.serialization.deserialization.MemberDeserializer 12 | 13 | val ProtoBuf.Type.isSuspendType: Boolean get() = Flags.SUSPEND_TYPE[flags] 14 | 15 | val ProtoBuf.Class.hasAnnotations: Boolean get() = Flags.HAS_ANNOTATIONS[flags] 16 | val ProtoBuf.Class.visibility: Visibility? get() = Flags.VISIBILITY[flags] 17 | val ProtoBuf.Class.modality: Modality? get() = Flags.MODALITY[flags] 18 | val ProtoBuf.Class.classKind: Kind get() = Flags.CLASS_KIND[flags] ?: ProtoBuf.Class.Kind.CLASS 19 | val ProtoBuf.Class.isInnerClass: Boolean get() = Flags.IS_INNER[flags] 20 | val ProtoBuf.Class.isDataClass: Boolean get() = Flags.IS_DATA[flags] 21 | val ProtoBuf.Class.isExternalClass: Boolean get() = Flags.IS_EXTERNAL_CLASS[flags] 22 | val ProtoBuf.Class.isExpectClass: Boolean get() = Flags.IS_EXPECT_CLASS[flags] 23 | 24 | val ProtoBuf.TypeAlias.hasAnnotations: Boolean get() = Flags.HAS_ANNOTATIONS[flags] 25 | val ProtoBuf.TypeAlias.visibility: Visibility? get() = Flags.VISIBILITY[flags] 26 | 27 | val ProtoBuf.Constructor.hasAnnotations: Boolean get() = Flags.HAS_ANNOTATIONS[flags] 28 | val ProtoBuf.Constructor.visibility: Visibility? get() = Flags.VISIBILITY[flags] 29 | val ProtoBuf.Constructor.isSecondary: Boolean get() = Flags.IS_SECONDARY[flags] 30 | val ProtoBuf.Constructor.isPrimary: Boolean get() = !isSecondary 31 | 32 | val ProtoBuf.ValueParameter.hasAnnotations: Boolean get() = Flags.HAS_ANNOTATIONS[flags] 33 | val ProtoBuf.ValueParameter.declaresDefaultValue: Boolean get() = Flags.DECLARES_DEFAULT_VALUE[flags] 34 | val ProtoBuf.ValueParameter.isCrossInline: Boolean get() = Flags.IS_CROSSINLINE[flags] 35 | val ProtoBuf.ValueParameter.isNoInline: Boolean get() = Flags.IS_NOINLINE[flags] 36 | 37 | val ProtoBuf.Function.hasAnnotations: Boolean get() = Flags.HAS_ANNOTATIONS[flagsOrOld] 38 | val ProtoBuf.Function.visibility: Visibility? get() = Flags.VISIBILITY[flagsOrOld] 39 | val ProtoBuf.Function.modality: Modality? get() = Flags.MODALITY[flagsOrOld] 40 | val ProtoBuf.Function.memberKind: MemberKind? get() = Flags.MEMBER_KIND[flagsOrOld] 41 | val ProtoBuf.Function.isOperator: Boolean get() = Flags.IS_OPERATOR[flagsOrOld] 42 | val ProtoBuf.Function.isInfix: Boolean get() = Flags.IS_INFIX[flagsOrOld] 43 | val ProtoBuf.Function.isInline: Boolean get() = Flags.IS_INLINE[flagsOrOld] 44 | val ProtoBuf.Function.isTailRec: Boolean get() = Flags.IS_TAILREC[flagsOrOld] 45 | val ProtoBuf.Function.isExternalFunction: Boolean get() = Flags.IS_EXTERNAL_FUNCTION[flagsOrOld] 46 | val ProtoBuf.Function.isSuspend: Boolean get() = Flags.IS_SUSPEND[flagsOrOld] 47 | val ProtoBuf.Function.isExpectFunction: Boolean get() = Flags.IS_EXPECT_FUNCTION[flagsOrOld] 48 | 49 | val ProtoBuf.Property.hasAnnotations: Boolean get() = Flags.HAS_ANNOTATIONS[flagsOrOld] 50 | val ProtoBuf.Property.visibility: Visibility? get() = Flags.VISIBILITY[flagsOrOld] 51 | val ProtoBuf.Property.modality: Modality? get() = Flags.MODALITY[flagsOrOld] 52 | val ProtoBuf.Property.memberKind: MemberKind? get() = Flags.MEMBER_KIND[flagsOrOld] 53 | val ProtoBuf.Property.isVar: Boolean get() = Flags.IS_VAR[flagsOrOld] 54 | val ProtoBuf.Property.isVal: Boolean get() = !isVar 55 | val ProtoBuf.Property.hasGetter: Boolean get() = Flags.HAS_GETTER[flagsOrOld] 56 | val ProtoBuf.Property.hasSetter: Boolean get() = Flags.HAS_SETTER[flagsOrOld] 57 | val ProtoBuf.Property.isConst: Boolean get() = Flags.IS_CONST[flagsOrOld] 58 | val ProtoBuf.Property.isLateInit: Boolean get() = Flags.IS_LATEINIT[flagsOrOld] 59 | val ProtoBuf.Property.hasConstant: Boolean get() = Flags.HAS_CONSTANT[flagsOrOld] 60 | val ProtoBuf.Property.isExternalProperty: Boolean get() = Flags.IS_EXTERNAL_PROPERTY[flagsOrOld] 61 | val ProtoBuf.Property.isDelegated: Boolean get() = Flags.IS_DELEGATED[flagsOrOld] 62 | val ProtoBuf.Property.isExpectProperty: Boolean get() = Flags.IS_EXPECT_PROPERTY[flagsOrOld] 63 | 64 | val ProtoBuf.Property.getterHasAnnotations: Boolean get() = Flags.HAS_ANNOTATIONS.get(getterFlags) 65 | val ProtoBuf.Property.getterVisibility: Visibility? get() = Flags.VISIBILITY.get(getterFlags) 66 | val ProtoBuf.Property.getterModality: Modality? get() = Flags.MODALITY.get(getterFlags) 67 | val ProtoBuf.Property.isGetterNotDefault: Boolean get() = hasGetterFlags() && Flags.IS_NOT_DEFAULT.get(getterFlags) 68 | val ProtoBuf.Property.isGetterDefault: Boolean get() = !isGetterNotDefault 69 | val ProtoBuf.Property.isGetterExternal: Boolean get() = hasGetterFlags() && Flags.IS_EXTERNAL_ACCESSOR.get(getterFlags) 70 | val ProtoBuf.Property.isGetterInline: Boolean get() = hasGetterFlags() && Flags.IS_INLINE_ACCESSOR.get(getterFlags) 71 | 72 | val ProtoBuf.Property.setterHasAnnotations: Boolean get() = Flags.HAS_ANNOTATIONS.get(setterFlags) 73 | val ProtoBuf.Property.setterVisibility: Visibility? get() = Flags.VISIBILITY.get(setterFlags) 74 | val ProtoBuf.Property.setterModality: Modality? get() = Flags.MODALITY.get(setterFlags) 75 | val ProtoBuf.Property.isSetterNotDefault: Boolean get() = hasSetterFlags() && Flags.IS_NOT_DEFAULT.get(setterFlags) 76 | val ProtoBuf.Property.isSetterDefault: Boolean get() = !isSetterNotDefault 77 | val ProtoBuf.Property.isSetterExternal: Boolean get() = hasSetterFlags() && Flags.IS_EXTERNAL_ACCESSOR.get(setterFlags) 78 | val ProtoBuf.Property.isSetterInline: Boolean get() = hasSetterFlags() && Flags.IS_INLINE_ACCESSOR.get(setterFlags) 79 | 80 | @Deprecated("The keyword 'header' was renamed to 'expect'", ReplaceWith("isExpectClass")) 81 | val ProtoBuf.Class.isHeaderClass: Boolean 82 | get() = isExpectClass 83 | 84 | @Deprecated("The keyword 'header' was renamed to 'expect'", ReplaceWith("isExpectFunction")) 85 | val ProtoBuf.Function.isHeaderFunction: Boolean 86 | get() = isExpectFunction 87 | 88 | @Deprecated("The keyword 'header' was renamed to 'expect'", ReplaceWith("isExpectProperty")) 89 | val ProtoBuf.Property.isHeaderProperty: Boolean 90 | get() = isExpectProperty 91 | 92 | private val ProtoBuf.Function.flagsOrOld: Int get() = if (hasFlags()) flags else loadOldFlags(oldFlags) 93 | private val ProtoBuf.Property.flagsOrOld: Int get() = if (hasFlags()) flags else loadOldFlags(oldFlags) 94 | 95 | /** 96 | * @see [MemberDeserializer.loadOldFlags] 97 | */ 98 | private fun loadOldFlags(oldFlags: Int): Int { 99 | val lowSixBits = oldFlags and 0x3f 100 | val rest = (oldFlags shr 8) shl 6 101 | return lowSixBits + rest 102 | } 103 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/me/eugeniomarletti/kotlin/metadata/KotlinClassHeader.kt: -------------------------------------------------------------------------------- 1 | package me.eugeniomarletti.kotlin.metadata 2 | 3 | import me.eugeniomarletti.kotlin.metadata.shadow.load.java.JvmAnnotationNames.BYTECODE_VERSION_FIELD_NAME 4 | import me.eugeniomarletti.kotlin.metadata.shadow.load.java.JvmAnnotationNames.KIND_FIELD_NAME 5 | import me.eugeniomarletti.kotlin.metadata.shadow.load.java.JvmAnnotationNames.METADATA_DATA_FIELD_NAME 6 | import me.eugeniomarletti.kotlin.metadata.shadow.load.java.JvmAnnotationNames.METADATA_EXTRA_INT_FIELD_NAME 7 | import me.eugeniomarletti.kotlin.metadata.shadow.load.java.JvmAnnotationNames.METADATA_EXTRA_STRING_FIELD_NAME 8 | import me.eugeniomarletti.kotlin.metadata.shadow.load.java.JvmAnnotationNames.METADATA_PACKAGE_NAME_FIELD_NAME 9 | import me.eugeniomarletti.kotlin.metadata.shadow.load.java.JvmAnnotationNames.METADATA_STRINGS_FIELD_NAME 10 | import me.eugeniomarletti.kotlin.metadata.shadow.load.java.JvmAnnotationNames.METADATA_VERSION_FIELD_NAME 11 | import me.eugeniomarletti.kotlin.metadata.shadow.load.java.JvmBytecodeBinaryVersion 12 | import me.eugeniomarletti.kotlin.metadata.shadow.load.kotlin.JvmMetadataVersion 13 | import me.eugeniomarletti.kotlin.metadata.shadow.load.kotlin.header.KotlinClassHeader 14 | import me.eugeniomarletti.kotlin.metadata.shadow.load.kotlin.header.KotlinClassHeader.Kind.CLASS 15 | import me.eugeniomarletti.kotlin.metadata.shadow.load.kotlin.header.KotlinClassHeader.Kind.FILE_FACADE 16 | import me.eugeniomarletti.kotlin.metadata.shadow.load.kotlin.header.KotlinClassHeader.Kind.MULTIFILE_CLASS_PART 17 | import me.eugeniomarletti.kotlin.metadata.shadow.load.kotlin.header.ReadKotlinClassHeaderAnnotationVisitor 18 | import javax.lang.model.element.AnnotationValue 19 | import javax.lang.model.element.Element 20 | import javax.lang.model.element.TypeElement 21 | 22 | /** 23 | * @see ReadKotlinClassHeaderAnnotationVisitor 24 | */ 25 | //TODO create a proper visitor 26 | internal val Element.kotlinClassHeader: KotlinClassHeader? 27 | get() { 28 | var metadataVersion: JvmMetadataVersion? = null 29 | var bytecodeVersion: JvmBytecodeBinaryVersion? = null 30 | var extraString: String? = null 31 | var extraInt = 0 32 | var data: Array? = null 33 | var strings: Array? = null 34 | var incompatibleData: Array? = null 35 | var headerKind: KotlinClassHeader.Kind? = null 36 | var packageName: String? = null 37 | 38 | for (annotation in annotationMirrors) { 39 | if ((annotation.annotationType.asElement() as TypeElement).qualifiedName.toString() != kotlinMetadataAnnotation) continue 40 | 41 | for ((element, _value) in annotation.elementValues) { 42 | val name = element.simpleName.toString().takeIf { it.isNotEmpty() } ?: continue 43 | val value: Any? = unwrapAnnotationValue(_value) 44 | when { 45 | KIND_FIELD_NAME == name && value is Int -> 46 | headerKind = KotlinClassHeader.Kind.getById(value) 47 | METADATA_VERSION_FIELD_NAME == name -> 48 | metadataVersion = JvmMetadataVersion(*@Suppress("UNCHECKED_CAST") (value as List).toIntArray()) 49 | BYTECODE_VERSION_FIELD_NAME == name -> 50 | bytecodeVersion = JvmBytecodeBinaryVersion(*@Suppress("UNCHECKED_CAST") (value as List).toIntArray()) 51 | METADATA_EXTRA_STRING_FIELD_NAME == name && value is String -> 52 | extraString = value 53 | METADATA_EXTRA_INT_FIELD_NAME == name && value is Int -> 54 | extraInt = value 55 | METADATA_DATA_FIELD_NAME == name -> 56 | data = @Suppress("UNCHECKED_CAST") (value as List).toTypedArray() 57 | METADATA_STRINGS_FIELD_NAME == name -> 58 | strings = @Suppress("UNCHECKED_CAST") (value as List).toTypedArray() 59 | METADATA_PACKAGE_NAME_FIELD_NAME == name && value is String -> 60 | packageName = value 61 | } 62 | } 63 | } 64 | 65 | if (headerKind == null) { 66 | return null 67 | } 68 | 69 | if (metadataVersion == null || !metadataVersion.isCompatible()) { 70 | incompatibleData = data 71 | data = null 72 | } 73 | else if ((headerKind == CLASS || headerKind == FILE_FACADE || headerKind == MULTIFILE_CLASS_PART) && data == null) { 74 | // This means that the annotation is found and its ABI version is compatible, but there's no "data" string array in it. 75 | // We tell the outside world that there's really no annotation at all 76 | return null 77 | } 78 | 79 | return KotlinClassHeader( 80 | headerKind, 81 | metadataVersion ?: JvmMetadataVersion.INVALID_VERSION, 82 | bytecodeVersion ?: JvmBytecodeBinaryVersion.INVALID_VERSION, 83 | data, 84 | incompatibleData, 85 | strings, 86 | extraString, 87 | extraInt, 88 | packageName 89 | ) 90 | } 91 | 92 | private tailrec fun unwrapAnnotationValue(value: Any?): Any? = 93 | when (value) { 94 | is AnnotationValue -> unwrapAnnotationValue(value.value) 95 | is List<*> -> value.map(::unwrapAnnotationValue) 96 | else -> value 97 | } 98 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/me/eugeniomarletti/kotlin/metadata/KotlinMetadata.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package me.eugeniomarletti.kotlin.metadata 4 | 5 | import me.eugeniomarletti.kotlin.metadata.shadow.load.kotlin.header.KotlinClassHeader 6 | import me.eugeniomarletti.kotlin.metadata.shadow.load.kotlin.header.KotlinClassHeader.Kind.CLASS 7 | import me.eugeniomarletti.kotlin.metadata.shadow.load.kotlin.header.KotlinClassHeader.Kind.FILE_FACADE 8 | import me.eugeniomarletti.kotlin.metadata.shadow.load.kotlin.header.KotlinClassHeader.Kind.MULTIFILE_CLASS 9 | import me.eugeniomarletti.kotlin.metadata.shadow.load.kotlin.header.KotlinClassHeader.Kind.MULTIFILE_CLASS_PART 10 | import me.eugeniomarletti.kotlin.metadata.shadow.load.kotlin.header.KotlinClassHeader.Kind.SYNTHETIC_CLASS 11 | import me.eugeniomarletti.kotlin.metadata.shadow.load.kotlin.header.KotlinClassHeader.Kind.UNKNOWN 12 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.jvm.deserialization.JvmProtoBufUtil 13 | import javax.lang.model.element.Element 14 | 15 | val Element.kotlinMetadata get() = kotlinClassHeader?.let { KotlinMetadata.from(it) } 16 | 17 | /** 18 | * Wrapper around [kotlin.Metadata]. 19 | */ 20 | sealed class KotlinMetadata(val header: KotlinClassHeader) { 21 | companion object { 22 | internal fun from(header: KotlinClassHeader) = when (header.kind) { 23 | CLASS -> KotlinClassMetadata(header) 24 | FILE_FACADE -> KotlinFileMetadata(header) 25 | MULTIFILE_CLASS_PART -> KotlinMultiFileClassPartMetadata(header) 26 | MULTIFILE_CLASS -> KotlinMultiFileClassFacadeMetadata(header) 27 | SYNTHETIC_CLASS -> KotlinSyntheticClassMetadata(header) 28 | UNKNOWN -> KotlinUnknownMetadata(header) 29 | } 30 | } 31 | 32 | val metadataVersion get() = header.metadataVersion 33 | val bytecodeVersion get() = header.bytecodeVersion 34 | 35 | val isPreRelease get() = header.isPreRelease 36 | val isScript get() = header.isScript 37 | val isMultiFile get() = multiFileClassKind != null 38 | 39 | val multiFileClassKind get() = header.multifileClassKind 40 | 41 | override fun equals(other: Any?) = other is KotlinMetadata && other.header == header 42 | override fun hashCode() = header.hashCode() 43 | override fun toString() = KotlinMetadata::class.java.simpleName + "." + javaClass.simpleName 44 | } 45 | 46 | sealed class KotlinPackageMetadata(header: KotlinClassHeader) : KotlinMetadata(header) { 47 | val data by lazy { 48 | JvmProtoBufUtil.readPackageDataFrom(header.data!!, header.strings!!) 49 | .let { (nameResolver, proto) -> PackageData(nameResolver, proto) } 50 | } 51 | } 52 | 53 | class KotlinClassMetadata internal constructor(header: KotlinClassHeader) : KotlinMetadata(header) { 54 | val data by lazy { 55 | JvmProtoBufUtil.readClassDataFrom(header.data!!, header.strings!!) 56 | .let { (nameResolver, proto) -> ClassData(nameResolver, proto) } 57 | } 58 | } 59 | 60 | class KotlinFileMetadata internal constructor(header: KotlinClassHeader) : KotlinPackageMetadata(header) 61 | 62 | class KotlinMultiFileClassPartMetadata internal constructor(header: KotlinClassHeader) : KotlinPackageMetadata(header) { 63 | val facadeClassName get() = header.multifileClassName 64 | } 65 | 66 | class KotlinMultiFileClassFacadeMetadata internal constructor(header: KotlinClassHeader) : KotlinMetadata(header) { 67 | val partsClassNames get() = header.multifilePartNames 68 | } 69 | 70 | class KotlinSyntheticClassMetadata internal constructor(header: KotlinClassHeader) : KotlinMetadata(header) 71 | 72 | class KotlinUnknownMetadata internal constructor(header: KotlinClassHeader) : KotlinMetadata(header) { 73 | override fun toString() = super.toString() + "(${header.incompatibleData})" 74 | } 75 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/me/eugeniomarletti/kotlin/metadata/KotlinMetadataUtils.kt: -------------------------------------------------------------------------------- 1 | package me.eugeniomarletti.kotlin.metadata 2 | 3 | import me.eugeniomarletti.kotlin.metadata.jvm.JvmDescriptorUtils 4 | import me.eugeniomarletti.kotlin.metadata.jvm.getJvmMethodSignature 5 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf 6 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.deserialization.NameResolver 7 | import javax.annotation.processing.ProcessingEnvironment 8 | import javax.lang.model.element.ExecutableElement 9 | import javax.lang.model.element.VariableElement 10 | 11 | data class ClassData( 12 | val nameResolver: NameResolver, 13 | val classProto: ProtoBuf.Class) 14 | 15 | data class PackageData( 16 | val nameResolver: NameResolver, 17 | val packageProto: ProtoBuf.Package) 18 | 19 | /** 20 | * Main repository for extensions that need to access stuff inside [ProcessingEnvironment]. 21 | */ 22 | interface KotlinMetadataUtils : JvmDescriptorUtils { 23 | 24 | /** 25 | * Returns the JVM signature in the form "$Name$MethodDescriptor", for example: `equals(Ljava/lang/Object;)Z`. 26 | * 27 | * Useful for comparing with [ProtoBuf.Function.getJvmMethodSignature][getJvmMethodSignature]. 28 | * 29 | * For reference, see the [JVM specification, section 4.3](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3). 30 | */ 31 | val ExecutableElement.jvmMethodSignature: String 32 | get() = "$simpleName${asType().descriptor}" 33 | 34 | /** 35 | * If possible, returns the [ProtoBuf.Function] inside [functionList] represented by [methodElement]. 36 | */ 37 | fun getFunctionOrNull( 38 | methodElement: ExecutableElement, 39 | nameResolver: NameResolver, 40 | functionList: List 41 | ): ProtoBuf.Function? = 42 | methodElement.jvmMethodSignature.let { methodSignature -> 43 | functionList.firstOrNull { methodSignature == it.getJvmMethodSignature(nameResolver) } 44 | } 45 | 46 | /** @see [getFunctionOrNull] */ 47 | fun ClassData.getFunctionOrNull(methodElement: ExecutableElement) = 48 | getFunctionOrNull(methodElement, nameResolver, proto.functionList) 49 | 50 | /** @see [getFunctionOrNull] */ 51 | fun PackageData.getFunctionOrNull(methodElement: ExecutableElement) = 52 | getFunctionOrNull(methodElement, nameResolver, proto.functionList) 53 | } 54 | 55 | /** 56 | * If this [isNotBlank] then it adds the optional [prefix] and [postfix]. 57 | */ 58 | fun String.plusIfNotBlank( 59 | prefix: String = "", 60 | postfix: String = "" 61 | ) = 62 | if (isNotBlank()) "$prefix${this}$postfix" else this 63 | 64 | /** 65 | * Returns the escaped "readable" version of the internal Kotlin class name. 66 | */ 67 | val String.escapedClassName 68 | get() = split('/', '.').joinToString("`.`").plusIfNotBlank(prefix = "`", postfix = "`") 69 | 70 | /** 71 | * Same as [ClassData.classProto], useful while copy/pasting when duplicating extensions for [PackageData]. 72 | */ 73 | inline val ClassData.proto get() = classProto 74 | 75 | /** 76 | * Same as [PackageData.packageProto], useful while copy/pasting when duplicating extensions for [ClassData]. 77 | */ 78 | inline val PackageData.proto get() = packageProto 79 | 80 | /** 81 | * If possible, returns the [ProtoBuf.Property] inside [propertyList] represented by [methodElement]. 82 | */ 83 | inline fun getPropertyOrNull( 84 | methodElement: ExecutableElement, 85 | nameResolver: NameResolver, 86 | propertyList: () -> List 87 | ): ProtoBuf.Property? = 88 | methodElement.simpleName.toString() 89 | .takeIf { it.endsWith(kotlinPropertyAnnotationsFunPostfix) } 90 | ?.substringBefore(kotlinPropertyAnnotationsFunPostfix) 91 | ?.let { propertyName -> propertyList().firstOrNull { propertyName == nameResolver.getString(it.name) } } 92 | 93 | /** @see [getPropertyOrNull] */ 94 | fun ClassData.getPropertyOrNull(methodElement: ExecutableElement) = 95 | getPropertyOrNull(methodElement, nameResolver, proto::getPropertyList) 96 | 97 | /** @see [getPropertyOrNull] */ 98 | fun PackageData.getPropertyOrNull(methodElement: ExecutableElement) = 99 | getPropertyOrNull(methodElement, nameResolver, proto::getPropertyList) 100 | 101 | /** 102 | * If possible, returns the [ProtoBuf.ValueParameter] inside [function] represented by [parameterElement]. 103 | */ 104 | fun getValueParameterOrNull( 105 | nameResolver: NameResolver, 106 | function: ProtoBuf.Function, 107 | parameterElement: VariableElement 108 | ): ProtoBuf.ValueParameter? = 109 | parameterElement.simpleName.toString().let { parameterName -> 110 | function.valueParameterList.firstOrNull { parameterName == nameResolver.getString(it.name) } 111 | } 112 | 113 | /** @see [getValueParameterOrNull] */ 114 | fun ClassData.getValueParameterOrNull(function: ProtoBuf.Function, parameterElement: VariableElement) = 115 | getValueParameterOrNull(nameResolver, function, parameterElement) 116 | 117 | /** @see [getValueParameterOrNull] */ 118 | fun PackageData.getValueParameterOrNull(function: ProtoBuf.Function, parameterElement: VariableElement) = 119 | getValueParameterOrNull(nameResolver, function, parameterElement) 120 | 121 | /** 122 | * Returns the fully qualified name of this type as it would be seen in the source code, including nullability and generic type parameters. 123 | * 124 | * Package and class names are escaped with backticks through [escapedClassName]. 125 | * 126 | * @param [getTypeParameter] 127 | * A function that returns the type parameter for the given index. 128 | * **Only called if [ProtoBuf.Type.hasTypeParameter] is `true`!** 129 | * 130 | * @param [outputTypeAlias] 131 | * If `true` type aliases will be used, otherwise they will be replaced by the concrete type. 132 | * 133 | * @param [throwOnGeneric] 134 | * If not `null` it will be thrown if this type contains generic information. 135 | */ 136 | fun ProtoBuf.Type.extractFullName( 137 | nameResolver: NameResolver, 138 | getTypeParameter: (index: Int) -> ProtoBuf.TypeParameter, 139 | outputTypeAlias: Boolean = true, 140 | throwOnGeneric: Throwable? = null 141 | ): String { 142 | 143 | if (!hasClassName() && throwOnGeneric != null) throw throwOnGeneric 144 | 145 | val name = when { 146 | hasTypeParameter() -> getTypeParameter(typeParameter).name 147 | hasTypeParameterName() -> typeParameterName 148 | outputTypeAlias && hasAbbreviatedType() -> abbreviatedType.typeAliasName 149 | else -> className 150 | }.let { nameResolver.getString(it).escapedClassName } 151 | 152 | val argumentList = when { 153 | outputTypeAlias && hasAbbreviatedType() -> abbreviatedType.argumentList 154 | else -> argumentList 155 | } 156 | val arguments = argumentList 157 | .takeIf { it.isNotEmpty() } 158 | ?.joinToString(prefix = "<", postfix = ">") { 159 | when { 160 | it.hasType() -> it.type.extractFullName(nameResolver, getTypeParameter, outputTypeAlias, throwOnGeneric) 161 | throwOnGeneric != null -> throw throwOnGeneric 162 | else -> "*" 163 | } 164 | } 165 | ?: "" 166 | 167 | val nullability = if (nullable) "?" else "" 168 | 169 | return name + arguments + nullability 170 | } 171 | 172 | /** @see [extractFullName] */ 173 | fun ProtoBuf.Type.extractFullName( 174 | data: ClassData, 175 | outputTypeAlias: Boolean = true, 176 | throwOnGeneric: Throwable? = null 177 | ) = 178 | extractFullName(data.nameResolver, data.proto::getTypeParameter, outputTypeAlias, throwOnGeneric) 179 | 180 | /** @see [extractFullName] */ 181 | fun ProtoBuf.Type.extractFullName( 182 | data: PackageData, 183 | outputTypeAlias: Boolean = true, 184 | throwOnGeneric: Throwable? = null 185 | ) = 186 | extractFullName(data.nameResolver, { throw IllegalStateException() }, outputTypeAlias, throwOnGeneric) 187 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/me/eugeniomarletti/kotlin/metadata/jvm/JvmDescriptorUtils.kt: -------------------------------------------------------------------------------- 1 | package me.eugeniomarletti.kotlin.metadata.jvm 2 | 3 | import me.eugeniomarletti.kotlin.processing.KotlinProcessingEnvironment 4 | import javax.lang.model.element.Element 5 | import javax.lang.model.element.QualifiedNameable 6 | import javax.lang.model.type.ArrayType 7 | import javax.lang.model.type.DeclaredType 8 | import javax.lang.model.type.ErrorType 9 | import javax.lang.model.type.ExecutableType 10 | import javax.lang.model.type.NoType 11 | import javax.lang.model.type.NullType 12 | import javax.lang.model.type.PrimitiveType 13 | import javax.lang.model.type.TypeKind.BOOLEAN 14 | import javax.lang.model.type.TypeKind.BYTE 15 | import javax.lang.model.type.TypeKind.CHAR 16 | import javax.lang.model.type.TypeKind.DOUBLE 17 | import javax.lang.model.type.TypeKind.FLOAT 18 | import javax.lang.model.type.TypeKind.INT 19 | import javax.lang.model.type.TypeKind.LONG 20 | import javax.lang.model.type.TypeKind.SHORT 21 | import javax.lang.model.type.TypeMirror 22 | import javax.lang.model.type.TypeVariable 23 | import javax.lang.model.type.WildcardType 24 | import javax.lang.model.util.AbstractTypeVisitor6 25 | import javax.lang.model.util.Types 26 | 27 | interface JvmDescriptorUtils : KotlinProcessingEnvironment { 28 | 29 | /** 30 | * @see [JvmDescriptorTypeVisitor] 31 | */ 32 | val TypeMirror.descriptor: String 33 | get() = descriptor(typeUtils) 34 | 35 | /** 36 | * Returns the "field descriptor" of this type. 37 | * @see [JvmDescriptorTypeVisitor] 38 | */ 39 | val WildcardType.descriptor: String 40 | get() = descriptor(typeUtils) 41 | 42 | /** 43 | * Returns the "field descriptor" of this type. 44 | * @see [JvmDescriptorTypeVisitor] 45 | */ 46 | val TypeVariable.descriptor: String 47 | get() = descriptor(typeUtils) 48 | 49 | /** 50 | * Returns the "field descriptor" of this type. 51 | * @see [JvmDescriptorTypeVisitor] 52 | */ 53 | val ArrayType.descriptor: String 54 | get() = descriptor(typeUtils) 55 | 56 | /** 57 | * Returns the "method descriptor" of this type. 58 | * @see [JvmDescriptorTypeVisitor] 59 | */ 60 | val ExecutableType.descriptor: String 61 | get() = descriptor(typeUtils) 62 | } 63 | 64 | /** 65 | * Returns the name of this [Element] in its "internal form". 66 | * 67 | * For reference, see the [JVM specification, section 4.2](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.2). 68 | */ 69 | val Element.internalName: String 70 | get() = when (this) { 71 | is QualifiedNameable -> qualifiedName.toString().replace('.', '/') 72 | else -> simpleName.toString() 73 | } 74 | /** 75 | * Returns the "field descriptor" of this type. 76 | * @see [JvmDescriptorTypeVisitor] 77 | */ 78 | @Suppress("unused") 79 | val NoType.descriptor: String 80 | get() = "V" 81 | /** 82 | * Returns the "field descriptor" of this type. 83 | * @see [JvmDescriptorTypeVisitor] 84 | */ 85 | val DeclaredType.descriptor: String 86 | get() = "L" + asElement().internalName + ";" 87 | /** 88 | * Returns the "field descriptor" of this type. 89 | * @see [JvmDescriptorTypeVisitor] 90 | */ 91 | val PrimitiveType.descriptor: String 92 | get() = when (this.kind) { 93 | BYTE -> "B" 94 | CHAR -> "C" 95 | DOUBLE -> "D" 96 | FLOAT -> "F" 97 | INT -> "I" 98 | LONG -> "J" 99 | SHORT -> "S" 100 | BOOLEAN -> "Z" 101 | else -> error("Unknown primitive type $this") 102 | } 103 | 104 | /** 105 | * @see [JvmDescriptorTypeVisitor] 106 | * @see [JvmDescriptorUtils] 107 | */ 108 | fun TypeMirror.descriptor(typeUtils: Types): String = 109 | accept(JvmDescriptorTypeVisitor, typeUtils) 110 | 111 | /** 112 | * Returns the "field descriptor" of this type. 113 | * @see [JvmDescriptorTypeVisitor] 114 | * @see [JvmDescriptorUtils] 115 | */ 116 | fun WildcardType.descriptor(typeUtils: Types): String = 117 | typeUtils.erasure(this).descriptor(typeUtils) 118 | 119 | /** 120 | * Returns the "field descriptor" of this type. 121 | * @see [JvmDescriptorTypeVisitor] 122 | * @see [JvmDescriptorUtils] 123 | */ 124 | fun TypeVariable.descriptor(typeUtils: Types): String = 125 | typeUtils.erasure(this).descriptor(typeUtils) 126 | 127 | /** 128 | * Returns the "field descriptor" of this type. 129 | * @see [JvmDescriptorTypeVisitor] 130 | * @see [JvmDescriptorUtils] 131 | */ 132 | fun ArrayType.descriptor(typeUtils: Types): String = 133 | "[" + componentType.descriptor(typeUtils) 134 | 135 | /** 136 | * Returns the "method descriptor" of this type. 137 | * @see [JvmDescriptorTypeVisitor] 138 | * @see [JvmDescriptorUtils] 139 | */ 140 | fun ExecutableType.descriptor(typeUtils: Types): String { 141 | val parameterDescriptors = parameterTypes.joinToString(separator = "") { it.descriptor(typeUtils) } 142 | val returnDescriptor = returnType.descriptor(typeUtils) 143 | return "($parameterDescriptors)$returnDescriptor" 144 | } 145 | 146 | /** 147 | * When applied over a type, it returns either: 148 | * + a "field descriptor", for example: `Ljava/lang/Object;` 149 | * + a "method descriptor", for example: `(Ljava/lang/Object;)Z` 150 | * 151 | * The easiest way to use this is through [TypeMirror.descriptor][JvmDescriptorUtils.descriptor] in [JvmDescriptorUtils]. 152 | * 153 | * For reference, see the [JVM specification, section 4.3](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3). 154 | */ 155 | object JvmDescriptorTypeVisitor : AbstractTypeVisitor6() { 156 | 157 | override fun visitNoType(t: NoType, typeUtils: Types): String = t.descriptor 158 | override fun visitDeclared(t: DeclaredType, typeUtils: Types): String = t.descriptor 159 | override fun visitPrimitive(t: PrimitiveType, typeUtils: Types): String = t.descriptor 160 | 161 | override fun visitArray(t: ArrayType, typeUtils: Types): String = t.descriptor(typeUtils) 162 | override fun visitWildcard(t: WildcardType, typeUtils: Types): String = t.descriptor(typeUtils) 163 | override fun visitExecutable(t: ExecutableType, typeUtils: Types): String = t.descriptor(typeUtils) 164 | override fun visitTypeVariable(t: TypeVariable, typeUtils: Types): String = t.descriptor(typeUtils) 165 | 166 | override fun visitNull(t: NullType, typeUtils: Types): String = visitUnknown(t, typeUtils) 167 | override fun visitError(t: ErrorType, typeUtils: Types): String = visitUnknown(t, typeUtils) 168 | 169 | override fun visitUnknown(t: TypeMirror, typeUtils: Types): String = error("Unsupported type $t") 170 | } 171 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/me/eugeniomarletti/kotlin/metadata/jvm/JvmProtoBuf.kt: -------------------------------------------------------------------------------- 1 | package me.eugeniomarletti.kotlin.metadata.jvm 2 | 3 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf 4 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.deserialization.getExtensionOrNull 5 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.jvm.JvmProtoBuf 6 | 7 | /** 8 | * @see [getJvmMethodSignature] 9 | */ 10 | val ProtoBuf.Function.jvmMethodSignature get() = getExtensionOrNull(JvmProtoBuf.methodSignature) 11 | val ProtoBuf.Constructor.jvmConstructorSignature get() = getExtensionOrNull(JvmProtoBuf.constructorSignature) 12 | val ProtoBuf.Property.jvmPropertySignature get() = getExtensionOrNull(JvmProtoBuf.propertySignature) 13 | val ProtoBuf.Type.jvmTypeAnnotation get() = getExtensionOrNull(JvmProtoBuf.typeAnnotation) as List? 14 | val ProtoBuf.Type.jvmIsRaw get() = getExtensionOrNull(JvmProtoBuf.isRaw) 15 | val ProtoBuf.TypeParameter.jvmTypeParameterAnnotation get() = getExtensionOrNull(JvmProtoBuf.typeParameterAnnotation) as List? 16 | val ProtoBuf.Class.jvmClassModuleName get() = getExtensionOrNull(JvmProtoBuf.classModuleName) 17 | val ProtoBuf.Package.jvmPackageModuleName get() = getExtensionOrNull(JvmProtoBuf.packageModuleName) 18 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/me/eugeniomarletti/kotlin/metadata/jvm/JvmProtoBufUtil.kt: -------------------------------------------------------------------------------- 1 | package me.eugeniomarletti.kotlin.metadata.jvm 2 | 3 | import me.eugeniomarletti.kotlin.metadata.KotlinMetadataUtils 4 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf 5 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.deserialization.NameResolver 6 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.deserialization.TypeTable 7 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.jvm.deserialization.JvmProtoBufUtil 8 | import javax.lang.model.element.ExecutableElement 9 | 10 | /** 11 | * Returns JVM signature in the format: `equals(Ljava/lang/Object;)Z`. 12 | * 13 | * See [ExecutableElement.jvmMethodSignature][KotlinMetadataUtils.jvmMethodSignature] for getting the same from an [ExecutableElement]. 14 | */ 15 | fun ProtoBuf.Function.getJvmMethodSignature(nameResolver: NameResolver, typeTable: ProtoBuf.TypeTable = this.typeTable) = 16 | JvmProtoBufUtil.getJvmMethodSignature(this, nameResolver, TypeTable(typeTable)) 17 | 18 | fun ProtoBuf.Constructor.getJvmConstructorSignature(nameResolver: NameResolver, typeTable: ProtoBuf.TypeTable) = 19 | JvmProtoBufUtil.getJvmConstructorSignature(this, nameResolver, TypeTable(typeTable)) 20 | 21 | fun ProtoBuf.Property.getJvmFieldSignature(nameResolver: NameResolver, typeTable: ProtoBuf.TypeTable) = 22 | JvmProtoBufUtil.getJvmFieldSignature(this, nameResolver, TypeTable(typeTable)) 23 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/me/eugeniomarletti/kotlin/metadata/jvm/package-info.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all the JVM-specific extensions. 3 | */ 4 | package me.eugeniomarletti.kotlin.metadata.jvm 5 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/me/eugeniomarletti/kotlin/processing/KotlinAbstractProcessor.kt: -------------------------------------------------------------------------------- 1 | package me.eugeniomarletti.kotlin.processing 2 | 3 | import me.eugeniomarletti.kotlin.metadata.kaptGeneratedOption 4 | import java.io.File 5 | import javax.annotation.processing.AbstractProcessor 6 | import javax.annotation.processing.Completion 7 | import javax.annotation.processing.ProcessingEnvironment 8 | import javax.annotation.processing.RoundEnvironment 9 | import javax.lang.model.SourceVersion 10 | import javax.lang.model.element.AnnotationMirror 11 | import javax.lang.model.element.Element 12 | import javax.lang.model.element.ExecutableElement 13 | import javax.lang.model.element.TypeElement 14 | 15 | /** 16 | * An [AbstractProcessor] that overrides every method to provide correct Kotlin types. 17 | * 18 | * Implements [KotlinProcessingEnvironment] for ease of use and extension. 19 | */ 20 | abstract class KotlinAbstractProcessor : AbstractProcessor(), KotlinProcessingEnvironment { 21 | 22 | override fun getSupportedOptions(): Set = super.getSupportedOptions() 23 | override fun getSupportedSourceVersion(): SourceVersion = super.getSupportedSourceVersion() 24 | override fun getSupportedAnnotationTypes(): Set = super.getSupportedAnnotationTypes() 25 | override fun init(processingEnv: ProcessingEnvironment) = super.init(processingEnv) 26 | 27 | override fun getCompletions( 28 | element: Element?, 29 | annotation: AnnotationMirror?, 30 | member: ExecutableElement?, 31 | userText: String? 32 | ): Iterable = 33 | super.getCompletions(element, annotation, member, userText) 34 | 35 | override abstract fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean 36 | 37 | /** @see [AbstractProcessor.processingEnv] **/ 38 | override val processingEnv: ProcessingEnvironment get() = super.processingEnv 39 | 40 | /** 41 | * Returns the directory where generated Kotlin sources should be placed in order to be compiled. 42 | * 43 | * If `null`, then this processor is probably not being run through kapt. 44 | */ 45 | val generatedDir: File? get() = options[kaptGeneratedOption]?.let(::File) 46 | } 47 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/me/eugeniomarletti/kotlin/processing/KotlinProcessingEnvironment.kt: -------------------------------------------------------------------------------- 1 | package me.eugeniomarletti.kotlin.processing 2 | 3 | import java.util.Locale 4 | import javax.annotation.processing.Filer 5 | import javax.annotation.processing.Messager 6 | import javax.annotation.processing.ProcessingEnvironment 7 | import javax.lang.model.SourceVersion 8 | import javax.lang.model.util.Elements 9 | import javax.lang.model.util.Types 10 | 11 | /** 12 | * Wraps [processingEnv] and exposes it at the top level. 13 | * Useful for creating extensions with a receiver that also need to access stuff inside [ProcessingEnvironment]. 14 | */ 15 | interface KotlinProcessingEnvironment { 16 | val processingEnv: ProcessingEnvironment 17 | 18 | val options: Map get() = processingEnv.options 19 | val messager: Messager get() = processingEnv.messager 20 | val filer: Filer get() = processingEnv.filer 21 | val elementUtils: Elements get() = processingEnv.elementUtils 22 | val typeUtils: Types get() = processingEnv.typeUtils 23 | val sourceVersion: SourceVersion get() = processingEnv.sourceVersion 24 | val locale: Locale get() = processingEnv.locale 25 | } 26 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'kotlin-metadata' 2 | include ':lib' 3 | include ':data-class-with:app' 4 | include ':data-class-with:api' 5 | include ':data-class-with:processor' 6 | --------------------------------------------------------------------------------