diff options
Diffstat (limited to 'subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MutableNode.java')
-rw-r--r-- | subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MutableNode.java | 499 |
1 files changed, 499 insertions, 0 deletions
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MutableNode.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MutableNode.java new file mode 100644 index 00000000..408ed62c --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MutableNode.java | |||
@@ -0,0 +1,499 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.map.internal.state; | ||
7 | |||
8 | import tools.refinery.store.map.ContinuousHashProvider; | ||
9 | |||
10 | import java.util.Arrays; | ||
11 | import java.util.Map; | ||
12 | |||
13 | public class MutableNode<K, V> extends Node<K, V> { | ||
14 | int cachedHash; | ||
15 | protected boolean cachedHashValid; | ||
16 | protected Object[] content; | ||
17 | |||
18 | protected MutableNode() { | ||
19 | this.content = new Object[2 * FACTOR]; | ||
20 | invalidateHash(); | ||
21 | } | ||
22 | |||
23 | public static <K, V> MutableNode<K, V> initialize(K key, V value, ContinuousHashProvider<? super K> hashProvider, V defaultValue) { | ||
24 | if (value == defaultValue) { | ||
25 | return null; | ||
26 | } else { | ||
27 | int hash = hashProvider.getHash(key, 0); | ||
28 | int fragment = hashFragment(hash, 0); | ||
29 | MutableNode<K, V> res = new MutableNode<>(); | ||
30 | res.content[2 * fragment] = key; | ||
31 | res.content[2 * fragment + 1] = value; | ||
32 | res.invalidateHash(); | ||
33 | return res; | ||
34 | } | ||
35 | } | ||
36 | |||
37 | /** | ||
38 | * Constructs a {@link MutableNode} as a copy of an {@link ImmutableNode} | ||
39 | * | ||
40 | * @param node to be transformed | ||
41 | */ | ||
42 | protected MutableNode(ImmutableNode<K, V> node) { | ||
43 | this.content = new Object[2 * FACTOR]; | ||
44 | int dataUsed = 0; | ||
45 | int nodeUsed = 0; | ||
46 | for (int i = 0; i < FACTOR; i++) { | ||
47 | int bitPosition = 1 << i; | ||
48 | if ((node.dataMap & bitPosition) != 0) { | ||
49 | content[2 * i] = node.content[dataUsed * 2]; | ||
50 | content[2 * i + 1] = node.content[dataUsed * 2 + 1]; | ||
51 | dataUsed++; | ||
52 | } else if ((node.nodeMap & bitPosition) != 0) { | ||
53 | content[2 * i + 1] = node.content[node.content.length - 1 - nodeUsed]; | ||
54 | nodeUsed++; | ||
55 | } | ||
56 | } | ||
57 | this.cachedHashValid = false; | ||
58 | } | ||
59 | |||
60 | @Override | ||
61 | public V getValue(K key, ContinuousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) { | ||
62 | int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); | ||
63 | @SuppressWarnings("unchecked") K keyCandidate = (K) this.content[2 * selectedHashFragment]; | ||
64 | if (keyCandidate != null) { | ||
65 | if (keyCandidate.equals(key)) { | ||
66 | @SuppressWarnings("unchecked") V value = (V) this.content[2 * selectedHashFragment + 1]; | ||
67 | return value; | ||
68 | } else { | ||
69 | return defaultValue; | ||
70 | } | ||
71 | } else { | ||
72 | @SuppressWarnings("unchecked") var nodeCandidate = (Node<K, V>) content[2 * selectedHashFragment + 1]; | ||
73 | if (nodeCandidate != null) { | ||
74 | int newDepth = incrementDepth(depth); | ||
75 | int newHash = newHash(hashProvider, key, hash, newDepth); | ||
76 | return nodeCandidate.getValue(key, hashProvider, defaultValue, newHash, newDepth); | ||
77 | } else { | ||
78 | return defaultValue; | ||
79 | } | ||
80 | } | ||
81 | } | ||
82 | |||
83 | @Override | ||
84 | public Node<K, V> putValue(K key, V value, OldValueBox<V> oldValueBox, ContinuousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) { | ||
85 | int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); | ||
86 | @SuppressWarnings("unchecked") K keyCandidate = (K) content[2 * selectedHashFragment]; | ||
87 | if (keyCandidate != null) { | ||
88 | // If it has key | ||
89 | if (keyCandidate.equals(key)) { | ||
90 | // The key is equals to an existing key -> update entry | ||
91 | if (value == defaultValue) { | ||
92 | return removeEntry(selectedHashFragment, oldValueBox); | ||
93 | } else { | ||
94 | return updateValue(value, oldValueBox, selectedHashFragment); | ||
95 | } | ||
96 | } else { | ||
97 | // The key is not equivalent to an existing key on the same hash bin | ||
98 | // -> split entry if it is necessary | ||
99 | if (value == defaultValue) { | ||
100 | // Value is default -> do not need to add new node | ||
101 | oldValueBox.setOldValue(defaultValue); | ||
102 | return this; | ||
103 | } else { | ||
104 | // Value is not default -> Split entry data to a new node | ||
105 | oldValueBox.setOldValue(defaultValue); | ||
106 | return moveDownAndSplit(hashProvider, key, value, keyCandidate, hash, depth, selectedHashFragment); | ||
107 | } | ||
108 | } | ||
109 | } | ||
110 | // If it does not have key, check for value | ||
111 | @SuppressWarnings("unchecked") var nodeCandidate = (Node<K, V>) content[2 * selectedHashFragment + 1]; | ||
112 | if (nodeCandidate != null) { | ||
113 | // If it has value, it is a sub-node -> update that | ||
114 | int newDepth = incrementDepth(depth); | ||
115 | var newNode = nodeCandidate.putValue(key, value, oldValueBox, hashProvider, defaultValue, newHash(hashProvider, key, hash, newDepth), newDepth); | ||
116 | return updateWithSubNode(selectedHashFragment, newNode, (value == null && defaultValue == null) || (value != null && value.equals(defaultValue))); | ||
117 | } else { | ||
118 | // If it does not have value, put it in the empty place | ||
119 | if (value == defaultValue) { | ||
120 | // don't need to add new key-value pair | ||
121 | oldValueBox.setOldValue(defaultValue); | ||
122 | return this; | ||
123 | } else { | ||
124 | return addEntry(key, value, oldValueBox, selectedHashFragment, defaultValue); | ||
125 | } | ||
126 | } | ||
127 | } | ||
128 | |||
129 | private Node<K, V> addEntry(K key, V value, OldValueBox<V> oldValueBox, int selectedHashFragment, V defaultValue) { | ||
130 | content[2 * selectedHashFragment] = key; | ||
131 | oldValueBox.setOldValue(defaultValue); | ||
132 | content[2 * selectedHashFragment + 1] = value; | ||
133 | invalidateHash(); | ||
134 | return this; | ||
135 | } | ||
136 | |||
137 | /** | ||
138 | * Updates an entry in a selected hash-fragment to a non-default value. | ||
139 | * | ||
140 | * @param value new value | ||
141 | * @param selectedHashFragment position of the value | ||
142 | * @return updated node | ||
143 | */ | ||
144 | @SuppressWarnings("unchecked") | ||
145 | Node<K, V> updateValue(V value, OldValueBox<V> oldValue, int selectedHashFragment) { | ||
146 | oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]); | ||
147 | content[2 * selectedHashFragment + 1] = value; | ||
148 | invalidateHash(); | ||
149 | return this; | ||
150 | } | ||
151 | |||
152 | /** | ||
153 | * Updates an entry in a selected hash-fragment with a subtree. | ||
154 | * | ||
155 | * @param selectedHashFragment position of the value | ||
156 | * @param newNode the subtree | ||
157 | * @return updated node | ||
158 | */ | ||
159 | Node<K, V> updateWithSubNode(int selectedHashFragment, Node<K, V> newNode, boolean deletionHappened) { | ||
160 | if (deletionHappened) { | ||
161 | if (newNode == null) { | ||
162 | // Check whether this node become empty | ||
163 | content[2 * selectedHashFragment + 1] = null; // i.e. the new node | ||
164 | if (hasContent()) { | ||
165 | invalidateHash(); | ||
166 | return this; | ||
167 | } else { | ||
168 | return null; | ||
169 | } | ||
170 | } else { | ||
171 | // check whether newNode is orphan | ||
172 | MutableNode<K, V> immutableNewNode = newNode.isMutable(); | ||
173 | if (immutableNewNode != null) { | ||
174 | int orphaned = immutableNewNode.isOrphaned(); | ||
175 | if (orphaned >= 0) { | ||
176 | // orphan sub-node data is replaced with data | ||
177 | content[2 * selectedHashFragment] = immutableNewNode.content[orphaned * 2]; | ||
178 | content[2 * selectedHashFragment + 1] = immutableNewNode.content[orphaned * 2 + 1]; | ||
179 | invalidateHash(); | ||
180 | return this; | ||
181 | } | ||
182 | } | ||
183 | } | ||
184 | } | ||
185 | // normal behaviour | ||
186 | content[2 * selectedHashFragment + 1] = newNode; | ||
187 | invalidateHash(); | ||
188 | return this; | ||
189 | } | ||
190 | |||
191 | private boolean hasContent() { | ||
192 | for (Object element : this.content) { | ||
193 | if (element != null) return true; | ||
194 | } | ||
195 | return false; | ||
196 | } | ||
197 | |||
198 | @Override | ||
199 | protected MutableNode<K, V> isMutable() { | ||
200 | return this; | ||
201 | } | ||
202 | |||
203 | protected int isOrphaned() { | ||
204 | int dataFound = -2; | ||
205 | for (int i = 0; i < FACTOR; i++) { | ||
206 | if (content[i * 2] != null) { | ||
207 | if (dataFound >= 0) { | ||
208 | return -1; | ||
209 | } else { | ||
210 | dataFound = i; | ||
211 | } | ||
212 | } else if (content[i * 2 + 1] != null) { | ||
213 | return -3; | ||
214 | } | ||
215 | } | ||
216 | return dataFound; | ||
217 | } | ||
218 | |||
219 | @SuppressWarnings("unchecked") | ||
220 | private Node<K, V> moveDownAndSplit(ContinuousHashProvider<? super K> hashProvider, K newKey, V newValue, K previousKey, int hashOfNewKey, int depth, int selectedHashFragmentOfCurrentDepth) { | ||
221 | V previousValue = (V) content[2 * selectedHashFragmentOfCurrentDepth + 1]; | ||
222 | |||
223 | MutableNode<K, V> newSubNode = newNodeWithTwoEntries(hashProvider, previousKey, previousValue, hashProvider.getHash(previousKey, hashDepth(depth)), newKey, newValue, hashOfNewKey, incrementDepth(depth)); | ||
224 | |||
225 | content[2 * selectedHashFragmentOfCurrentDepth] = null; | ||
226 | content[2 * selectedHashFragmentOfCurrentDepth + 1] = newSubNode; | ||
227 | invalidateHash(); | ||
228 | return this; | ||
229 | } | ||
230 | |||
231 | // Pass everything as parameters for performance. | ||
232 | @SuppressWarnings("squid:S107") | ||
233 | private MutableNode<K, V> newNodeWithTwoEntries(ContinuousHashProvider<? super K> hashProvider, K key1, V value1, int oldHash1, K key2, V value2, int oldHash2, int newDepth) { | ||
234 | int newHash1 = newHash(hashProvider, key1, oldHash1, newDepth); | ||
235 | int newHash2 = newHash(hashProvider, key2, oldHash2, newDepth); | ||
236 | int newFragment1 = hashFragment(newHash1, shiftDepth(newDepth)); | ||
237 | int newFragment2 = hashFragment(newHash2, shiftDepth(newDepth)); | ||
238 | |||
239 | MutableNode<K, V> subNode = new MutableNode<>(); | ||
240 | if (newFragment1 != newFragment2) { | ||
241 | subNode.content[newFragment1 * 2] = key1; | ||
242 | subNode.content[newFragment1 * 2 + 1] = value1; | ||
243 | |||
244 | subNode.content[newFragment2 * 2] = key2; | ||
245 | subNode.content[newFragment2 * 2 + 1] = value2; | ||
246 | } else { | ||
247 | MutableNode<K, V> subSubNode = newNodeWithTwoEntries(hashProvider, key1, value1, newHash1, key2, value2, newHash2, incrementDepth(newDepth)); | ||
248 | subNode.content[newFragment1 * 2 + 1] = subSubNode; | ||
249 | } | ||
250 | subNode.invalidateHash(); | ||
251 | return subNode; | ||
252 | } | ||
253 | |||
254 | @SuppressWarnings("unchecked") | ||
255 | Node<K, V> removeEntry(int selectedHashFragment, OldValueBox<V> oldValue) { | ||
256 | content[2 * selectedHashFragment] = null; | ||
257 | oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]); | ||
258 | content[2 * selectedHashFragment + 1] = null; | ||
259 | if (hasContent()) { | ||
260 | invalidateHash(); | ||
261 | return this; | ||
262 | } else { | ||
263 | return null; | ||
264 | } | ||
265 | } | ||
266 | |||
267 | @SuppressWarnings("unchecked") | ||
268 | @Override | ||
269 | public long getSize() { | ||
270 | int size = 0; | ||
271 | for (int i = 0; i < FACTOR; i++) { | ||
272 | if (content[i * 2] != null) { | ||
273 | size++; | ||
274 | } else { | ||
275 | Node<K, V> nodeCandidate = (Node<K, V>) content[i * 2 + 1]; | ||
276 | if (nodeCandidate != null) { | ||
277 | size += nodeCandidate.getSize(); | ||
278 | } | ||
279 | } | ||
280 | } | ||
281 | return size; | ||
282 | } | ||
283 | |||
284 | @Override | ||
285 | protected MutableNode<K, V> toMutable() { | ||
286 | return this; | ||
287 | } | ||
288 | |||
289 | @Override | ||
290 | public ImmutableNode<K, V> toImmutable(Map<Node<K, V>, ImmutableNode<K, V>> cache) { | ||
291 | return ImmutableNode.constructImmutable(this, cache); | ||
292 | } | ||
293 | |||
294 | @SuppressWarnings("unchecked") | ||
295 | @Override | ||
296 | boolean moveToNext(MapCursor<K, V> cursor) { | ||
297 | // 1. try to move to data | ||
298 | if (cursor.dataIndex != MapCursor.INDEX_FINISH) { | ||
299 | for (int index = cursor.dataIndex + 1; index < FACTOR; index++) { | ||
300 | if (this.content[index * 2] != null) { | ||
301 | // 1.1 found next data | ||
302 | cursor.dataIndex = index; | ||
303 | cursor.key = (K) this.content[index * 2]; | ||
304 | cursor.value = (V) this.content[index * 2 + 1]; | ||
305 | return true; | ||
306 | } | ||
307 | } | ||
308 | cursor.dataIndex = MapCursor.INDEX_FINISH; | ||
309 | } | ||
310 | |||
311 | // 2. look inside the sub-nodes | ||
312 | if(cursor.nodeIndexStack.peek()==null) { | ||
313 | throw new IllegalStateException("Cursor moved to the next state when the state is empty."); | ||
314 | } | ||
315 | for (int index = cursor.nodeIndexStack.peek() + 1; index < FACTOR; index++) { | ||
316 | if (this.content[index * 2] == null && this.content[index * 2 + 1] != null) { | ||
317 | // 2.1 found next sub-node, move down to the sub-node | ||
318 | Node<K, V> subnode = (Node<K, V>) this.content[index * 2 + 1]; | ||
319 | |||
320 | cursor.dataIndex = MapCursor.INDEX_START; | ||
321 | cursor.nodeIndexStack.pop(); | ||
322 | cursor.nodeIndexStack.push(index); | ||
323 | cursor.nodeIndexStack.push(MapCursor.INDEX_START); | ||
324 | cursor.nodeStack.push(subnode); | ||
325 | |||
326 | return subnode.moveToNext(cursor); | ||
327 | } | ||
328 | } | ||
329 | // 3. no sub-node found, move up | ||
330 | cursor.nodeStack.pop(); | ||
331 | cursor.nodeIndexStack.pop(); | ||
332 | if (!cursor.nodeStack.isEmpty()) { | ||
333 | Node<K, V> supernode = cursor.nodeStack.peek(); | ||
334 | return supernode.moveToNext(cursor); | ||
335 | } else { | ||
336 | cursor.key = null; | ||
337 | cursor.value = null; | ||
338 | return false; | ||
339 | } | ||
340 | } | ||
341 | |||
342 | @Override | ||
343 | @SuppressWarnings("unchecked") | ||
344 | boolean moveToNextInorder(InOrderMapCursor<K,V> cursor) { | ||
345 | if(cursor.nodeIndexStack.peek()==null || cursor.nodeStack.peek()==null) { | ||
346 | throw new IllegalStateException("Cursor moved to the next state when the state is empty."); | ||
347 | } | ||
348 | |||
349 | int position = cursor.nodeIndexStack.peek(); | ||
350 | |||
351 | for (int index = position + 1; index < FACTOR; index++) { | ||
352 | // data found | ||
353 | if (this.content[index * 2] != null) { | ||
354 | cursor.nodeIndexStack.pop(); | ||
355 | cursor.nodeIndexStack.push(index); | ||
356 | |||
357 | cursor.key = (K) this.content[index * 2]; | ||
358 | cursor.value = (V) this.content[index * 2 + 1]; | ||
359 | return true; | ||
360 | } else if (this.content[index * 2 +1] != null) { | ||
361 | // sub-node found | ||
362 | Node<K,V> subnode = (Node<K, V>) this.content[index * 2 +1]; | ||
363 | cursor.nodeIndexStack.pop(); | ||
364 | cursor.nodeIndexStack.push(index); | ||
365 | cursor.nodeIndexStack.push(InOrderMapCursor.INDEX_START); | ||
366 | cursor.nodeStack.push(subnode); | ||
367 | |||
368 | return subnode.moveToNextInorder(cursor); | ||
369 | } | ||
370 | } | ||
371 | |||
372 | // nothing found | ||
373 | cursor.nodeStack.pop(); | ||
374 | cursor.nodeIndexStack.pop(); | ||
375 | if (!cursor.nodeStack.isEmpty()) { | ||
376 | Node<K, V> supernode = cursor.nodeStack.peek(); | ||
377 | return supernode.moveToNextInorder(cursor); | ||
378 | } else { | ||
379 | cursor.key = null; | ||
380 | cursor.value = null; | ||
381 | return false; | ||
382 | } | ||
383 | } | ||
384 | |||
385 | @Override | ||
386 | public void prettyPrint(StringBuilder builder, int depth, int code) { | ||
387 | builder.append("\t".repeat(Math.max(0, depth))); | ||
388 | if (code >= 0) { | ||
389 | builder.append(code); | ||
390 | builder.append(":"); | ||
391 | } | ||
392 | builder.append("Mutable("); | ||
393 | // print content | ||
394 | boolean hadContent = false; | ||
395 | for (int i = 0; i < FACTOR; i++) { | ||
396 | if (content[2 * i] != null) { | ||
397 | if (hadContent) { | ||
398 | builder.append(","); | ||
399 | } | ||
400 | builder.append(i); | ||
401 | builder.append(":["); | ||
402 | builder.append(content[2 * i].toString()); | ||
403 | builder.append("]->["); | ||
404 | builder.append(content[2 * i + 1].toString()); | ||
405 | builder.append("]"); | ||
406 | hadContent = true; | ||
407 | } | ||
408 | } | ||
409 | builder.append(")"); | ||
410 | // print sub-nodes | ||
411 | for (int i = 0; i < FACTOR; i++) { | ||
412 | if (content[2 * i] == null && content[2 * i + 1] != null) { | ||
413 | @SuppressWarnings("unchecked") Node<K, V> subNode = (Node<K, V>) content[2 * i + 1]; | ||
414 | builder.append("\n"); | ||
415 | subNode.prettyPrint(builder, incrementDepth(depth), i); | ||
416 | } | ||
417 | } | ||
418 | } | ||
419 | |||
420 | @Override | ||
421 | public void checkIntegrity(ContinuousHashProvider<? super K> hashProvider, V defaultValue, int depth) { | ||
422 | // check for orphan nodes | ||
423 | if (depth > 0) { | ||
424 | int orphaned = isOrphaned(); | ||
425 | if (orphaned >= 0) { | ||
426 | throw new IllegalStateException("Orphaned node! " + orphaned + ": " + content[2 * orphaned]); | ||
427 | } | ||
428 | } | ||
429 | // check the place of data | ||
430 | for (int i = 0; i < FACTOR; i++) { | ||
431 | if (this.content[2 * i] != null) { | ||
432 | @SuppressWarnings("unchecked") K key = (K) this.content[2 * i]; | ||
433 | @SuppressWarnings("unchecked") V value = (V) this.content[2 * i + 1]; | ||
434 | |||
435 | if (value == defaultValue) { | ||
436 | throw new IllegalStateException("Node contains default value!"); | ||
437 | } | ||
438 | int hashCode = hashProvider.getHash(key, hashDepth(depth)); | ||
439 | int shiftDepth = shiftDepth(depth); | ||
440 | int selectedHashFragment = hashFragment(hashCode, shiftDepth); | ||
441 | if (i != selectedHashFragment) { | ||
442 | throw new IllegalStateException("Key " + key + " with hash code " + hashCode + " is in bad place! Fragment=" + selectedHashFragment + ", Place=" + i); | ||
443 | } | ||
444 | } | ||
445 | } | ||
446 | // check sub-nodes | ||
447 | for (int i = 0; i < FACTOR; i++) { | ||
448 | if (this.content[2 * i + 1] != null && this.content[2 * i] == null) { | ||
449 | @SuppressWarnings("unchecked") var subNode = (Node<K, V>) this.content[2 * i + 1]; | ||
450 | subNode.checkIntegrity(hashProvider, defaultValue, incrementDepth(depth)); | ||
451 | } | ||
452 | } | ||
453 | // check the hash | ||
454 | if (cachedHashValid) { | ||
455 | int oldHash = this.cachedHash; | ||
456 | invalidateHash(); | ||
457 | int newHash = hashCode(); | ||
458 | if (oldHash != newHash) { | ||
459 | throw new IllegalStateException("Hash code was not up to date! (old=" + oldHash + ",new=" + newHash + ")"); | ||
460 | } | ||
461 | } | ||
462 | } | ||
463 | |||
464 | protected void invalidateHash() { | ||
465 | this.cachedHashValid = false; | ||
466 | } | ||
467 | |||
468 | @Override | ||
469 | public int hashCode() { | ||
470 | if (!this.cachedHashValid) { | ||
471 | this.cachedHash = Arrays.hashCode(content); | ||
472 | this.cachedHashValid = true; | ||
473 | } | ||
474 | return this.cachedHash; | ||
475 | } | ||
476 | |||
477 | @Override | ||
478 | public boolean equals(Object obj) { | ||
479 | if (this == obj) return true; | ||
480 | if (obj == null) return false; | ||
481 | if (obj instanceof MutableNode<?, ?> mutableObj) { | ||
482 | if (obj.hashCode() != this.hashCode()) { | ||
483 | return false; | ||
484 | } else { | ||
485 | for (int i = 0; i < FACTOR * 2; i++) { | ||
486 | Object thisContent = this.content[i]; | ||
487 | if (thisContent != null && !thisContent.equals(mutableObj.content[i])) { | ||
488 | return false; | ||
489 | } | ||
490 | } | ||
491 | return true; | ||
492 | } | ||
493 | } else if (obj instanceof ImmutableNode<?, ?> immutableObj) { | ||
494 | return ImmutableNode.compareImmutableMutable(immutableObj, this); | ||
495 | } else { | ||
496 | return false; | ||
497 | } | ||
498 | } | ||
499 | } | ||