Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed so that inherited properties can also be parsed #124

Merged
merged 4 commits into from
Aug 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import kotlinx.metadata.KmClass
import kotlinx.metadata.KmConstructor
import kotlinx.metadata.KmFunction
import kotlinx.metadata.KmProperty
import kotlinx.metadata.jvm.fieldSignature
import kotlinx.metadata.jvm.getterSignature
import kotlinx.metadata.jvm.signature
import java.lang.reflect.Constructor
Expand All @@ -15,11 +16,31 @@ import java.lang.reflect.Method
// Jackson Metadata Class
internal class JmClass(
private val clazz: Class<*>,
kmClass: KmClass
kmClass: KmClass,
superJmClass: JmClass?,
interfaceJmClasses: List<JmClass>
) {
private val allPropsMap: Map<String, KmProperty> = mutableMapOf<String, KmProperty>().apply {
kmClass.properties.forEach {
this[it.name] = it
}

// 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?.allPropsMap?.forEach {
this.putIfAbsent(it.key, it.value)
}
interfaceJmClasses.forEach { i ->
i.allPropsMap.forEach {
this.putIfAbsent(it.key, it.value)
}
}
}

val flags: Flags = kmClass.flags
val constructors: List<KmConstructor> = kmClass.constructors
val properties: List<KmProperty> = kmClass.properties
val properties: List<KmProperty> = allPropsMap.values.toList()
private val functions: List<KmFunction> = kmClass.functions
val sealedSubclasses: List<ClassName> = kmClass.sealedSubclasses
private val companionPropName: String? = kmClass.companionObject
Expand All @@ -45,6 +66,10 @@ internal class JmClass(
}
}

// Field name always matches property name
fun findPropertyByField(field: Field): KmProperty? = allPropsMap[field.name]
?.takeIf { it.fieldSignature == field.toSignature() }

fun findPropertyByGetter(getter: Method): KmProperty? {
val signature = getter.toSignature()
return properties.find { it.getterSignature == signature }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,18 @@ internal class ReflectionCache(reflectionCacheSize: Int) : Serializable {
fun getJmClass(clazz: Class<*>): JmClass? {
return classCache[clazz] ?: run {
val kmClass = clazz.toKmClass() ?: return null
val value = JmClass(clazz, kmClass)

// Do not parse super class for interfaces.
val superJmClass = if (!clazz.isInterface) {
clazz.superclass
?.takeIf { it != Any::class.java } // Stop parsing when `Object` is reached
?.let { getJmClass(it) }
} else {
null
}
val interfaceJmClasses = clazz.interfaces.mapNotNull { getJmClass(it) }

val value = JmClass(clazz, kmClass, superJmClass, interfaceJmClasses)
(classCache.putIfAbsent(clazz, value) ?: value)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import kotlinx.metadata.Flag
import kotlinx.metadata.KmClassifier
import kotlinx.metadata.KmProperty
import kotlinx.metadata.KmValueParameter
import kotlinx.metadata.jvm.fieldSignature
import kotlinx.metadata.jvm.getterSignature
import kotlinx.metadata.jvm.setterSignature
import kotlinx.metadata.jvm.signature
Expand Down Expand Up @@ -64,12 +63,10 @@ internal class KotlinPrimaryAnnotationIntrospector(
// but deserialization is preferred because there is currently no way to distinguish between contexts.
private fun AnnotatedField.hasRequiredMarker(jmClass: JmClass): Boolean? {
val member = annotated
val fieldSignature = member.toSignature()

// Direct access to `AnnotatedField` is only performed if there is no accessor (defined as JvmField),
// so if an accessor is defined, it is ignored.
return jmClass.properties
.find { it.fieldSignature == fieldSignature }
return jmClass.findPropertyByField(member)
// Since a property that does not currently have a getter cannot be defined,
// only a check for the existence of a getter is performed.
// https://youtrack.jetbrains.com/issue/KT-6519
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.github.projectmapk.jackson.module.kogera._integration.ser

import io.github.projectmapk.jackson.module.kogera.ReflectionCache
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

private class InheritanceTest {
abstract class A { val a: Int get() = 0 }

interface I { val i: Int get() = -1 }

interface J : I { val j: Int get() = -2 }

private class X : A(), J

val cache = ReflectionCache(5)

@Test
fun test() {
val c = cache.getJmClass(X::class.java)!!
val props = c.properties.associateBy { it.name }

assertEquals(3, props.size)
assertTrue(props.containsKey("a"))
assertTrue(props.containsKey("i"))
assertTrue(props.containsKey("j"))
}
}