Skip to content

Commit 5c01bbd

Browse files
authored
Merge pull request #1520 from modelix/git-import
Incremental Git import
2 parents 3d997d2 + 3b0a8b6 commit 5c01bbd

File tree

63 files changed

+2758
-300
lines changed

Some content is hidden

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

63 files changed

+2758
-300
lines changed

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

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import org.modelix.model.api.IReferenceLinkReference
88
import org.modelix.model.api.IRoleReference
99
import org.modelix.model.api.IWritableNode
1010
import org.modelix.model.api.NewNodeSpec
11-
import org.modelix.model.api.getOriginalReference
1211
import org.modelix.model.api.isOrdered
1312
import org.modelix.model.api.matches
1413
import org.modelix.model.api.mergeWith
@@ -100,37 +99,49 @@ class ModelSynchronizer(
10099
): Unit = withPushSyncStack(sourceNode) {
101100
nodeAssociation.associate(sourceNode, targetNode)
102101
if (forceSyncDescendants || filter.needsSynchronization(sourceNode)) {
103-
LOG.trace { "Synchronizing changed node. sourceNode = $sourceNode" }
104-
runSafe { synchronizeProperties(sourceNode, targetNode) }
105-
runSafe { synchronizeReferences(sourceNode, targetNode) }
106-
107-
val conceptCorrectedTargetNode = runSafe {
108-
val sourceConcept = sourceNode.getConceptReference()
109-
val targetConcept = targetNode.getConceptReference()
110-
111-
if (sourceConcept != targetConcept) {
112-
targetNode.changeConcept(sourceConcept)
113-
} else {
114-
targetNode
115-
}
116-
}.getOrDefault(targetNode)
117-
118-
runSafe {
119-
syncChildren(sourceNode, conceptCorrectedTargetNode, forceSyncDescendants)
120-
}
102+
synchronizeNodeAndChildren(sourceNode, targetNode, forceSyncDescendants)
121103
} else if (filter.needsDescentIntoSubtree(sourceNode)) {
122-
for (sourceChild in sourceMask.getFilteredChildren(sourceNode)) {
123-
runSafe {
124-
val targetChild = nodeAssociation.resolveTarget(sourceChild)
125-
?: error("Expected target node was not found. sourceChild=${sourceChild.getNodeReference()}, originalId=${sourceChild.getOriginalReference()}")
126-
synchronizeNode(sourceChild, targetChild, forceSyncDescendants)
104+
val matchingChildren = sourceMask.getFilteredChildren(sourceNode).map { it to nodeAssociation.resolveTarget(it) }
105+
if (matchingChildren.any { it.second == null }) {
106+
// Inconsistent information provided by filter. Apparently, the node itself needs synchronization.
107+
synchronizeNodeAndChildren(sourceNode, targetNode, forceSyncDescendants)
108+
} else {
109+
for ((sourceChild, targetChild) in matchingChildren) {
110+
runSafe {
111+
synchronizeNode(sourceChild, targetChild!!, forceSyncDescendants)
112+
}
127113
}
128114
}
129115
} else {
130116
LOG.trace { "Skipping subtree due to filter. root = $sourceNode" }
131117
}
132118
}
133119

120+
private fun synchronizeNodeAndChildren(
121+
sourceNode: IReadableNode,
122+
targetNode: IWritableNode,
123+
forceSyncDescendants: Boolean,
124+
) {
125+
LOG.trace { "Synchronizing changed node. sourceNode = $sourceNode" }
126+
runSafe { synchronizeProperties(sourceNode, targetNode) }
127+
runSafe { synchronizeReferences(sourceNode, targetNode) }
128+
129+
val conceptCorrectedTargetNode = runSafe {
130+
val sourceConcept = sourceNode.getConceptReference()
131+
val targetConcept = targetNode.getConceptReference()
132+
133+
if (sourceConcept != targetConcept) {
134+
targetNode.changeConcept(sourceConcept)
135+
} else {
136+
targetNode
137+
}
138+
}.getOrDefault(targetNode)
139+
140+
runSafe {
141+
syncChildren(sourceNode, conceptCorrectedTargetNode, forceSyncDescendants)
142+
}
143+
}
144+
134145
private fun synchronizeReferences(
135146
sourceNode: IReadableNode,
136147
targetNode: IWritableNode,

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ abstract class GenericInvalidationTree<ID, M>(root: ID, val sizeLimit: Int = 100
5959
*/
6060
fun invalidate(containmentPath: List<ID>, includingDescendants: Boolean = false) {
6161
require(containmentPath[0] == rootNode.id) { "Path must start with the root node. Expected: ${rootNode.id}, was: $containmentPath" }
62-
rootNode.invalidate(containmentPath, 0, includingDescendants)
62+
rootNode.invalidate(containmentPath, 1, includingDescendants)
6363
rootNode.rebalance(sizeLimit)
6464
}
6565

@@ -80,13 +80,25 @@ abstract class GenericInvalidationTree<ID, M>(root: ID, val sizeLimit: Int = 100
8080
fun hasAnyInvalidations(): Boolean = rootNode.hasAnyInvalidations()
8181

8282
override fun needsDescentIntoSubtree(subtreeRoot: IReadableNode): Boolean {
83-
return rootNode.needsDescentIntoSubtree(getContainmentPath(subtreeRoot), 0)
83+
return needsDescentIntoSubtree(getContainmentPath(subtreeRoot))
84+
}
85+
86+
fun needsDescentIntoSubtree(containmentPath: List<ID>): Boolean {
87+
require(containmentPath[0] == rootNode.id) { "Path must start with the root node. Expected: ${rootNode.id}, was: $containmentPath" }
88+
return rootNode.needsDescentIntoSubtree(containmentPath, 1)
8489
}
8590

8691
override fun needsSynchronization(node: IReadableNode): Boolean {
87-
return rootNode.nodeNeedsUpdate(getContainmentPath(node), 0)
92+
return needsSynchronization(getContainmentPath(node))
93+
}
94+
95+
fun needsSynchronization(containmentPath: List<ID>): Boolean {
96+
require(containmentPath[0] == rootNode.id) { "Path must start with the root node. Expected: ${rootNode.id}, was: $containmentPath" }
97+
return rootNode.nodeNeedsUpdate(containmentPath, 1)
8898
}
8999

100+
fun size() = rootNode.size()
101+
90102
private class Node<E>(val id: E) {
91103
private var subtreeSize = 1
92104
private var nodeNeedsUpdate: Boolean = false
@@ -95,6 +107,8 @@ abstract class GenericInvalidationTree<ID, M>(root: ID, val sizeLimit: Int = 100
95107

96108
fun hasAnyInvalidations() = nodeNeedsUpdate || allDescendantsNeedUpdate || invalidChildren.isNotEmpty()
97109

110+
fun size() = subtreeSize
111+
98112
fun reset() {
99113
subtreeSize = 1
100114
nodeNeedsUpdate = false

commitlint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module.exports = {
1919
"model-server",
2020
"model-server-openapi",
2121
"modelql",
22+
"mps-git-import-plugin",
2223
"mps-model-adapters",
2324
"mps-model-server",
2425
"mps-sync-plugin",

datastructures/src/commonMain/kotlin/org/modelix/datastructures/IPersistentMap.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.modelix.datastructures
33
import org.modelix.datastructures.objects.IDataTypeConfiguration
44
import org.modelix.datastructures.objects.IObjectData
55
import org.modelix.datastructures.objects.Object
6+
import org.modelix.datastructures.objects.ObjectReference
67
import org.modelix.streams.IStream
78
import org.modelix.streams.IStreamExecutorProvider
89

@@ -37,3 +38,7 @@ interface IPersistentMapRootData<K, V> : IObjectData {
3738
fun <K, V> Object<IPersistentMapRootData<K, V>>.createMapInstance(): IPersistentMap<K, V> {
3839
return data.createMapInstance(this)
3940
}
41+
42+
fun <K, V> ObjectReference<IPersistentMapRootData<K, V>>.createMapInstance(): IStream.One<IPersistentMap<K, V>> {
43+
return resolve().map { it.createMapInstance() }
44+
}

datastructures/src/commonMain/kotlin/org/modelix/datastructures/objects/Object.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ open class Object<out E : IObjectData>(val data: E, val ref: ObjectReference<E>)
3535
override fun hashCode(): Int {
3636
error("Use .getHash() for comparing objects")
3737
}
38+
39+
override fun toString(): String {
40+
return getHashString() + " -> " + data.serialize()
41+
}
3842
}
3943

4044
/**

datastructures/src/commonMain/kotlin/org/modelix/datastructures/patricia/PatriciaNode.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -376,13 +376,13 @@ data class PatriciaNode<K, V : Any>(
376376
IStream.of(self) + changesFromValue + changesFromChildren
377377
} else {
378378
val commonPrefix = ownPrefix.commonPrefixWith(oldNode.ownPrefix)
379-
(
380-
IStream.of(self) + self.split(commonPrefix).let {
381-
it.data.objectDiff(it, oldObject.split(commonPrefix), path).filter { it.graph == config.graph }
379+
IStream.of(self) + (
380+
self.split(commonPrefix).let {
381+
it.data.objectDiff(it, oldObject.split(commonPrefix), path)
382382
}
383383
).filter {
384384
// filter out the split result, which isn't part of the real object graph
385-
it.graph == config.graph
385+
it.graph == config.graph && it.getHash() != self.getHash()
386386
}
387387
}
388388
}
@@ -416,7 +416,7 @@ data class PatriciaNode<K, V : Any>(
416416

417417
private class CharSequenceConcatenation(val a: CharSequence, val b: CharSequence) : CharSequence {
418418
override fun get(index: Int): Char {
419-
return if (index < a.length) a.get(index) else b.get(index + a.length)
419+
return if (index < a.length) a.get(index) else b.get(index - a.length)
420420
}
421421

422422
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {

datastructures/src/commonMain/kotlin/org/modelix/datastructures/patricia/PatriciaTrie.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import org.modelix.datastructures.MapChangeEvent
55
import org.modelix.datastructures.objects.IDataTypeConfiguration
66
import org.modelix.datastructures.objects.IObjectGraph
77
import org.modelix.datastructures.objects.Object
8+
import org.modelix.datastructures.objects.ObjectReference
89
import org.modelix.datastructures.objects.StringDataTypeConfiguration
910
import org.modelix.datastructures.objects.asObject
1011
import org.modelix.streams.IStream
1112
import org.modelix.streams.IStreamExecutorProvider
1213

14+
typealias PatriciaTrieRef<K, V> = ObjectReference<PatriciaNode<K, V>>
15+
1316
/**
1417
* Entries with the same prefix are put into the same subtree. The prefix has a variable length.
1518
*/

datastructures/src/commonTest/kotlin/org/modelix/datastructures/TreeDiffTest.kt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,49 @@ class TreeDiffTest {
6666

6767
assertTree()
6868
}
69+
70+
@Test
71+
fun `diff returns no duplicate objects`() {
72+
val graph = FullyLoadedObjectGraph()
73+
var tree = PatriciaTrie.withStrings(graph)
74+
var previousTrees: MutableList<PatriciaTrie<String, String>> = mutableListOf()
75+
76+
val alphabet = "abcd"
77+
val rand = Random(5465)
78+
fun randomString(length: Int) = (0 until length).joinToString("") { alphabet.random(rand).toString() }
79+
80+
val removedEntries = (1..1000).map { randomString(rand.nextInt(0, 6)) }.toMutableSet()
81+
val values = removedEntries.associateWith { "value_of_$it" }
82+
val addedEntries = mutableSetOf<String>()
83+
84+
fun assertTree() {
85+
for (previousTree in (1..5).map { previousTrees.random(rand) }) {
86+
val diff = tree.asObject().objectDiff(previousTree.asObject())
87+
.map { it.getHash() to it.data.serialize() }
88+
.toList().getBlocking(tree)
89+
90+
val duplicateObjects = diff.groupBy { it.first }.filter { it.value.size > 1 }.map { it.value.first() }
91+
assertEquals(emptyList(), duplicateObjects)
92+
}
93+
}
94+
95+
repeat(1_000) {
96+
previousTrees.add(tree)
97+
if (removedEntries.size > addedEntries.size) {
98+
val key = removedEntries.random(rand)
99+
removedEntries.remove(key)
100+
addedEntries.add(key)
101+
tree = tree.put(key, values[key]!!).getBlocking(tree)
102+
assertTree()
103+
} else {
104+
val key = addedEntries.random(rand)
105+
removedEntries.add(key)
106+
addedEntries.remove(key)
107+
tree = tree.remove(key).getBlocking(tree)
108+
assertTree()
109+
}
110+
}
111+
112+
assertTree()
113+
}
69114
}

gradle/libs.versions.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ kotest = "5.9.1"
4242
reaktive = "2.2.0"
4343
testcontainers = "1.20.6"
4444
keycloak = "26.0.4"
45+
arrow = "2.0.1"
4546

4647
[libraries]
4748

@@ -152,3 +153,20 @@ nimbus-jose-jwt = { group = "com.nimbusds", name = "nimbus-jose-jwt", version =
152153
bouncycastle-bcpkix = { group = "org.bouncycastle", name = "bcpkix-lts8on", version = "2.73.7" }
153154

154155
kotlincrypto-sha2 = { module = "org.kotlincrypto.hash:sha2", version = "0.7.0" }
156+
157+
arrow-core = { group = "io.arrow-kt", name = "arrow-core", version.ref = "arrow" }
158+
arrow-fx-coroutines = { group = "io.arrow-kt", name = "arrow-fx-coroutines", version.ref = "arrow" }
159+
arrow-arrow-stack = { group = "io.arrow-kt", name = "arrow-stack", version.ref = "arrow" }
160+
arrow-optics = { group = "io.arrow-kt", name = "arrow-optics", version.ref = "arrow" }
161+
arrow-optics-ksp-plugin = { group = "io.arrow-kt", name = "arrow-optics-ksp-plugin", version.ref = "arrow" }
162+
arrow-autoclose = { group = "io.arrow-kt", name = "arrow-autoclose", version.ref = "arrow" }
163+
arrow-resilience = { group = "io.arrow-kt", name = "arrow-resilience", version.ref = "arrow" }
164+
arrow-fx-stm = { group = "io.arrow-kt", name = "arrow-fx-stm", version.ref = "arrow" }
165+
arrow-atomic = { group = "io.arrow-kt", name = "arrow-atomic", version.ref = "arrow" }
166+
arrow-collectors = { group = "io.arrow-kt", name = "arrow-collectors", version.ref = "arrow" }
167+
arrow-eval = { group = "io.arrow-kt", name = "arrow-eval", version.ref = "arrow" }
168+
arrow-functions = { group = "io.arrow-kt", name = "arrow-functions", version.ref = "arrow" }
169+
arrow-core-high-arity = { group = "io.arrow-kt", name = "arrow-core-high-arity", version.ref = "arrow" }
170+
arrow-core-serialization = { group = "io.arrow-kt", name = "arrow-core-serialization", version.ref = "arrow" }
171+
arrow-cache4k = { group = "io.arrow-kt", name = "arrow-cache4k", version.ref = "arrow" }
172+
arrow-optics-compose = { group = "io.arrow-kt", name = "arrow-optics-compose", version.ref = "arrow" }

model-api/src/commonMain/kotlin/org/modelix/model/api/INodeReference.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ fun INodeReference.toSerialized(): NodeReference = if (this is NodeReference) th
6464
class NodeReferenceKSerializer : KSerializer<INodeReference> {
6565
override fun deserialize(decoder: Decoder): INodeReference {
6666
val serialized = decoder.decodeString()
67-
return INodeReferenceSerializer.tryDeserialize(serialized) ?: SerializedNodeReference(serialized)
67+
return NodeReference(serialized)
6868
}
6969

7070
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("modelix.INodeReference", PrimitiveKind.STRING)

0 commit comments

Comments
 (0)