Skip to content

Commit 9b7de09

Browse files
author
Oleksandr Dzhychko
authored
Merge pull request #263 from modelix/fix/detect-changed-children-in-a-node-if-a-child-node-changes-its-role
fix(model-datastructure): detect changed children in a node if a child node changes its role
2 parents d65ffb6 + 3a51328 commit 9b7de09

File tree

3 files changed

+71
-12
lines changed

3 files changed

+71
-12
lines changed

model-datastructure/src/commonMain/kotlin/org/modelix/model/lazy/CLTree.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,12 @@ class CLTree : ITree, IBulkTree {
424424
nodesMap!!.visitChanges(
425425
oldVersion.nodesMap,
426426
object : CPHamtNode.IChangeVisitor {
427+
private val childrenChangeEvents = HashSet<Pair<Long, String?>>()
428+
429+
private fun notifyChildrenChange(parent: Long, role: String?) {
430+
if (childrenChangeEvents.add(parent to role)) visitor.childrenChanged(parent, role)
431+
}
432+
427433
override fun visitChangesOnly(): Boolean {
428434
return changesOnly
429435
}
@@ -450,8 +456,12 @@ class CLTree : ITree, IBulkTree {
450456
if (oldElement!!::class != newElement!!::class) {
451457
throw RuntimeException("Unsupported type change of node " + key + "from " + oldElement::class.simpleName + " to " + newElement::class.simpleName)
452458
}
453-
if (oldElement.parentId != newElement.parentId || oldElement.roleInParent != newElement.roleInParent) {
459+
if (oldElement.parentId != newElement.parentId) {
460+
visitor.containmentChanged(key)
461+
} else if (oldElement.roleInParent != newElement.roleInParent) {
454462
visitor.containmentChanged(key)
463+
notifyChildrenChange(oldElement.parentId, oldElement.roleInParent)
464+
notifyChildrenChange(newElement.parentId, newElement.roleInParent)
455465
}
456466
oldElement.propertyRoles.asSequence()
457467
.plus(newElement.propertyRoles.asSequence())
@@ -486,7 +496,7 @@ class CLTree : ITree, IBulkTree {
486496
val oldValues = oldChildrenInRole?.map { it.id }
487497
val newValues = newChildrenInRole?.map { it.id }
488498
if (oldValues != newValues) {
489-
visitor.childrenChanged(newElement.id, role)
499+
notifyChildrenChange(newElement.id, role)
490500
}
491501
}
492502
}

model-datastructure/src/commonMain/kotlin/org/modelix/model/persistent/CPHamtNode.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import org.modelix.model.persistent.SerializationUtil.intFromHex
2323
import org.modelix.model.persistent.SerializationUtil.longFromHex
2424
import kotlin.jvm.JvmStatic
2525

26+
/**
27+
* Implementation of a hash array mapped trie.
28+
*/
2629
abstract class CPHamtNode : IKVValue {
2730
override var isWritten: Boolean = false
2831

model-datastructure/src/commonTest/kotlin/DiffTest.kt

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,3 @@
1-
import org.modelix.model.api.ConceptReference
2-
import org.modelix.model.api.ITree
3-
import org.modelix.model.api.NodeReference
4-
import org.modelix.model.lazy.CLTree
5-
import org.modelix.model.lazy.ObjectStoreCache
6-
import org.modelix.model.persistent.MapBaseStore
7-
import kotlin.test.Test
8-
import kotlin.test.assertEquals
9-
101
/*
112
* Copyright (c) 2023.
123
*
@@ -23,6 +14,16 @@ import kotlin.test.assertEquals
2314
* limitations under the License.
2415
*/
2516

17+
import org.modelix.model.api.ConceptReference
18+
import org.modelix.model.api.ITree
19+
import org.modelix.model.api.NodeReference
20+
import org.modelix.model.lazy.CLTree
21+
import org.modelix.model.lazy.ObjectStoreCache
22+
import org.modelix.model.persistent.MapBaseStore
23+
import kotlin.test.Test
24+
import kotlin.test.assertEquals
25+
import kotlin.test.fail
26+
2627
class DiffTest {
2728

2829
@Test
@@ -70,6 +71,43 @@ class DiffTest {
7071
}
7172
}
7273

74+
@Test
75+
fun moveChildOutsideNode() {
76+
runTest(
77+
setOf(
78+
TreeChangeCollector.ChildrenChangedEvent(ITree.ROOT_ID, "children1"),
79+
TreeChangeCollector.ChildrenChangedEvent(200, "children1"),
80+
TreeChangeCollector.ContainmentChangedEvent(100),
81+
),
82+
{
83+
it.addNewChild(ITree.ROOT_ID, "children1", -1, 100, null as ConceptReference?)
84+
.addNewChild(ITree.ROOT_ID, "children2", -1, 200, null as ConceptReference?)
85+
},
86+
{
87+
it.moveChild(200, "children1", -1, 100)
88+
},
89+
)
90+
}
91+
92+
@Test
93+
fun moveChildInsideNode() {
94+
runTest(
95+
setOf(
96+
TreeChangeCollector.ChildrenChangedEvent(ITree.ROOT_ID, "children1"),
97+
TreeChangeCollector.ChildrenChangedEvent(ITree.ROOT_ID, "children2"),
98+
TreeChangeCollector.ContainmentChangedEvent(100),
99+
),
100+
{
101+
it
102+
.addNewChild(ITree.ROOT_ID, "children1", -1, 100, null as ConceptReference?)
103+
.addNewChild(ITree.ROOT_ID, "children1", -1, 101, null as ConceptReference?)
104+
},
105+
{
106+
it.moveChild(ITree.ROOT_ID, "children2", -1, 100)
107+
},
108+
)
109+
}
110+
73111
@Test
74112
fun removeChild() {
75113
runTest(
@@ -90,11 +128,19 @@ class DiffTest {
90128
runTest(expectedEvents, { it }, mutator)
91129
}
92130

93-
private fun runTest(expectedEvents: Set<TreeChangeCollector.ChangeEvent>, initialMutator: (ITree) -> ITree, mutator: (ITree) -> ITree) {
131+
private fun runTest(
132+
expectedEvents: Set<TreeChangeCollector.ChangeEvent>,
133+
initialMutator: (ITree) -> ITree,
134+
mutator: (ITree) -> ITree,
135+
) {
94136
val tree1 = initialMutator(CLTree.builder(ObjectStoreCache(MapBaseStore())).build())
95137
val tree2 = mutator(tree1)
96138
val collector = TreeChangeCollector()
97139
tree2.visitChanges(tree1, collector)
140+
val duplicateEvents = collector.events.groupBy { it }.filter { it.value.size > 1 }.map { it.key }
141+
if (duplicateEvents.isNotEmpty()) {
142+
fail("duplicate events: $duplicateEvents")
143+
}
98144

99145
assertEquals(
100146
expectedEvents,

0 commit comments

Comments
 (0)