diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoManager.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoManager.kt index 3b3eecf5..96b02768 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoManager.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoManager.kt @@ -1,29 +1,19 @@ package ru.mipt.npm.root +import kotlinx.serialization.Contextual import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.decodeFromJsonElement @Serializable @SerialName("TGeoManager") public class TGeoManager : TNamed() { + @Contextual public val fMatrices: TObjArray = TObjArray.empty + + @Contextual public val fShapes: TObjArray = TObjArray.empty + + @Contextual public val fVolumes: TObjArray = TObjArray.empty - - - public companion object { - - - /** - * Load Json encoded TGeoManager - */ - public fun decodeFromJson(jsonElement: JsonElement): TGeoManager = - rootJson().decodeFromJsonElement(jsonElement) - - public fun decodeFromString(string: String): TGeoManager = - rootJson().decodeFromString(serializer(), string) - } } diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoHMatrix.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoMatrix.kt similarity index 90% rename from cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoHMatrix.kt rename to cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoMatrix.kt index 3e1362f8..0c6ec7ab 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoHMatrix.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoMatrix.kt @@ -1,5 +1,6 @@ package ru.mipt.npm.root +import kotlinx.serialization.Contextual import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -30,11 +31,12 @@ public class TGeoTranslation( @SerialName("TGeoRotation") public class TGeoRotation( public val fRotationMatrix: DoubleArray -): TGeoMatrix() +) : TGeoMatrix() @Serializable @SerialName("TGeoCombiTrans") public class TGeoCombiTrans( public val fTranslation: DoubleArray, + @Contextual public val fRotation: TGeoRotation? = null, -): TGeoMatrix() \ No newline at end of file +) : TGeoMatrix() \ No newline at end of file diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoMedium.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoMedium.kt index 3e598753..72b72c4c 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoMedium.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoMedium.kt @@ -1,12 +1,14 @@ package ru.mipt.npm.root +import kotlinx.serialization.Contextual import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable @SerialName("TGeoMedium") public class TGeoMedium( - public val fId : Int, + public val fId: Int, + @Contextual public val fMaterial: TGeoMaterial, public val fParams: DoubleArray -): TNamed() \ No newline at end of file +) : TNamed() \ No newline at end of file diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoNode.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoNode.kt index 5805f3ea..f507aeb2 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoNode.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoNode.kt @@ -1,5 +1,6 @@ package ru.mipt.npm.root +import kotlinx.serialization.Contextual import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -7,7 +8,10 @@ import kotlinx.serialization.Serializable @SerialName("TGeoNode") public open class TGeoNode : TNamed() { //val fGeoAtt: UInt + @Contextual public val fVolume: TGeoVolume? = null + + @Contextual public val fMother: TGeoVolume? = null public val fNumber: Int = 0 public val fNovlp: Int = 0 @@ -17,5 +21,6 @@ public open class TGeoNode : TNamed() { @Serializable @SerialName("TGeoNodeMatrix") public class TGeoNodeMatrix : TGeoNode() { + @Contextual public val fMatrix: TGeoMatrix? = null } \ No newline at end of file diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoShape.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoShape.kt index f26261da..c986f899 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoShape.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoShape.kt @@ -1,5 +1,6 @@ package ru.mipt.npm.root +import kotlinx.serialization.Contextual import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -22,36 +23,55 @@ public open class TGeoBBox : TGeoShape() { @Serializable @SerialName("TGeoBoolNode") public sealed class TGeoBoolNode : TObject() { + @Contextual public abstract val fLeft: TGeoShape + + @Contextual public abstract val fLeftMat: TGeoMatrix + + @Contextual public abstract val fRight: TGeoShape + + @Contextual public abstract val fRightMat: TGeoMatrix } @Serializable @SerialName("TGeoUnion") public class TGeoUnion( + @Contextual override val fLeft: TGeoShape, + @Contextual override val fLeftMat: TGeoMatrix, + @Contextual override val fRight: TGeoShape, + @Contextual override val fRightMat: TGeoMatrix ) : TGeoBoolNode() @Serializable @SerialName("TGeoSubtraction") public class TGeoSubtraction( + @Contextual override val fLeft: TGeoShape, + @Contextual override val fLeftMat: TGeoMatrix, + @Contextual override val fRight: TGeoShape, + @Contextual override val fRightMat: TGeoMatrix ) : TGeoBoolNode() @Serializable @SerialName("TGeoIntersection") public class TGeoIntersection( + @Contextual override val fLeft: TGeoShape, + @Contextual override val fLeftMat: TGeoMatrix, + @Contextual override val fRight: TGeoShape, + @Contextual override val fRightMat: TGeoMatrix ) : TGeoBoolNode() @@ -103,6 +123,7 @@ public class TGeoTubeSeg( @Serializable @SerialName("TGeoShapeAssembly") public class TGeoShapeAssembly( + @Contextual public val fVolume: TGeoVolumeAssembly, public val fBBoxOK: Boolean = true ) : TGeoBBox() \ No newline at end of file diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoVolume.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoVolume.kt index dc986729..afa7c7d9 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoVolume.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoVolume.kt @@ -1,20 +1,30 @@ package ru.mipt.npm.root +import kotlinx.serialization.Contextual import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable @SerialName("TGeoVolume") -public open class TGeoVolume : TNamed(){ +public open class TGeoVolume : TNamed() { // "fGeoAtt" : 3084, // "fLineColor" : 3, // "fLineStyle" : 1, // "fLineWidth" : 1, // "fFillColor" : 19, // "fFillStyle" : 1001, + @Contextual public lateinit var fNodes: TObjArray + internal set + + @Contextual public lateinit var fShape: TGeoShape + internal set + + @Contextual public lateinit var fMedium: TGeoMedium + internal set + public val fNumber: Int = 1 public val fNtotal: Int = 1 public val fRefCount: Int = 1 diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TObject.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TObject.kt index bf528e6f..f70cc589 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TObject.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TObject.kt @@ -10,15 +10,23 @@ public abstract class TObject { } @Serializable -public abstract class TNamed : TObject() { +public open class TNamed : TObject() { public val fName: String = "" public val fTitle: String = "" } @Serializable @SerialName("TObjArray") -public class TObjArray(public val arr: List){ +public class TObjArray(public val arr: List): TObject() { public companion object{ - public val empty = TObjArray(emptyList()) + public val empty: TObjArray = TObjArray(emptyList()) } -} \ No newline at end of file +} + +@Serializable +@SerialName("TList") +public class TList(public val arr: List): TObject() + +@Serializable +@SerialName("THashList") +public class THashList(public val arr: List): TObject() \ No newline at end of file diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/rootJson.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/rootJson.kt index f0b25d18..33b8b6c2 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/rootJson.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/rootJson.kt @@ -1,107 +1,223 @@ package ru.mipt.npm.root +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.* -import kotlinx.serialization.modules.PolymorphicModuleBuilder -import kotlinx.serialization.modules.SerializersModule -import kotlinx.serialization.modules.polymorphic -import kotlinx.serialization.modules.subclass +import kotlinx.serialization.modules.* +import kotlin.reflect.KClass -private typealias RefCache = MutableList +/** + * Load Json encoded TObject + */ +public fun TObject.Companion.decodeFromJson(serializer: KSerializer, jsonElement: JsonElement): TObject = + RootDecoder.decode(serializer, jsonElement) -private class RootJsonSerializer( - private val refCache: RefCache, - private val tSerializer: KSerializer -) : KSerializer { +public fun TObject.Companion.decodeFromString(serializer: KSerializer, string: String): TObject { + val json = RootDecoder.json.parseToJsonElement(string) + return RootDecoder.decode(serializer, json) +} - override val descriptor: SerialDescriptor get() = tSerializer.descriptor +private object RootDecoder { - override fun deserialize(decoder: Decoder): T { - val input = decoder as JsonDecoder - val element = input.decodeJsonElement() - val refId = (element as? JsonObject)?.get("\$ref")?.jsonPrimitive?.int - return if (refId != null) { - //Do unref - val refValue = refCache[refId] - refValue as T //TODO research means to make it safe - } else { - val res = input.json.decodeFromJsonElement(tSerializer, element) - //val uid = res.fUniqueID - refCache.add(res) - res + private class RootUnrefSerializer( + private val tSerializer: KSerializer, + private val refCache: MutableList,// = ArrayList(4096) + private val counter: ReferenceCounter + ) : KSerializer by tSerializer { + + override fun deserialize(decoder: Decoder): T { + val input = decoder as JsonDecoder + val element = input.decodeJsonElement() + val refId = (element as? JsonObject)?.get("\$ref")?.jsonPrimitive?.int + val ref = if (refId != null) { + //Do unref + refCache[refId] + } else { + refCache[counter.value].also { + counter.increment() + } + } + return ref.value(tSerializer) as T //TODO research means to make it safe } } - override fun serialize(encoder: Encoder, value: T) { - tSerializer.serialize(encoder, value) + private fun KSerializer.unref(refCache: MutableList, counter: ReferenceCounter): KSerializer = + RootUnrefSerializer(this, refCache, counter) + + @OptIn(ExperimentalSerializationApi::class) + fun unrefSerializersModule( + refCache: MutableList, counter: ReferenceCounter + ): SerializersModule = SerializersModule { + val collector = this + val unrefCollector = object : SerializersModuleCollector { + + override fun contextual( + kClass: KClass, + provider: (typeArgumentsSerializers: List>) -> KSerializer<*> + ) { + collector.contextual(kClass) { provider(it).unref(refCache, counter) } + } + + override fun polymorphic( + baseClass: KClass, + actualClass: KClass, + actualSerializer: KSerializer + ) { + collector.polymorphic(baseClass, actualClass, actualSerializer.unref(refCache, counter)) + } + + override fun polymorphicDefault( + baseClass: KClass, + defaultSerializerProvider: (className: String?) -> DeserializationStrategy? + ) { + collector.polymorphicDefault(baseClass) { + (defaultSerializerProvider(it) as KSerializer).unref(refCache, counter) + } + } + } + serializersModule.dumpTo(unrefCollector) } -} - -private fun KSerializer.unref(refCache: RefCache): RootJsonSerializer = - RootJsonSerializer(refCache, this) - - -private fun PolymorphicModuleBuilder.shapes(refCache: RefCache) { - subclass(TGeoBBox.serializer().unref(refCache)) - subclass(TGeoCompositeShape.serializer().unref(refCache)) - subclass(TGeoXtru.serializer().unref(refCache)) - subclass(TGeoTube.serializer().unref(refCache)) - subclass(TGeoTubeSeg.serializer().unref(refCache)) - subclass(TGeoShapeAssembly.serializer().unref(refCache)) -} - -private fun PolymorphicModuleBuilder.matrices(refCache: RefCache) { - subclass(TGeoIdentity.serializer().unref(refCache)) - subclass(TGeoHMatrix.serializer().unref(refCache)) - subclass(TGeoTranslation.serializer().unref(refCache)) - subclass(TGeoRotation.serializer().unref(refCache)) - subclass(TGeoCombiTrans.serializer().unref(refCache)) -} - -/** - * Create an instance of Json with unfolding Root references. This instance could not be reused because of the cache. - */ -internal fun rootJson(): Json { - val refCache = ArrayList(4096) - return Json { + /** + * Create an instance of Json with unfolding Root references. This instance could not be reused because of the cache. + */ + private fun unrefJson(refCache: MutableList, counter: ReferenceCounter): Json = Json { encodeDefaults = true ignoreUnknownKeys = true classDiscriminator = "_typename" - serializersModule = SerializersModule { - polymorphic(TGeoShape::class) { - default { TGeoBBox.serializer().unref(refCache) } - shapes(refCache) - } + serializersModule = unrefSerializersModule(refCache, counter) + } - polymorphic(TGeoMatrix::class) { - matrices(refCache) - } - polymorphic(TObject::class) { - shapes(refCache) - matrices(refCache) + fun decode(sourceDeserializer: KSerializer, source: JsonElement): TObject { + val counter = ReferenceCounter() + val refCache = ArrayList() - subclass(TGeoMaterial.serializer().unref(refCache)) - subclass(TGeoMixture.serializer().unref(refCache)) - - subclass(TGeoMedium.serializer().unref(refCache)) - - subclass(TGeoNode.serializer().unref(refCache)) - subclass(TGeoNodeMatrix.serializer().unref(refCache)) - subclass(TGeoVolume.serializer().unref(refCache)) - subclass(TGeoVolumeAssembly.serializer().unref(refCache)) - } - polymorphic(TGeoNode::class, TGeoNode.serializer().unref(refCache)) { - subclass(TGeoNodeMatrix.serializer().unref(refCache)) - } - polymorphic(TGeoVolume::class, TGeoVolume.serializer().unref(refCache)) { - subclass(TGeoVolumeAssembly.serializer().unref(refCache)) + fun fillCache(element: JsonElement) { + when (element) { + is JsonObject -> { + if (element["_typename"] != null) { + refCache.add(RefEntry(element)) + } + element.values.forEach { + fillCache(it) + } + } + is JsonArray -> { + element.forEach { + fillCache(it) + } + } + else -> { + } } } + fillCache(source) + + return unrefJson(refCache, counter).decodeFromJsonElement(sourceDeserializer.unref(refCache, counter), source) } + + class ReferenceCounter(var value: Int = 0) { + fun increment() { + value += 1 + } + + override fun toString(): String = value.toString() + } + + class RefEntry(val obj: JsonObject) { + + private var cachedValue: Any? = null + + fun value(serializer: KSerializer<*>): Any { + if (cachedValue == null) { + cachedValue = json.decodeFromJsonElement(serializer, obj) + } + return cachedValue!! + } + + override fun toString(): String = obj.toString() + } + + private fun PolymorphicModuleBuilder.shapes() { + subclass(TGeoBBox.serializer()) + subclass(TGeoCompositeShape.serializer()) + subclass(TGeoXtru.serializer()) + subclass(TGeoTube.serializer()) + subclass(TGeoTubeSeg.serializer()) + subclass(TGeoShapeAssembly.serializer()) + } + + private fun PolymorphicModuleBuilder.matrices() { + subclass(TGeoIdentity.serializer()) + subclass(TGeoHMatrix.serializer()) + subclass(TGeoTranslation.serializer()) + subclass(TGeoRotation.serializer()) + subclass(TGeoCombiTrans.serializer()) + } + + private fun PolymorphicModuleBuilder.boolNodes() { + subclass(TGeoIntersection.serializer()) + subclass(TGeoUnion.serializer()) + subclass(TGeoSubtraction.serializer()) + } + + private val serializersModule = SerializersModule { + contextual(TGeoManager::class) { TGeoManager.serializer() } + contextual(TObjArray::class) { TObjArray.serializer() } + contextual(TGeoVolumeAssembly::class) { TGeoVolumeAssembly.serializer() } + contextual(TGeoShapeAssembly::class) { TGeoShapeAssembly.serializer() } + contextual(TGeoRotation::class) { TGeoRotation.serializer() } + contextual(TGeoMedium::class) { TGeoMedium.serializer() } + + polymorphic(TGeoShape::class) { + default { TGeoBBox.serializer() } + shapes() + } + + polymorphic(TGeoMatrix::class) { + matrices() + } + + polymorphic(TGeoBoolNode::class) { + boolNodes() + } + + polymorphic(TObject::class) { + subclass(TObjArray.serializer()) + + shapes() + matrices() + boolNodes() + + subclass(TGeoMaterial.serializer()) + subclass(TGeoMixture.serializer()) + + subclass(TGeoMedium.serializer()) + + subclass(TGeoNode.serializer()) + subclass(TGeoNodeMatrix.serializer()) + subclass(TGeoVolume.serializer()) + subclass(TGeoVolumeAssembly.serializer()) + } + + polymorphic(TGeoNode::class, TGeoNode.serializer()) { + subclass(TGeoNodeMatrix.serializer()) + } + + polymorphic(TGeoVolume::class, TGeoVolume.serializer()) { + subclass(TGeoVolumeAssembly.serializer()) + } + } + + val json = Json { + encodeDefaults = true + ignoreUnknownKeys = true + classDiscriminator = "_typename" + serializersModule = this@RootDecoder.serializersModule + } + } \ No newline at end of file diff --git a/cern-root-loader/src/jvmTest/kotlin/ru/mipt/npm/root/loadBM@N.kt b/cern-root-loader/src/jvmTest/kotlin/ru/mipt/npm/root/loadBM@N.kt index 5d3a03af..2c2aa86c 100644 --- a/cern-root-loader/src/jvmTest/kotlin/ru/mipt/npm/root/loadBM@N.kt +++ b/cern-root-loader/src/jvmTest/kotlin/ru/mipt/npm/root/loadBM@N.kt @@ -1,7 +1,31 @@ package ru.mipt.npm.root +import kotlinx.serialization.json.* + +private fun JsonElement.countTypes(): Sequence = sequence { + val json = this@countTypes + when (json){ + is JsonObject -> { + json["_typename"]?.let { yield(it.jsonPrimitive.content) } + json.values.forEach { yieldAll(it.countTypes()) } + } + is JsonArray -> { + json.forEach { + yieldAll(it.countTypes()) + } + } + else -> {} + } +} + fun main() { val string = TGeoManager::class.java.getResourceAsStream("/BM@N.root.json")!! .readAllBytes().decodeToString() - val geo = TGeoManager.decodeFromString(string) + val json = Json.parseToJsonElement(string) + val sizes = json.countTypes().groupBy { it }.mapValues { it.value.size } + sizes.forEach { + println(it) + } + + val geo = TObject.decodeFromString(TGeoManager.serializer(), string) } \ No newline at end of file