1
1
package org.modelix.datastructures.patricia
2
2
3
+ import org.modelix.datastructures.EntryAddedEvent
4
+ import org.modelix.datastructures.EntryChangedEvent
5
+ import org.modelix.datastructures.EntryRemovedEvent
3
6
import org.modelix.datastructures.IPersistentMap
4
7
import org.modelix.datastructures.IPersistentMapRootData
8
+ import org.modelix.datastructures.MapChangeEvent
5
9
import org.modelix.datastructures.objects.IObjectData
6
10
import org.modelix.datastructures.objects.IObjectDeserializer
7
11
import org.modelix.datastructures.objects.IObjectGraph
@@ -14,11 +18,18 @@ import org.modelix.datastructures.serialization.SplitJoinSerializer
14
18
import org.modelix.kotlin.utils.urlDecode
15
19
import org.modelix.kotlin.utils.urlEncode
16
20
import org.modelix.streams.IStream
21
+ import org.modelix.streams.flatten
22
+ import org.modelix.streams.ifEmpty
17
23
import org.modelix.streams.plus
18
24
19
25
data class PatriciaNode <K , V : Any >(
20
26
val config : PatriciaTrieConfig <K , V >,
21
27
val ownPrefix : String ,
28
+
29
+ /* *
30
+ * Equivalent to `children.map { it.ownPrefix.first() }.join("")`, just allows choosing the correct child without
31
+ * resolving all of them. Sorted.
32
+ */
22
33
val firstChars : String ,
23
34
val children : List <ObjectReference <PatriciaNode <K , V >>>,
24
35
@@ -72,6 +83,10 @@ data class PatriciaNode<K, V : Any>(
72
83
73
84
fun withValue (newValue : V ? ) = copy(value = newValue)
74
85
86
+ /* *
87
+ * Returns all entries.
88
+ * @param prefix the prefix from the root to this node. Required to assemble the key.
89
+ */
75
90
fun getEntries (prefix : CharSequence ): IStream .Many <Pair <CharSequence , V >> {
76
91
val fullPrefix = prefix + ownPrefix
77
92
val descendants = IStream .Companion .many(children).flatMap { it.resolveData() }
@@ -135,40 +150,61 @@ data class PatriciaNode<K, V : Any>(
135
150
}
136
151
}
137
152
138
- fun put (newKey : CharSequence , newValue : V ? ): IStream .ZeroOrOne <PatriciaNode <K , V >> {
153
+ fun updateSubtree (newKey : CharSequence , updater : ( PatriciaNode < K , V >) -> IStream . ZeroOrOne < PatriciaNode < K , V >> ): IStream .ZeroOrOne <PatriciaNode <K , V >> {
139
154
val commonPrefix = newKey.commonPrefixWith(this .ownPrefix)
140
155
val remainingNewKey = newKey.drop(commonPrefix.length)
141
156
val remainingOwnPrefix = this .ownPrefix.drop(commonPrefix.length)
142
157
return (
143
158
if (remainingOwnPrefix.isEmpty() && remainingNewKey.isEmpty()) {
144
- // key references this node -> overwrite the value
145
- IStream .of(withValue(newValue) )
159
+ // key references this node
160
+ updater( this )
146
161
} else if (remainingOwnPrefix.isEmpty()) {
147
162
// key is longer -> insert into children
148
163
val index = firstChars.binarySearch(remainingNewKey.first())
149
164
if (index >= 0 ) {
150
165
children[index].resolveData()
151
- .flatMapOne { it.put (remainingNewKey, newValue ).orNull() }
166
+ .flatMapOne { it.updateSubtree (remainingNewKey, updater ).orNull() }
152
167
.mapNotNull { withChildReplacedNullable(index, it) }
153
168
} else {
154
169
val insertionIndex = (- index) - 1
155
- val newChild = PatriciaNode <K , V >(
170
+ PatriciaNode <K , V >(
156
171
config = config,
157
172
ownPrefix = remainingNewKey.toString(),
158
- value = newValue ,
173
+ value = null ,
159
174
firstChars = " " ,
160
175
children = emptyList(),
161
- )
162
- IStream .of(withChildInserted(insertionIndex, remainingNewKey.first(), newChild))
176
+ ).let { updater(it) }
177
+ .map { withChildInserted(insertionIndex, remainingNewKey.first(), it) }
178
+ .ifEmpty {
179
+ // updater returned an empty node, and since this part is about inserting, nothing changed
180
+ this
181
+ }
163
182
}
164
183
} else {
165
184
// key is shorter -> need to split into a node with a shorter prefix
166
- split(commonPrefix).put (newKey, newValue )
185
+ split(commonPrefix).updateSubtree (newKey, updater )
167
186
}
168
187
).flatMapZeroOrOne { it.tryMerge() }
169
188
}
170
189
171
- fun split (commonPrefix : CharSequence ): PatriciaNode <K , V > {
190
+ fun put (newKey : CharSequence , newValue : V ? ): IStream .ZeroOrOne <PatriciaNode <K , V >> {
191
+ return updateSubtree(newKey) {
192
+ IStream .of(it.copy(value = newValue))
193
+ }
194
+ }
195
+
196
+ fun replaceSubtree (prefix : CharSequence , newSubtree : PatriciaNode <K , V >? ): IStream .ZeroOrOne <PatriciaNode <K , V >> {
197
+ return updateSubtree(prefix) {
198
+ IStream .ofNotNull(newSubtree?.copy(ownPrefix = it.ownPrefix))
199
+ }
200
+ }
201
+
202
+ /* *
203
+ * Shortens the prefix of this node to the given one as a preparation for inserting children or setting a value.
204
+ */
205
+ private fun split (commonPrefix : CharSequence ): PatriciaNode <K , V > {
206
+ require(ownPrefix.startsWith(commonPrefix))
207
+ if (ownPrefix.length == commonPrefix.length) return this
172
208
val remainingPrefix = this .ownPrefix.drop(commonPrefix.length)
173
209
return PatriciaNode <K , V >(
174
210
config = config,
@@ -195,7 +231,7 @@ data class PatriciaNode<K, V : Any>(
195
231
return ownPrefix.urlEncode() +
196
232
S1 + firstChars.urlEncode() +
197
233
S1 + children.joinToString(S2 .toString()) { it.getHashString() } +
198
- S1 + value
234
+ S1 + value?. let { config.valueConfig.serialize(it) }.urlEncode()
199
235
}
200
236
201
237
override fun getDeserializer (): IObjectDeserializer <* > {
@@ -206,6 +242,79 @@ data class PatriciaNode<K, V : Any>(
206
242
return children + (value?.let { config.valueConfig.getContainmentReferences(it) } ? : emptyList())
207
243
}
208
244
245
+ fun getChanges (path : CharSequence , oldNode : PatriciaNode <K , V >? , changesOnly : Boolean ): IStream .Many <MapChangeEvent <K , V >> {
246
+ if (oldNode == null ) {
247
+ return if (changesOnly) {
248
+ IStream .empty()
249
+ } else {
250
+ getEntries(path).map { EntryAddedEvent (config.keyConfig.deserialize(it.first.toString()), it.second) }
251
+ }
252
+ }
253
+ return if (ownPrefix == oldNode.ownPrefix) {
254
+ val pathForChildren = path + ownPrefix
255
+ val matchingChildren = if (firstChars == oldNode.firstChars) {
256
+ children.zip(oldNode.children)
257
+ } else {
258
+ val newChildren = firstChars.asSequence().zip(children.asSequence()).toMap()
259
+ val oldChildren = oldNode.firstChars.asSequence().zip(oldNode.children.asSequence()).toMap()
260
+ val allFirstChars = newChildren.keys.plus(oldChildren.keys).distinct()
261
+ allFirstChars.map { newChildren[it] to oldChildren[it] }
262
+ }
263
+ val changesFromChildren = IStream .many(matchingChildren).flatMap { (newChildRef, oldChildRef) ->
264
+ val newChild = newChildRef?.resolveData() ? : IStream .of(null )
265
+ val oldChild = oldChildRef?.resolveData() ? : IStream .of(null )
266
+
267
+ newChild.zipWith(oldChild) { newChild, oldChild ->
268
+ if (newChild == null ) {
269
+ if (oldChild == null ) {
270
+ IStream .empty()
271
+ } else {
272
+ if (changesOnly) {
273
+ IStream .empty()
274
+ } else {
275
+ oldChild.getEntries(pathForChildren).map {
276
+ EntryRemovedEvent (config.keyConfig.deserialize(it.first.toString()), it.second)
277
+ }
278
+ }
279
+ }
280
+ } else {
281
+ newChild.getChanges(pathForChildren, oldChild, changesOnly)
282
+ }
283
+ }
284
+ }.flatten()
285
+
286
+ fun ownKey () = config.keyConfig.deserialize(pathForChildren.toString())
287
+ val ownChange = if (this .value == null ) {
288
+ if (oldNode.value == null ) {
289
+ IStream .empty()
290
+ } else {
291
+ IStream .of(EntryRemovedEvent (ownKey(), oldNode.value))
292
+ }
293
+ } else {
294
+ if (oldNode.value == null ) {
295
+ IStream .of(EntryAddedEvent (ownKey(), this .value))
296
+ } else {
297
+ if (config.equal(this .value, oldNode.value)) {
298
+ IStream .empty()
299
+ } else {
300
+ IStream .of(
301
+ EntryChangedEvent (
302
+ key = config.keyConfig.deserialize(pathForChildren.toString()),
303
+ oldValue = oldNode.value,
304
+ newValue = this .value,
305
+ ),
306
+ )
307
+ }
308
+ }
309
+ }
310
+
311
+ ownChange + changesFromChildren
312
+ } else {
313
+ val commonPrefix = ownPrefix.commonPrefixWith(oldNode.ownPrefix)
314
+ split(commonPrefix).getChanges(path, oldNode.split(commonPrefix), changesOnly)
315
+ }
316
+ }
317
+
209
318
class Deserializer <K , V : Any >(val config : (IObjectGraph ) -> PatriciaTrieConfig <K , V >) : IObjectDeserializer<PatriciaNode<K, V>> {
210
319
constructor (config: PatriciaTrieConfig <K , V >) : this ({ config })
211
320
override fun deserialize (
@@ -221,7 +330,7 @@ data class PatriciaNode<K, V : Any>(
221
330
parts[0 ].urlDecode()!! ,
222
331
parts[1 ].urlDecode()!! ,
223
332
parts[2 ].split(S2 ).filter { it.isNotEmpty() }.map { referenceFactory.fromHashString(it, this ) },
224
- config.valueConfig.deserialize(parts[ 3 ]) ,
333
+ parts[ 3 ].urlDecode()?. let { config.valueConfig.deserialize(it) } ,
225
334
)
226
335
}
227
336
}
0 commit comments