Skip to content

Commit 88afb9b

Browse files
authored
Merge pull request #145 from ProjectMapK/develop
For release 2.15.2-beta4
2 parents ef5c047 + e0100a2 commit 88afb9b

File tree

14 files changed

+229
-106
lines changed

14 files changed

+229
-106
lines changed

.github/workflows/lint-and-test-dev.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ jobs:
3535
timeout-minutes: 15
3636
steps:
3737
- name: Checkout
38-
uses: actions/checkout@v3
38+
uses: actions/checkout@v4
3939
- name: Validate Gradle wrapper
4040
uses: gradle/wrapper-validation-action@v1
4141
- name: Set up java
4242
uses: actions/setup-java@v3
4343
with:
4444
java-version: '8'
45-
distribution: 'adopt'
45+
distribution: 'corretto'
4646
- name: Lint
4747
uses: gradle/gradle-build-action@v2
4848
with:

.github/workflows/test-main.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,24 +34,24 @@ jobs:
3434
fail-fast: false
3535
matrix:
3636
# LTS versions, latest version (if exists)
37-
java-version: [ '8', '11', '17', '19' ]
37+
java-version: [ '8', '11', '17', '20' ]
3838
# Minimum version, latest release version, latest pre-release version (if exists)
39-
kotlin-version: [ '1.7.21', '1.8.22', '1.9.0' ]
39+
kotlin-version: [ '1.7.21', '1.8.22', '1.9.10', '1.9.20-Beta' ]
4040
env:
4141
KOTLIN_VERSION: ${{ matrix.kotlin-version }}
4242
name: test-main
4343
runs-on: ubuntu-latest
4444
timeout-minutes: 15
4545
steps:
4646
- name: Checkout
47-
uses: actions/checkout@v3
47+
uses: actions/checkout@v4
4848
- name: Validate Gradle wrapper
4949
uses: gradle/wrapper-validation-action@v1
5050
- name: 'Set up java ${{ matrix.java-version }}'
5151
uses: actions/setup-java@v3
5252
with:
5353
java-version: '${{ matrix.java-version }}'
54-
distribution: 'adopt'
54+
distribution: 'corretto'
5555
- name: Test
5656
uses: gradle/gradle-build-action@v2
5757
with:

.idea/misc.xml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ Details are summarized in [KogeraSpecificImplementations](./docs/KogeraSpecificI
4242
Compatibility checks for `Java` and `Kotlin` are done by `CI` grid tests.
4343

4444
The `Java` test covers all currently supported LTS versions and the latest versions.
45-
Currently 8,11,17 and 19 are covered.
45+
Currently 8,11,17 and 20 are covered.
4646

4747
`Kotlin` is tested on the latest patch version and the latest `Beta` or `RC` version within each minor version since 1.7.21.
48-
Currently 1.7.21 and 1.8.21 are covered.
48+
Currently 1.7.21 and 1.9.20-Beta are covered.
4949
I hope to lower this version in the future, but currently the minimum `Kotlin` version that can be supported is 1.7 due to `kotlinx-metadata-jvm` constraints.
5050

5151
# Installation

build.gradle.kts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,23 @@ val jacksonVersion = "2.15.2"
1414
val generatedSrcPath = "$buildDir/generated/kotlin"
1515

1616
group = groupStr
17-
version = "${jacksonVersion}-beta3"
17+
version = "${jacksonVersion}-beta4"
1818

1919
repositories {
2020
mavenCentral()
2121
}
2222

2323
dependencies {
2424
implementation(kotlin("stdlib"))
25-
implementation("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.6.0")
25+
implementation("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.6.2")
2626

2727
api("com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}")
2828
api("com.fasterxml.jackson.core:jackson-annotations:${jacksonVersion}")
2929

3030
// test libs
31-
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3")
31+
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
3232
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
33-
testImplementation("io.mockk:mockk:1.13.3")
33+
testImplementation("io.mockk:mockk:1.13.7")
3434

3535
testImplementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
3636
testImplementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.github.projectmapk.jackson.module.kogera
2+
3+
import com.fasterxml.jackson.databind.AbstractTypeResolver
4+
import com.fasterxml.jackson.databind.DeserializationConfig
5+
import com.fasterxml.jackson.databind.JavaType
6+
7+
/*
8+
* ClosedRange, which is not a concrete type like IntRange, does not have a type to deserialize to,
9+
* so deserialization by ClosedRangeMixin does not work.
10+
* Therefore, this process provides a concrete type.
11+
*
12+
* The target of processing is ClosedRange and interfaces or abstract classes that inherit from it.
13+
* As of Kotlin 1.5.32, ClosedRange and ClosedFloatingPointRange are processed.
14+
*/
15+
internal object ClosedRangeResolver : AbstractTypeResolver() {
16+
// At present, it depends on the private class, but if it is made public, it must be switched to a direct reference.
17+
// see https://youtrack.jetbrains.com/issue/KT-55376
18+
val closedDoubleRangeRef: Class<*> by lazy {
19+
Class.forName("kotlin.ranges.ClosedDoubleRange")
20+
}
21+
22+
val closedFloatRangeRef: Class<*>? by lazy {
23+
Class.forName("kotlin.ranges.ClosedFloatRange")
24+
}
25+
26+
fun findClosedFloatingPointRangeRef(contentType: Class<*>): Class<*>? = when (contentType) {
27+
Double::class.javaPrimitiveType, Double::class.javaObjectType -> closedDoubleRangeRef
28+
Float::class.javaPrimitiveType, Float::class.javaObjectType -> closedFloatRangeRef
29+
else -> null
30+
}
31+
32+
val comparableRangeClass: Class<*> by lazy {
33+
Class.forName("kotlin.ranges.ComparableRange")
34+
}
35+
36+
override fun findTypeMapping(config: DeserializationConfig, type: JavaType): JavaType? {
37+
val rawClass = type.rawClass
38+
39+
return if (rawClass == ClosedRange::class.java || rawClass == ClosedFloatingPointRange::class.java) {
40+
type.bindings.typeParameters.firstOrNull()
41+
?.let { typeParam ->
42+
findClosedFloatingPointRangeRef(typeParam.rawClass)?.let {
43+
config.typeFactory.constructType(it)
44+
}
45+
}
46+
?: config.typeFactory.constructParametricType(comparableRangeClass, type.bindings)
47+
} else {
48+
super.findTypeMapping(config, type)
49+
}
50+
}
51+
}

src/main/kotlin/io/github/projectmapk/jackson/module/kogera/InternalCommons.kt

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,28 @@ package io.github.projectmapk.jackson.module.kogera
22

33
import com.fasterxml.jackson.annotation.JsonCreator
44
import kotlinx.metadata.Flag
5-
import kotlinx.metadata.KmClass
65
import kotlinx.metadata.KmClassifier
76
import kotlinx.metadata.KmType
87
import kotlinx.metadata.KmValueParameter
8+
import kotlinx.metadata.internal.accept
9+
import kotlinx.metadata.internal.metadata.jvm.deserialization.JvmProtoBufUtil
910
import kotlinx.metadata.jvm.JvmMethodSignature
10-
import kotlinx.metadata.jvm.KotlinClassMetadata
1111
import java.lang.reflect.AnnotatedElement
1212
import java.lang.reflect.Constructor
13-
import java.lang.reflect.Field
1413
import java.lang.reflect.Method
1514

1615
internal typealias JavaDuration = java.time.Duration
1716
internal typealias KotlinDuration = kotlin.time.Duration
1817

1918
internal fun Class<*>.isUnboxableValueClass() = this.getAnnotation(JvmInline::class.java) != null
2019

21-
internal fun Class<*>.toKmClass(): KmClass? = this.getAnnotation(Metadata::class.java)
22-
?.let { (KotlinClassMetadata.read(it) as KotlinClassMetadata.Class).toKmClass() }
20+
internal fun Metadata.accept(visitor: ReducedKmClassVisitor) {
21+
val (strings, proto) = JvmProtoBufUtil.readClassDataFrom(data1.takeIf(Array<*>::isNotEmpty)!!, data2)
22+
proto.accept(visitor, strings)
23+
}
24+
25+
internal fun Class<*>.toReducedKmClass(): ReducedKmClass? = this.getAnnotation(Metadata::class.java)
26+
?.let { ReducedKmClass().apply { it.accept(this) } }
2327

2428
private val primitiveClassToDesc by lazy {
2529
mapOf(
@@ -69,8 +73,6 @@ private val Class<*>.descriptor: String
6973
else -> "L${this.descName()};"
7074
}
7175

72-
internal fun Field.desc(): String = this.type.descriptor
73-
7476
internal fun List<KmValueParameter>.hasVarargParam(): Boolean =
7577
lastOrNull()?.let { it.varargElementType != null } ?: false
7678

src/main/kotlin/io/github/projectmapk/jackson/module/kogera/JmClass.kt

Lines changed: 102 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,102 @@
11
package io.github.projectmapk.jackson.module.kogera
22

33
import kotlinx.metadata.ClassName
4+
import kotlinx.metadata.ExperimentalContextReceivers
45
import kotlinx.metadata.Flags
5-
import kotlinx.metadata.KmClass
6+
import kotlinx.metadata.KmClassExtensionVisitor
7+
import kotlinx.metadata.KmClassVisitor
68
import kotlinx.metadata.KmConstructor
9+
import kotlinx.metadata.KmConstructorVisitor
10+
import kotlinx.metadata.KmExtensionType
711
import kotlinx.metadata.KmFunction
12+
import kotlinx.metadata.KmFunctionVisitor
813
import kotlinx.metadata.KmProperty
9-
import kotlinx.metadata.jvm.fieldSignature
14+
import kotlinx.metadata.KmPropertyVisitor
15+
import kotlinx.metadata.KmType
16+
import kotlinx.metadata.KmTypeAliasVisitor
17+
import kotlinx.metadata.KmTypeParameterVisitor
18+
import kotlinx.metadata.KmTypeVisitor
19+
import kotlinx.metadata.KmVariance
20+
import kotlinx.metadata.KmVersionRequirementVisitor
21+
import kotlinx.metadata.flagsOf
1022
import kotlinx.metadata.jvm.getterSignature
1123
import kotlinx.metadata.jvm.signature
1224
import java.lang.reflect.Constructor
1325
import java.lang.reflect.Field
1426
import java.lang.reflect.Method
1527

28+
// KmClassVisitor with all processing disabled as much as possible to reduce load
29+
internal sealed class ReducedKmClassVisitor : KmClassVisitor() {
30+
final override val delegate: KmClassVisitor? get() = null
31+
32+
// from KmDeclarationContainerVisitor
33+
override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor? = null
34+
override fun visitProperty(
35+
flags: Flags,
36+
name: String,
37+
getterFlags: Flags,
38+
setterFlags: Flags
39+
): KmPropertyVisitor? = null
40+
override fun visitTypeAlias(flags: Flags, name: String): KmTypeAliasVisitor? = null
41+
override fun visitExtensions(type: KmExtensionType): KmClassExtensionVisitor? = null
42+
43+
// from KmClassVisitor
44+
override fun visit(flags: Flags, name: ClassName) {}
45+
override fun visitTypeParameter(
46+
flags: Flags,
47+
name: String,
48+
id: Int,
49+
variance: KmVariance
50+
): KmTypeParameterVisitor? = null
51+
override fun visitSupertype(flags: Flags): KmTypeVisitor? = null
52+
override fun visitConstructor(flags: Flags): KmConstructorVisitor? = null
53+
override fun visitCompanionObject(name: String) {}
54+
override fun visitNestedClass(name: String) {}
55+
override fun visitEnumEntry(name: String) {}
56+
override fun visitSealedSubclass(name: ClassName) {}
57+
override fun visitInlineClassUnderlyingPropertyName(name: String) {}
58+
override fun visitInlineClassUnderlyingType(flags: Flags): KmTypeVisitor? = null
59+
60+
@OptIn(ExperimentalContextReceivers::class)
61+
override fun visitContextReceiverType(flags: Flags): KmTypeVisitor? = null
62+
override fun visitVersionRequirement(): KmVersionRequirementVisitor? = null
63+
override fun visitEnd() {}
64+
}
65+
66+
internal class ReducedKmClass : ReducedKmClassVisitor() {
67+
var flags: Flags = flagsOf()
68+
val properties: MutableList<KmProperty> = ArrayList()
69+
val constructors: MutableList<KmConstructor> = ArrayList(1)
70+
var companionObject: String? = null
71+
val sealedSubclasses: MutableList<ClassName> = ArrayList(0)
72+
var inlineClassUnderlyingType: KmType? = null
73+
74+
override fun visit(flags: Flags, name: ClassName) {
75+
this.flags = flags
76+
}
77+
78+
override fun visitProperty(flags: Flags, name: String, getterFlags: Flags, setterFlags: Flags): KmPropertyVisitor =
79+
KmProperty(flags, name, getterFlags, setterFlags).apply { properties.add(this) }
80+
81+
override fun visitConstructor(flags: Flags): KmConstructorVisitor =
82+
KmConstructor(flags).apply { constructors.add(this) }
83+
84+
override fun visitCompanionObject(name: String) {
85+
this.companionObject = name
86+
}
87+
88+
override fun visitSealedSubclass(name: ClassName) {
89+
sealedSubclasses.add(name)
90+
}
91+
92+
override fun visitInlineClassUnderlyingType(flags: Flags): KmTypeVisitor =
93+
KmType(flags).also { inlineClassUnderlyingType = it }
94+
}
95+
1696
// Jackson Metadata Class
1797
internal class JmClass(
1898
clazz: Class<*>,
19-
kmClass: KmClass,
99+
kmClass: ReducedKmClass,
20100
superJmClass: JmClass?,
21101
interfaceJmClasses: List<JmClass>
22102
) {
@@ -40,10 +120,12 @@ internal class JmClass(
40120

41121
val flags: Flags = kmClass.flags
42122
val constructors: List<KmConstructor> = kmClass.constructors
123+
val propertyNameSet: Set<String> get() = allPropsMap.keys
43124
val properties: List<KmProperty> = allPropsMap.values.toList()
44125
val sealedSubclasses: List<ClassName> = kmClass.sealedSubclasses
45126
private val companionPropName: String? = kmClass.companionObject
46127
val companion: CompanionObject? by lazy { companionPropName?.let { CompanionObject(clazz, it) } }
128+
val inlineClassUnderlyingType: KmType? = kmClass.inlineClassUnderlyingType
47129

48130
fun findKmConstructor(constructor: Constructor<*>): KmConstructor? {
49131
val descHead = constructor.parameterTypes.toDescBuilder()
@@ -67,21 +149,30 @@ internal class JmClass(
67149

68150
// Field name always matches property name
69151
fun findPropertyByField(field: Field): KmProperty? = allPropsMap[field.name]
70-
?.takeIf { it.fieldSignature?.desc == field.desc() }
71152

72153
fun findPropertyByGetter(getter: Method): KmProperty? {
73-
val signature = getter.toSignature()
74-
return properties.find { it.getterSignature == signature }
154+
val getterName = getter.name
155+
return properties.find { it.getterSignature?.name == getterName }
75156
}
76157

77-
internal class CompanionObject(
78-
declaringClass: Class<*>,
79-
companionObject: String
80-
) {
158+
internal class CompanionObject(declaringClass: Class<*>, companionObject: String) {
159+
private class ReducedCompanionVisitor : ReducedKmClassVisitor() {
160+
val functions: MutableList<KmFunction> = arrayListOf()
161+
162+
override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor? = KmFunction(flags, name)
163+
.apply { functions.add(this) }
164+
165+
companion object {
166+
fun from(companionClass: Class<*>): ReducedCompanionVisitor = ReducedCompanionVisitor().apply {
167+
companionClass.getAnnotation(Metadata::class.java)!!.accept(this)
168+
}
169+
}
170+
}
171+
81172
private val companionField: Field = declaringClass.getDeclaredField(companionObject)
82173
val type: Class<*> = companionField.type
83174
val isAccessible: Boolean = companionField.isAccessible
84-
private val functions by lazy { type.toKmClass()!!.functions }
175+
private val functions by lazy { ReducedCompanionVisitor.from(type).functions }
85176
val instance: Any by lazy {
86177
// To prevent the call from failing, save the initial value and then rewrite the flag.
87178
if (!companionField.isAccessible) companionField.isAccessible = true

src/main/kotlin/io/github/projectmapk/jackson/module/kogera/KotlinModule.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ public class KotlinModule private constructor(
107107

108108
// ranges
109109
context.setMixInAnnotations(ClosedRange::class.java, ClosedRangeMixin::class.java)
110+
context.addAbstractTypeResolver(ClosedRangeResolver)
110111
}
111112

112113
public class Builder {

src/main/kotlin/io/github/projectmapk/jackson/module/kogera/ReflectionCache.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ internal class ReflectionCache(reflectionCacheSize: Int) : Serializable {
2828

2929
fun getJmClass(clazz: Class<*>): JmClass? {
3030
return classCache[clazz] ?: run {
31-
val kmClass = clazz.toKmClass() ?: return null
31+
val kmClass = clazz.toReducedKmClass() ?: return null
3232

3333
// Do not parse super class for interfaces.
3434
val superJmClass = if (!clazz.isInterface) {

0 commit comments

Comments
 (0)