aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MutableNode.java
diff options
context:
space:
mode:
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.java499
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 */
6package tools.refinery.store.map.internal.state;
7
8import tools.refinery.store.map.ContinuousHashProvider;
9
10import java.util.Arrays;
11import java.util.Map;
12
13public 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}