Skip to content

Commit a16add5

Browse files
authored
Merge pull request #1515 from modelix/sync-plugin-using-original-ids
mps-sync-plugin: Preserve identity when importing MPS models into model-server
2 parents 04f08db + 5a816de commit a16add5

File tree

127 files changed

+3896
-1297
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

127 files changed

+3896
-1297
lines changed

.github/CODEOWNERS

Lines changed: 0 additions & 28 deletions
This file was deleted.

bulk-model-sync-lib/src/commonMain/kotlin/org/modelix/model/sync/bulk/IModelMask.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ interface IModelMask {
1212
.toSet()
1313
return children.filter { included.contains(it) }
1414
}
15+
16+
fun <T : IReadableNode> getFilteredChildren(parent: T): List<T> {
17+
return filterChildren(parent, parent.getAllChildren() as List<T>)
18+
}
1519
}
1620

1721
class UnfilteredModelMask : IModelMask {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package org.modelix.model.sync.bulk
2+
3+
import org.modelix.model.api.IMutableModel
4+
import org.modelix.model.api.INodeReference
5+
import org.modelix.model.api.IReadableNode
6+
import org.modelix.model.api.IWritableNode
7+
8+
/**
9+
* A trivial implementation that expects the source and target nodes to have the same identity,
10+
* meaning they can both be resolved by the same INodeReference.
11+
*/
12+
class IdentityPreservingNodeAssociation(
13+
val targetModel: IMutableModel,
14+
val overrides: Map<INodeReference, INodeReference>,
15+
) : INodeAssociation {
16+
17+
override fun resolveTarget(sourceNode: IReadableNode): IWritableNode? {
18+
val sourceReference = sourceNode.getNodeReference()
19+
val targetReference = overrides[sourceReference] ?: sourceReference
20+
return targetModel.tryResolveNode(targetReference)
21+
}
22+
23+
override fun associate(
24+
sourceNode: IReadableNode,
25+
targetNode: IWritableNode,
26+
) {
27+
val sourceReference = sourceNode.getNodeReference()
28+
val expectedTargetReference = overrides[sourceReference] ?: sourceReference
29+
val actualTargetReference = targetNode.getNodeReference()
30+
require(expectedTargetReference == actualTargetReference) {
31+
"Cannot associate $sourceReference with $actualTargetReference, expected: $expectedTargetReference"
32+
}
33+
}
34+
}

bulk-model-sync-lib/src/commonMain/kotlin/org/modelix/model/sync/bulk/ModelSynchronizer.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ class ModelSynchronizer(
119119
syncChildren(sourceNode, conceptCorrectedTargetNode, forceSyncDescendants)
120120
}
121121
} else if (filter.needsDescentIntoSubtree(sourceNode)) {
122-
for (sourceChild in sourceMask.filterChildren(sourceNode, sourceNode.getAllChildren())) {
122+
for (sourceChild in sourceMask.getFilteredChildren(sourceNode)) {
123123
runSafe {
124124
val targetChild = nodeAssociation.resolveTarget(sourceChild)
125125
?: error("Expected target node was not found. sourceChild=${sourceChild.getNodeReference()}, originalId=${sourceChild.getOriginalReference()}")
@@ -173,8 +173,8 @@ class ModelSynchronizer(
173173

174174
private fun syncChildren(sourceParent: IReadableNode, targetParent: IWritableNode, forceSyncDescendants: Boolean) {
175175
iterateMergedRoles(
176-
sourceParent.getAllChildren().map { it.getContainmentLink() }.distinct(),
177-
targetParent.getAllChildren().map { it.getContainmentLink() }.distinct(),
176+
sourceMask.getFilteredChildren(sourceParent).map { it.getContainmentLink() }.distinct(),
177+
targetMask.getFilteredChildren(targetParent).map { it.getContainmentLink() }.distinct(),
178178
) { role ->
179179
runSafe {
180180
syncChildrenInRole(sourceParent, role, targetParent, forceSyncDescendants)
@@ -215,7 +215,7 @@ class ModelSynchronizer(
215215
}
216216
}
217217

218-
val isOrdered = targetParent.isOrdered(role)
218+
val isOrdered = targetParent.isOrdered(role) && sourceParent.isOrdered(role)
219219

220220
// optimization for when there is no change in the child list
221221
if (associatedChildren.all { it.alreadyMatches(isOrdered) }) {

bulk-model-sync-lib/src/commonMain/kotlin/org/modelix/model/sync/bulk/NodeAssociationToModelServer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class NodeAssociationFromModelServer(val branch: IBranch, val targetModel: IMuta
6161

6262
override fun resolveTarget(sourceNode: IReadableNode): IWritableNode? {
6363
return (pendingAssociations[nodeId(sourceNode)] ?: sourceNode.getOriginalReference())
64-
?.let { targetModel.resolveNode(NodeReference(it)) }
64+
?.let { targetModel.tryResolveNode(NodeReference(it)) }
6565
}
6666

6767
override fun associate(sourceNode: IReadableNode, targetNode: IWritableNode) {

bulk-model-sync-lib/src/commonMain/kotlin/org/modelix/model/sync/bulk/TransientNodeAssociation.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class TransientNodeAssociation(val writeOriginalIds: Boolean, val targetModel: I
3030
override fun resolveTarget(sourceNode: IReadableNode): IWritableNode? {
3131
val ref = sourceNode.getOriginalOrCurrentReference()
3232
return associations[ref]
33-
?: targetModel.resolveNode(NodeReference(ref))
33+
?: targetModel.tryResolveNode(NodeReference(ref))
3434
}
3535

3636
override fun associate(

bulk-model-sync-lib/src/jvmMain/kotlin/org/modelix/model/sync/bulk/InvalidationTree.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.modelix.model.sync.bulk
22

33
import org.modelix.model.api.IModel
4+
import org.modelix.model.api.INodeReference
45
import org.modelix.model.api.IReadableNode
56
import org.modelix.model.api.ITree
67
import org.modelix.model.api.NodeReference
@@ -27,13 +28,13 @@ class InvalidationTree(sizeLimit: Int = 100_000) : GenericInvalidationTree<Long,
2728
}
2829
}
2930

30-
class DefaultInvalidationTree(val root: NodeReference, sizeLimit: Int = 100_000) :
31-
GenericInvalidationTree<NodeReference, IModel>(root, sizeLimit = sizeLimit) {
31+
class DefaultInvalidationTree(val root: INodeReference, sizeLimit: Int = 100_000) :
32+
GenericInvalidationTree<INodeReference, IModel>(root, sizeLimit = sizeLimit) {
3233
override fun ancestorsAndSelf(
3334
model: IModel,
34-
nodeId: NodeReference,
35-
): List<NodeReference> {
36-
return model.resolveNode(nodeId)
35+
nodeId: INodeReference,
36+
): List<INodeReference> {
37+
return model.tryResolveNode(nodeId)
3738
?.ancestors(includeSelf = true)
3839
?.map { it.getNodeReference().toSerialized() }
3940
?.toList()

datastructures/src/commonMain/kotlin/org/modelix/datastructures/btree/BTree.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
package org.modelix.datastructures.btree
22

33
import org.modelix.datastructures.objects.IObjectGraph
4-
import org.modelix.kotlin.utils.DelicateModelixApi
54
import org.modelix.streams.IStream
5+
import org.modelix.streams.IStreamExecutorProvider
6+
import org.modelix.streams.getBlocking
67

7-
data class BTree<K, V>(val root: BTreeNode<K, V>) {
8+
data class BTree<K, V>(val root: BTreeNode<K, V>) : IStreamExecutorProvider by root.config.graph {
89
constructor(config: BTreeConfig<K, V>) : this(BTreeNodeLeaf(config, emptyList()))
910

1011
val graph: IObjectGraph get() = root.config.graph
1112

1213
fun validate() {
1314
graph.getStreamExecutor().query {
1415
root.validate(true)
15-
@OptIn(DelicateModelixApi::class)
16-
check(root.getEntries().toList().getSynchronous().map { it.key }.toSet().size == root.getEntries().map { it.key }.count().getSynchronous()) {
16+
check(root.getEntries().toList().getBlocking(graph).map { it.key }.toSet().size == root.getEntries().map { it.key }.count().getBlocking(graph)) {
1717
"duplicate entries: $root"
1818
}
19-
check(root.getEntries().map { it.key }.toList().getSynchronous().sortedWith(root.config.keyConfiguration) == root.getEntries().map { it.key }.toList().getSynchronous()) {
19+
check(root.getEntries().map { it.key }.toList().getBlocking(graph).sortedWith(root.config.keyConfiguration) == root.getEntries().map { it.key }.toList().getBlocking(graph)) {
2020
"not sorted: $this"
2121
}
2222
IStream.of(Unit)

datastructures/src/commonMain/kotlin/org/modelix/datastructures/btree/BTreeNode.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package org.modelix.datastructures.btree
33
import org.modelix.datastructures.objects.IObjectData
44
import org.modelix.datastructures.objects.IObjectDeserializer
55
import org.modelix.datastructures.objects.IObjectReferenceFactory
6+
import org.modelix.datastructures.objects.Object
7+
import org.modelix.datastructures.objects.getDescendantsAndSelf
68
import org.modelix.datastructures.serialization.SerializationSeparators
79
import org.modelix.streams.IStream
810

@@ -33,6 +35,11 @@ sealed class BTreeNode<K, V> : IObjectData {
3335
fun splitIfNecessary(): Replacement<K, V> = if (isOverfilled()) split() else Replacement.Single(this)
3436
abstract fun mergeWithSibling(knownSeparator: K, right: BTreeNode<K, V>): BTreeNode<K, V>
3537

38+
override fun objectDiff(self: Object<*>, oldObject: Object<*>?): IStream.Many<Object<*>> {
39+
// TODO performance
40+
return self.getDescendantsAndSelf()
41+
}
42+
3643
class Deserializer<K, V>(val config: BTreeConfig<K, V>) : IObjectDeserializer<BTreeNode<K, V>> {
3744
override fun deserialize(
3845
serialized: String,
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package org.modelix.datastructures.list
2+
3+
import kotlinx.serialization.KSerializer
4+
import kotlinx.serialization.builtins.ListSerializer
5+
import kotlinx.serialization.builtins.serializer
6+
import kotlinx.serialization.descriptors.SerialDescriptor
7+
import kotlinx.serialization.encoding.Decoder
8+
import kotlinx.serialization.encoding.Encoder
9+
import org.modelix.datastructures.objects.IDataTypeConfiguration
10+
import org.modelix.datastructures.objects.IObjectData
11+
import org.modelix.datastructures.objects.IObjectDeserializer
12+
import org.modelix.datastructures.objects.IObjectGraph
13+
import org.modelix.datastructures.objects.IObjectReferenceFactory
14+
import org.modelix.datastructures.objects.Object
15+
import org.modelix.datastructures.objects.ObjectHash
16+
import org.modelix.datastructures.objects.ObjectReference
17+
import org.modelix.datastructures.objects.getDescendantsAndSelf
18+
import org.modelix.datastructures.objects.getHashString
19+
import org.modelix.datastructures.serialization.SerializationSeparators
20+
import org.modelix.kotlin.utils.urlDecode
21+
import org.modelix.kotlin.utils.urlEncode
22+
import org.modelix.streams.IStream
23+
import kotlin.collections.chunked
24+
25+
class LargeListConfig<E>(
26+
val graph: IObjectGraph,
27+
val elementType: IDataTypeConfiguration<E>,
28+
val maxNodeSize: Int = 20,
29+
) : IObjectDeserializer<LargeList<E>> {
30+
override fun deserialize(input: String, referenceFactory: IObjectReferenceFactory): LargeList<E> {
31+
val data = if (input.startsWith(LargeList.LARGE_LIST_PREFIX)) {
32+
val subLists = input.substring(LargeList.LARGE_LIST_PREFIX.length)
33+
.split(SerializationSeparators.LEVEL2)
34+
.map { referenceFactory(it, this) }
35+
LargeListInternalNode(this, subLists)
36+
} else {
37+
LargeListLeafNode(
38+
this,
39+
input.split(SerializationSeparators.LEVEL2)
40+
.filter { it.isNotEmpty() }
41+
.map { elementType.deserialize(it.urlDecode()!!) },
42+
)
43+
}
44+
return data
45+
}
46+
47+
fun createEmptyList(): LargeList<E> = LargeListLeafNode(this, emptyList())
48+
49+
fun createList(elements: List<E>): LargeList<E> {
50+
return if (elements.size <= maxNodeSize) {
51+
LargeListLeafNode(this, elements)
52+
} else {
53+
// split the elements into at most maxNodeSize sub lists, but also minimize the number of objects
54+
val sublistSizes = ((elements.size + maxNodeSize - 1) / maxNodeSize).coerceAtLeast(maxNodeSize)
55+
LargeListInternalNode(this, elements.chunked(sublistSizes) { graph.fromCreated(createList(it.toList())) }.toList())
56+
}
57+
}
58+
}
59+
60+
class LargeListKSerializer<E>(val config: LargeListConfig<E>) : KSerializer<LargeList<E>> {
61+
private val listSerializer = ListSerializer(String.serializer())
62+
override val descriptor: SerialDescriptor = listSerializer.descriptor
63+
64+
override fun serialize(encoder: Encoder, value: LargeList<E>) {
65+
when (value) {
66+
is LargeListInternalNode<E> -> listSerializer.serialize(encoder, value.subLists.map { it.getHashString() })
67+
is LargeListLeafNode<E> -> listSerializer.serialize(encoder, value.elements.map { config.elementType.serialize(it) })
68+
}
69+
}
70+
71+
override fun deserialize(decoder: Decoder): LargeList<E> {
72+
val strings = listSerializer.deserialize(decoder)
73+
return if (strings.isNotEmpty() && ObjectHash.isValidHashString(strings.first())) {
74+
LargeListInternalNode(
75+
config,
76+
strings.map { config.graph.fromHashString(it, config) },
77+
)
78+
} else {
79+
LargeListLeafNode(config, strings.map { config.elementType.deserialize(it) })
80+
}
81+
}
82+
}
83+
84+
sealed class LargeList<E>() : IObjectData {
85+
companion object {
86+
const val LARGE_LIST_PREFIX = "OL" + SerializationSeparators.LEVEL1
87+
}
88+
89+
abstract fun getElements(): IStream.Many<E>
90+
91+
override fun objectDiff(self: Object<*>, oldObject: Object<*>?): IStream.Many<Object<*>> {
92+
return self.getDescendantsAndSelf()
93+
}
94+
}
95+
96+
class LargeListInternalNode<E>(val config: LargeListConfig<E>, val subLists: List<ObjectReference<LargeList<E>>>) : LargeList<E>() {
97+
override fun serialize(): String {
98+
return LARGE_LIST_PREFIX + subLists.joinToString(SerializationSeparators.LEVEL2) { it.getHashString() }
99+
}
100+
101+
override fun getDeserializer(): IObjectDeserializer<LargeList<E>> = config
102+
103+
override fun getContainmentReferences(): List<ObjectReference<IObjectData>> {
104+
return subLists.toList()
105+
}
106+
107+
override fun getElements(): IStream.Many<E> {
108+
return IStream.many(subLists).flatMap {
109+
it.resolveData().flatMap { it.getElements() }
110+
}
111+
}
112+
}
113+
114+
class LargeListLeafNode<E>(val config: LargeListConfig<E>, val elements: List<E>) : LargeList<E>() {
115+
override fun serialize(): String {
116+
return if (elements.isEmpty()) {
117+
""
118+
} else {
119+
elements.joinToString(SerializationSeparators.LEVEL2) { config.elementType.serialize(it).urlEncode() }
120+
}
121+
}
122+
123+
override fun getDeserializer(): IObjectDeserializer<LargeList<E>> = config
124+
125+
override fun getContainmentReferences(): List<ObjectReference<IObjectData>> {
126+
return elements.flatMap { config.elementType.getContainmentReferences(it) }
127+
}
128+
129+
override fun getElements(): IStream.Many<E> {
130+
return IStream.many(elements)
131+
}
132+
}

0 commit comments

Comments
 (0)