@@ -7,10 +7,6 @@ import java.io.File
7
7
8
8
class ModelImporter (private val root : INode , val stats : ImportStats ? = null ) {
9
9
10
- private lateinit var originalIdToSpec: MutableMap <String , NodeData >
11
- private lateinit var originalIdToExisting: MutableMap <String , INode >
12
- private lateinit var originalIdToRef: MutableMap <String , INodeReference >
13
-
14
10
fun import (jsonFile : File ) {
15
11
require(jsonFile.exists())
16
12
require(jsonFile.extension == " json" )
@@ -20,60 +16,81 @@ class ModelImporter(private val root: INode, val stats: ImportStats? = null) {
20
16
}
21
17
22
18
fun import (data : ModelData ) {
23
- originalIdToExisting = mutableMapOf ()
24
- buildExistingIndex(root)
19
+ syncProperties(root, data.root) // root original id is required for following operations
20
+ val allExistingNodes = root.getDescendants(true ).toList()
21
+ val originalIdToSpec: MutableMap <String , NodeData > = buildSpecIndex(data.root)
22
+
23
+ syncAllProperties(allExistingNodes, originalIdToSpec)
24
+ val originalIdToExisting = buildExistingIndex(allExistingNodes)
25
25
26
- originalIdToSpec = mutableMapOf ()
27
- buildSpecIndex(data.root)
26
+ sortAllExistingChildren(allExistingNodes, originalIdToExisting, originalIdToSpec)
27
+ val addedNodes = addAllMissingChildren(root, originalIdToExisting, originalIdToSpec)
28
+ syncAllProperties(addedNodes, originalIdToSpec)
28
29
29
- syncNode(root, data.root )
30
+ handleAllMovesAcrossParents(allExistingNodes, originalIdToExisting, originalIdToSpec )
30
31
31
- originalIdToRef = mutableMapOf ()
32
- buildRefIndex(root)
32
+ val allNodes = allExistingNodes + addedNodes
33
+ val originalIdToRef: MutableMap <String , INodeReference > = buildRefIndex(allNodes)
34
+ syncAllReferences(allNodes, originalIdToSpec, originalIdToRef)
35
+ deleteAllExtraChildren(allNodes, originalIdToSpec)
33
36
34
- syncAllReferences(root, data.root)
35
37
}
36
38
37
- private fun buildExistingIndex (root : INode ) {
38
- root.originalId()?.let { originalIdToExisting[it] = root }
39
- root.allChildren.forEach { buildExistingIndex(it) }
39
+ private fun buildExistingIndex (allNodes : List <INode >): MutableMap <String , INode > {
40
+ val originalIdToExisting: MutableMap <String , INode > = mutableMapOf ()
41
+ allNodes.forEach {node ->
42
+ node.originalId()?.let { originalIdToExisting[it] = node }
43
+ }
44
+ return originalIdToExisting
40
45
}
41
46
42
- private fun buildRefIndex (node : INode ) {
43
- node.originalId()?.let { originalIdToRef[it] = node.reference }
44
- node.allChildren.forEach { buildRefIndex(it) }
47
+ private fun buildRefIndex (allNodes : List <INode >): MutableMap <String , INodeReference > {
48
+ val originalIdToRef: MutableMap <String , INodeReference > = mutableMapOf ()
49
+ allNodes.forEach {node ->
50
+ node.originalId()?.let { originalIdToRef[it] = node.reference }
51
+ }
52
+ return originalIdToRef
45
53
}
46
54
47
- private fun buildSpecIndex (nodeData : NodeData ) {
55
+ private fun buildSpecIndex (
56
+ nodeData : NodeData ,
57
+ originalIdToSpec : MutableMap <String , NodeData > = mutableMapOf()
58
+ ): MutableMap <String , NodeData > {
48
59
nodeData.originalId()?.let { originalIdToSpec[it] = nodeData }
49
- nodeData.children.forEach { buildSpecIndex(it) }
60
+ nodeData.children.forEach { buildSpecIndex(it, originalIdToSpec) }
61
+ return originalIdToSpec
50
62
}
51
63
52
- private fun syncNode (node : INode , nodeData : NodeData ) {
53
- syncProperties(node, nodeData)
54
- syncChildNodes(node, nodeData)
64
+ private fun syncAllProperties (allNodes : List <INode >, originalIdToSpec : MutableMap <String , NodeData >) {
65
+ allNodes.forEach {node ->
66
+ originalIdToSpec[node.originalId()]?.let { spec -> syncProperties(node, spec) }
67
+ }
55
68
}
56
69
57
70
private fun syncProperties (node : INode , nodeData : NodeData ) {
71
+ if (node.getPropertyValue(NodeData .idPropertyKey) == null ) {
72
+ node.setPropertyValue(NodeData .idPropertyKey, nodeData.originalId())
73
+ }
74
+
58
75
nodeData.properties.forEach {
59
76
if (node.getPropertyValue(it.key) != it.value) {
60
77
node.setPropertyValueWithStats(it.key, it.value)
61
78
}
62
79
}
80
+
63
81
val toBeRemoved = node.getPropertyRoles().toSet()
64
82
.subtract(nodeData.properties.keys)
65
83
.filter { it != NodeData .idPropertyKey }
66
84
toBeRemoved.forEach { node.setPropertyValueWithStats(it, null ) }
67
85
}
68
86
69
- private fun syncAllReferences (root : INode , rootData : NodeData ) {
70
- syncReferences(root, rootData)
71
- for ((node, data) in root.allChildren.zip(rootData.children)) {
72
- syncAllReferences(node, data)
87
+ private fun syncAllReferences (allNodes : List <INode >, originalIdToSpec : MutableMap <String , NodeData >, originalIdToRef : Map <String , INodeReference >) {
88
+ allNodes.forEach {node ->
89
+ originalIdToSpec[node.originalId()]?.let { spec -> syncReferences(node, spec, originalIdToRef) }
73
90
}
74
91
}
75
92
76
- private fun syncReferences (node : INode , nodeData : NodeData ) {
93
+ private fun syncReferences (node : INode , nodeData : NodeData , originalIdToRef : Map < String , INodeReference > ) {
77
94
nodeData.references.forEach {
78
95
if (node.getReferenceTargetRef(it.key) != originalIdToRef[it.value]) {
79
96
node.setReferenceTargetWithStats(it.key, originalIdToRef[it.value])
@@ -83,77 +100,143 @@ class ModelImporter(private val root: INode, val stats: ImportStats? = null) {
83
100
toBeRemoved.forEach { node.setReferenceTargetWithStats(it, null ) }
84
101
}
85
102
86
- private fun syncChildNodes (node : INode , nodeData : NodeData ) {
87
- val specifiedNodes = nodeData.children.toSet()
88
- val existingIds = node.allChildren.map { it.originalId() }.toSet()
89
- val missingNodes = specifiedNodes.filter { ! existingIds.contains(it.originalId()) }
90
-
91
- val toBeRemoved = node.allChildren.filter { ! originalIdToSpec.contains(it.originalId()) }
92
- toBeRemoved.forEach { node.removeChildWithStats(it) }
93
-
94
- val toBeMovedAwayFromHere = existingIds.subtract(specifiedNodes.map { it.originalId() }.toSet())
95
-
96
- syncExistingChildOrder(node, nodeData, existingIds, toBeMovedAwayFromHere)
97
-
98
- val toBeMovedHere = missingNodes.filter { originalIdToExisting.containsKey(it.originalId()) }.toSet()
99
- toBeMovedHere.forEach {
100
- val actualNode = originalIdToExisting[it.originalId()]
101
- val targetIndex = it.getIndexWithinRole(nodeData,existingIds.size - 1 )
102
- if (actualNode != null ) {
103
- node.moveChildWithStats(it.role, targetIndex, actualNode)
104
- }
103
+ private fun addAllMissingChildren (
104
+ node : INode ,
105
+ originalIdToExisting : MutableMap <String , INode >,
106
+ originalIdToSpec : MutableMap <String , NodeData >
107
+ ): MutableList <INode > {
108
+ val addedNodes = mutableListOf<INode >()
109
+ originalIdToSpec[node.originalId()]?.let {
110
+ addedNodes.addAll(
111
+ addMissingChildren(node, it, originalIdToExisting, originalIdToSpec)
112
+ )
105
113
}
106
-
107
- val toBeAdded = missingNodes.subtract(toBeMovedHere)
108
- toBeAdded.forEach {
109
- val index = it.getIndexWithinRole(nodeData, existingIds.size - 1 )
110
- node.addNewChildWithStats(it, index)
114
+ node.allChildren.forEach {
115
+ addedNodes.addAll(addAllMissingChildren(it, originalIdToExisting, originalIdToSpec))
111
116
}
117
+ return addedNodes
118
+ }
112
119
113
- node.allChildren.forEach {
114
- val childData = originalIdToSpec[it.originalId()]
115
- if (childData != null ) {
116
- syncNode(it, childData)
120
+ private fun addMissingChildren (
121
+ node : INode ,
122
+ nodeData : NodeData ,
123
+ originalIdToExisting : MutableMap <String , INode >,
124
+ originalIdToSpec : MutableMap <String , NodeData >
125
+ ): List <INode > {
126
+ val specifiedChildren = nodeData.children.toList()
127
+ val toBeAdded = specifiedChildren.filter { ! originalIdToExisting.contains(it.originalId()) }
128
+
129
+ return toBeAdded.map { nodeToBeAdded ->
130
+ val childrenInRole = node.allChildren.filter { it.roleInParent == nodeToBeAdded.role }
131
+ val existingIds = childrenInRole.map { it.originalId() }
132
+ val baseIndex = nodeToBeAdded.getIndexWithinRole(nodeData, childrenInRole.size)
133
+ var offset = 0
134
+ offset + = childrenInRole.slice(0 .. minOf(baseIndex, childrenInRole.lastIndex)).count {
135
+ ! originalIdToSpec.containsKey(it.originalId()) // node will be deleted
136
+ }
137
+ offset - = specifiedChildren.filter { it.role == nodeToBeAdded.role }.slice(0 until baseIndex).count {
138
+ ! existingIds.contains(it.originalId()) // node will be moved here
117
139
}
140
+ node.addNewChildWithStats(nodeToBeAdded, baseIndex + offset)
118
141
}
119
142
}
120
143
121
- private fun syncExistingChildOrder (
144
+ private fun sortAllExistingChildren (
145
+ allNodes : List <INode >,
146
+ originalIdToExisting : MutableMap <String , INode >,
147
+ originalIdToSpec : MutableMap <String , NodeData >
148
+ ) {
149
+ allNodes.forEach { node ->
150
+ originalIdToSpec[node.originalId()]?.let { sortExistingChildren(node, it, originalIdToExisting, originalIdToSpec) }
151
+ }
152
+ }
153
+
154
+ private fun sortExistingChildren (
122
155
node : INode ,
123
156
nodeData : NodeData ,
124
- existingIds : Set <String ? >,
125
- toBeMovedAwayFromHere : Set <String ? >
157
+ originalIdToExisting : MutableMap <String , INode >,
158
+ originalIdToSpec : MutableMap <String , NodeData >
126
159
) {
127
160
val existingChildren = node.allChildren.toList()
161
+ val existingIds = existingChildren.map { it.originalId() }
128
162
val specifiedChildren = nodeData.children
129
- require(existingChildren.size <= specifiedChildren.size)
130
-
131
- val filteredSpecifiedChildren = specifiedChildren.filter { existingIds.contains(it.originalId()) }
163
+ val toBeSortedSpec = specifiedChildren.filter { originalIdToExisting.containsKey(it.originalId()) }
132
164
133
165
val targetIndices = HashMap <String ?, Int >(nodeData.children.size)
134
- for (specifiedChild in filteredSpecifiedChildren) {
135
- val index = filteredSpecifiedChildren.filter { it.role == node.roleInParent }.indexOf(specifiedChild)
136
- targetIndices[specifiedChild.originalId()] = minOf(index, existingChildren.size - 1 )
166
+ for (child in toBeSortedSpec) {
167
+ val baseIndex = toBeSortedSpec.filter { it.role == node.roleInParent }.indexOf(child)
168
+ var offset = 0
169
+ offset + = existingChildren.filter { it.roleInParent == child.role }.slice(0 .. baseIndex).count {
170
+ ! originalIdToSpec.containsKey(it.originalId()) // node will be deleted
171
+ }
172
+ offset - = specifiedChildren.filter { it.role == child.role }.slice(0 .. baseIndex).count {
173
+ ! existingIds.contains(it.originalId()) // node will be moved here
174
+ }
175
+ val index = baseIndex + offset
176
+ targetIndices[child.originalId()] = minOf(index, existingChildren.size - 1 )
137
177
}
138
178
139
179
for ((index, child) in existingChildren.withIndex()) {
140
- val targetIndex = targetIndices[child.originalId()] ? : - 1
141
- if (targetIndex != index && ! toBeMovedAwayFromHere.contains(child.originalId()) ) {
180
+ val targetIndex = targetIndices[child.originalId()]
181
+ if (targetIndex != null && targetIndex != index ) {
142
182
node.moveChildWithStats(child.roleInParent, targetIndex, child)
143
183
}
144
184
}
145
185
}
146
186
147
- private fun NodeData.getIndexWithinRole (parent : NodeData , maxIndex : Int ) : Int {
148
- return minOf(parent.children.filter { it.role == this .role }.indexOf(this ), maxIndex)
187
+ private fun handleAllMovesAcrossParents (
188
+ allNodes : List <INode >,
189
+ originalIdToExisting : MutableMap <String , INode >,
190
+ originalIdToSpec : MutableMap <String , NodeData >
191
+ ) {
192
+ allNodes.forEach {
193
+ originalIdToSpec[it.originalId()]?.let { spec -> handleMoveAcrossParents(it, spec, originalIdToExisting, originalIdToSpec) }
194
+ }
149
195
}
150
196
151
- private fun INode.originalId (): String? {
152
- return this .getPropertyValue(NodeData .idPropertyKey)
197
+ private fun handleMoveAcrossParents (
198
+ node : INode ,
199
+ nodeData : NodeData ,
200
+ originalIdToExisting : MutableMap <String , INode >,
201
+ originalIdToSpec : MutableMap <String , NodeData >
202
+ ) {
203
+
204
+ val existingChildren = node.allChildren.toList()
205
+ val missingIds = nodeData.children.map { it.originalId() }.toSet()
206
+ .subtract(node.allChildren.map { it.originalId() }.toSet())
207
+ val toBeMovedHere = missingIds
208
+ .filter { originalIdToSpec.containsKey(it) }
209
+ .mapNotNull { originalIdToExisting[it] }
210
+
211
+ toBeMovedHere.forEach {nodeToBeMoved ->
212
+ val spec = originalIdToSpec[nodeToBeMoved.originalId()]!!
213
+
214
+ val baseTargetIndex = spec.getIndexWithinRole(nodeData, existingChildren.size - 1 )
215
+ val offset = existingChildren.slice(0 .. baseTargetIndex).count {
216
+ ! originalIdToSpec.containsKey(it.originalId()) // node will be deleted
217
+ }
218
+ val targetIndex = baseTargetIndex + offset
219
+
220
+ node.moveChildWithStats(spec.role, targetIndex, nodeToBeMoved)
221
+ }
222
+ }
223
+
224
+ private fun deleteAllExtraChildren (allNodes : List <INode >, originalIdToSpec : MutableMap <String , NodeData >) {
225
+ val toBeRemoved = mutableListOf<INode >()
226
+ allNodes.forEach { node ->
227
+ node.allChildren.forEach {
228
+ if (! originalIdToSpec.containsKey(it.originalId())) {
229
+ toBeRemoved.add(it)
230
+ }
231
+ }
232
+ }
233
+ toBeRemoved.asReversed().forEach {// delete bottom-up
234
+ it.parent?.removeChildWithStats(it)
235
+ }
153
236
}
154
237
155
- private fun NodeData.originalId (): String? {
156
- return properties[ NodeData .idPropertyKey] ? : id
238
+ private fun NodeData.getIndexWithinRole ( parent : NodeData , maxIndex : Int ) : Int {
239
+ return minOf(parent.children.filter { it.role == this .role }.indexOf( this ), maxIndex)
157
240
}
158
241
159
242
private fun INode.addNewChildWithStats (spec : NodeData , index : Int ) : INode {
@@ -208,4 +291,12 @@ class ModelImporter(private val root: INode, val stats: ImportStats? = null) {
208
291
setReferenceTarget(role, target)
209
292
}
210
293
294
+ }
295
+
296
+ internal fun INode.originalId (): String? {
297
+ return this .getPropertyValue(NodeData .idPropertyKey)
298
+ }
299
+
300
+ internal fun NodeData.originalId (): String? {
301
+ return properties[NodeData .idPropertyKey] ? : id
211
302
}
0 commit comments