aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store/src/main/java/tools
diff options
context:
space:
mode:
authorLibravatar OszkarSemerath <semerath@mit.bme.hu>2023-07-18 15:38:59 +0200
committerLibravatar OszkarSemerath <semerath@mit.bme.hu>2023-07-18 15:38:59 +0200
commit89e142514ae45d793c7dbe38f728b33451261338 (patch)
tree17cebb157c5b3e65871384a0d71eb7f2dd54625b /subprojects/store/src/main/java/tools
parentInitialization bugs with empty DeltaDiffCursor fixed (diff)
downloadrefinery-89e142514ae45d793c7dbe38f728b33451261338.tar.gz
refinery-89e142514ae45d793c7dbe38f728b33451261338.tar.zst
refinery-89e142514ae45d793c7dbe38f728b33451261338.zip
Fixing long-standing bug with state based diff cursor.
By implementing an InOrderMapCursor cursor, and a MapDiffCursor that synchronize two cursors.
Diffstat (limited to 'subprojects/store/src/main/java/tools')
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java17
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java74
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/InOrderMapCursor.java141
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java59
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java316
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java111
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java2
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java20
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java15
9 files changed, 475 insertions, 280 deletions
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java
index 113874e7..beeed110 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java
@@ -1,9 +1,6 @@
1package tools.refinery.store.map; 1package tools.refinery.store.map;
2 2
3import tools.refinery.store.map.internal.ImmutableNode; 3import tools.refinery.store.map.internal.*;
4import tools.refinery.store.map.internal.MapDiffCursor;
5import tools.refinery.store.map.internal.Node;
6import tools.refinery.store.map.internal.VersionedMapImpl;
7 4
8import java.util.*; 5import java.util.*;
9 6
@@ -93,7 +90,7 @@ public class VersionedMapStoreImpl<K, V> implements VersionedMapStore<K, V> {
93 } else { 90 } else {
94 ArrayList<Long> existingKeys = new ArrayList<>(states.keySet()); 91 ArrayList<Long> existingKeys = new ArrayList<>(states.keySet());
95 Collections.sort(existingKeys); 92 Collections.sort(existingKeys);
96 throw new IllegalArgumentException("Store does not contain state " + state + "! Avaliable states: " 93 throw new IllegalArgumentException("Store does not contain state " + state + "! Available states: "
97 + Arrays.toString(existingKeys.toArray())); 94 + Arrays.toString(existingKeys.toArray()));
98 } 95 }
99 } 96 }
@@ -118,10 +115,10 @@ public class VersionedMapStoreImpl<K, V> implements VersionedMapStore<K, V> {
118 115
119 @Override 116 @Override
120 public DiffCursor<K, V> getDiffCursor(long fromState, long toState) { 117 public DiffCursor<K, V> getDiffCursor(long fromState, long toState) {
121 VersionedMap<K, V> map1 = createMap(fromState); 118 VersionedMapImpl<K, V> map1 = (VersionedMapImpl<K, V>) createMap(fromState);
122 VersionedMap<K, V> map2 = createMap(toState); 119 VersionedMapImpl<K, V> map2 = (VersionedMapImpl<K, V>) createMap(toState);
123 Cursor<K, V> cursor1 = map1.getAll(); 120 InOrderMapCursor<K, V> cursor1 = new InOrderMapCursor<>(map1);
124 Cursor<K, V> cursor2 = map2.getAll(); 121 InOrderMapCursor<K, V> cursor2 = new InOrderMapCursor<>(map2);
125 return new MapDiffCursor<>(this.hashProvider, this.defaultValue, cursor1, cursor2); 122 return new MapDiffCursor<>(this.defaultValue, cursor1, cursor2);
126 } 123 }
127} 124}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java
index 914bab08..92446711 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java
@@ -15,7 +15,7 @@ public class ImmutableNode<K, V> extends Node<K, V> {
15 */ 15 */
16 final int nodeMap; 16 final int nodeMap;
17 /** 17 /**
18 * Stores Keys, Values, and subnodes. Structure: (K,V)*,NODE; NODES are stored 18 * Stores Keys, Values, and sub-nodes. Structure: (K,V)*,NODE; NODES are stored
19 * backwards. 19 * backwards.
20 */ 20 */
21 final Object[] content; 21 final Object[] content;
@@ -65,24 +65,24 @@ public class ImmutableNode<K, V> extends Node<K, V> {
65 int resultDataMap = 0; 65 int resultDataMap = 0;
66 int resultNodeMap = 0; 66 int resultNodeMap = 0;
67 final Object[] resultContent = new Object[size]; 67 final Object[] resultContent = new Object[size];
68 int bitposition = 1; 68 int bitPosition = 1;
69 for (int i = 0; i < FACTOR; i++) { 69 for (int i = 0; i < FACTOR; i++) {
70 Object key = node.content[i * 2]; 70 Object key = node.content[i * 2];
71 if (key != null) { 71 if (key != null) {
72 resultDataMap |= bitposition; 72 resultDataMap |= bitPosition;
73 resultContent[datas * 2] = key; 73 resultContent[datas * 2] = key;
74 resultContent[datas * 2 + 1] = node.content[i * 2 + 1]; 74 resultContent[datas * 2 + 1] = node.content[i * 2 + 1];
75 datas++; 75 datas++;
76 } else { 76 } else {
77 @SuppressWarnings("unchecked") var subnode = (Node<K, V>) node.content[i * 2 + 1]; 77 @SuppressWarnings("unchecked") var subnode = (Node<K, V>) node.content[i * 2 + 1];
78 if (subnode != null) { 78 if (subnode != null) {
79 ImmutableNode<K, V> immutableSubnode = subnode.toImmutable(cache); 79 ImmutableNode<K, V> immutableSubNode = subnode.toImmutable(cache);
80 resultNodeMap |= bitposition; 80 resultNodeMap |= bitPosition;
81 resultContent[size - 1 - nodes] = immutableSubnode; 81 resultContent[size - 1 - nodes] = immutableSubNode;
82 nodes++; 82 nodes++;
83 } 83 }
84 } 84 }
85 bitposition <<= 1; 85 bitPosition <<= 1;
86 } 86 }
87 final int resultHash = node.hashCode(); 87 final int resultHash = node.hashCode();
88 var newImmutable = new ImmutableNode<K, V>(resultDataMap, resultNodeMap, resultContent, resultHash); 88 var newImmutable = new ImmutableNode<K, V>(resultDataMap, resultNodeMap, resultContent, resultHash);
@@ -130,9 +130,9 @@ public class ImmutableNode<K, V> extends Node<K, V> {
130 @Override 130 @Override
131 public Node<K, V> putValue(K key, V value, OldValueBox<V> oldValue, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) { 131 public Node<K, V> putValue(K key, V value, OldValueBox<V> oldValue, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) {
132 int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); 132 int selectedHashFragment = hashFragment(hash, shiftDepth(depth));
133 int bitposition = 1 << selectedHashFragment; 133 int bitPosition = 1 << selectedHashFragment;
134 if ((dataMap & bitposition) != 0) { 134 if ((dataMap & bitPosition) != 0) {
135 int keyIndex = 2 * index(dataMap, bitposition); 135 int keyIndex = 2 * index(dataMap, bitPosition);
136 @SuppressWarnings("unchecked") K keyCandidate = (K) content[keyIndex]; 136 @SuppressWarnings("unchecked") K keyCandidate = (K) content[keyIndex];
137 if (keyCandidate.equals(key)) { 137 if (keyCandidate.equals(key)) {
138 if (value == defaultValue) { 138 if (value == defaultValue) {
@@ -159,8 +159,8 @@ public class ImmutableNode<K, V> extends Node<K, V> {
159 return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth); 159 return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth);
160 } 160 }
161 } 161 }
162 } else if ((nodeMap & bitposition) != 0) { 162 } else if ((nodeMap & bitPosition) != 0) {
163 int keyIndex = content.length - 1 - index(nodeMap, bitposition); 163 int keyIndex = content.length - 1 - index(nodeMap, bitPosition);
164 @SuppressWarnings("unchecked") var subNode = (ImmutableNode<K, V>) content[keyIndex]; 164 @SuppressWarnings("unchecked") var subNode = (ImmutableNode<K, V>) content[keyIndex];
165 int newDepth = incrementDepth(depth); 165 int newDepth = incrementDepth(depth);
166 int newHash = newHash(hashProvider, key, hash, newDepth); 166 int newHash = newHash(hashProvider, key, hash, newDepth);
@@ -254,6 +254,49 @@ public class ImmutableNode<K, V> extends Node<K, V> {
254 } 254 }
255 255
256 @Override 256 @Override
257 @SuppressWarnings("unchecked")
258 boolean moveToNextInorder(InOrderMapCursor<K, V> cursor) {
259 if(cursor.nodeIndexStack.peek()==null) {
260 throw new IllegalStateException("Cursor moved to the next state when the state is empty.");
261 }
262
263 int position = cursor.nodeIndexStack.peek();
264 for (int index = position + 1; index < FACTOR; index++) {
265 final int mask = 1<<index;
266 if((this.dataMap & mask) != 0) {
267 // data found
268 cursor.nodeIndexStack.pop();
269 cursor.nodeIndexStack.push(index);
270
271 cursor.key = (K) this.content[2 * index(dataMap, mask)];
272 cursor.value = (V) this.content[2 * index(dataMap, mask) +1];
273 return true;
274 } else if((this.nodeMap & mask) != 0) {
275 // node found
276 Node<K,V> subnode = (Node<K, V>) this.content[this.content.length - 1 - index(nodeMap, mask)];
277 cursor.nodeIndexStack.pop();
278 cursor.nodeIndexStack.push(index);
279 cursor.nodeIndexStack.push(InOrderMapCursor.INDEX_START);
280 cursor.nodeStack.push(subnode);
281
282 return subnode.moveToNextInorder(cursor);
283 }
284 }
285
286 // nothing found
287 cursor.nodeStack.pop();
288 cursor.nodeIndexStack.pop();
289 if (!cursor.nodeStack.isEmpty()) {
290 Node<K, V> supernode = cursor.nodeStack.peek();
291 return supernode.moveToNextInorder(cursor);
292 } else {
293 cursor.key = null;
294 cursor.value = null;
295 return false;
296 }
297 }
298
299 @Override
257 public void prettyPrint(StringBuilder builder, int depth, int code) { 300 public void prettyPrint(StringBuilder builder, int depth, int code) {
258 builder.append("\t".repeat(Math.max(0, depth))); 301 builder.append("\t".repeat(Math.max(0, depth)));
259 if (code >= 0) { 302 if (code >= 0) {
@@ -348,8 +391,8 @@ public class ImmutableNode<K, V> extends Node<K, V> {
348 var mutableSubnode = (Node<?, ?>) mutable.content[i * 2 + 1]; 391 var mutableSubnode = (Node<?, ?>) mutable.content[i * 2 + 1];
349 if (mutableSubnode != null) { 392 if (mutableSubnode != null) {
350 if (datas * 2 + nodes + 1 <= immutableLength) { 393 if (datas * 2 + nodes + 1 <= immutableLength) {
351 Object immutableSubnode = immutable.content[immutableLength - 1 - nodes]; 394 Object immutableSubNode = immutable.content[immutableLength - 1 - nodes];
352 if (!mutableSubnode.equals(immutableSubnode)) { 395 if (!mutableSubnode.equals(immutableSubNode)) {
353 return false; 396 return false;
354 } 397 }
355 nodes++; 398 nodes++;
@@ -359,6 +402,7 @@ public class ImmutableNode<K, V> extends Node<K, V> {
359 } 402 }
360 } 403 }
361 } 404 }
362 return true; 405
406 return datas * 2 + nodes == immutable.content.length;
363 } 407 }
364} 408}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/InOrderMapCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/InOrderMapCursor.java
new file mode 100644
index 00000000..c559d9ad
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/InOrderMapCursor.java
@@ -0,0 +1,141 @@
1package tools.refinery.store.map.internal;
2
3import tools.refinery.store.map.AnyVersionedMap;
4import tools.refinery.store.map.ContentHashCode;
5import tools.refinery.store.map.Cursor;
6import tools.refinery.store.map.VersionedMap;
7
8import java.util.*;
9
10public class InOrderMapCursor<K, V> implements Cursor<K, V> {
11 // Constants
12 static final int INDEX_START = -1;
13
14 // Tree stack
15 ArrayDeque<Node<K, V>> nodeStack;
16 ArrayDeque<Integer> nodeIndexStack;
17
18
19 // Values
20 K key;
21 V value;
22
23 // Hash code for checking concurrent modifications
24 final VersionedMap<K, V> map;
25 final int creationHash;
26
27 public InOrderMapCursor(VersionedMapImpl<K, V> map) {
28 // Initializing tree stack
29 super();
30 this.nodeStack = new ArrayDeque<>();
31 this.nodeIndexStack = new ArrayDeque<>();
32 if (map.root != null) {
33 this.nodeStack.add(map.root);
34 this.nodeIndexStack.push(INDEX_START);
35 }
36
37 // Initializing cache
38 this.key = null;
39 this.value = null;
40
41 // Initializing state
42 this.map = map;
43 this.creationHash = map.contentHashCode(ContentHashCode.APPROXIMATE_FAST);
44 }
45
46 public K getKey() {
47 return key;
48 }
49
50 public V getValue() {
51 return value;
52 }
53
54 public boolean isTerminated() {
55 return this.nodeStack.isEmpty();
56 }
57
58 public boolean move() {
59 if (isDirty()) {
60 throw new ConcurrentModificationException();
61 }
62 if (!isTerminated()) {
63 var node = this.nodeStack.peek();
64 if (node == null) {
65 throw new IllegalStateException("Cursor is not terminated but the current node is missing");
66 }
67 boolean result = node.moveToNextInorder(this);
68 if (this.nodeIndexStack.size() != this.nodeStack.size()) {
69 throw new IllegalArgumentException("Node stack is corrupted by illegal moves!");
70 }
71 return result;
72 }
73 return false;
74 }
75
76 public boolean skipCurrentNode() {
77 nodeStack.pop();
78 nodeIndexStack.pop();
79 return move();
80 }
81
82 @Override
83 public boolean isDirty() {
84 return this.map.contentHashCode(ContentHashCode.APPROXIMATE_FAST) != this.creationHash;
85 }
86
87 @Override
88 public Set<AnyVersionedMap> getDependingMaps() {
89 return Set.of(this.map);
90 }
91
92 public static <K, V> boolean sameSubNode(InOrderMapCursor<K, V> cursor1, InOrderMapCursor<K, V> cursor2) {
93 Node<K, V> nodeOfCursor1 = cursor1.nodeStack.peek();
94 Node<K, V> nodeOfCursor2 = cursor2.nodeStack.peek();
95 return Objects.equals(nodeOfCursor1, nodeOfCursor2);
96 }
97
98 /**
99 * Compares the state of two cursors started on two {@link VersionedMap} of the same
100 * {@link tools.refinery.store.map.VersionedMapStore}.
101 * @param <K> Key type
102 * @param <V> Value type
103 * @param cursor1 first cursor
104 * @param cursor2 second cursor
105 * @return Positive number if cursor 1 is behind, negative number if cursor 2 is behind, and 0 if they are at the
106 * same position.
107 */
108 public static <K, V> int comparePosition(InOrderMapCursor<K, V> cursor1, InOrderMapCursor<K, V> cursor2) {
109 // If the state does not determine the order, then compare @nodeIndexStack.
110 Iterator<Integer> nodeIndexStack1 = cursor1.nodeIndexStack.descendingIterator();
111 Iterator<Integer> nodeIndexStack2 = cursor2.nodeIndexStack.descendingIterator();
112
113 while(nodeIndexStack1.hasNext() && nodeIndexStack2.hasNext()){
114 final int index1 = nodeIndexStack1.next();
115 final int index2 = nodeIndexStack2.next();
116 if(index1 < index2) {
117 return 1;
118 } else if(index1 > index2) {
119 return -1;
120 }
121 }
122
123 return 0;
124 }
125
126 /**
127 * Compares the depth of two cursors started on @{@link VersionedMap} of the same
128 * {@link tools.refinery.store.map.VersionedMapStore}.
129 * @param <K> Key type
130 * @param <V> Value type
131 * @param cursor1 first cursor
132 * @param cursor2 second cursor
133 * @return Positive number if cursor 1 is deeper, negative number if cursor 2 is deeper, and 0 if they are at the
134 * same depth.
135 */
136 public static <K, V> int compareDepth(InOrderMapCursor<K, V> cursor1, InOrderMapCursor<K, V> cursor2) {
137 int d1 = cursor1.nodeIndexStack.size();
138 int d2 = cursor2.nodeIndexStack.size();
139 return Integer.compare(d1, d2);
140 }
141}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java
index 50fcfcd3..7e4f82e8 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java
@@ -7,7 +7,6 @@ import tools.refinery.store.map.VersionedMap;
7 7
8import java.util.ArrayDeque; 8import java.util.ArrayDeque;
9import java.util.ConcurrentModificationException; 9import java.util.ConcurrentModificationException;
10import java.util.Iterator;
11import java.util.Set; 10import java.util.Set;
12 11
13public class MapCursor<K, V> implements Cursor<K, V> { 12public class MapCursor<K, V> implements Cursor<K, V> {
@@ -79,13 +78,6 @@ public class MapCursor<K, V> implements Cursor<K, V> {
79 return false; 78 return false;
80 } 79 }
81 80
82 public boolean skipCurrentNode() {
83 nodeStack.pop();
84 nodeIndexStack.pop();
85 dataIndex = INDEX_FINISH;
86 return move();
87 }
88
89 @Override 81 @Override
90 public boolean isDirty() { 82 public boolean isDirty() {
91 return this.map.contentHashCode(ContentHashCode.APPROXIMATE_FAST) != this.creationHash; 83 return this.map.contentHashCode(ContentHashCode.APPROXIMATE_FAST) != this.creationHash;
@@ -95,55 +87,4 @@ public class MapCursor<K, V> implements Cursor<K, V> {
95 public Set<AnyVersionedMap> getDependingMaps() { 87 public Set<AnyVersionedMap> getDependingMaps() {
96 return Set.of(this.map); 88 return Set.of(this.map);
97 } 89 }
98
99 public static <K, V> boolean sameSubNode(MapCursor<K, V> cursor1, MapCursor<K, V> cursor2) {
100 Node<K, V> nodeOfCursor1 = cursor1.nodeStack.peek();
101 Node<K, V> nodeOfCursor2 = cursor2.nodeStack.peek();
102 if (nodeOfCursor1 != null && nodeOfCursor2 != null) {
103 return nodeOfCursor1.equals(nodeOfCursor2);
104 } else {
105 return false;
106 }
107 }
108
109 /**
110 * Compares the state of two cursors started on two @{@link VersionedMap of the }same
111 * {@link tools.refinery.store.map.VersionedMapStore}.
112 * @param <K> Key type
113 * @param <V> Value type
114 * @param cursor1 first cursor
115 * @param cursor2 second cursor
116 * @return Positive number if cursor 1 is behind, negative number if cursor 2 is behind, and 0 if they are at the
117 * same position.
118 */
119 public static <K, V> int compare(MapCursor<K, V> cursor1, MapCursor<K, V> cursor2) {
120 // Checking the state of the cursors
121 if(!cursor1.isTerminated() && cursor2.isTerminated()) return -1;
122 else if(cursor1.isTerminated() && !cursor2.isTerminated()) return 1;
123 else if(cursor1.isTerminated() && cursor2.isTerminated()) return 0;
124
125 // If the state does not determine the order, then compare @nodeIndexStack.
126 Iterator<Integer> stack1 = cursor1.nodeIndexStack.descendingIterator();
127 Iterator<Integer> stack2 = cursor2.nodeIndexStack.descendingIterator();
128 if (stack1.hasNext()) {
129 if (!stack2.hasNext()) {
130 // stack 2 has no more element, thus stack 1 is deeper
131 return 1;
132 }
133 int val1 = stack1.next();
134 int val2 = stack2.next();
135 if (val1 < val2) {
136 return -1;
137 } else if (val2 < val1) {
138 return 1;
139 }
140 }
141 if (stack2.hasNext()) {
142 // stack 2 has more element, thus stack 2 is deeper
143 return 1;
144 }
145
146 // two cursors are equally deep decide by data index
147 return Integer.compare(cursor1.dataIndex, cursor2.dataIndex);
148 }
149} 90}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java
index 6c076ce5..59e8d738 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java
@@ -1,7 +1,6 @@
1package tools.refinery.store.map.internal; 1package tools.refinery.store.map.internal;
2 2
3import tools.refinery.store.map.AnyVersionedMap; 3import tools.refinery.store.map.AnyVersionedMap;
4import tools.refinery.store.map.ContinousHashProvider;
5import tools.refinery.store.map.Cursor; 4import tools.refinery.store.map.Cursor;
6import tools.refinery.store.map.DiffCursor; 5import tools.refinery.store.map.DiffCursor;
7 6
@@ -14,37 +13,78 @@ import java.util.stream.Stream;
14 * A cursor representing the difference between two states of a map. 13 * A cursor representing the difference between two states of a map.
15 * 14 *
16 * @author Oszkar Semerath 15 * @author Oszkar Semerath
17 *
18 */ 16 */
19public class MapDiffCursor<K, V> implements DiffCursor<K, V>, Cursor<K, V> { 17public class MapDiffCursor<K, V> implements DiffCursor<K, V>, Cursor<K, V> {
18 private enum State {
19 /**
20 * initialized state.
21 */
22 INIT,
23 /**
24 * Unstable state.
25 */
26 MOVING_MOVING_SAME_KEY_SAME_VALUE,
27 /**
28 * Both cursors are moving, and they are on the same sub-node.
29 */
30 MOVING_MOVING_SAME_NODE,
31 /**
32 * Both cursors are moving, cursor 1 is behind.
33 */
34 MOVING_MOVING_BEHIND1,
35 /**
36 * Both cursors are moving, cursor 2 is behind.
37 */
38 MOVING_MOVING_BEHIND2,
39 /**
40 * Both cursors are moving, cursor 1 is on the same key as cursor 2, values are different
41 */
42 MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE,
43 /**
44 * Cursor 1 is moving, Cursor 2 is terminated.
45 */
46 MOVING_TERMINATED,
47 /**
48 * Cursor 1 is terminated , Cursor 2 is moving.
49 */
50 TERMINATED_MOVING,
51 /**
52 * Both cursors are terminated.
53 */
54 TERMINATED_TERMINATED,
55 /**
56 * Both Cursors are moving, and they are on an incomparable position.
57 * It is resolved by showing Cursor 1.
58 */
59 MOVING_MOVING_HASH1,
60 /**
61 * Both Cursors are moving, and they are on an incomparable position.
62 * It is resolved by showing Cursor 2.
63 */
64 MOVING_MOVING_HASH2
65 }
66
20 /** 67 /**
21 * Default nodeId representing missing elements. 68 * Default nodeId representing missing elements.
22 */ 69 */
23 private final V defaultValue; 70 private final V defaultValue;
24 private final MapCursor<K, V> cursor1; 71 private final InOrderMapCursor<K, V> cursor1;
25 private final MapCursor<K, V> cursor2; 72 private final InOrderMapCursor<K, V> cursor2;
26 private final ContinousHashProvider<? super K> hashProvider; 73
74 // State
75 State state = State.INIT;
27 76
28 // Values 77 // Values
29 private K key; 78 private K key;
30 private V fromValue; 79 private V fromValue;
31 private V toValue; 80 private V toValue;
32 81
33 // State
34 /**
35 * Positive number if cursor 1 is behind, negative number if cursor 2 is behind,
36 * and 0 if they are at the same position.
37 */
38 private int cursorRelation;
39 private HashClash hashClash = HashClash.NONE;
40 82
41 public MapDiffCursor(ContinousHashProvider<? super K> hashProvider, V defaultValue, Cursor<K, V> cursor1, 83 public MapDiffCursor(V defaultValue, InOrderMapCursor<K, V> cursor1, InOrderMapCursor<K, V> cursor2) {
42 Cursor<K, V> cursor2) {
43 super(); 84 super();
44 this.hashProvider = hashProvider;
45 this.defaultValue = defaultValue; 85 this.defaultValue = defaultValue;
46 this.cursor1 = (MapCursor<K, V>) cursor1; 86 this.cursor1 = cursor1;
47 this.cursor2 = (MapCursor<K, V>) cursor2; 87 this.cursor2 = cursor2;
48 } 88 }
49 89
50 @Override 90 @Override
@@ -68,7 +108,7 @@ public class MapDiffCursor<K, V> implements DiffCursor<K, V>, Cursor<K, V> {
68 } 108 }
69 109
70 public boolean isTerminated() { 110 public boolean isTerminated() {
71 return cursor1.isTerminated() && cursor2.isTerminated(); 111 return this.state == State.TERMINATED_TERMINATED;
72 } 112 }
73 113
74 @Override 114 @Override
@@ -78,148 +118,142 @@ public class MapDiffCursor<K, V> implements DiffCursor<K, V>, Cursor<K, V> {
78 118
79 @Override 119 @Override
80 public Set<AnyVersionedMap> getDependingMaps() { 120 public Set<AnyVersionedMap> getDependingMaps() {
81 return Stream.concat(cursor1.getDependingMaps().stream(), cursor2.getDependingMaps().stream()) 121 return Stream.concat(cursor1.getDependingMaps().stream(), cursor2.getDependingMaps().stream()).map(AnyVersionedMap.class::cast).collect(Collectors.toUnmodifiableSet());
82 .map(AnyVersionedMap.class::cast)
83 .collect(Collectors.toUnmodifiableSet());
84 }
85
86 protected void updateState() {
87 if (!isTerminated()) {
88 this.cursorRelation = MapCursor.compare(cursor1, cursor2);
89 if (cursorRelation > 0 || cursor2.isTerminated()) {
90 this.key = cursor1.getKey();
91 this.fromValue = cursor1.getValue();
92 this.toValue = defaultValue;
93 } else if (cursorRelation < 0 || cursor1.isTerminated()) {
94 this.key = cursor2.getKey();
95 this.fromValue = defaultValue;
96 this.toValue = cursor1.getValue();
97 } else {
98 // cursor1 = cursor2
99 if (cursor1.getKey().equals(cursor2.getKey())) {
100 this.key = cursor1.getKey();
101 this.fromValue = cursor1.getValue();
102 this.toValue = defaultValue;
103 } else {
104 resolveHashClashWithFirstEntry();
105 }
106 }
107 }
108 } 122 }
109 123
110 protected void resolveHashClashWithFirstEntry() { 124 private boolean isInStableState() {
111 int compareResult = this.hashProvider.compare(cursor1.key, cursor2.key); 125 return this.state != State.MOVING_MOVING_SAME_KEY_SAME_VALUE
112 if (compareResult < 0) { 126 && this.state != State.MOVING_MOVING_SAME_NODE && this.state != State.INIT;
113 this.hashClash = HashClash.STUCK_CURSOR_2;
114 this.cursorRelation = 0;
115 this.key = cursor1.key;
116 this.fromValue = cursor1.value;
117 this.toValue = defaultValue;
118 } else if (compareResult > 0) {
119 this.hashClash = HashClash.STUCK_CURSOR_1;
120 this.cursorRelation = 0;
121 this.key = cursor2.key;
122 this.fromValue = defaultValue;
123 this.toValue = cursor2.value;
124 } else {
125 throw new IllegalArgumentException("Inconsistent compare result for diffCursor");
126 }
127 } 127 }
128 128
129 protected boolean isInHashClash() { 129 private boolean updateAndReturnWithResult() {
130 return this.hashClash != HashClash.NONE; 130 return switch (this.state) {
131 case INIT -> throw new IllegalStateException("DiffCursor terminated without starting!");
132 case MOVING_MOVING_SAME_KEY_SAME_VALUE, MOVING_MOVING_SAME_NODE ->
133 throw new IllegalStateException("DiffCursor terminated in unstable state!");
134 case MOVING_MOVING_BEHIND1, MOVING_TERMINATED, MOVING_MOVING_HASH1 -> {
135 this.key = this.cursor1.getKey();
136 this.fromValue = this.cursor1.getValue();
137 this.toValue = this.defaultValue;
138 yield true;
139 }
140 case MOVING_MOVING_BEHIND2, TERMINATED_MOVING, MOVING_MOVING_HASH2 -> {
141 this.key = this.cursor2.getKey();
142 this.fromValue = this.defaultValue;
143 this.toValue = cursor2.getValue();
144 yield true;
145 }
146 case MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE -> {
147 this.key = this.cursor1.getKey();
148 this.fromValue = this.cursor1.getValue();
149 this.toValue = this.cursor2.getValue();
150 yield true;
151 }
152 case TERMINATED_TERMINATED -> {
153 this.key = null;
154 this.fromValue = null;
155 this.toValue = null;
156 yield false;
157 }
158 };
131 } 159 }
132 160
133 protected void resolveHashClashWithSecondEntry() { 161 public boolean move() {
134 switch (this.hashClash) { 162 do {
135 case STUCK_CURSOR_1 -> { 163 this.state = moveOne(this.state);
136 this.hashClash = HashClash.NONE; 164 } while (!isInStableState());
137 this.cursorRelation = 0; 165 return updateAndReturnWithResult();
138 this.key = cursor1.key;
139 this.fromValue = cursor1.value;
140 this.toValue = defaultValue;
141 }
142 case STUCK_CURSOR_2 -> {
143 this.hashClash = HashClash.NONE;
144 this.cursorRelation = 0;
145 this.key = cursor2.key;
146 this.fromValue = defaultValue;
147 this.toValue = cursor2.value;
148 }
149 default -> throw new IllegalArgumentException("Inconsistent compare result for diffCursor");
150 }
151 } 166 }
152 167
153 /** 168 private State moveOne(State currentState) {
154 * Checks if two states has the same values, i.e., there is no difference. 169 return switch (currentState) {
155 * @return whether two states has the same values 170 case INIT, MOVING_MOVING_HASH2, MOVING_MOVING_SAME_KEY_SAME_VALUE, MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE -> {
156 */ 171 boolean cursor1Moved = cursor1.move();
157 protected boolean sameValues() { 172 boolean cursor2Moved = cursor2.move();
158 if(cursor1.isTerminated() || cursor2.isTerminated()) return false; 173 yield recalculateStateAfterCursorMovement(cursor1Moved, cursor2Moved);
159 else return Objects.equals(this.fromValue, this.toValue); 174 }
175 case MOVING_MOVING_SAME_NODE -> {
176 boolean cursor1Moved = cursor1.skipCurrentNode();
177 boolean cursor2Moved = cursor2.skipCurrentNode();
178 yield recalculateStateAfterCursorMovement(cursor1Moved, cursor2Moved);
179 }
180 case MOVING_MOVING_BEHIND1 -> {
181 boolean cursorMoved = cursor1.move();
182 if (cursorMoved) {
183 yield recalculateStateBasedOnCursorRelation();
184 } else {
185 yield State.TERMINATED_MOVING;
186 }
187 }
188 case MOVING_MOVING_BEHIND2 -> {
189 boolean cursorMoved = cursor2.move();
190 if (cursorMoved) {
191 yield recalculateStateBasedOnCursorRelation();
192 } else {
193 yield State.MOVING_TERMINATED;
194 }
195 }
196 case TERMINATED_MOVING -> {
197 boolean cursorMoved = cursor2.move();
198 if (cursorMoved) {
199 yield State.TERMINATED_MOVING;
200 } else {
201 yield State.TERMINATED_TERMINATED;
202 }
203 }
204 case MOVING_TERMINATED -> {
205 boolean cursorMoved = cursor1.move();
206 if (cursorMoved) {
207 yield State.MOVING_TERMINATED;
208 } else {
209 yield State.TERMINATED_TERMINATED;
210 }
211 }
212 case MOVING_MOVING_HASH1 -> State.MOVING_MOVING_HASH2;
213 case TERMINATED_TERMINATED -> throw new IllegalStateException("Trying to move while terminated!");
214 };
160 } 215 }
161 216
162 protected boolean moveOne() { 217 private State recalculateStateAfterCursorMovement(boolean cursor1Moved, boolean cursor2Moved) {
163 if (isTerminated()) { 218 if (cursor1Moved && cursor2Moved) {
164 return false; 219 return recalculateStateBasedOnCursorRelation();
165 } 220 } else if (cursor1Moved) {
166 if (this.cursorRelation > 0 || cursor2.isTerminated()) { 221 return State.MOVING_TERMINATED;
167 return cursor1.move(); 222 } else if (cursor2Moved) {
168 } else if (this.cursorRelation < 0 || cursor1.isTerminated()) { 223 return State.TERMINATED_MOVING;
169 return cursor2.move();
170 } else { 224 } else {
171 boolean moved1 = cursor1.move(); 225 return State.TERMINATED_TERMINATED;
172 boolean moved2 = cursor2.move();
173 return moved1 && moved2;
174 } 226 }
175 } 227 }
176 228
177 private boolean skipNode() { 229 private State recalculateStateBasedOnCursorRelation() {
178 if (isTerminated()) { 230 final int relation = InOrderMapCursor.comparePosition(cursor1, cursor2);
179 throw new IllegalStateException("DiffCursor tries to skip when terminated!");
180 }
181 boolean update1 = cursor1.skipCurrentNode();
182 boolean update2 = cursor2.skipCurrentNode();
183 updateState();
184 return update1 && update2;
185 }
186 231
187 protected boolean moveToConsistentState() { 232 if (relation > 0) {
188 if (!isTerminated()) { 233 return State.MOVING_MOVING_BEHIND1;
189 boolean changed; 234 } else if (relation < 0) {
190 boolean lastResult = true; 235 return State.MOVING_MOVING_BEHIND2;
191 do {
192 changed = false;
193 if (MapCursor.sameSubNode(cursor1, cursor2)) {
194 lastResult = skipNode();
195 changed = true;
196 }
197 if (sameValues()) {
198 lastResult = moveOne();
199 changed = true;
200 }
201 updateState();
202 } while (changed && !isTerminated());
203 return lastResult;
204 } else {
205 return false;
206 } 236 }
207 }
208 237
209 public boolean move() { 238 if (InOrderMapCursor.sameSubNode(cursor1, cursor2)) {
210 if (!isTerminated()) { 239 return State.MOVING_MOVING_SAME_NODE;
211 if (isInHashClash()) { 240 } else if (Objects.equals(cursor1.getKey(), cursor2.getKey())) {
212 this.resolveHashClashWithSecondEntry(); 241 if (Objects.equals(cursor1.getValue(), cursor2.getValue())) {
213 return true; 242 return State.MOVING_MOVING_SAME_KEY_SAME_VALUE;
214 } else { 243 } else {
215 if (moveOne()) { 244 return State.MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE;
216 return moveToConsistentState();
217 } else {
218 return false;
219 }
220 } 245 }
246 }
247
248 final int depth = InOrderMapCursor.compareDepth(cursor1, cursor2);
249
250 if (depth > 0) {
251 return State.MOVING_MOVING_BEHIND1;
252 } else if (depth < 0) {
253 return State.MOVING_MOVING_BEHIND2;
254 } else {
255 return State.MOVING_MOVING_HASH1;
256 }
221 257
222 } else
223 return false;
224 } 258 }
225} 259}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java
index cdc66a10..81bf6188 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java
@@ -39,12 +39,12 @@ public class MutableNode<K, V> extends Node<K, V> {
39 int dataUsed = 0; 39 int dataUsed = 0;
40 int nodeUsed = 0; 40 int nodeUsed = 0;
41 for (int i = 0; i < FACTOR; i++) { 41 for (int i = 0; i < FACTOR; i++) {
42 int bitposition = 1 << i; 42 int bitPosition = 1 << i;
43 if ((node.dataMap & bitposition) != 0) { 43 if ((node.dataMap & bitPosition) != 0) {
44 content[2 * i] = node.content[dataUsed * 2]; 44 content[2 * i] = node.content[dataUsed * 2];
45 content[2 * i + 1] = node.content[dataUsed * 2 + 1]; 45 content[2 * i + 1] = node.content[dataUsed * 2 + 1];
46 dataUsed++; 46 dataUsed++;
47 } else if ((node.nodeMap & bitposition) != 0) { 47 } else if ((node.nodeMap & bitPosition) != 0) {
48 content[2 * i + 1] = node.content[node.content.length - 1 - nodeUsed]; 48 content[2 * i + 1] = node.content[node.content.length - 1 - nodeUsed];
49 nodeUsed++; 49 nodeUsed++;
50 } 50 }
@@ -80,7 +80,7 @@ public class MutableNode<K, V> extends Node<K, V> {
80 int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); 80 int selectedHashFragment = hashFragment(hash, shiftDepth(depth));
81 @SuppressWarnings("unchecked") K keyCandidate = (K) content[2 * selectedHashFragment]; 81 @SuppressWarnings("unchecked") K keyCandidate = (K) content[2 * selectedHashFragment];
82 if (keyCandidate != null) { 82 if (keyCandidate != null) {
83 // If has key 83 // If it has key
84 if (keyCandidate.equals(key)) { 84 if (keyCandidate.equals(key)) {
85 // The key is equals to an existing key -> update entry 85 // The key is equals to an existing key -> update entry
86 if (value == defaultValue) { 86 if (value == defaultValue) {
@@ -101,24 +101,22 @@ public class MutableNode<K, V> extends Node<K, V> {
101 return moveDownAndSplit(hashProvider, key, value, keyCandidate, hash, depth, selectedHashFragment); 101 return moveDownAndSplit(hashProvider, key, value, keyCandidate, hash, depth, selectedHashFragment);
102 } 102 }
103 } 103 }
104 }
105 // If it does not have key, check for value
106 @SuppressWarnings("unchecked") var nodeCandidate = (Node<K, V>) content[2 * selectedHashFragment + 1];
107 if (nodeCandidate != null) {
108 // If it has value, it is a sub-node -> update that
109 int newDepth = incrementDepth(depth);
110 var newNode = nodeCandidate.putValue(key, value, oldValueBox, hashProvider, defaultValue, newHash(hashProvider, key, hash, newDepth), newDepth);
111 return updateWithSubNode(selectedHashFragment, newNode, (value == null && defaultValue == null) || (value != null && value.equals(defaultValue)));
104 } else { 112 } else {
105 // If it does not have key, check for value 113 // If it does not have value, put it in the empty place
106 @SuppressWarnings("unchecked") var nodeCandidate = (Node<K, V>) content[2 * selectedHashFragment + 1]; 114 if (value == defaultValue) {
107 if (nodeCandidate != null) { 115 // don't need to add new key-value pair
108 // If it has value, it is a subnode -> upate that 116 oldValueBox.setOldValue(defaultValue);
109 int newDepth = incrementDepth(depth); 117 return this;
110 var newNode = nodeCandidate.putValue(key, value, oldValueBox, hashProvider, defaultValue, newHash(hashProvider, key, hash, newDepth), newDepth);
111 return updateWithSubNode(selectedHashFragment, newNode, (value == null && defaultValue == null) || (value != null && value.equals(defaultValue)));
112 } else { 118 } else {
113 // If it does not have value, put it in the empty place 119 return addEntry(key, value, oldValueBox, selectedHashFragment, defaultValue);
114 if (value == defaultValue) {
115 // dont need to add new key-value pair
116 oldValueBox.setOldValue(defaultValue);
117 return this;
118 } else {
119 return addEntry(key, value, oldValueBox, selectedHashFragment, defaultValue);
120 }
121
122 } 120 }
123 } 121 }
124 } 122 }
@@ -170,7 +168,7 @@ public class MutableNode<K, V> extends Node<K, V> {
170 if (immutableNewNode != null) { 168 if (immutableNewNode != null) {
171 int orphaned = immutableNewNode.isOrphaned(); 169 int orphaned = immutableNewNode.isOrphaned();
172 if (orphaned >= 0) { 170 if (orphaned >= 0) {
173 // orphan subnode data is replaced with data 171 // orphan sub-node data is replaced with data
174 content[2 * selectedHashFragment] = immutableNewNode.content[orphaned * 2]; 172 content[2 * selectedHashFragment] = immutableNewNode.content[orphaned * 2];
175 content[2 * selectedHashFragment + 1] = immutableNewNode.content[orphaned * 2 + 1]; 173 content[2 * selectedHashFragment + 1] = immutableNewNode.content[orphaned * 2 + 1];
176 invalidateHash(); 174 invalidateHash();
@@ -227,11 +225,11 @@ public class MutableNode<K, V> extends Node<K, V> {
227 225
228 // Pass everything as parameters for performance. 226 // Pass everything as parameters for performance.
229 @SuppressWarnings("squid:S107") 227 @SuppressWarnings("squid:S107")
230 private MutableNode<K, V> newNodeWithTwoEntries(ContinousHashProvider<? super K> hashProvider, K key1, V value1, int oldHash1, K key2, V value2, int oldHash2, int newdepth) { 228 private MutableNode<K, V> newNodeWithTwoEntries(ContinousHashProvider<? super K> hashProvider, K key1, V value1, int oldHash1, K key2, V value2, int oldHash2, int newDepth) {
231 int newHash1 = newHash(hashProvider, key1, oldHash1, newdepth); 229 int newHash1 = newHash(hashProvider, key1, oldHash1, newDepth);
232 int newHash2 = newHash(hashProvider, key2, oldHash2, newdepth); 230 int newHash2 = newHash(hashProvider, key2, oldHash2, newDepth);
233 int newFragment1 = hashFragment(newHash1, shiftDepth(newdepth)); 231 int newFragment1 = hashFragment(newHash1, shiftDepth(newDepth));
234 int newFragment2 = hashFragment(newHash2, shiftDepth(newdepth)); 232 int newFragment2 = hashFragment(newHash2, shiftDepth(newDepth));
235 233
236 MutableNode<K, V> subNode = new MutableNode<>(); 234 MutableNode<K, V> subNode = new MutableNode<>();
237 if (newFragment1 != newFragment2) { 235 if (newFragment1 != newFragment2) {
@@ -241,7 +239,7 @@ public class MutableNode<K, V> extends Node<K, V> {
241 subNode.content[newFragment2 * 2] = key2; 239 subNode.content[newFragment2 * 2] = key2;
242 subNode.content[newFragment2 * 2 + 1] = value2; 240 subNode.content[newFragment2 * 2 + 1] = value2;
243 } else { 241 } else {
244 MutableNode<K, V> subSubNode = newNodeWithTwoEntries(hashProvider, key1, value1, newHash1, key2, value2, newHash2, incrementDepth(newdepth)); 242 MutableNode<K, V> subSubNode = newNodeWithTwoEntries(hashProvider, key1, value1, newHash1, key2, value2, newHash2, incrementDepth(newDepth));
245 subNode.content[newFragment1 * 2 + 1] = subSubNode; 243 subNode.content[newFragment1 * 2 + 1] = subSubNode;
246 } 244 }
247 subNode.invalidateHash(); 245 subNode.invalidateHash();
@@ -305,13 +303,13 @@ public class MutableNode<K, V> extends Node<K, V> {
305 cursor.dataIndex = MapCursor.INDEX_FINISH; 303 cursor.dataIndex = MapCursor.INDEX_FINISH;
306 } 304 }
307 305
308 // 2. look inside the subnodes 306 // 2. look inside the sub-nodes
309 if(cursor.nodeIndexStack.peek()==null) { 307 if(cursor.nodeIndexStack.peek()==null) {
310 throw new IllegalStateException("Cursor moved to the next state when the state is empty."); 308 throw new IllegalStateException("Cursor moved to the next state when the state is empty.");
311 } 309 }
312 for (int index = cursor.nodeIndexStack.peek() + 1; index < FACTOR; index++) { 310 for (int index = cursor.nodeIndexStack.peek() + 1; index < FACTOR; index++) {
313 if (this.content[index * 2] == null && this.content[index * 2 + 1] != null) { 311 if (this.content[index * 2] == null && this.content[index * 2 + 1] != null) {
314 // 2.1 found next subnode, move down to the subnode 312 // 2.1 found next sub-node, move down to the sub-node
315 Node<K, V> subnode = (Node<K, V>) this.content[index * 2 + 1]; 313 Node<K, V> subnode = (Node<K, V>) this.content[index * 2 + 1];
316 314
317 cursor.dataIndex = MapCursor.INDEX_START; 315 cursor.dataIndex = MapCursor.INDEX_START;
@@ -323,7 +321,7 @@ public class MutableNode<K, V> extends Node<K, V> {
323 return subnode.moveToNext(cursor); 321 return subnode.moveToNext(cursor);
324 } 322 }
325 } 323 }
326 // 3. no subnode found, move up 324 // 3. no sub-node found, move up
327 cursor.nodeStack.pop(); 325 cursor.nodeStack.pop();
328 cursor.nodeIndexStack.pop(); 326 cursor.nodeIndexStack.pop();
329 if (!cursor.nodeStack.isEmpty()) { 327 if (!cursor.nodeStack.isEmpty()) {
@@ -337,6 +335,49 @@ public class MutableNode<K, V> extends Node<K, V> {
337 } 335 }
338 336
339 @Override 337 @Override
338 @SuppressWarnings("unchecked")
339 boolean moveToNextInorder(InOrderMapCursor<K,V> cursor) {
340 if(cursor.nodeIndexStack.peek()==null || cursor.nodeStack.peek()==null) {
341 throw new IllegalStateException("Cursor moved to the next state when the state is empty.");
342 }
343
344 int position = cursor.nodeIndexStack.peek();
345
346 for (int index = position + 1; index < FACTOR; index++) {
347 // data found
348 if (this.content[index * 2] != null) {
349 cursor.nodeIndexStack.pop();
350 cursor.nodeIndexStack.push(index);
351
352 cursor.key = (K) this.content[index * 2];
353 cursor.value = (V) this.content[index * 2 + 1];
354 return true;
355 } else if (this.content[index * 2 +1] != null) {
356 // sub-node found
357 Node<K,V> subnode = (Node<K, V>) this.content[index * 2 +1];
358 cursor.nodeIndexStack.pop();
359 cursor.nodeIndexStack.push(index);
360 cursor.nodeIndexStack.push(InOrderMapCursor.INDEX_START);
361 cursor.nodeStack.push(subnode);
362
363 return subnode.moveToNextInorder(cursor);
364 }
365 }
366
367 // nothing found
368 cursor.nodeStack.pop();
369 cursor.nodeIndexStack.pop();
370 if (!cursor.nodeStack.isEmpty()) {
371 Node<K, V> supernode = cursor.nodeStack.peek();
372 return supernode.moveToNextInorder(cursor);
373 } else {
374 cursor.key = null;
375 cursor.value = null;
376 return false;
377 }
378 }
379
380 @Override
340 public void prettyPrint(StringBuilder builder, int depth, int code) { 381 public void prettyPrint(StringBuilder builder, int depth, int code) {
341 builder.append("\t".repeat(Math.max(0, depth))); 382 builder.append("\t".repeat(Math.max(0, depth)));
342 if (code >= 0) { 383 if (code >= 0) {
@@ -361,7 +402,7 @@ public class MutableNode<K, V> extends Node<K, V> {
361 } 402 }
362 } 403 }
363 builder.append(")"); 404 builder.append(")");
364 // print subnodes 405 // print sub-nodes
365 for (int i = 0; i < FACTOR; i++) { 406 for (int i = 0; i < FACTOR; i++) {
366 if (content[2 * i] == null && content[2 * i + 1] != null) { 407 if (content[2 * i] == null && content[2 * i + 1] != null) {
367 @SuppressWarnings("unchecked") Node<K, V> subNode = (Node<K, V>) content[2 * i + 1]; 408 @SuppressWarnings("unchecked") Node<K, V> subNode = (Node<K, V>) content[2 * i + 1];
@@ -397,7 +438,7 @@ public class MutableNode<K, V> extends Node<K, V> {
397 } 438 }
398 } 439 }
399 } 440 }
400 // check subnodes 441 // check sub-nodes
401 for (int i = 0; i < FACTOR; i++) { 442 for (int i = 0; i < FACTOR; i++) {
402 if (this.content[2 * i + 1] != null && this.content[2 * i] == null) { 443 if (this.content[2 * i + 1] != null && this.content[2 * i] == null) {
403 @SuppressWarnings("unchecked") var subNode = (Node<K, V>) this.content[2 * i + 1]; 444 @SuppressWarnings("unchecked") var subNode = (Node<K, V>) this.content[2 * i + 1];
@@ -421,13 +462,11 @@ public class MutableNode<K, V> extends Node<K, V> {
421 462
422 @Override 463 @Override
423 public int hashCode() { 464 public int hashCode() {
424 if (this.cachedHashValid) { 465 if (!this.cachedHashValid) {
425 return this.cachedHash;
426 } else {
427 this.cachedHash = Arrays.hashCode(content); 466 this.cachedHash = Arrays.hashCode(content);
428 this.cachedHashValid = true; 467 this.cachedHashValid = true;
429 return this.cachedHash;
430 } 468 }
469 return this.cachedHash;
431 } 470 }
432 471
433 @Override 472 @Override
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java
index c49be280..3dd332da 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java
@@ -108,10 +108,12 @@ public abstract class Node<K, V> {
108 * @return Whether there was a next value to move on. 108 * @return Whether there was a next value to move on.
109 */ 109 */
110 abstract boolean moveToNext(MapCursor<K, V> cursor); 110 abstract boolean moveToNext(MapCursor<K, V> cursor);
111 abstract boolean moveToNextInorder(InOrderMapCursor<K, V> cursor);
111 112
112 ///////// FOR printing 113 ///////// FOR printing
113 public abstract void prettyPrint(StringBuilder builder, int depth, int code); 114 public abstract void prettyPrint(StringBuilder builder, int depth, int code);
114 115
116
115 @Override 117 @Override
116 public String toString() { 118 public String toString() {
117 StringBuilder stringBuilder = new StringBuilder(); 119 StringBuilder stringBuilder = new StringBuilder();
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java
index fb359431..2ceca463 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java
@@ -75,7 +75,9 @@ public class VersionedMapImpl<K, V> implements VersionedMap<K, V> {
75 Iterator<K> keyIterator = keys.iterator(); 75 Iterator<K> keyIterator = keys.iterator();
76 Iterator<V> valueIterator = values.iterator(); 76 Iterator<V> valueIterator = values.iterator();
77 while (keyIterator.hasNext()) { 77 while (keyIterator.hasNext()) {
78 this.put(keyIterator.next(), valueIterator.next()); 78 var key = keyIterator.next();
79 var value = valueIterator.next();
80 this.put(key,value);
79 } 81 }
80 } else { 82 } else {
81 while (cursor.move()) { 83 while (cursor.move()) {
@@ -109,12 +111,10 @@ public class VersionedMapImpl<K, V> implements VersionedMap<K, V> {
109 111
110 @Override 112 @Override
111 public DiffCursor<K, V> getDiffCursor(long toVersion) { 113 public DiffCursor<K, V> getDiffCursor(long toVersion) {
112 114 InOrderMapCursor<K, V> fromCursor = new InOrderMapCursor<>(this);
113 Cursor<K, V> fromCursor = this.getAll(); 115 VersionedMapImpl<K, V> toMap = (VersionedMapImpl<K, V>) this.store.createMap(toVersion);
114 VersionedMap<K, V> toMap = this.store.createMap(toVersion); 116 InOrderMapCursor<K, V> toCursor = new InOrderMapCursor<>(toMap);
115 Cursor<K, V> toCursor = toMap.getAll(); 117 return new MapDiffCursor<>(this.defaultValue, fromCursor, toCursor);
116 return new MapDiffCursor<>(this.hashProvider, this.defaultValue, fromCursor, toCursor);
117
118 } 118 }
119 119
120 120
@@ -152,7 +152,11 @@ public class VersionedMapImpl<K, V> implements VersionedMap<K, V> {
152 @Override 152 @Override
153 public int contentHashCode(ContentHashCode mode) { 153 public int contentHashCode(ContentHashCode mode) {
154 // Calculating the root hashCode is always fast, because {@link Node} caches its hashCode. 154 // Calculating the root hashCode is always fast, because {@link Node} caches its hashCode.
155 return Objects.hashCode(root); 155 if(root == null) {
156 return 0;
157 } else {
158 return root.hashCode();
159 }
156 } 160 }
157 161
158 @Override 162 @Override
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java
index 6d82f5d7..c850d334 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java
@@ -4,11 +4,9 @@ import tools.refinery.store.map.Cursor;
4import tools.refinery.store.map.DiffCursor; 4import tools.refinery.store.map.DiffCursor;
5import tools.refinery.store.map.VersionedMap; 5import tools.refinery.store.map.VersionedMap;
6import tools.refinery.store.map.VersionedMapStore; 6import tools.refinery.store.map.VersionedMapStore;
7import tools.refinery.store.map.internal.MapDiffCursor;
8import tools.refinery.store.model.Interpretation; 7import tools.refinery.store.model.Interpretation;
9import tools.refinery.store.model.InterpretationListener; 8import tools.refinery.store.model.InterpretationListener;
10import tools.refinery.store.model.Model; 9import tools.refinery.store.model.Model;
11import tools.refinery.store.model.TupleHashProvider;
12import tools.refinery.store.representation.AnySymbol; 10import tools.refinery.store.representation.AnySymbol;
13import tools.refinery.store.representation.Symbol; 11import tools.refinery.store.representation.Symbol;
14import tools.refinery.store.tuple.Tuple; 12import tools.refinery.store.tuple.Tuple;
@@ -19,16 +17,13 @@ import java.util.List;
19public class VersionedInterpretation<T> implements Interpretation<T> { 17public class VersionedInterpretation<T> implements Interpretation<T> {
20 private final ModelImpl model; 18 private final ModelImpl model;
21 private final Symbol<T> symbol; 19 private final Symbol<T> symbol;
22 private final VersionedMapStore<Tuple, T> store;
23 private final VersionedMap<Tuple, T> map; 20 private final VersionedMap<Tuple, T> map;
24 private final List<InterpretationListener<T>> listeners = new ArrayList<>(); 21 private final List<InterpretationListener<T>> listeners = new ArrayList<>();
25 private final List<InterpretationListener<T>> restoreListeners = new ArrayList<>(); 22 private final List<InterpretationListener<T>> restoreListeners = new ArrayList<>();
26 23
27 private VersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMapStore<Tuple, T> store, 24 private VersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMap<Tuple, T> map) {
28 VersionedMap<Tuple, T> map) {
29 this.model = model; 25 this.model = model;
30 this.symbol = symbol; 26 this.symbol = symbol;
31 this.store = store;
32 this.map = map; 27 this.map = map;
33 } 28 }
34 29
@@ -111,9 +106,7 @@ public class VersionedInterpretation<T> implements Interpretation<T> {
111 106
112 @Override 107 @Override
113 public DiffCursor<Tuple, T> getDiffCursor(long to) { 108 public DiffCursor<Tuple, T> getDiffCursor(long to) {
114 var fromCursor = getAll(); 109 return map.getDiffCursor(to);
115 var toCursor = store.createMap(to).getAll();
116 return new MapDiffCursor<>(TupleHashProvider.INSTANCE, symbol.defaultValue(), fromCursor, toCursor);
117 } 110 }
118 111
119 public long commit() { 112 public long commit() {
@@ -148,7 +141,7 @@ public class VersionedInterpretation<T> implements Interpretation<T> {
148 @SuppressWarnings("unchecked") 141 @SuppressWarnings("unchecked")
149 var typedSymbol = (Symbol<T>) symbol; 142 var typedSymbol = (Symbol<T>) symbol;
150 var map = store.createMap(); 143 var map = store.createMap();
151 return new VersionedInterpretation<>(model, typedSymbol, store, map); 144 return new VersionedInterpretation<>(model, typedSymbol, map);
152 } 145 }
153 146
154 static <T> VersionedInterpretation<T> of(ModelImpl model, AnySymbol symbol, VersionedMapStore<Tuple, T> store, 147 static <T> VersionedInterpretation<T> of(ModelImpl model, AnySymbol symbol, VersionedMapStore<Tuple, T> store,
@@ -156,6 +149,6 @@ public class VersionedInterpretation<T> implements Interpretation<T> {
156 @SuppressWarnings("unchecked") 149 @SuppressWarnings("unchecked")
157 var typedSymbol = (Symbol<T>) symbol; 150 var typedSymbol = (Symbol<T>) symbol;
158 var map = store.createMap(state); 151 var map = store.createMap(state);
159 return new VersionedInterpretation<>(model, typedSymbol, store, map); 152 return new VersionedInterpretation<>(model, typedSymbol, map);
160 } 153 }
161} 154}