-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathOperationSerializer.kt
287 lines (263 loc) · 13.7 KB
/
OperationSerializer.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
package org.modelix.model.persistent
import org.modelix.model.api.IConceptReference
import org.modelix.model.api.INodeReference
import org.modelix.model.api.LocalPNodeReference
import org.modelix.model.api.NodeReference
import org.modelix.model.api.PNodeReference
import org.modelix.model.lazy.KVEntryReference
import org.modelix.model.operations.AddNewChildOp
import org.modelix.model.operations.AddNewChildSubtreeOp
import org.modelix.model.operations.AddNewChildrenOp
import org.modelix.model.operations.BulkUpdateOp
import org.modelix.model.operations.DeleteNodeOp
import org.modelix.model.operations.IOperation
import org.modelix.model.operations.MoveNodeOp
import org.modelix.model.operations.NoOp
import org.modelix.model.operations.PositionInRole
import org.modelix.model.operations.RevertToOp
import org.modelix.model.operations.SetConceptOp
import org.modelix.model.operations.SetPropertyOp
import org.modelix.model.operations.SetReferenceOp
import org.modelix.model.operations.UndoOp
import org.modelix.model.persistent.SerializationUtil.escape
import org.modelix.model.persistent.SerializationUtil.longFromHex
import org.modelix.model.persistent.SerializationUtil.longToHex
import org.modelix.model.persistent.SerializationUtil.unescape
import kotlin.reflect.KClass
private val localNodeIdPattern = Regex("[a-fA-F0-9]+")
@Deprecated("uses invalid '/' separator that is already reserved for top level objects")
private val globalNodeIdPattern = Regex("[a-fA-F0-9]+/.+")
class OperationSerializer private constructor() {
companion object {
val INSTANCE = OperationSerializer()
private const val SEPARATOR = Separators.OP_PARTS
fun serializeConcept(concept: IConceptReference?): String {
return escape(concept?.serialize())
}
fun deserializeConcept(serialized: String?): IConceptReference? {
return IConceptReference.deserialize(unescape(serialized))
}
fun serializeReference(obj: INodeReference?): String {
return when (obj) {
null -> ""
is LocalPNodeReference -> longToHex(obj.id)
// This was previously using the '/' separator, that is already used for the top level objects
// and would fail to deserialize.
// is PNodeReference -> {
// "${longToHex(obj.id)}/${escape(obj.branchId)}"
// }
else -> escape(obj.serialize())
}
}
fun deserializeReference(serialized: String?): INodeReference? {
return when {
serialized.isNullOrEmpty() -> null
serialized.matches(localNodeIdPattern) -> LocalPNodeReference(longFromHex(serialized))
serialized.matches(globalNodeIdPattern) -> {
// This is how PNodeReference was serialized before, but the deserialization of the CPVersion
// should fail with this duplicate usage of the '/' separator.
// This branch should not be reachable and can be removed if we are absolutely sure about that.
val parts = serialized.split('/', limit = 2)
PNodeReference(longFromHex(parts[0]), unescape(parts[1])!!)
}
else -> unescape(serialized)?.let {
PNodeReference.tryDeserialize(serialized) ?: NodeReference(it)
}
}
}
init {
INSTANCE.registerSerializer(
AddNewChildOp::class,
object : Serializer<AddNewChildOp> {
override fun serialize(op: AddNewChildOp): String {
return longToHex(op.position.nodeId) + SEPARATOR + escape(op.position.role) + SEPARATOR + op.position.index + SEPARATOR + longToHex(op.childId) + SEPARATOR + serializeConcept(op.concept)
}
override fun deserialize(serialized: String): AddNewChildOp {
val parts = serialized.split(SEPARATOR).toTypedArray()
return AddNewChildOp(PositionInRole(longFromHex(parts[0]), unescape(parts[1]), parts[2].toInt()), longFromHex(parts[3]), deserializeConcept(parts[4]))
}
},
)
INSTANCE.registerSerializer(
AddNewChildrenOp::class,
object : Serializer<AddNewChildrenOp> {
override fun serialize(op: AddNewChildrenOp): String {
val ids = op.childIds.joinToString(Separators.LEVEL4) { longToHex(it) }
val concepts = op.concepts.joinToString(Separators.LEVEL4) { serializeConcept(it) }
return longToHex(op.position.nodeId) + SEPARATOR + escape(op.position.role) + SEPARATOR + op.position.index + SEPARATOR + ids + SEPARATOR + concepts
}
override fun deserialize(serialized: String): AddNewChildrenOp {
val parts = serialized.split(SEPARATOR).toTypedArray()
val ids = parts[3].split(Separators.LEVEL4).filter { it.isNotEmpty() }.map { longFromHex(it) }.toLongArray()
val concepts = parts[4].split(Separators.LEVEL4).map { deserializeConcept(it) }.toTypedArray()
return AddNewChildrenOp(PositionInRole(longFromHex(parts[0]), unescape(parts[1]), parts[2].toInt()), ids, concepts)
}
},
)
INSTANCE.registerSerializer(
AddNewChildSubtreeOp::class,
object : Serializer<AddNewChildSubtreeOp> {
override fun serialize(op: AddNewChildSubtreeOp): String {
return longToHex(op.position.nodeId) + SEPARATOR + escape(op.position.role) + SEPARATOR + op.position.index + SEPARATOR + longToHex(op.childId) + SEPARATOR + serializeConcept(op.concept) + SEPARATOR + op.resultTreeHash.getHash()
}
override fun deserialize(serialized: String): AddNewChildSubtreeOp {
val parts = serialized.split(SEPARATOR).toTypedArray()
return AddNewChildSubtreeOp(KVEntryReference(parts[5], CPTree.DESERIALIZER), PositionInRole(longFromHex(parts[0]), unescape(parts[1]), parts[2].toInt()), longFromHex(parts[3]), deserializeConcept(parts[4]))
}
},
)
INSTANCE.registerSerializer(
BulkUpdateOp::class,
object : Serializer<BulkUpdateOp> {
override fun serialize(op: BulkUpdateOp): String {
return longToHex(op.subtreeRootId) + SEPARATOR + op.resultTreeHash.getHash()
}
override fun deserialize(serialized: String): BulkUpdateOp {
val parts = serialized.split(SEPARATOR).toTypedArray()
return BulkUpdateOp(KVEntryReference(parts[1], CPTree.DESERIALIZER), longFromHex(parts[0]))
}
},
)
INSTANCE.registerSerializer(
DeleteNodeOp::class,
object : Serializer<DeleteNodeOp> {
override fun serialize(op: DeleteNodeOp): String {
return longToHex(op.childId)
}
override fun deserialize(serialized: String): DeleteNodeOp {
val parts = serialized.split(SEPARATOR)
return if (parts.size == 1) {
DeleteNodeOp(longFromHex(parts[0]))
} else {
DeleteNodeOp(longFromHex(parts[3]))
}
}
},
)
INSTANCE.registerSerializer(
MoveNodeOp::class,
object : Serializer<MoveNodeOp> {
override fun serialize(op: MoveNodeOp): String {
return longToHex(op.childId) + SEPARATOR +
longToHex(op.targetPosition.nodeId) + SEPARATOR +
escape(op.targetPosition.role) + SEPARATOR +
op.targetPosition.index
}
override fun deserialize(serialized: String): MoveNodeOp {
val parts = serialized.split(SEPARATOR)
return if (parts.size == 4) {
MoveNodeOp(
longFromHex(parts[0]),
PositionInRole(longFromHex(parts[1]), unescape(parts[2]), parts[3].toInt()),
)
} else {
MoveNodeOp(
longFromHex(parts[0]),
PositionInRole(longFromHex(parts[4]), unescape(parts[5]), parts[6].toInt()),
)
}
}
},
)
INSTANCE.registerSerializer(
NoOp::class,
object : Serializer<NoOp> {
override fun serialize(op: NoOp): String {
return ""
}
override fun deserialize(serialized: String): NoOp {
return NoOp()
}
},
)
INSTANCE.registerSerializer(
SetPropertyOp::class,
object : Serializer<SetPropertyOp> {
override fun serialize(op: SetPropertyOp): String {
return longToHex(op.nodeId) + SEPARATOR + escape(op.role) + SEPARATOR + escape(op.value)
}
override fun deserialize(serialized: String): SetPropertyOp {
val parts = serialized.split(SEPARATOR).toTypedArray()
return SetPropertyOp(longFromHex(parts[0]), unescape(parts[1])!!, unescape(parts[2]))
}
},
)
INSTANCE.registerSerializer(
SetReferenceOp::class,
object : Serializer<SetReferenceOp> {
override fun serialize(op: SetReferenceOp): String {
return longToHex(op.sourceId) + SEPARATOR + escape(op.role) + SEPARATOR + serializeReference(op.target)
}
override fun deserialize(serialized: String): SetReferenceOp {
val parts = serialized.split(SEPARATOR).toTypedArray()
return SetReferenceOp(longFromHex(parts[0]), unescape(parts[1])!!, deserializeReference(parts[2]))
}
},
)
INSTANCE.registerSerializer(
SetConceptOp::class,
object : Serializer<SetConceptOp> {
override fun serialize(op: SetConceptOp): String {
return longToHex(op.nodeId) + SEPARATOR + serializeConcept(op.concept)
}
override fun deserialize(serialized: String): SetConceptOp {
val parts = serialized.split(SEPARATOR)
return SetConceptOp(nodeId = longFromHex(parts[0]), concept = deserializeConcept(parts[1]))
}
},
)
INSTANCE.registerSerializer(
UndoOp::class,
object : Serializer<UndoOp> {
override fun serialize(op: UndoOp): String {
return op.versionHash.getHash()
}
override fun deserialize(serialized: String): UndoOp {
return UndoOp(KVEntryReference(serialized, CPVersion.DESERIALIZER))
}
},
)
INSTANCE.registerSerializer(
RevertToOp::class,
object : Serializer<RevertToOp> {
override fun serialize(op: RevertToOp): String {
return op.latestKnownVersionRef.getHash() + SEPARATOR + op.versionToRevertToRef.getHash()
}
override fun deserialize(serialized: String): RevertToOp {
val parts = serialized.split(SEPARATOR).toTypedArray()
return RevertToOp(KVEntryReference(parts[0], CPVersion.DESERIALIZER), KVEntryReference(parts[1], CPVersion.DESERIALIZER))
}
},
)
}
}
private val serializers: MutableMap<KClass<out IOperation>, Serializer<*>> = HashMap()
private val deserializers: MutableMap<String, Serializer<*>> = HashMap()
fun <T : IOperation> registerSerializer(type: KClass<T>, serializer: Serializer<T>) {
serializers[type] = serializer
deserializers[type.simpleName!!] = serializer
}
fun serialize(op: IOperation): String {
val serializer: Serializer<*> = serializers[op::class]
?: throw RuntimeException("Unknown operation type: " + op::class.simpleName)
return op::class.simpleName + SEPARATOR + serializer.genericSerialize(op)
}
fun deserialize(serialized: String): IOperation {
val parts = serialized.split(SEPARATOR, limit = 2).toTypedArray()
val deserializer = deserializers[parts[0]]
?: throw RuntimeException("Unknown operation type: " + parts[0])
return deserializer.deserialize(parts[1])!!
}
interface Serializer<E : IOperation?> {
fun genericSerialize(op: Any): String {
val p = op as? E
if (p == null) {
throw IllegalArgumentException()
} else {
return serialize(p)
}
}
fun serialize(op: E): String
fun deserialize(serialized: String): E
}
}