Skip to content

Commit

Permalink
Add support for referencing direct Map keys in graphs.
Browse files Browse the repository at this point in the history
  • Loading branch information
jurmous committed Jul 7, 2024
1 parent a354eea commit 36683e9
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package maryk.core.properties.graph

import maryk.core.models.IsDataModel
import maryk.core.properties.definitions.IsSerializablePropertyDefinition
import maryk.core.properties.references.IsPropertyReference
import maryk.core.query.ContainsDataModelContext

/** Context for Graph serializing */
class GraphContext(
override var dataModel: IsDataModel? = null,
var subDataModel: IsDataModel? = null
var subDataModel: IsDataModel? = null,
var reference: IsPropertyReference<*, IsSerializablePropertyDefinition<*, *>, *>? = null,
) : ContainsDataModelContext<IsDataModel>
125 changes: 125 additions & 0 deletions core/src/commonMain/kotlin/maryk/core/properties/graph/GraphMapItem.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package maryk.core.properties.graph

import maryk.core.exceptions.ContextNotFoundException
import maryk.core.models.ContextualDataModel
import maryk.core.models.IsDataModel
import maryk.core.models.IsValuesDataModel
import maryk.core.models.serializers.ObjectDataModelSerializer
import maryk.core.models.values
import maryk.core.properties.IsPropertyContext
import maryk.core.properties.definitions.IsMapDefinition
import maryk.core.properties.definitions.contextual.ContextualPropertyReferenceDefinition
import maryk.core.properties.definitions.contextual.ContextualSubDefinition
import maryk.core.properties.definitions.wrapper.IsDefinitionWrapper
import maryk.core.properties.definitions.wrapper.IsMapDefinitionWrapper
import maryk.core.properties.definitions.wrapper.contextual
import maryk.core.properties.graph.PropRefGraph.Companion.parent
import maryk.core.properties.graph.PropRefGraph.Companion.properties
import maryk.core.properties.references.IsMapReference
import maryk.core.query.ContainsDataModelContext
import maryk.core.values.ObjectValues
import maryk.json.IsJsonLikeReader
import maryk.json.IsJsonLikeWriter
import maryk.json.JsonToken
import maryk.lib.exceptions.ParseException


operator fun <K: Any, DM: IsDataModel> IsMapDefinitionWrapper<K, *, *, *, *>.get(
key: K
) = GraphMapItem<K, DM>(
mapReference = this.ref() as IsMapReference<*, *, *, *>,
key = key,
)

/**
* Represents a Property Reference Graph branch below a [parent] with all [properties] to fetch
* [properties] should always be sorted by index so processing graphs is a lot easier
*/
data class GraphMapItem<K: Any, DM: IsDataModel> internal constructor(
val mapReference: IsMapReference<*, *, *, *>,
val key: K,
) : IsPropRefGraphNode<DM>, IsTransportablePropRefGraphNode {
override val index = mapReference.index
override val graphType = PropRefGraphType.MapKey

override fun toString() = "${this.mapReference.name}[$key]"

companion object : ContextualDataModel<GraphMapItem<*, *>, Companion, ContainsDataModelContext<*>, GraphContext>(
contextTransformer = {
if (it is GraphContext && it.subDataModel != null) {
GraphContext(it.subDataModel)
} else {
GraphContext(it?.dataModel)
}
}
) {
val mapReference by contextual(
index = 1u,
getter = GraphMapItem<*, *>::mapReference,
definition = ContextualPropertyReferenceDefinition(
contextualResolver = { context: GraphContext? ->
(context?.subDataModel ?: context?.dataModel) as? IsValuesDataModel? ?: throw ContextNotFoundException()
}
),
capturer = { context, value ->
@Suppress("UNCHECKED_CAST")
context.reference = value as IsMapReference<Comparable<Any>, Any, IsPropertyContext, IsMapDefinitionWrapper<Comparable<Any>, Any, Any, IsPropertyContext, Any>>
}
)

val key by contextual(
index = 2u,
getter = GraphMapItem<*, *>::key,
definition = ContextualSubDefinition(
contextualResolver = { context: GraphContext? ->
@Suppress("UNCHECKED_CAST")
(context?.reference as IsMapReference<Comparable<Any>, Any, IsPropertyContext, IsMapDefinitionWrapper<Comparable<Any>, Any, Any, IsPropertyContext, Any>>?)?.propertyDefinition?.definition?.keyDefinition
?: throw ContextNotFoundException()
}
)
)

override fun invoke(values: ObjectValues<GraphMapItem<*, *>, Companion>): GraphMapItem<*, *> = GraphMapItem<Any, IsDataModel>(
mapReference = values(1u),
key = values(2u)
)

override val Serializer = object: ObjectDataModelSerializer<GraphMapItem<*, *>, Companion, ContainsDataModelContext<*>, GraphContext>(this) {
override fun writeObjectAsJson(
obj: GraphMapItem<*, *>,
writer: IsJsonLikeWriter,
context: GraphContext?,
skip: List<IsDefinitionWrapper<*, *, *, GraphMapItem<*, *>>>?
) {
writer.writeString("${obj.mapReference.name}[${obj.key}]")
}

override fun readJson(
reader: IsJsonLikeReader,
context: GraphContext?
): ObjectValues<GraphMapItem<*, *>, Companion> {
val tokenToParse: JsonToken.Value<*> = reader.currentToken as? JsonToken.Value<*>
?: throw ParseException("Expected value token")

val (name, keyAsString) = tokenToParse.value.toString().split("[", "]")

val mapReference = context?.dataModel?.getPropertyReferenceByName(name, context) as? IsMapReference<*, *, *, *>
?: throw ParseException("Expected MapReference")

@Suppress("UNCHECKED_CAST")
val key = when (val mapDef = mapReference.comparablePropertyDefinition) {
is IsMapDefinition<*, *, *> -> mapDef.keyDefinition.fromString(keyAsString) as Comparable<Any>
else -> throw ParseException("Unknown MapReference type")
}

return values {
mapNonNulls(
this.mapReference withSerializable mapReference,
this.key withSerializable key
)
}
}
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import maryk.core.models.IsDataModel
import maryk.core.properties.definitions.wrapper.IsDefinitionWrapper
import maryk.core.properties.references.EmbeddedObjectPropertyRef
import maryk.core.properties.references.EmbeddedValuesPropertyRef
import maryk.core.properties.references.IsMapReference
import maryk.core.properties.references.IsPropertyReference
import maryk.core.properties.references.IsPropertyReferenceForValues
import maryk.core.properties.references.MapValueReference

/** Defines a graph element */
interface IsPropRefGraph<in DM : IsDataModel> {
Expand Down Expand Up @@ -41,23 +43,36 @@ interface IsPropRefGraph<in DM : IsDataModel> {
fun contains(reference: IsPropertyReference<*, *, *>): Boolean {
val elements = reference.unwrap()

var referenceIndex = 0
var currentReference = elements[referenceIndex++]
var currentSelect: IsPropRefGraph<*> = this
var currentNode: IsPropRefGraph<*> = this
var currentMapKey: GraphMapItem<*, *>? = null

loop@ while (referenceIndex <= elements.size) {
for ((index, currentReference) in elements.withIndex()) {
return when (currentReference) {
is IsMapReference<*, *, *, *> -> {
if (index != elements.lastIndex) {
when (val node = currentNode.selectNodeOrNull(currentReference.index)) {
is PropRefGraph<*, *> -> break // Should not contain a PropRefGraph
is GraphMapItem<*, *> -> {
currentMapKey = node
continue // To next MapValueReference
}
}
}
currentNode.contains(currentReference.index)
}
is MapValueReference<*, *, *> -> {
currentMapKey != null && currentMapKey.key == currentReference.key
}
is IsPropertyReferenceForValues<*, *, *, *> -> {
if (referenceIndex < elements.size && currentReference is EmbeddedValuesPropertyRef<*, *> || currentReference is EmbeddedObjectPropertyRef<*, *, *, *, *>) {
when (val node = currentSelect.selectNodeOrNull(currentReference.index)) {
if (index != elements.lastIndex && currentReference is EmbeddedValuesPropertyRef<*, *> || currentReference is EmbeddedObjectPropertyRef<*, *, *, *, *>) {
when (val node = currentNode.selectNodeOrNull(currentReference.index)) {
is PropRefGraph<*, *> -> {
currentReference = elements[referenceIndex++]
currentSelect = node
continue@loop
currentNode = node
continue
}
else -> currentSelect.contains(currentReference.index)
else -> currentNode.contains(currentReference.index)
}
} else currentSelect.contains(currentReference.index)
} else currentNode.contains(currentReference.index)
}
else -> false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package maryk.core.properties.graph
import maryk.core.models.IsDataModel

/** Defines an element which can be used within a graph */
interface IsPropRefGraphNode<in P : IsDataModel> {
interface IsPropRefGraphNode<in DM : IsDataModel> {
val index: UInt
val graphType: PropRefGraphType
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import maryk.core.properties.definitions.wrapper.IsDefinitionWrapper
import maryk.core.properties.definitions.wrapper.ListDefinitionWrapper
import maryk.core.properties.definitions.wrapper.contextual
import maryk.core.properties.graph.PropRefGraphType.Graph
import maryk.core.properties.graph.PropRefGraphType.MapKey
import maryk.core.properties.graph.PropRefGraphType.PropRef
import maryk.core.properties.references.AnyPropertyReference
import maryk.core.properties.references.IsPropertyReferenceForValues
Expand Down Expand Up @@ -94,6 +95,9 @@ data class PropRefGraph<DM : IsValuesDataModel, DMS : IsValuesDataModel> interna
Graph to EmbeddedObjectDefinition(
dataModel = { this@Companion }
),
MapKey to EmbeddedObjectDefinition(
dataModel = { GraphMapItem }
),
PropRef to ContextualPropertyReferenceDefinition(
contextualResolver = { context: GraphContext? ->
context?.subDataModel as? IsValuesDataModel? ?: throw ContextNotFoundException()
Expand All @@ -108,6 +112,7 @@ data class PropRefGraph<DM : IsValuesDataModel, DMS : IsValuesDataModel> interna
when (it) {
is IsDefinitionWrapper<*, *, *, *> -> TypedValue(it.graphType, it.ref() as IsTransportablePropRefGraphNode)
is PropRefGraph<*, *> -> TypedValue(it.graphType, it)
is GraphMapItem<*, *> -> TypedValue(it.graphType, it)
else -> throw ParseException("Unknown PropRefGraphType ${it.graphType}")
}
}
Expand All @@ -116,6 +121,7 @@ data class PropRefGraph<DM : IsValuesDataModel, DMS : IsValuesDataModel> interna
when (value.type) {
PropRef -> (value.value as IsPropertyReferenceForValues<*, *, *, *>).propertyDefinition
Graph -> value.value as IsPropRefGraphNode<*>
MapKey -> value.value as GraphMapItem<*, *>
}
}
)
Expand All @@ -133,7 +139,8 @@ data class PropRefGraph<DM : IsValuesDataModel, DMS : IsValuesDataModel> interna
context: GraphContext?,
skip: List<IsDefinitionWrapper<*, *, *, PropRefGraph<*, *>>>?
) {
writeJsonValues(obj.parent.ref(), obj.properties, writer, context)
val newContext = transformContext(context)
writeJsonValues(obj.parent.ref(), obj.properties, writer, newContext)
}

private fun writeJsonValues(
Expand Down Expand Up @@ -240,6 +247,9 @@ internal fun writePropertiesToJson(
is PropRefGraph<*, *> -> PropRefGraph.Serializer.writeObjectAsJson(
value, writer, context
)
is GraphMapItem<*, *> -> GraphMapItem.Serializer.writeObjectAsJson(
value, writer, context
)
is IsDefinitionWrapper<*, *, *, *> -> {
writer.writeString(value.ref().completeName)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ enum class PropRefGraphType(
override val alternativeNames: Set<String>? = null
) : IndexedEnumComparable<PropRefGraphType>, IsCoreEnum, TypeEnum<IsTransportablePropRefGraphNode> {
PropRef(1u),
Graph(2u);
Graph(2u),
MapKey(3u);

companion object : IndexedEnumDefinition<PropRefGraphType>(
PropRefGraphType::class, { entries }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import maryk.core.properties.definitions.list
import maryk.core.properties.definitions.wrapper.IsDefinitionWrapper
import maryk.core.properties.definitions.wrapper.ListDefinitionWrapper
import maryk.core.properties.graph.PropRefGraphType.Graph
import maryk.core.properties.graph.PropRefGraphType.MapKey
import maryk.core.properties.graph.PropRefGraphType.PropRef
import maryk.core.properties.references.IsPropertyReferenceForValues
import maryk.core.properties.types.TypedValue
Expand Down Expand Up @@ -47,24 +48,27 @@ data class RootPropRefGraph<DM : IsRootDataModel> internal constructor(
contextualResolver = { context: GraphContext? ->
context?.dataModel as? IsValuesDataModel? ?: throw ContextNotFoundException()
}
),
MapKey to EmbeddedObjectDefinition(
dataModel = { GraphMapItem }
)
),
typeEnum = PropRefGraphType
),
getter = RootPropRefGraph<*>::properties,
toSerializable = { value: IsPropRefGraphNode<*> ->
value.let {
when (it) {
is IsDefinitionWrapper<*, *, *, *> -> TypedValue(it.graphType, it.ref() as IsTransportablePropRefGraphNode)
is PropRefGraph<*, *> -> TypedValue(it.graphType, it)
else -> throw ParseException("Unknown PropRefGraphType ${it.graphType}")
}
when (value) {
is IsDefinitionWrapper<*, *, *, *> -> TypedValue(value.graphType, value.ref() as IsTransportablePropRefGraphNode)
is PropRefGraph<*, *> -> TypedValue(value.graphType, value)
is GraphMapItem<*, *> -> TypedValue(value.graphType, value)
else -> throw ParseException("Unknown PropRefGraphType ${value.graphType}")
}
},
fromSerializable = { value: TypedValue<PropRefGraphType, IsTransportablePropRefGraphNode> ->
when (value.type) {
PropRef -> (value.value as IsPropertyReferenceForValues<*, *, *, *>).propertyDefinition
Graph -> value.value as IsPropRefGraphNode<*>
MapKey -> value.value as IsPropRefGraphNode<*>
}
}
)
Expand Down Expand Up @@ -111,21 +115,30 @@ data class RootPropRefGraph<DM : IsRootDataModel> internal constructor(
while (currentToken != JsonToken.EndArray && currentToken !is JsonToken.Stopped) {
when (currentToken) {
is JsonToken.StartObject -> {
val newContext = transformContext(context)

propertiesList.add(
TypedValue(
Graph,
PropRefGraph.Serializer.readJson(reader, context).toDataObject()
PropRefGraph.Serializer.readJson(reader, newContext).toDataObject()
)
)
}
is JsonToken.Value<*> -> {
val multiTypeDefinition =
this@Companion.properties.valueDefinition as IsMultiTypeDefinition<PropRefGraphType, IsTransportablePropRefGraphNode, GraphContext>

val currentTokenValue = currentToken.value

val type = when {
currentTokenValue is String && currentTokenValue.contains(char = '[') -> MapKey
else -> PropRef
}

propertiesList.add(
TypedValue(
PropRef,
multiTypeDefinition.definition(PropRef)!!.readJson(reader, context)
type,
multiTypeDefinition.definition(type)!!.readJson(reader, context)
)
)
}
Expand Down
Loading

0 comments on commit 36683e9

Please sign in to comment.