Skip to content

Commit

Permalink
Merge pull request #220 from ProjectMapK/develop
Browse files Browse the repository at this point in the history
Release 2024-02-10 16:25:16 +0000
  • Loading branch information
k163377 committed Feb 10, 2024
2 parents b9c4b1f + 6cd18c0 commit c682e69
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 140 deletions.
24 changes: 13 additions & 11 deletions .github/workflows/lint-and-test-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ on:
- "**.kt"
- "**.java"
- .github/workflows/lint-and-test-dev.yml

permissions:
contents: write # for Dependency submission

jobs:
lint-and-test-dev:
name: lint-and-test-dev
Expand All @@ -39,20 +43,18 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@v1
uses: gradle/wrapper-validation-action@v2
- name: Set up java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: '8'
distribution: 'corretto'
- name: Lint
uses: gradle/gradle-build-action@v2
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
arguments: lintKotlin
dependency-graph: generate-and-submit
dependency-graph-continue-on-failure: false
- name: Lint
run: ./gradlew lintKotlin
- name: Test
uses: gradle/gradle-build-action@v2
with:
arguments: test
# TODO: Prepare a separate WF to be executed only when there is a change in build.gradle.
- name: Run snapshot action
uses: mikepenz/[email protected]
run: ./gradlew lintKotlin test
1 change: 1 addition & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
name: release
on:
workflow_dispatch:
jobs:
Expand Down
18 changes: 9 additions & 9 deletions .github/workflows/test-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ jobs:
- name: '1.9.21 K2'
version: '1.9.21'
k2: true
- name: '2.0.0-Beta1'
version: '2.0.0-Beta1'
- name: '2.0.0-Beta3'
version: '2.0.0-Beta3'
k2: false
- name: '2.0.0-Beta1 K2'
version: '2.0.0-Beta1'
- name: '2.0.0-Beta3 K2'
version: '2.0.0-Beta3'
k2: true
env:
KOTLIN_VERSION: ${{ matrix.kotlin.version }}
Expand All @@ -63,13 +63,13 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@v1
uses: gradle/wrapper-validation-action@v2
- name: 'Set up java ${{ matrix.java-version }}'
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: '${{ matrix.java-version }}'
distribution: 'corretto'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Test
uses: gradle/gradle-build-action@v2
with:
arguments: test
run: ./gradlew test
7 changes: 6 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ val jacksonVersion = libs.versions.jackson.get()
val generatedSrcPath = "${layout.buildDirectory.get()}/generated/kotlin"

group = groupStr
version = "${jacksonVersion}-beta10"
version = "${jacksonVersion}-beta11"

repositories {
mavenCentral()
Expand Down Expand Up @@ -98,6 +98,11 @@ public val kogeraVersion: Version = VersionUtil.parseVersion("$version", "$group
into(file("$generatedSrcPath/${packageStr.replace(".", "/")}"))
}

// Added to avoid failure in generating dependency graphs in CI.
lintKotlinMain {
dependsOn.add(generateKogeraVersion)
}

compileKotlin {
dependsOn.add(generateKogeraVersion)
kotlinOptions.jvmTarget = "1.8"
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ junit = "5.10.1"

[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib" }
kotlinx-metadata-jvm = "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.7.0"
kotlinx-metadata-jvm = "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.9.0"
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jackson" }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
package io.github.projectmapk.jackson.module.kogera

import com.fasterxml.jackson.annotation.JsonCreator
import kotlinx.metadata.KmClass
import kotlinx.metadata.KmClassifier
import kotlinx.metadata.KmType
import kotlinx.metadata.isNullable
import kotlinx.metadata.jvm.JvmMethodSignature
import kotlinx.metadata.jvm.KotlinClassMetadata
import java.lang.reflect.AnnotatedElement
import java.lang.reflect.Constructor
import java.lang.reflect.Method

internal typealias JavaDuration = java.time.Duration
internal typealias KotlinDuration = kotlin.time.Duration

internal fun Class<*>.toKmClass(): KmClass? = getAnnotation(Metadata::class.java)?.let {
(KotlinClassMetadata.readStrict(it) as KotlinClassMetadata.Class).kmClass
}

internal fun Class<*>.isUnboxableValueClass() = this.isAnnotationPresent(JvmInline::class.java)

// JmClass must be value class.
Expand Down
139 changes: 27 additions & 112 deletions src/main/kotlin/io/github/projectmapk/jackson/module/kogera/JmClass.kt
Original file line number Diff line number Diff line change
@@ -1,88 +1,29 @@
// Visitor API has already been deprecated, but the error is being suppressed for now.
@file:Suppress("DEPRECATION_ERROR")

package io.github.projectmapk.jackson.module.kogera

import kotlinx.metadata.ClassKind
import kotlinx.metadata.ClassName
import kotlinx.metadata.ExperimentalContextReceivers
import kotlinx.metadata.Flags
import kotlinx.metadata.KmClassExtensionVisitor
import kotlinx.metadata.KmClassVisitor
import kotlinx.metadata.KmClass
import kotlinx.metadata.KmConstructor
import kotlinx.metadata.KmConstructorVisitor
import kotlinx.metadata.KmExtensionType
import kotlinx.metadata.KmFunction
import kotlinx.metadata.KmFunctionVisitor
import kotlinx.metadata.KmProperty
import kotlinx.metadata.KmPropertyVisitor
import kotlinx.metadata.KmType
import kotlinx.metadata.KmTypeAliasVisitor
import kotlinx.metadata.KmTypeParameterVisitor
import kotlinx.metadata.KmTypeVisitor
import kotlinx.metadata.KmVariance
import kotlinx.metadata.KmVersionRequirementVisitor
import kotlinx.metadata.flagsOf
import kotlinx.metadata.internal.accept
import kotlinx.metadata.internal.metadata.jvm.deserialization.JvmProtoBufUtil
import kotlinx.metadata.jvm.getterSignature
import kotlinx.metadata.jvm.signature
import java.lang.reflect.Constructor
import java.lang.reflect.Field
import java.lang.reflect.Method

// KmClassVisitor with all processing disabled as much as possible to reduce load
internal sealed class ReducedKmClassVisitor : KmClassVisitor() {
final override val delegate: KmClassVisitor? get() = null

// from KmDeclarationContainerVisitor
override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor? = null
override fun visitProperty(
flags: Flags,
name: String,
getterFlags: Flags,
setterFlags: Flags
): KmPropertyVisitor? = null
override fun visitTypeAlias(flags: Flags, name: String): KmTypeAliasVisitor? = null
override fun visitExtensions(type: KmExtensionType): KmClassExtensionVisitor? = null

// from KmClassVisitor
override fun visit(flags: Flags, name: ClassName) {}
override fun visitTypeParameter(
flags: Flags,
name: String,
id: Int,
variance: KmVariance
): KmTypeParameterVisitor? = null
override fun visitSupertype(flags: Flags): KmTypeVisitor? = null
override fun visitConstructor(flags: Flags): KmConstructorVisitor? = null
override fun visitCompanionObject(name: String) {}
override fun visitNestedClass(name: String) {}
override fun visitEnumEntry(name: String) {}
override fun visitSealedSubclass(name: ClassName) {}
override fun visitInlineClassUnderlyingPropertyName(name: String) {}
override fun visitInlineClassUnderlyingType(flags: Flags): KmTypeVisitor? = null

@OptIn(ExperimentalContextReceivers::class)
override fun visitContextReceiverType(flags: Flags): KmTypeVisitor? = null
override fun visitVersionRequirement(): KmVersionRequirementVisitor? = null
override fun visitEnd() {}
}
import kotlinx.metadata.internal.metadata.deserialization.Flags as ProtoFlags

// Jackson Metadata Class
internal sealed interface JmClass {
class CompanionObject(declaringClass: Class<*>, companionObject: String) {
private class ReducedCompanionVisitor(companionClass: Class<*>) : ReducedKmClassVisitor() {
val functions: MutableList<KmFunction> = arrayListOf()

init {
companionClass.getAnnotation(Metadata::class.java)!!.accept(this)
}

override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor = KmFunction(flags, name)
.apply { functions.add(this) }
}

private val companionField: Field = declaringClass.getDeclaredField(companionObject)
val type: Class<*> = companionField.type
val isAccessible: Boolean = companionField.isAccessible
private val functions by lazy { ReducedCompanionVisitor(type).functions }
private val functions by lazy { type.toKmClass()!!.functions }
val instance: Any by lazy {
// To prevent the call from failing, save the initial value and then rewrite the flag.
if (!companionField.isAccessible) companionField.isAccessible = true
Expand All @@ -95,7 +36,7 @@ internal sealed interface JmClass {
}
}

val flags: Flags
val kind: ClassKind
val constructors: List<KmConstructor>
val sealedSubclasses: List<ClassName>
val inlineClassUnderlyingType: KmType?
Expand All @@ -110,34 +51,35 @@ internal sealed interface JmClass {

private class JmClassImpl(
clazz: Class<*>,
metadata: Metadata,
kmClass: KmClass,
superJmClass: JmClass?,
interfaceJmClasses: List<JmClass>
) : ReducedKmClassVisitor(), JmClass {
private val allPropsMap: MutableMap<String, KmProperty> = mutableMapOf()
) : JmClass {
private val allPropsMap: Map<String, KmProperty>

// Defined as non-lazy because it is always read in both serialization and deserialization
override val properties: List<KmProperty>

private var companionPropName: String? = null
override var flags: Flags = flagsOf()
override val constructors: MutableList<KmConstructor> = mutableListOf()
override val sealedSubclasses: MutableList<ClassName> = mutableListOf()
override var inlineClassUnderlyingType: KmType? = null
private val companionPropName: String? = kmClass.companionObject
override val kind: ClassKind = ClassKind.values()[ProtoFlags.CLASS_KIND.get(kmClass.flags).number]
override val constructors: List<KmConstructor> = kmClass.constructors
override val sealedSubclasses: List<ClassName> = kmClass.sealedSubclasses
override val inlineClassUnderlyingType: KmType? = kmClass.inlineClassUnderlyingType

init {
metadata.accept(this)

// Add properties of inherited classes and interfaces
// If an `interface` is implicitly implemented by an abstract class,
// it is necessary to obtain a more specific type, so always add it from the abstract class first.
(superJmClass as JmClassImpl?)?.allPropsMap?.forEach {
this.allPropsMap.putIfAbsent(it.key, it.value)
val tempPropsMap = ((superJmClass as JmClassImpl?)?.allPropsMap?.toMutableMap() ?: mutableMapOf()).apply {
kmClass.properties.forEach {
this[it.name] = it
}
}
@Suppress("UNCHECKED_CAST")
(interfaceJmClasses as List<JmClassImpl>).forEach { i ->
i.allPropsMap.forEach {
this.allPropsMap.putIfAbsent(it.key, it.value)

allPropsMap = interfaceJmClasses.fold(tempPropsMap) { acc, cur ->
val curProps = (cur as JmClassImpl).allPropsMap
acc.apply {
curProps.forEach { acc.putIfAbsent(it.key, it.value) }
}
}

Expand Down Expand Up @@ -178,38 +120,11 @@ private class JmClassImpl(
val getterName = getter.name
return properties.find { it.getterSignature?.name == getterName }
}

// KmClassVisitor
override fun visit(flags: Flags, name: ClassName) {
this.flags = flags
}

override fun visitProperty(flags: Flags, name: String, getterFlags: Flags, setterFlags: Flags): KmPropertyVisitor =
KmProperty(flags, name, getterFlags, setterFlags).apply { allPropsMap[name] = this }

override fun visitConstructor(flags: Flags): KmConstructorVisitor =
KmConstructor(flags).apply { constructors.add(this) }

override fun visitCompanionObject(name: String) {
this.companionPropName = name
}

override fun visitSealedSubclass(name: ClassName) {
sealedSubclasses.add(name)
}

override fun visitInlineClassUnderlyingType(flags: Flags): KmTypeVisitor =
KmType(flags).also { inlineClassUnderlyingType = it }
}

private fun Metadata.accept(visitor: ReducedKmClassVisitor) {
val (strings, proto) = JvmProtoBufUtil.readClassDataFrom(data1.takeIf(Array<*>::isNotEmpty)!!, data2)
proto.accept(visitor, strings)
}

internal fun JmClass(
clazz: Class<*>,
metadata: Metadata,
kmClass: KmClass,
superJmClass: JmClass?,
interfaceJmClasses: List<JmClass>
): JmClass = JmClassImpl(clazz, metadata, superJmClass, interfaceJmClasses)
): JmClass = JmClassImpl(clazz, kmClass, superJmClass, interfaceJmClasses)
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ internal class ReflectionCache(initialCacheSize: Int, maxCacheSize: Int) : Seria

fun getJmClass(clazz: Class<*>): JmClass? {
return find(clazz) ?: run {
val metadata = clazz.getAnnotation(Metadata::class.java) ?: return null
val kmClass = clazz.toKmClass() ?: return null

// Do not parse super class for interfaces.
val superJmClass = if (!clazz.isInterface) {
Expand All @@ -61,7 +61,7 @@ internal class ReflectionCache(initialCacheSize: Int, maxCacheSize: Int) : Seria
}
val interfaceJmClasses = clazz.interfaces.mapNotNull { getJmClass(it) }

val value = JmClass(clazz, metadata, superJmClass, interfaceJmClasses)
val value = JmClass(clazz, kmClass, superJmClass, interfaceJmClasses)
putIfAbsent(clazz, value)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.fasterxml.jackson.databind.DeserializationConfig
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier
import io.github.projectmapk.jackson.module.kogera.ReflectionCache
import kotlinx.metadata.Flag
import kotlinx.metadata.ClassKind
import java.io.Serializable

// [module-kotlin#225]: keep Kotlin singletons as singletons
Expand All @@ -19,10 +19,8 @@ internal class KotlinBeanDeserializerModifier(
}

private fun objectSingletonInstance(beanClass: Class<*>): Any? = cache.getJmClass(beanClass)?.let {
val flags = it.flags

// It is not assumed that the companion object is the target
if (Flag.Class.IS_OBJECT(flags) && !Flag.Class.IS_COMPANION_OBJECT(flags)) {
if (it.kind == ClassKind.OBJECT) {
beanClass.getDeclaredField("INSTANCE").get(null)
} else {
null
Expand Down

0 comments on commit c682e69

Please sign in to comment.