@@ -8,7 +8,8 @@ import java.io.File
8
8
class ModelImporter (private val root : INode ) {
9
9
10
10
private val originalIdToExisting: MutableMap <String , INode > = mutableMapOf ()
11
- private val originalIdToSpec: MutableMap <String , NodeData > = mutableMapOf ()
11
+ private val postponedReferences = ArrayList < () -> Unit > ()
12
+ private val nodesToRemove = HashSet <INode >()
12
13
13
14
fun import (jsonFile : File ) {
14
15
require(jsonFile.exists())
@@ -20,27 +21,60 @@ class ModelImporter(private val root: INode) {
20
21
21
22
fun import (data : ModelData ) {
22
23
originalIdToExisting.clear()
23
- originalIdToSpec.clear()
24
-
25
- syncProperties(root, data.root) // root original id is required for following operations
26
- buildSpecIndex(data.root)
27
-
28
- syncAllProperties(root.getDescendants(false ))
24
+ postponedReferences.clear()
25
+ nodesToRemove.clear()
29
26
buildExistingIndex(root)
27
+ data.root.originalId()?.let { originalIdToExisting[it] = root }
28
+ syncNode(root, data.root)
29
+ postponedReferences.forEach { it.invoke() }
30
+ nodesToRemove.forEach { it.remove() }
31
+ }
30
32
31
- sortAllExistingChildren(root)
32
- val addedNodes = addAllMissingChildren(root)
33
- syncAllProperties(addedNodes.asSequence())
33
+ private fun syncNode (node : INode , data : NodeData ) {
34
+ syncProperties(node, data)
35
+ syncChildren(node, data)
36
+ syncReferences(node, data)
37
+ }
34
38
35
- handleAllMovesAcrossParents(root)
39
+ private fun syncChildren (node : INode , data : NodeData ) {
40
+ val allRoles = (data.children.map { it.role } + node.allChildren.map { it.roleInParent }).distinct()
41
+ for (role in allRoles) {
42
+ val expectedNodes = data.children.filter { it.role == role }
43
+ val existingNodes = node.getChildren(role).toList()
36
44
37
- addedNodes.forEach {node ->
38
- node.originalId()?.let { originalIdToExisting[it] = node }
39
- }
45
+ // optimization for when there is no change in the child list
46
+ // size check first to avoid querying the original ID
47
+ if (expectedNodes.size == existingNodes.size && expectedNodes.map { it.originalId() } == existingNodes.map { it.originalId() }) {
48
+ existingNodes.zip(expectedNodes).forEach { syncNode(it.first, it.second) }
49
+ continue
50
+ }
40
51
41
- syncAllReferences(root)
52
+ expectedNodes.forEachIndexed { index, expected ->
53
+ val nodeAtIndex = node.getChildren(role).toList().getOrNull(index)
54
+ val expectedId = expected.originalId() ? : TODO ()
55
+ val expectedConcept = expected.concept?.let { s -> ConceptReference (s) }
56
+ val childNode = if (nodeAtIndex?.originalId() != expectedId) {
57
+ val existingNode = originalIdToExisting[expectedId]
58
+ if (existingNode == null ) {
59
+ val newChild = node.addNewChild(role, index, expectedConcept)
60
+ newChild.setPropertyValue(NodeData .idPropertyKey, expectedId)
61
+ originalIdToExisting[expectedId] = newChild
62
+ newChild
63
+ } else {
64
+ node.moveChild(role, index, existingNode)
65
+ nodesToRemove.remove(existingNode)
66
+ existingNode
67
+ }
68
+ } else {
69
+ nodeAtIndex
70
+ }
71
+ if (childNode.getConceptReference() != expectedConcept) TODO ()
42
72
43
- deleteAllExtraChildren(root)
73
+ syncNode(childNode, expected)
74
+ }
75
+
76
+ nodesToRemove + = node.getChildren(role).drop(expectedNodes.size)
77
+ }
44
78
}
45
79
46
80
private fun buildExistingIndex (root : INode ) {
@@ -49,17 +83,6 @@ class ModelImporter(private val root: INode) {
49
83
}
50
84
}
51
85
52
- private fun buildSpecIndex (nodeData : NodeData ) {
53
- nodeData.originalId()?.let { originalIdToSpec[it] = nodeData }
54
- nodeData.children.forEach { buildSpecIndex(it) }
55
- }
56
-
57
- private fun syncAllProperties (nodeSequence : Sequence <INode >) {
58
- nodeSequence.forEach {node ->
59
- originalIdToSpec[node.originalId()]?.let { spec -> syncProperties(node, spec) }
60
- }
61
- }
62
-
63
86
private fun syncProperties (node : INode , nodeData : NodeData ) {
64
87
if (node.getPropertyValue(NodeData .idPropertyKey) == null ) {
65
88
node.setPropertyValue(NodeData .idPropertyKey, nodeData.originalId())
@@ -77,164 +100,34 @@ class ModelImporter(private val root: INode) {
77
100
toBeRemoved.forEach { node.setPropertyValue(it, null ) }
78
101
}
79
102
80
- private fun syncAllReferences (root : INode ) {
81
- root.getDescendants(true ).forEach {node ->
82
- originalIdToSpec[node.originalId()]?.let { spec -> syncReferences(node, spec) }
83
- }
84
- }
85
-
86
103
private fun syncReferences (node : INode , nodeData : NodeData ) {
87
104
nodeData.references.forEach {
88
- if (node.getReferenceTargetRef(it.key) != originalIdToExisting[it.value]?.reference) {
89
- node.setReferenceTarget(it.key, originalIdToExisting[it.value]?.reference)
105
+ val expectedTargetId = it.value
106
+ val actualTargetId = node.getReferenceTarget(it.key)?.originalId()
107
+ if (actualTargetId != expectedTargetId) {
108
+ val expectedTarget = originalIdToExisting[expectedTargetId]
109
+ if (expectedTarget == null ) {
110
+ postponedReferences + = {
111
+ val expectedTarget = originalIdToExisting[expectedTargetId]
112
+ if (expectedTarget == null ) {
113
+ // The target node is not part of the model. Assuming it exists in some other model we can
114
+ // store the reference and try to resolve it dynamically on access.
115
+ node.setReferenceTarget(it.key, SerializedNodeReference (expectedTargetId))
116
+ } else {
117
+ node.setReferenceTarget(it.key, expectedTarget)
118
+ }
119
+ }
120
+ } else {
121
+ node.setReferenceTarget(it.key, expectedTarget)
122
+ }
90
123
}
91
124
}
92
- val toBeRemoved = node.getReferenceRoles().toSet().subtract( nodeData.references.keys)
125
+ val toBeRemoved = node.getReferenceRoles().toSet() - nodeData.references.keys
93
126
toBeRemoved.forEach {
94
127
val nullReference: INodeReference ? = null
95
128
node.setReferenceTarget(it, nullReference)
96
129
}
97
130
}
98
-
99
- private fun addAllMissingChildren (node : INode ): List <INode > {
100
- val addedNodes = mutableListOf<INode >()
101
- originalIdToSpec[node.originalId()]?.let {
102
- addedNodes.addAll(
103
- addMissingChildren(node, it)
104
- )
105
- }
106
- node.allChildren.forEach {
107
- addedNodes.addAll(addAllMissingChildren(it))
108
- }
109
- return addedNodes
110
- }
111
-
112
- private fun addMissingChildren (node : INode , nodeData : NodeData ): List <INode > {
113
- val specifiedChildren = nodeData.children.toList()
114
- val toBeAdded = specifiedChildren.filter { ! originalIdToExisting.contains(it.originalId()) }
115
-
116
- return toBeAdded.map { nodeToBeAdded ->
117
- val childrenInRole = node.allChildren.filter { it.roleInParent == nodeToBeAdded.role }
118
- val existingIds = childrenInRole.map { it.originalId() }
119
- val baseIndex = nodeToBeAdded.getIndexWithinRole(nodeData)
120
- var offset = 0
121
- offset + = childrenInRole.slice(0 .. minOf(baseIndex, childrenInRole.lastIndex)).count {
122
- ! originalIdToSpec.containsKey(it.originalId()) // node will be deleted
123
- }
124
- offset - = specifiedChildren.filter { it.role == nodeToBeAdded.role }.slice(0 until baseIndex).count {
125
- ! existingIds.contains(it.originalId()) // node will be moved here
126
- }
127
- val index = (baseIndex + offset).coerceIn(0 .. childrenInRole.size)
128
- val concept = nodeToBeAdded.concept?.let { s -> ConceptReference (s) }
129
-
130
- node.addNewChild(nodeToBeAdded.role, index, concept).apply {
131
- setPropertyValue(NodeData .idPropertyKey, nodeToBeAdded.originalId())
132
- }
133
- }
134
- }
135
-
136
- private fun sortAllExistingChildren (root : INode ) {
137
- root.getDescendants(true ).forEach { node ->
138
- originalIdToSpec[node.originalId()]?.let { sortExistingChildren(node, it) }
139
- }
140
- }
141
-
142
- private fun sortExistingChildren (node : INode , nodeData : NodeData ) {
143
- val existingChildren = node.allChildren.toList()
144
- val existingIds = existingChildren.map { it.originalId() }
145
- val specifiedChildren = nodeData.children
146
- val toBeSortedSpec = specifiedChildren.filter { originalIdToExisting.containsKey(it.originalId()) }
147
-
148
- val targetIndices = HashMap <String ?, Int >(nodeData.children.size)
149
- for (child in toBeSortedSpec) {
150
- val childrenInRole = existingChildren.filter { it.roleInParent == child.role }
151
- val baseIndex = child.getIndexWithinRole(nodeData)
152
- var offset = 0
153
- offset + = childrenInRole.slice(0 .. baseIndex.coerceAtMost(childrenInRole.lastIndex)).count {
154
- ! originalIdToSpec.containsKey(it.originalId()) // node will be deleted
155
- }
156
- offset - = specifiedChildren
157
- .filter { it.role == child.role }
158
- .slice(0 .. baseIndex.coerceIn(0 .. specifiedChildren.lastIndex))
159
- .count {
160
- ! existingIds.contains(it.originalId()) // node will be moved here
161
- }
162
- val index = (baseIndex + offset).coerceIn(0 .. childrenInRole.size)
163
- targetIndices[child.originalId()] = index
164
- }
165
-
166
- existingChildren.forEach { child ->
167
- val currentIndex = child.index()
168
- val targetRole = originalIdToSpec[child.originalId()]?.role
169
- val targetIndex = targetIndices[child.originalId()]
170
- if (targetIndex != null && (targetIndex != currentIndex || child.roleInParent != targetRole)) {
171
- node.moveChild(targetRole, targetIndex, child)
172
- }
173
- }
174
- }
175
-
176
- private fun handleAllMovesAcrossParents (root : INode ) {
177
- val moves = collectMovesAcrossParents(root)
178
- while (moves.isNotEmpty()) {
179
- val nextMove = moves.first { ! it.nodeToBeMoved.getDescendants(false ).contains(it.targetParent) }
180
- performMoveAcrossParents(nextMove.targetParent, nextMove.nodeToBeMoved)
181
- moves.remove(nextMove)
182
- }
183
- }
184
-
185
- private fun collectMovesAcrossParents (root : INode ): MutableList <MoveAcrossParents > {
186
- val movesAcrossParents = mutableListOf<MoveAcrossParents >()
187
- root.getDescendants(true ).forEach {node ->
188
- val nodeData = originalIdToSpec[node.originalId()] ? : return @forEach
189
-
190
- val missingIds = nodeData.children.map { it.originalId() }.toSet()
191
- .subtract(node.allChildren.map { it.originalId() }.toSet())
192
- val toBeMovedHere = missingIds
193
- .filter { originalIdToSpec.containsKey(it) }
194
- .mapNotNull { originalIdToExisting[it] }
195
-
196
- toBeMovedHere.forEach {
197
- movesAcrossParents.add(MoveAcrossParents (node, it))
198
- }
199
- }
200
- return movesAcrossParents
201
- }
202
-
203
- private data class MoveAcrossParents (val targetParent : INode , val nodeToBeMoved : INode )
204
-
205
- private fun performMoveAcrossParents (node : INode , toBeMovedHere : INode ) {
206
- val nodeData = originalIdToSpec[node.originalId()] ? : return
207
- val existingChildren = node.allChildren.toList()
208
- val spec = originalIdToSpec[toBeMovedHere.originalId()]!!
209
- val childrenInRole = existingChildren.filter { it.roleInParent == spec.role }
210
- val baseTargetIndex = spec.getIndexWithinRole(nodeData).coerceAtMost(childrenInRole.size)
211
- val offset = childrenInRole.slice(0 until baseTargetIndex).count {
212
- ! originalIdToSpec.containsKey(it.originalId()) // node will be deleted
213
- }
214
- val targetIndex = (baseTargetIndex + offset).coerceIn(0 .. childrenInRole.size)
215
-
216
- node.moveChild(spec.role, targetIndex, toBeMovedHere)
217
-
218
- }
219
-
220
- private fun deleteAllExtraChildren (root : INode ) {
221
- val toBeRemoved = mutableListOf<INode >()
222
- root.allChildren.forEach {
223
- if (! originalIdToSpec.containsKey(it.originalId())) {
224
- toBeRemoved.add(it)
225
- }
226
- }
227
- toBeRemoved.forEach {
228
- it.parent?.removeChild(it)
229
- }
230
- root.allChildren.forEach {
231
- deleteAllExtraChildren(it)
232
- }
233
- }
234
-
235
- private fun NodeData.getIndexWithinRole (parent : NodeData ) : Int =
236
- parent.children.filter { it.role == this .role }.indexOf(this )
237
-
238
131
}
239
132
240
133
internal fun INode.originalId (): String? {
0 commit comments