From 58e396f50c947eec733b3a84ed70210b02571c7b Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 3 Nov 2019 20:39:08 +0300 Subject: [PATCH] Updated serialization utils --- .../vis/spatial/gdml/generateSchema.kt | 169 ++++++++++++++++++ .../kotlin/hep/dataforge/vis/spatial/Box.kt | 2 +- .../hep/dataforge/vis/spatial/Composite.kt | 2 +- .../hep/dataforge/vis/spatial/ConeSegment.kt | 2 +- .../hep/dataforge/vis/spatial/Convex.kt | 2 +- .../hep/dataforge/vis/spatial/Extruded.kt | 2 +- .../hep/dataforge/vis/spatial/PolyLine.kt | 2 +- .../kotlin/hep/dataforge/vis/spatial/Proxy.kt | 4 +- .../hep/dataforge/vis/spatial/Sphere.kt | 2 +- .../kotlin/hep/dataforge/vis/spatial/Tube.kt | 2 +- .../dataforge/vis/spatial/Visual3DPlugin.kt | 4 +- .../dataforge/vis/spatial/VisualGroup3D.kt | 4 +- .../hep/dataforge/vis/spatial/serilization.kt | 80 +++++++-- .../vis/spatial/SerializationTest.kt | 2 + 14 files changed, 252 insertions(+), 27 deletions(-) create mode 100644 dataforge-vis-spatial-gdml/src/jvmMain/kotlin/hep/dataforge/vis/spatial/gdml/generateSchema.kt diff --git a/dataforge-vis-spatial-gdml/src/jvmMain/kotlin/hep/dataforge/vis/spatial/gdml/generateSchema.kt b/dataforge-vis-spatial-gdml/src/jvmMain/kotlin/hep/dataforge/vis/spatial/gdml/generateSchema.kt new file mode 100644 index 00000000..fc417efb --- /dev/null +++ b/dataforge-vis-spatial-gdml/src/jvmMain/kotlin/hep/dataforge/vis/spatial/gdml/generateSchema.kt @@ -0,0 +1,169 @@ +package hep.dataforge.vis.spatial.gdml + +import hep.dataforge.meta.Meta +import hep.dataforge.vis.spatial.* +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.SerialModule +import kotlinx.serialization.modules.SerialModuleCollector +import kotlin.reflect.KClass + +internal val SerialDescriptor.jsonType + get() = when (this.kind) { + StructureKind.LIST -> "array" + PrimitiveKind.BYTE, PrimitiveKind.SHORT, PrimitiveKind.INT, PrimitiveKind.LONG, + PrimitiveKind.FLOAT, PrimitiveKind.DOUBLE -> "number" + PrimitiveKind.STRING, PrimitiveKind.CHAR, UnionKind.ENUM_KIND -> "string" + PrimitiveKind.BOOLEAN -> "boolean" + else -> "object" + } + + +private fun SerialModule.enumerate(type: KClass<*>): Sequence { + val list = ArrayList() + fun send(descriptor: SerialDescriptor) = list.add(descriptor) + + val enumerator = object : SerialModuleCollector { + override fun contextual(kClass: KClass, serializer: KSerializer) { + if (kClass == type) { + send(serializer.descriptor) + } + } + + override fun polymorphic( + baseClass: KClass, + actualClass: KClass, + actualSerializer: KSerializer + ) { + if (baseClass == type) { + send(actualSerializer.descriptor) + } + } + + } + dumpTo(enumerator) + return list.asSequence() +} + +/** + * Creates an [JsonObject] which contains Json Schema of given [descriptor]. + * + * Schema can contain following fields: + * `description`, `type` for all descriptors; + * `properties` and `required` for objects; + * `enum` for enums; + * `items` for arrays. + * + * User can modify this schema to add additional validation keywords + * (as per [https://json-schema.org/latest/json-schema-validation.html]) + * if they want. + */ +private fun jsonSchema(descriptor: SerialDescriptor, context: SerialModule): JsonObject { + + if (descriptor.name in arrayOf( + "hep.dataforge.vis.spatial.Point3D", + "hep.dataforge.vis.spatial.Point2D", + Meta::class.qualifiedName + ) + ) return json { + "\$ref" to "#/definitions/${descriptor.name}" + } + + + val properties: MutableMap = mutableMapOf() + val requiredProperties: MutableSet = mutableSetOf() + val isEnum = descriptor.kind == UnionKind.ENUM_KIND + val isPolymorphic = descriptor.kind == UnionKind.POLYMORPHIC + + + if (!isEnum && !isPolymorphic) descriptor.elementDescriptors().forEachIndexed { index, child -> + val elementName = descriptor.getElementName(index) + + properties[elementName] = when (elementName) { + "templates" -> json { + "\$ref" to "#/definitions/hep.dataforge.vis.spatial.VisualGroup3D" + } + "properties" -> json { + "\$ref" to "#/definitions/${Meta::class.qualifiedName}" + } + "first", "second" -> json{ + "\$ref" to "#/definitions/children" + } + "styleSheet" -> json { + "type" to "object" + "additionalProperties" to json { + "\$ref" to "#/definitions/${Meta::class.qualifiedName}" + } + } + in arrayOf("children") -> json { + "type" to "object" + "additionalProperties" to json { + "\$ref" to "#/definitions/children" + } + } + else -> jsonSchema(child, context) + } + + if (!descriptor.isElementOptional(index)) requiredProperties.add(elementName) + } + + val jsonType = descriptor.jsonType + val objectData: MutableMap = mutableMapOf( + "description" to JsonLiteral(descriptor.name), + "type" to JsonLiteral(jsonType) + ) + if (isEnum) { + val allElementNames = (0 until descriptor.elementsCount).map(descriptor::getElementName) + objectData += "enum" to JsonArray(allElementNames.map(::JsonLiteral)) + } + when (jsonType) { + "object" -> { + objectData["properties"] = JsonObject(properties) + val required = requiredProperties.map { JsonLiteral(it) } + if (required.isNotEmpty()) { + objectData["required"] = JsonArray(required) + } + } + "array" -> objectData["items"] = properties.values.let { + check(it.size == 1) { "Array descriptor has returned inconsistent number of elements: expected 1, found ${it.size}" } + it.first() + } + else -> { /* no-op */ + } + } + return JsonObject(objectData) +} + +fun main() { + val context = Visual3DPlugin.serialModule + val definitions = json { + "children" to json { + "anyOf" to jsonArray { + context.enumerate(VisualObject3D::class).forEach { + if (it.name == "hep.dataforge.vis.spatial.VisualGroup3D") { + +json { + "\$ref" to "#/definitions/${it.name}" + } + } else { + +jsonSchema(it, context) + } + } + } + } + "hep.dataforge.vis.spatial.Point3D" to jsonSchema(Point3DSerializer.descriptor, context) + "hep.dataforge.vis.spatial.Point2D" to jsonSchema(Point2DSerializer.descriptor, context) + "hep.dataforge.vis.spatial.VisualGroup3D" to jsonSchema(VisualGroup3D.serializer().descriptor, context) + + } + + println( + Json.indented.stringify( + JsonObjectSerializer, + json { + "definitions" to definitions + "\$ref" to "#/definitions/hep.dataforge.vis.spatial.VisualGroup3D" + } + ) + ) +} + diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt index e71803f1..8b78f991 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt @@ -2,7 +2,7 @@ package hep.dataforge.vis.spatial import hep.dataforge.context.Context -import hep.dataforge.io.ConfigSerializer +import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.meta.* import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.VisualFactory diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Composite.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Composite.kt index 0b296fae..ba20b4b3 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Composite.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Composite.kt @@ -1,7 +1,7 @@ @file:UseSerializers(Point3DSerializer::class) package hep.dataforge.vis.spatial -import hep.dataforge.io.ConfigSerializer +import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.meta.Config import hep.dataforge.meta.MetaBuilder import hep.dataforge.meta.update diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/ConeSegment.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/ConeSegment.kt index 26d4942f..db87acc7 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/ConeSegment.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/ConeSegment.kt @@ -2,7 +2,7 @@ package hep.dataforge.vis.spatial -import hep.dataforge.io.ConfigSerializer +import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.meta.Config import hep.dataforge.vis.common.AbstractVisualObject import kotlinx.serialization.Serializable diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Convex.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Convex.kt index 5f665bde..7231f6c2 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Convex.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Convex.kt @@ -2,7 +2,7 @@ package hep.dataforge.vis.spatial -import hep.dataforge.io.ConfigSerializer +import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.meta.Config import hep.dataforge.meta.MetaBuilder import hep.dataforge.vis.common.AbstractVisualObject diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Extruded.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Extruded.kt index 32e8082d..7255b7ef 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Extruded.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Extruded.kt @@ -1,7 +1,7 @@ @file:UseSerializers(Point2DSerializer::class, Point3DSerializer::class) package hep.dataforge.vis.spatial -import hep.dataforge.io.ConfigSerializer +import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.meta.Config import hep.dataforge.vis.common.AbstractVisualObject import kotlinx.serialization.Serializable diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/PolyLine.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/PolyLine.kt index eb8b4699..3e0e554b 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/PolyLine.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/PolyLine.kt @@ -2,7 +2,7 @@ package hep.dataforge.vis.spatial -import hep.dataforge.io.ConfigSerializer +import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.meta.Config import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.number diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Proxy.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Proxy.kt index 4dd11e79..60756f7a 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Proxy.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Proxy.kt @@ -2,7 +2,7 @@ package hep.dataforge.vis.spatial -import hep.dataforge.io.ConfigSerializer +import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.io.serialization.NameSerializer import hep.dataforge.meta.* @@ -15,6 +15,7 @@ import hep.dataforge.vis.common.MutableVisualGroup import hep.dataforge.vis.common.VisualGroup import hep.dataforge.vis.common.VisualObject import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient import kotlinx.serialization.UseSerializers import kotlin.collections.component1 import kotlin.collections.component2 @@ -72,6 +73,7 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua ProxyChild(it.key.asName()) } ?: emptyMap() + @Transient private val propertyCache: HashMap = HashMap() fun childPropertyName(childName: Name, propertyName: Name): Name { diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Sphere.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Sphere.kt index 7cc2c411..ab7b79f9 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Sphere.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Sphere.kt @@ -2,7 +2,7 @@ package hep.dataforge.vis.spatial -import hep.dataforge.io.ConfigSerializer +import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.meta.Config import hep.dataforge.vis.common.AbstractVisualObject import kotlinx.serialization.Serializable diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Tube.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Tube.kt index 77f707ee..384f3cfb 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Tube.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Tube.kt @@ -1,7 +1,7 @@ @file:UseSerializers(Point3DSerializer::class) package hep.dataforge.vis.spatial -import hep.dataforge.io.ConfigSerializer +import hep.dataforge.io.serialization.ConfigSerializer import hep.dataforge.meta.Config import hep.dataforge.vis.common.AbstractVisualObject import kotlinx.serialization.Serializable diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Visual3DPlugin.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Visual3DPlugin.kt index 093c7cfe..39c62bc2 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Visual3DPlugin.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Visual3DPlugin.kt @@ -4,8 +4,8 @@ import hep.dataforge.context.AbstractPlugin import hep.dataforge.context.Context import hep.dataforge.context.PluginFactory import hep.dataforge.context.PluginTag -import hep.dataforge.io.ConfigSerializer -import hep.dataforge.io.MetaSerializer +import hep.dataforge.io.serialization.ConfigSerializer +import hep.dataforge.io.serialization.MetaSerializer import hep.dataforge.io.serialization.NameSerializer import hep.dataforge.meta.* import hep.dataforge.names.Name diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualGroup3D.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualGroup3D.kt index fca6f7ed..622f345d 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualGroup3D.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualGroup3D.kt @@ -8,8 +8,8 @@ package hep.dataforge.vis.spatial -import hep.dataforge.io.ConfigSerializer -import hep.dataforge.io.MetaSerializer +import hep.dataforge.io.serialization.ConfigSerializer +import hep.dataforge.io.serialization.MetaSerializer import hep.dataforge.io.serialization.NameSerializer import hep.dataforge.meta.Config import hep.dataforge.meta.Meta diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/serilization.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/serilization.kt index c7b3cbfa..b4f87d39 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/serilization.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/serilization.kt @@ -1,45 +1,97 @@ package hep.dataforge.vis.spatial +import hep.dataforge.io.serialization.descriptor import hep.dataforge.names.NameToken import hep.dataforge.names.toName import kotlinx.serialization.* +import kotlinx.serialization.internal.DoubleSerializer import kotlinx.serialization.internal.StringDescriptor +import kotlinx.serialization.internal.nullable -@Serializable -private data class Point2DSerial(val x: Double, val y: Double) +inline fun Decoder.decodeStructure( + desc: SerialDescriptor, + vararg typeParams: KSerializer<*> = emptyArray(), + crossinline block: CompositeDecoder.() -> R +): R { + val decoder = beginStructure(desc, *typeParams) + val res = decoder.block() + decoder.endStructure(desc) + return res +} -@Serializable -private data class Point3DSerial(val x: Double, val y: Double, val z: Double) +inline fun Encoder.encodeStructure( + desc: SerialDescriptor, + vararg typeParams: KSerializer<*> = emptyArray(), + block: CompositeEncoder.() -> Unit +) { + val encoder = beginStructure(desc, *typeParams) + encoder.block() + encoder.endStructure(desc) +} @Serializer(Point3D::class) object Point3DSerializer : KSerializer { - private val serializer = Point3DSerial.serializer() - override val descriptor: SerialDescriptor get() = serializer.descriptor + override val descriptor: SerialDescriptor = descriptor("hep.dataforge.vis.spatial.Point3D") { + double("x", true) + double("y", true) + double("z", true) + } override fun deserialize(decoder: Decoder): Point3D { - return serializer.deserialize(decoder).let { - Point3D(it.x, it.y, it.z) + var x: Double? = null + var y: Double? = null + var z: Double? = null + decoder.decodeStructure(descriptor) { + loop@ while (true) { + when (val i = decodeElementIndex(descriptor)) { + CompositeDecoder.READ_DONE -> break@loop + 0 -> x = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0 + 1 -> y = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0 + 2 -> z = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0 + else -> throw SerializationException("Unknown index $i") + } + } } + return Point3D(x?:0.0, y?:0.0, z?:0.0) } override fun serialize(encoder: Encoder, obj: Point3D) { - serializer.serialize(encoder, Point3DSerial(obj.x, obj.y, obj.z)) + encoder.encodeStructure(descriptor) { + if (obj.x != 0.0) encodeDoubleElement(descriptor, 0, obj.x) + if (obj.y != 0.0) encodeDoubleElement(descriptor, 1, obj.y) + if (obj.z != 0.0) encodeDoubleElement(descriptor, 2, obj.z) + } } } @Serializer(Point2D::class) object Point2DSerializer : KSerializer { - private val serializer = Point2DSerial.serializer() - override val descriptor: SerialDescriptor get() = serializer.descriptor + override val descriptor: SerialDescriptor = descriptor("hep.dataforge.vis.spatial.Point2D") { + double("x", true) + double("y", true) + } override fun deserialize(decoder: Decoder): Point2D { - return serializer.deserialize(decoder).let { - Point2D(it.x, it.y) + var x: Double? = null + var y: Double? = null + decoder.decodeStructure(descriptor) { + loop@ while (true) { + when (val i = decodeElementIndex(descriptor)) { + CompositeDecoder.READ_DONE -> break@loop + 0 -> x = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0 + 1 -> y = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0 + else -> throw SerializationException("Unknown index $i") + } + } } + return Point2D(x?:0.0, y?:0.0) } override fun serialize(encoder: Encoder, obj: Point2D) { - serializer.serialize(encoder, Point2DSerial(obj.x, obj.y)) + encoder.encodeStructure(descriptor) { + if (obj.x != 0.0) encodeDoubleElement(descriptor, 0, obj.x) + if (obj.y != 0.0) encodeDoubleElement(descriptor, 1, obj.y) + } } } diff --git a/dataforge-vis-spatial/src/commonTest/kotlin/hep/dataforge/vis/spatial/SerializationTest.kt b/dataforge-vis-spatial/src/commonTest/kotlin/hep/dataforge/vis/spatial/SerializationTest.kt index 09d59f29..958f98c3 100644 --- a/dataforge-vis-spatial/src/commonTest/kotlin/hep/dataforge/vis/spatial/SerializationTest.kt +++ b/dataforge-vis-spatial/src/commonTest/kotlin/hep/dataforge/vis/spatial/SerializationTest.kt @@ -11,6 +11,8 @@ class SerializationTest { fun testCubeSerialization(){ val cube = Box(100f,100f,100f).apply{ color(222) + x = 100 + z = -100 } val string = json.stringify(Box.serializer(),cube) println(string)