diff options
Diffstat (limited to 'store/src/main/java/org/eclipse/viatra/solver/data/map/internal')
7 files changed, 1448 insertions, 0 deletions
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/ImmutableNode.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/ImmutableNode.java new file mode 100644 index 00000000..04a9b19a --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/ImmutableNode.java | |||
@@ -0,0 +1,379 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.internal; | ||
2 | |||
3 | import java.util.Arrays; | ||
4 | import java.util.Map; | ||
5 | |||
6 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
7 | |||
8 | public class ImmutableNode<K, V> extends Node<K, V> { | ||
9 | /** | ||
10 | * Bitmap defining the stored key and values. | ||
11 | */ | ||
12 | final int dataMap; | ||
13 | /** | ||
14 | * Bitmap defining the positions of further nodes. | ||
15 | */ | ||
16 | final int nodeMap; | ||
17 | /** | ||
18 | * Stores Keys, Values, and subnodes. Structure: (K,V)*,NODE; NODES are stored backwards. | ||
19 | */ | ||
20 | final Object[] content; | ||
21 | |||
22 | /** | ||
23 | * Hash code derived from immutable hash code | ||
24 | */ | ||
25 | final int precalculatedHash; | ||
26 | |||
27 | private ImmutableNode(int dataMap, int nodeMap, Object[] content, int precalculatedHash) { | ||
28 | super(); | ||
29 | this.dataMap = dataMap; | ||
30 | this.nodeMap = nodeMap; | ||
31 | this.content = content; | ||
32 | this.precalculatedHash = precalculatedHash; | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Constructor that copies a mutable node to an immutable. | ||
37 | * | ||
38 | * @param node A mutable node. | ||
39 | * @param cache A cache of existing immutable nodes. It can be used to search | ||
40 | * and place reference immutable nodes. It can be null, if no cache | ||
41 | * available. | ||
42 | * @return an immutable version of the input node. | ||
43 | */ | ||
44 | @SuppressWarnings("unchecked") | ||
45 | static <K,V> ImmutableNode<K,V> constructImmutable(MutableNode<K,V> node, Map<Node<K, V>, ImmutableNode<K, V>> cache) { | ||
46 | // 1. try to return from cache | ||
47 | if(cache != null) { | ||
48 | ImmutableNode<K, V> cachedResult = cache.get(node); | ||
49 | if(cachedResult != null) { | ||
50 | // 1.1 Already cached, return from cache. | ||
51 | return cachedResult; | ||
52 | } | ||
53 | } | ||
54 | |||
55 | // 2. otherwise construct a new ImmutableNode | ||
56 | int size = 0; | ||
57 | for(int i = 0; i<node.content.length; i++) { | ||
58 | if(node.content[i]!=null) { | ||
59 | size++; | ||
60 | } | ||
61 | } | ||
62 | |||
63 | int datas = 0; | ||
64 | int nodes = 0; | ||
65 | int resultDataMap = 0; | ||
66 | int resultNodeMap = 0; | ||
67 | final Object[] resultContent = new Object[size]; | ||
68 | int bitposition = 1; | ||
69 | for(int i = 0; i<FACTOR; i++) { | ||
70 | Object key = node.content[i*2]; | ||
71 | if(key != null) { | ||
72 | resultDataMap |= bitposition; | ||
73 | resultContent[datas*2] = key; | ||
74 | resultContent[datas*2+1] = node.content[i*2+1]; | ||
75 | datas++; | ||
76 | } else { | ||
77 | Node<K,V> subnode = (Node<K, V>) node.content[i*2+1]; | ||
78 | if(subnode != null) { | ||
79 | ImmutableNode<K, V> immutableSubnode = subnode.toImmutable(cache); | ||
80 | resultNodeMap |=bitposition; | ||
81 | resultContent[size-1-nodes] = immutableSubnode; | ||
82 | nodes++; | ||
83 | } | ||
84 | } | ||
85 | bitposition<<=1; | ||
86 | } | ||
87 | final int resultHash = node.hashCode(); | ||
88 | ImmutableNode<K,V> newImmutable = new ImmutableNode<>(resultDataMap, resultNodeMap, resultContent, resultHash); | ||
89 | |||
90 | // 3. save new immutable. | ||
91 | if(cache != null) { | ||
92 | cache.put(newImmutable, newImmutable); | ||
93 | } | ||
94 | return newImmutable; | ||
95 | } | ||
96 | |||
97 | private int index(int bitmap, int bitpos) { | ||
98 | return Integer.bitCount(bitmap & (bitpos-1)); | ||
99 | } | ||
100 | |||
101 | @SuppressWarnings("unchecked") | ||
102 | @Override | ||
103 | public V getValue(K key, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) { | ||
104 | int selectedHashFragment = hashFragment(hash,shiftDepth(depth)); | ||
105 | int bitposition = 1 << selectedHashFragment; | ||
106 | // If the key is stored as a data | ||
107 | if((dataMap & bitposition) != 0) { | ||
108 | int keyIndex = 2*index(dataMap, bitposition); | ||
109 | K keyCandidate = (K) content[keyIndex]; | ||
110 | if(keyCandidate.equals(key)) { | ||
111 | return (V) content[keyIndex+1]; | ||
112 | } else { | ||
113 | return defaultValue; | ||
114 | } | ||
115 | } | ||
116 | // the key is stored as a node | ||
117 | else if((nodeMap & bitposition) != 0) { | ||
118 | int keyIndex = content.length-1-index(nodeMap, bitposition); | ||
119 | ImmutableNode<K,V> subNode = (ImmutableNode<K,V>) content[keyIndex]; | ||
120 | int newDepth = depth+1; | ||
121 | int newHash = newHash(hashProvider, key, hash, newDepth); | ||
122 | return subNode.getValue(key, hashProvider, defaultValue, newHash, newDepth); | ||
123 | } | ||
124 | // the key is not stored at all | ||
125 | else { | ||
126 | return defaultValue; | ||
127 | } | ||
128 | } | ||
129 | |||
130 | @SuppressWarnings("unchecked") | ||
131 | @Override | ||
132 | public Node<K,V> putValue(K key, V value, OldValueBox<V> oldValue, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) { | ||
133 | int selectedHashFragment = hashFragment(hash,shiftDepth(depth)); | ||
134 | int bitposition = 1 << selectedHashFragment; | ||
135 | if((dataMap & bitposition) != 0) { | ||
136 | int keyIndex = 2*index(dataMap, bitposition); | ||
137 | K keyCandidate = (K) content[keyIndex]; | ||
138 | if(keyCandidate.equals(key)) { | ||
139 | if(value == defaultValue) { | ||
140 | // delete | ||
141 | MutableNode<K, V> mutable = this.toMutable(); | ||
142 | return mutable.removeEntry(selectedHashFragment,oldValue); | ||
143 | } else if(value == content[keyIndex+1]) { | ||
144 | // dont change | ||
145 | oldValue.setOldValue(value); | ||
146 | return this; | ||
147 | } else { | ||
148 | // update existing value | ||
149 | MutableNode<K, V> mutable = this.toMutable(); | ||
150 | return mutable.updateValue(value, oldValue, selectedHashFragment); | ||
151 | } | ||
152 | } else { | ||
153 | if(value == defaultValue) { | ||
154 | // dont change | ||
155 | oldValue.setOldValue(defaultValue); | ||
156 | return this; | ||
157 | } else { | ||
158 | // add new key + value | ||
159 | MutableNode<K, V> mutable = this.toMutable(); | ||
160 | return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth); | ||
161 | } | ||
162 | } | ||
163 | } else if((nodeMap & bitposition)!=0) { | ||
164 | |||
165 | int keyIndex = content.length-1-index(nodeMap, bitposition); | ||
166 | ImmutableNode<K,V> subNode = (ImmutableNode<K,V>) content[keyIndex]; | ||
167 | int newDepth = depth+1; | ||
168 | int newHash = newHash(hashProvider, key, hash, newDepth); | ||
169 | Node<K,V> newsubNode = subNode.putValue(key, value, oldValue, hashProvider, defaultValue, newHash, newDepth); | ||
170 | |||
171 | if(subNode == newsubNode) { | ||
172 | // nothing changed | ||
173 | return this; | ||
174 | } else { | ||
175 | MutableNode<K, V> mutable = toMutable(); | ||
176 | return mutable.updateWithSubNode(selectedHashFragment, newsubNode, value.equals(defaultValue)); | ||
177 | } | ||
178 | } else { | ||
179 | // add new key + value | ||
180 | MutableNode<K, V> mutable = this.toMutable(); | ||
181 | return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth); | ||
182 | } | ||
183 | } | ||
184 | |||
185 | |||
186 | @SuppressWarnings("unchecked") | ||
187 | @Override | ||
188 | public long getSize() { | ||
189 | int result = Integer.bitCount(this.dataMap); | ||
190 | for(int subnodeIndex = 0; subnodeIndex < Integer.bitCount(this.nodeMap); subnodeIndex++) { | ||
191 | ImmutableNode<K,V> subnode = | ||
192 | (ImmutableNode<K, V>) this.content[this.content.length-1-subnodeIndex]; | ||
193 | result += subnode.getSize(); | ||
194 | } | ||
195 | return result; | ||
196 | } | ||
197 | |||
198 | @Override | ||
199 | protected MutableNode<K,V> toMutable() { | ||
200 | return new MutableNode<>(this); | ||
201 | } | ||
202 | |||
203 | @Override | ||
204 | public ImmutableNode<K, V> toImmutable( | ||
205 | Map<Node<K, V>, ImmutableNode<K, V>> cache) { | ||
206 | return this; | ||
207 | } | ||
208 | |||
209 | @Override | ||
210 | protected MutableNode<K, V> isMutable() { | ||
211 | return null; | ||
212 | } | ||
213 | |||
214 | @SuppressWarnings("unchecked") | ||
215 | @Override | ||
216 | boolean moveToNext(MapCursor<K, V> cursor) { | ||
217 | // 1. try to move to data | ||
218 | int datas = Integer.bitCount(this.dataMap); | ||
219 | if(cursor.dataIndex != MapCursor.INDEX_FINISH) { | ||
220 | int newDataIndex = cursor.dataIndex + 1; | ||
221 | if(newDataIndex < datas) { | ||
222 | cursor.dataIndex = newDataIndex; | ||
223 | cursor.key = (K) this.content[newDataIndex*2]; | ||
224 | cursor.value = (V) this.content[newDataIndex*2+1]; | ||
225 | return true; | ||
226 | } else { | ||
227 | cursor.dataIndex = MapCursor.INDEX_FINISH; | ||
228 | } | ||
229 | } | ||
230 | |||
231 | // 2. look inside the subnodes | ||
232 | int nodes = Integer.bitCount(this.nodeMap); | ||
233 | int newNodeIndex = cursor.nodeIndexStack.peek() + 1; | ||
234 | if(newNodeIndex < nodes) { | ||
235 | // 2.1 found next subnode, move down to the subnode | ||
236 | Node<K, V> subnode = (Node<K, V>) this.content[this.content.length-1-newNodeIndex]; | ||
237 | cursor.dataIndex = MapCursor.INDEX_START; | ||
238 | cursor.nodeIndexStack.pop(); | ||
239 | cursor.nodeIndexStack.push(newNodeIndex); | ||
240 | cursor.nodeIndexStack.push(MapCursor.INDEX_START); | ||
241 | cursor.nodeStack.push(subnode); | ||
242 | return subnode.moveToNext(cursor); | ||
243 | } else { | ||
244 | // 3. no subnode found, move up | ||
245 | cursor.nodeStack.pop(); | ||
246 | cursor.nodeIndexStack.pop(); | ||
247 | if(!cursor.nodeStack.isEmpty()) { | ||
248 | Node<K, V> supernode = cursor.nodeStack.peek(); | ||
249 | return supernode.moveToNext(cursor); | ||
250 | } else { | ||
251 | cursor.key = null; | ||
252 | cursor.value = null; | ||
253 | return false; | ||
254 | } | ||
255 | } | ||
256 | } | ||
257 | |||
258 | @Override | ||
259 | public void prettyPrint(StringBuilder builder, int depth, int code) { | ||
260 | for(int i = 0; i<depth; i++) { | ||
261 | builder.append("\t"); | ||
262 | } | ||
263 | if(code>=0) { | ||
264 | builder.append(code); | ||
265 | builder.append(":"); | ||
266 | } | ||
267 | builder.append("Immutable("); | ||
268 | boolean hadContent = false; | ||
269 | int dataMask = 1; | ||
270 | for(int i = 0; i<FACTOR; i++) { | ||
271 | if((dataMask & dataMap) != 0) { | ||
272 | if(hadContent) { | ||
273 | builder.append(","); | ||
274 | } | ||
275 | builder.append(i); | ||
276 | builder.append(":["); | ||
277 | builder.append(content[2*index(dataMap, dataMask)].toString()); | ||
278 | builder.append("]->["); | ||
279 | builder.append(content[2*index(dataMap, dataMask)+1].toString()); | ||
280 | builder.append("]"); | ||
281 | hadContent = true; | ||
282 | } | ||
283 | dataMask<<=1; | ||
284 | } | ||
285 | builder.append(")"); | ||
286 | int nodeMask = 1; | ||
287 | for(int i = 0; i<FACTOR; i++) { | ||
288 | if((nodeMask & nodeMap)!=0) { | ||
289 | @SuppressWarnings("unchecked") | ||
290 | Node<K,V> subNode = (Node<K, V>) content[content.length-1-index(nodeMap, nodeMask)]; | ||
291 | builder.append("\n"); | ||
292 | subNode.prettyPrint(builder, depth+1, i); | ||
293 | } | ||
294 | nodeMask<<=1; | ||
295 | } | ||
296 | } | ||
297 | |||
298 | @SuppressWarnings("unchecked") | ||
299 | @Override | ||
300 | public void checkIntegrity(ContinousHashProvider<? super K> hashProvider, V defaultValue, int depth) { | ||
301 | if(depth>0) { | ||
302 | boolean orphaned = Integer.bitCount(dataMap) == 1 && nodeMap == 0; | ||
303 | if(orphaned) { | ||
304 | throw new IllegalStateException("Orphaned node! " + dataMap + ": " + content[0]); | ||
305 | } | ||
306 | } | ||
307 | // check the place of data | ||
308 | |||
309 | // check subnodes | ||
310 | for(int i = 0; i<Integer.bitCount(nodeMap); i++) { | ||
311 | Node<K,V> subnode = (Node<K, V>) this.content[this.content.length-1-i]; | ||
312 | if(! (subnode instanceof ImmutableNode<?,?>)) { | ||
313 | throw new IllegalStateException("Immutable node contains mutable subnodes!"); | ||
314 | } else { | ||
315 | subnode.checkIntegrity(hashProvider, defaultValue, depth+1); | ||
316 | } | ||
317 | } | ||
318 | } | ||
319 | |||
320 | @Override | ||
321 | public int hashCode() { | ||
322 | return this.precalculatedHash; | ||
323 | } | ||
324 | |||
325 | @Override | ||
326 | public boolean equals(Object obj) { | ||
327 | if (this == obj) | ||
328 | return true; | ||
329 | if (obj == null) | ||
330 | return false; | ||
331 | if (obj instanceof ImmutableNode<?,?>) { | ||
332 | ImmutableNode<?,?> other = (ImmutableNode<?,?>) obj; | ||
333 | if (precalculatedHash != other.precalculatedHash || dataMap != other.dataMap || nodeMap != other.nodeMap || !Arrays.deepEquals(content, other.content)) | ||
334 | return false; | ||
335 | else return true; | ||
336 | } else if(obj instanceof MutableNode<?,?>) { | ||
337 | return ImmutableNode.compareImmutableMutable(this, (MutableNode<?, ?>) obj); | ||
338 | } else { | ||
339 | return false; | ||
340 | } | ||
341 | } | ||
342 | public static boolean compareImmutableMutable( | ||
343 | ImmutableNode<?, ?> immutable, | ||
344 | MutableNode<?, ?> mutable) | ||
345 | { | ||
346 | int datas = 0; | ||
347 | int nodes = 0; | ||
348 | final int immutableLength = immutable.content.length; | ||
349 | for(int i = 0; i<FACTOR; i++) { | ||
350 | Object key = mutable.content[i*2]; | ||
351 | // For each key candidate | ||
352 | if(key != null) { | ||
353 | // Check whether a new Key-Value pair can fit into the immutable container | ||
354 | if(datas*2+nodes+2 <= immutableLength) { | ||
355 | if( !immutable.content[datas*2].equals(key) || | ||
356 | !immutable.content[datas*2+1].equals(mutable.content[i*2+1])) | ||
357 | { | ||
358 | return false; | ||
359 | } | ||
360 | } else return false; | ||
361 | datas++; | ||
362 | } else { | ||
363 | Node<?,?> mutableSubnode = (Node<?, ?>) mutable.content[i*2+1]; | ||
364 | if(mutableSubnode != null) { | ||
365 | if(datas*2+nodes+1 <= immutableLength) { | ||
366 | Object immutableSubnode = immutable.content[immutableLength-1-nodes]; | ||
367 | if(!mutableSubnode.equals(immutableSubnode)) { | ||
368 | return false; | ||
369 | } | ||
370 | nodes++; | ||
371 | } else { | ||
372 | return false; | ||
373 | } | ||
374 | } | ||
375 | } | ||
376 | } | ||
377 | return true; | ||
378 | } | ||
379 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MapCursor.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MapCursor.java new file mode 100644 index 00000000..cc5a3982 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MapCursor.java | |||
@@ -0,0 +1,131 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.internal; | ||
2 | |||
3 | import java.util.ArrayDeque; | ||
4 | import java.util.ConcurrentModificationException; | ||
5 | import java.util.Iterator; | ||
6 | import java.util.List; | ||
7 | |||
8 | import org.eclipse.viatra.solver.data.map.Cursor; | ||
9 | import org.eclipse.viatra.solver.data.map.VersionedMap; | ||
10 | |||
11 | public class MapCursor<K,V> implements Cursor<K,V> { | ||
12 | // Constants | ||
13 | static final int INDEX_START = -1; | ||
14 | static final int INDEX_FINISH = -2; | ||
15 | |||
16 | // Tree stack | ||
17 | ArrayDeque<Node<K,V>> nodeStack; | ||
18 | ArrayDeque<Integer> nodeIndexStack; | ||
19 | int dataIndex; | ||
20 | |||
21 | // Values | ||
22 | K key; | ||
23 | V value; | ||
24 | |||
25 | // Hash code for checking concurrent modifications | ||
26 | final VersionedMap<K,V> map; | ||
27 | final int creationHash; | ||
28 | |||
29 | public MapCursor(Node<K, V> root, VersionedMap<K,V> map) { | ||
30 | // Initializing tree stack | ||
31 | super(); | ||
32 | this.nodeStack = new ArrayDeque<>(); | ||
33 | this.nodeIndexStack = new ArrayDeque<>(); | ||
34 | if(root != null) { | ||
35 | this.nodeStack.add(root); | ||
36 | this.nodeIndexStack.push(INDEX_START); | ||
37 | } | ||
38 | |||
39 | this.dataIndex = INDEX_START; | ||
40 | |||
41 | // Initializing cache | ||
42 | this.key = null; | ||
43 | this.value = null; | ||
44 | |||
45 | // Initializing state | ||
46 | this.map=map; | ||
47 | this.creationHash = map.hashCode(); | ||
48 | } | ||
49 | |||
50 | public K getKey() { | ||
51 | return key; | ||
52 | } | ||
53 | |||
54 | public V getValue() { | ||
55 | return value; | ||
56 | } | ||
57 | |||
58 | public boolean isTerminated() { | ||
59 | return this.nodeStack.isEmpty(); | ||
60 | } | ||
61 | |||
62 | public boolean move() { | ||
63 | if(isDirty()) { | ||
64 | throw new ConcurrentModificationException(); | ||
65 | } | ||
66 | if(!isTerminated()) { | ||
67 | boolean result = this.nodeStack.peek().moveToNext(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 | public boolean skipCurrentNode() { | ||
76 | nodeStack.pop(); | ||
77 | nodeIndexStack.pop(); | ||
78 | dataIndex = INDEX_FINISH; | ||
79 | return move(); | ||
80 | } | ||
81 | @Override | ||
82 | public boolean isDirty() { | ||
83 | return this.map.hashCode() != this.creationHash; | ||
84 | } | ||
85 | @Override | ||
86 | public List<VersionedMap<?, ?>> getDependingMaps() { | ||
87 | return List.of(this.map); | ||
88 | } | ||
89 | |||
90 | public static <K,V> boolean sameSubnode(MapCursor<K,V> cursor1, MapCursor<K,V> cursor2) { | ||
91 | Node<K, V> nodeOfCursor1 = cursor1.nodeStack.peek(); | ||
92 | Node<K, V> nodeOfCursor2 = cursor2.nodeStack.peek(); | ||
93 | if(nodeOfCursor1 != null && nodeOfCursor2 != null) { | ||
94 | return nodeOfCursor1.equals(nodeOfCursor2); | ||
95 | } else { | ||
96 | return false; | ||
97 | } | ||
98 | } | ||
99 | |||
100 | /** | ||
101 | * | ||
102 | * @param <K> | ||
103 | * @param <V> | ||
104 | * @param cursor1 | ||
105 | * @param cursor2 | ||
106 | * @return Positive number if cursor 1 is behind, negative number if cursor 2 is behind, and 0 if they are at the same position. | ||
107 | */ | ||
108 | public static <K,V> int compare(MapCursor<K,V> cursor1, MapCursor<K,V> cursor2) { | ||
109 | // two cursors are equally deep | ||
110 | Iterator<Integer> stack1 = cursor1.nodeIndexStack.descendingIterator(); | ||
111 | Iterator<Integer> stack2 = cursor2.nodeIndexStack.descendingIterator(); | ||
112 | if(stack1.hasNext()) { | ||
113 | if(!stack2.hasNext()) { | ||
114 | // stack 2 has no more element, thus stack 1 is deeper | ||
115 | return 1; | ||
116 | } | ||
117 | int val1 = stack1.next(); | ||
118 | int val2 = stack2.next(); | ||
119 | if(val1 < val2) { | ||
120 | return -1; | ||
121 | } else if(val2 < val1) { | ||
122 | return 1; | ||
123 | } | ||
124 | } | ||
125 | if(stack2.hasNext()) { | ||
126 | // stack 2 has more element, thus stack 2 is deeper | ||
127 | return 1; | ||
128 | } | ||
129 | return Integer.compare(cursor1.dataIndex, cursor2.dataIndex); | ||
130 | } | ||
131 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MapDiffCursor.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MapDiffCursor.java new file mode 100644 index 00000000..e97e4aa1 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MapDiffCursor.java | |||
@@ -0,0 +1,214 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.internal; | ||
2 | |||
3 | import java.util.List; | ||
4 | import java.util.stream.Collectors; | ||
5 | import java.util.stream.Stream; | ||
6 | |||
7 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
8 | import org.eclipse.viatra.solver.data.map.Cursor; | ||
9 | import org.eclipse.viatra.solver.data.map.DiffCursor; | ||
10 | import org.eclipse.viatra.solver.data.map.VersionedMap; | ||
11 | |||
12 | /** | ||
13 | * A cursor representing the difference between two states of a map. | ||
14 | * @author Oszkar Semerath | ||
15 | * | ||
16 | */ | ||
17 | public class MapDiffCursor<K,V> implements DiffCursor<K, V>, Cursor<K,V>{ | ||
18 | /** | ||
19 | * Default value representing missing elements. | ||
20 | */ | ||
21 | private V defaultValue; | ||
22 | private MapCursor<K,V> cursor1; | ||
23 | private MapCursor<K,V> cursor2; | ||
24 | private ContinousHashProvider<? super K> hashProvider; | ||
25 | |||
26 | // Values | ||
27 | private K key; | ||
28 | private V fromValue; | ||
29 | private V toValue; | ||
30 | |||
31 | // State | ||
32 | /** | ||
33 | * Positive number if cursor 1 is behind, negative number if cursor 2 is behind, and 0 if they are at the same position. | ||
34 | */ | ||
35 | private int cursorRelation; | ||
36 | /** | ||
37 | * Denotes a state when two cursors are in the same position, but they contain different keys. | ||
38 | * Possible values: | ||
39 | * <ul> | ||
40 | * <li>0: not stuck</li> | ||
41 | * <li>1: hashClash, next it should return the key of cursor 1.</li> | ||
42 | * <li>2: hashClash, next it should return the key of cursor 2.</li> | ||
43 | * </ul> | ||
44 | */ | ||
45 | private int hashClash=0; | ||
46 | |||
47 | public MapDiffCursor(ContinousHashProvider<? super K> hashProvider, V defaultValue, Cursor<K, V> cursor1, Cursor<K, V> cursor2) { | ||
48 | super(); | ||
49 | this.hashProvider = hashProvider; | ||
50 | this.defaultValue = defaultValue; | ||
51 | this.cursor1 = (MapCursor<K, V>) cursor1; | ||
52 | this.cursor2 = (MapCursor<K, V>) cursor2; | ||
53 | } | ||
54 | @Override | ||
55 | public K getKey() { | ||
56 | return key; | ||
57 | } | ||
58 | @Override | ||
59 | public V getFromValue() { | ||
60 | return fromValue; | ||
61 | } | ||
62 | @Override | ||
63 | public V getToValue() { | ||
64 | return toValue; | ||
65 | } | ||
66 | @Override | ||
67 | public V getValue() { | ||
68 | return getToValue(); | ||
69 | } | ||
70 | public boolean isTerminated() { | ||
71 | return cursor1.isTerminated() && cursor2.isTerminated(); | ||
72 | } | ||
73 | @Override | ||
74 | public boolean isDirty() { | ||
75 | return this.cursor1.isDirty() || this.cursor2.isDirty(); | ||
76 | } | ||
77 | @Override | ||
78 | public List<VersionedMap<?, ?>> getDependingMaps() { | ||
79 | return Stream.concat( | ||
80 | cursor1.getDependingMaps().stream(), | ||
81 | cursor2.getDependingMaps().stream() | ||
82 | ).collect(Collectors.toList()); | ||
83 | } | ||
84 | |||
85 | protected void updateState() { | ||
86 | if(!isTerminated()) { | ||
87 | this.cursorRelation = MapCursor.compare(cursor1, cursor2); | ||
88 | if(cursorRelation > 0 || cursor2.isTerminated()) { | ||
89 | this.key = cursor1.getKey(); | ||
90 | this.fromValue = cursor1.getValue(); | ||
91 | this.toValue = defaultValue; | ||
92 | } else if(cursorRelation < 0 || cursor1.isTerminated()){ | ||
93 | this.key = cursor2.getKey(); | ||
94 | this.fromValue = defaultValue; | ||
95 | this.toValue = cursor1.getValue(); | ||
96 | } else { | ||
97 | // cursor1 = cursor2 | ||
98 | if(cursor1.getKey().equals(cursor2.getKey())) { | ||
99 | this.key = cursor1.getKey(); | ||
100 | this.fromValue = cursor1.getValue(); | ||
101 | this.toValue = defaultValue; | ||
102 | } else { | ||
103 | resolveHashClash1(); | ||
104 | } | ||
105 | } | ||
106 | } | ||
107 | } | ||
108 | protected void resolveHashClash1() { | ||
109 | int compareResult = this.hashProvider.compare(cursor1.key, cursor2.key); | ||
110 | if(compareResult<0) { | ||
111 | this.hashClash = 2; | ||
112 | this.cursorRelation = 0; | ||
113 | this.key = cursor1.key; | ||
114 | this.fromValue = cursor1.value; | ||
115 | this.toValue = defaultValue; | ||
116 | } else if(compareResult>0) { | ||
117 | this.hashClash = 1; | ||
118 | this.cursorRelation = 0; | ||
119 | this.key = cursor2.key; | ||
120 | this.fromValue = defaultValue; | ||
121 | this.toValue = cursor2.value; | ||
122 | } else { | ||
123 | throw new IllegalArgumentException("Inconsistent compare result for diffcursor"); | ||
124 | } | ||
125 | } | ||
126 | protected boolean isInHashClash() { | ||
127 | return this.hashClash != 0; | ||
128 | } | ||
129 | protected void resolveHashClash2() { | ||
130 | if(hashClash == 1) { | ||
131 | this.hashClash = 0; | ||
132 | this.cursorRelation = 0; | ||
133 | this.key = cursor1.key; | ||
134 | this.fromValue = cursor1.value; | ||
135 | this.toValue = defaultValue; | ||
136 | } else if(hashClash == 2) { | ||
137 | this.hashClash = 0; | ||
138 | this.cursorRelation = 0; | ||
139 | this.key = cursor2.key; | ||
140 | this.fromValue = defaultValue; | ||
141 | this.toValue = cursor2.value; | ||
142 | } else throw new IllegalArgumentException("Inconsistent compare result for diffcursor"); | ||
143 | } | ||
144 | |||
145 | |||
146 | protected boolean sameValues() { | ||
147 | if(this.fromValue == null) { | ||
148 | return this.toValue == null; | ||
149 | } else { | ||
150 | return this.fromValue.equals(this.toValue); | ||
151 | } | ||
152 | } | ||
153 | protected boolean moveOne() { | ||
154 | if(isTerminated()) { | ||
155 | return false; | ||
156 | } | ||
157 | if(this.cursorRelation > 0 || cursor2.isTerminated()) { | ||
158 | return cursor1.move(); | ||
159 | } else if(this.cursorRelation < 0 || cursor1.isTerminated()) { | ||
160 | return cursor2.move(); | ||
161 | } else { | ||
162 | boolean moved1 = cursor1.move(); | ||
163 | boolean moved2 = cursor2.move(); | ||
164 | return moved1 && moved2; | ||
165 | } | ||
166 | } | ||
167 | private boolean skipNode() { | ||
168 | if(isTerminated()) { | ||
169 | throw new IllegalStateException("DiffCursor tries to skip when terminated!"); | ||
170 | } | ||
171 | boolean update1 = cursor1.skipCurrentNode(); | ||
172 | boolean update2 = cursor2.skipCurrentNode(); | ||
173 | updateState(); | ||
174 | return update1 && update2; | ||
175 | } | ||
176 | |||
177 | protected boolean moveToConsistentState() { | ||
178 | if(!isTerminated()) { | ||
179 | boolean changed; | ||
180 | boolean lastResult = true; | ||
181 | do { | ||
182 | changed = false; | ||
183 | if(MapCursor.sameSubnode(cursor1, cursor2)) { | ||
184 | lastResult = skipNode(); | ||
185 | changed = true; | ||
186 | } | ||
187 | if(sameValues()) { | ||
188 | lastResult = moveOne(); | ||
189 | changed = true; | ||
190 | } | ||
191 | updateState(); | ||
192 | } while(changed && !isTerminated()); | ||
193 | return lastResult; | ||
194 | } else { | ||
195 | return false; | ||
196 | } | ||
197 | } | ||
198 | |||
199 | public boolean move() { | ||
200 | if(!isTerminated()) { | ||
201 | if(isInHashClash()) { | ||
202 | this.resolveHashClash2(); | ||
203 | return true; | ||
204 | } else { | ||
205 | if(moveOne()) { | ||
206 | return moveToConsistentState(); | ||
207 | } else { | ||
208 | return false; | ||
209 | } | ||
210 | } | ||
211 | |||
212 | } else return false; | ||
213 | } | ||
214 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MutableNode.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MutableNode.java new file mode 100644 index 00000000..9e63b941 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MutableNode.java | |||
@@ -0,0 +1,449 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.internal; | ||
2 | |||
3 | import java.util.Arrays; | ||
4 | import java.util.Map; | ||
5 | |||
6 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
7 | |||
8 | public class MutableNode<K, V> extends Node<K, V> { | ||
9 | int cachedHash; | ||
10 | protected Object[] content; | ||
11 | |||
12 | protected MutableNode() { | ||
13 | this.content = new Object[2 * FACTOR]; | ||
14 | updateHash(); | ||
15 | } | ||
16 | |||
17 | public static <K, V> MutableNode<K, V> initialize(K key, V value, ContinousHashProvider<? super K> hashProvider, | ||
18 | V defaultValue) { | ||
19 | if (value == defaultValue) { | ||
20 | return null; | ||
21 | } else { | ||
22 | int hash = hashProvider.getHash(key, 0); | ||
23 | int fragment = hashFragment(hash, 0); | ||
24 | MutableNode<K, V> res = new MutableNode<>(); | ||
25 | res.content[2 * fragment] = key; | ||
26 | res.content[2 * fragment + 1] = value; | ||
27 | res.updateHash(); | ||
28 | return res; | ||
29 | } | ||
30 | } | ||
31 | |||
32 | /** | ||
33 | * Constructs a {@link MutableNode} as a copy of an {@link ImmutableNode} | ||
34 | * | ||
35 | * @param node | ||
36 | */ | ||
37 | protected MutableNode(ImmutableNode<K, V> node) { | ||
38 | this.content = new Object[2 * FACTOR]; | ||
39 | int dataUsed = 0; | ||
40 | int nodeUsed = 0; | ||
41 | for (int i = 0; i < FACTOR; i++) { | ||
42 | int bitposition = 1 << i; | ||
43 | if ((node.dataMap & bitposition) != 0) { | ||
44 | content[2 * i] = node.content[dataUsed * 2]; | ||
45 | content[2 * i + 1] = node.content[dataUsed * 2 + 1]; | ||
46 | dataUsed++; | ||
47 | } else if ((node.nodeMap & bitposition) != 0) { | ||
48 | content[2 * i + 1] = node.content[node.content.length - 1 - nodeUsed]; | ||
49 | nodeUsed++; | ||
50 | } | ||
51 | } | ||
52 | this.cachedHash = node.hashCode(); | ||
53 | } | ||
54 | |||
55 | @SuppressWarnings("unchecked") | ||
56 | @Override | ||
57 | public V getValue(K key, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) { | ||
58 | int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); | ||
59 | K keyCandidate = (K) this.content[2 * selectedHashFragment]; | ||
60 | if (keyCandidate != null) { | ||
61 | if (keyCandidate.equals(key)) { | ||
62 | return (V) this.content[2 * selectedHashFragment + 1]; | ||
63 | } else { | ||
64 | return defaultValue; | ||
65 | } | ||
66 | } else { | ||
67 | Node<K, V> nodeCandidate = (Node<K, V>) content[2 * selectedHashFragment + 1]; | ||
68 | if (nodeCandidate != null) { | ||
69 | int newDepth = depth + 1; | ||
70 | int newHash = newHash(hashProvider, key, hash, newDepth); | ||
71 | return nodeCandidate.getValue(key, hashProvider, defaultValue, newHash, newDepth); | ||
72 | } else { | ||
73 | return defaultValue; | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | |||
78 | @SuppressWarnings("unchecked") | ||
79 | @Override | ||
80 | public Node<K, V> putValue(K key, V value, OldValueBox<V> oldValue, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, | ||
81 | int depth) { | ||
82 | int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); | ||
83 | K keyCandidate = (K) content[2 * selectedHashFragment]; | ||
84 | if (keyCandidate != null) { | ||
85 | // If has key | ||
86 | if (keyCandidate.equals(key)) { | ||
87 | // The key is equals to an existing key -> update entry | ||
88 | if (value == defaultValue) { | ||
89 | return removeEntry(selectedHashFragment, oldValue); | ||
90 | } else { | ||
91 | return updateValue(value, oldValue, selectedHashFragment); | ||
92 | } | ||
93 | } else { | ||
94 | // The key is not equivalent to an existing key on the same hash bin | ||
95 | // -> split entry if it is necessary | ||
96 | if (value == defaultValue) { | ||
97 | // Value is default -> do not need to add new node | ||
98 | oldValue.setOldValue(defaultValue); | ||
99 | return this; | ||
100 | } else { | ||
101 | // Value is not default -> Split entry data to a new node | ||
102 | oldValue.setOldValue(defaultValue); | ||
103 | return moveDownAndSplit(hashProvider, key, value, keyCandidate, hash, depth, selectedHashFragment); | ||
104 | } | ||
105 | } | ||
106 | } else { | ||
107 | // If it does not have key, check for value | ||
108 | Node<K, V> nodeCandidate = (Node<K, V>) content[2 * selectedHashFragment + 1]; | ||
109 | if (nodeCandidate != null) { | ||
110 | // If it has value, it is a subnode -> upate that | ||
111 | Node<K, V> newNode = nodeCandidate.putValue(key, value, oldValue, hashProvider, defaultValue, | ||
112 | newHash(hashProvider, key, hash, depth + 1), depth + 1); | ||
113 | return updateWithSubNode(selectedHashFragment, newNode, value.equals(defaultValue)); | ||
114 | } else { | ||
115 | // If it does not have value, put it in the empty place | ||
116 | if (value == defaultValue) { | ||
117 | // dont need to add new key-value pair | ||
118 | oldValue.setOldValue(defaultValue); | ||
119 | return this; | ||
120 | } else { | ||
121 | return addEntry(key, value, oldValue, selectedHashFragment); | ||
122 | } | ||
123 | |||
124 | } | ||
125 | } | ||
126 | } | ||
127 | |||
128 | @SuppressWarnings("unchecked") | ||
129 | private Node<K, V> addEntry(K key, V value, OldValueBox<V> oldValue, int selectedHashFragment) { | ||
130 | content[2 * selectedHashFragment] = key; | ||
131 | oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]); | ||
132 | content[2 * selectedHashFragment + 1] = value; | ||
133 | updateHash(); | ||
134 | return this; | ||
135 | } | ||
136 | |||
137 | /** | ||
138 | * Updates an entry in a selected hash-fragment to a non-default value. | ||
139 | * | ||
140 | * @param value | ||
141 | * @param selectedHashFragment | ||
142 | * @return | ||
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 | updateHash(); | ||
149 | return this; | ||
150 | } | ||
151 | |||
152 | /** | ||
153 | * | ||
154 | * @param selectedHashFragment | ||
155 | * @param newNode | ||
156 | * @return | ||
157 | */ | ||
158 | Node<K, V> updateWithSubNode(int selectedHashFragment, Node<K, V> newNode, boolean deletionHappened) { | ||
159 | if (deletionHappened) { | ||
160 | if (newNode == null) { | ||
161 | // Check whether this node become empty | ||
162 | content[2 * selectedHashFragment + 1] = null; // i.e. the new node | ||
163 | if (hasContent()) { | ||
164 | updateHash(); | ||
165 | return this; | ||
166 | } else { | ||
167 | return null; | ||
168 | } | ||
169 | } else { | ||
170 | // check whether newNode is orphan | ||
171 | MutableNode<K, V> immutableNewNode = newNode.isMutable(); | ||
172 | if (immutableNewNode != null) { | ||
173 | int orphaned = immutableNewNode.isOrphaned(); | ||
174 | if (orphaned >= 0) { | ||
175 | // orphan subnode data is replaced with data | ||
176 | content[2 * selectedHashFragment] = immutableNewNode.content[orphaned * 2]; | ||
177 | content[2 * selectedHashFragment + 1] = immutableNewNode.content[orphaned * 2 + 1]; | ||
178 | updateHash(); | ||
179 | return this; | ||
180 | } | ||
181 | } | ||
182 | } | ||
183 | } | ||
184 | // normal behaviour | ||
185 | content[2 * selectedHashFragment + 1] = newNode; | ||
186 | updateHash(); | ||
187 | return this; | ||
188 | |||
189 | } | ||
190 | |||
191 | private boolean hasContent() { | ||
192 | for (Object element : this.content) { | ||
193 | if (element != null) | ||
194 | return true; | ||
195 | } | ||
196 | return false; | ||
197 | } | ||
198 | |||
199 | @Override | ||
200 | protected MutableNode<K, V> isMutable() { | ||
201 | return this; | ||
202 | } | ||
203 | |||
204 | protected int isOrphaned() { | ||
205 | int dataFound = -2; | ||
206 | for (int i = 0; i < FACTOR; i++) { | ||
207 | if (content[i * 2] != null) { | ||
208 | if (dataFound >= 0) { | ||
209 | return -1; | ||
210 | } else { | ||
211 | dataFound = i; | ||
212 | } | ||
213 | } else if (content[i * 2 + 1] != null) { | ||
214 | return -3; | ||
215 | } | ||
216 | } | ||
217 | return dataFound; | ||
218 | } | ||
219 | |||
220 | @SuppressWarnings("unchecked") | ||
221 | private Node<K, V> moveDownAndSplit(ContinousHashProvider<? super K> hashProvider, K newKey, V newValue, | ||
222 | K previousKey, int hashOfNewKey, int depth, int selectedHashFragmentOfCurrentDepth) { | ||
223 | V previousValue = (V) content[2 * selectedHashFragmentOfCurrentDepth + 1]; | ||
224 | |||
225 | MutableNode<K, V> newSubNode = newNodeWithTwoEntries(hashProvider, previousKey, previousValue, | ||
226 | hashProvider.getHash(previousKey, hashDepth(depth)), newKey, newValue, hashOfNewKey, depth + 1); | ||
227 | |||
228 | content[2 * selectedHashFragmentOfCurrentDepth] = null; | ||
229 | content[2 * selectedHashFragmentOfCurrentDepth + 1] = newSubNode; | ||
230 | updateHash(); | ||
231 | return this; | ||
232 | } | ||
233 | |||
234 | private MutableNode<K, V> newNodeWithTwoEntries(ContinousHashProvider<? super K> hashProvider, K key1, V value1, | ||
235 | int oldHash1, K key2, V value2, int oldHash2, int newdepth) { | ||
236 | int newHash1 = newHash(hashProvider, key1, oldHash1, newdepth); | ||
237 | int newHash2 = newHash(hashProvider, key2, oldHash2, newdepth); | ||
238 | int newFragment1 = hashFragment(newHash1, shiftDepth(newdepth)); | ||
239 | int newFragment2 = hashFragment(newHash2, shiftDepth(newdepth)); | ||
240 | |||
241 | MutableNode<K, V> subNode = new MutableNode<>(); | ||
242 | if (newFragment1 != newFragment2) { | ||
243 | subNode.content[newFragment1 * 2] = key1; | ||
244 | subNode.content[newFragment1 * 2 + 1] = value1; | ||
245 | |||
246 | subNode.content[newFragment2 * 2] = key2; | ||
247 | subNode.content[newFragment2 * 2 + 1] = value2; | ||
248 | } else { | ||
249 | MutableNode<K, V> subSubNode = newNodeWithTwoEntries(hashProvider, key1, value1, newHash1, key2, value2, | ||
250 | newHash2, newdepth + 1); | ||
251 | subNode.content[newFragment1 * 2 + 1] = subSubNode; | ||
252 | } | ||
253 | subNode.updateHash(); | ||
254 | return subNode; | ||
255 | } | ||
256 | |||
257 | @SuppressWarnings("unchecked") | ||
258 | Node<K, V> removeEntry(int selectedHashFragment, OldValueBox<V> oldValue) { | ||
259 | content[2 * selectedHashFragment] = null; | ||
260 | oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]); | ||
261 | content[2 * selectedHashFragment + 1] = null; | ||
262 | if (hasContent()) { | ||
263 | updateHash(); | ||
264 | return this; | ||
265 | } else { | ||
266 | return null; | ||
267 | } | ||
268 | } | ||
269 | |||
270 | @SuppressWarnings("unchecked") | ||
271 | @Override | ||
272 | public long getSize() { | ||
273 | int size = 0; | ||
274 | for (int i = 0; i < FACTOR; i++) { | ||
275 | if (content[i * 2] != null) { | ||
276 | size++; | ||
277 | } else { | ||
278 | Node<K, V> nodeCandidate = (Node<K, V>) content[i * 2 + 1]; | ||
279 | if (nodeCandidate != null) { | ||
280 | size += nodeCandidate.getSize(); | ||
281 | } | ||
282 | } | ||
283 | } | ||
284 | return size; | ||
285 | } | ||
286 | |||
287 | @Override | ||
288 | protected MutableNode<K, V> toMutable() { | ||
289 | return this; | ||
290 | } | ||
291 | |||
292 | @Override | ||
293 | public ImmutableNode<K, V> toImmutable(Map<Node<K, V>, ImmutableNode<K, V>> cache) { | ||
294 | return ImmutableNode.constructImmutable(this, cache); | ||
295 | } | ||
296 | |||
297 | @SuppressWarnings("unchecked") | ||
298 | @Override | ||
299 | boolean moveToNext(MapCursor<K, V> cursor) { | ||
300 | // 1. try to move to data | ||
301 | if (cursor.dataIndex != MapCursor.INDEX_FINISH) { | ||
302 | for (int index = cursor.dataIndex + 1; index < FACTOR; index++) { | ||
303 | if (this.content[index * 2] != null) { | ||
304 | // 1.1 found next data | ||
305 | cursor.dataIndex = index; | ||
306 | cursor.key = (K) this.content[index * 2]; | ||
307 | cursor.value = (V) this.content[index * 2 + 1]; | ||
308 | return true; | ||
309 | } | ||
310 | } | ||
311 | cursor.dataIndex = MapCursor.INDEX_FINISH; | ||
312 | } | ||
313 | |||
314 | // 2. look inside the subnodes | ||
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 subnode, move down to the subnode | ||
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 subnode 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 | public void prettyPrint(StringBuilder builder, int depth, int code) { | ||
344 | for (int i = 0; i < depth; i++) { | ||
345 | builder.append("\t"); | ||
346 | } | ||
347 | if (code >= 0) { | ||
348 | builder.append(code); | ||
349 | builder.append(":"); | ||
350 | } | ||
351 | builder.append("Mutable("); | ||
352 | // print content | ||
353 | boolean hadContent = false; | ||
354 | for (int i = 0; i < FACTOR; i++) { | ||
355 | if (content[2 * i] != null) { | ||
356 | if (hadContent) { | ||
357 | builder.append(","); | ||
358 | } | ||
359 | builder.append(i); | ||
360 | builder.append(":["); | ||
361 | builder.append(content[2 * i].toString()); | ||
362 | builder.append("]->["); | ||
363 | builder.append(content[2 * i + 1].toString()); | ||
364 | builder.append("]"); | ||
365 | hadContent = true; | ||
366 | } | ||
367 | } | ||
368 | builder.append(")"); | ||
369 | // print subnodes | ||
370 | for (int i = 0; i < FACTOR; i++) { | ||
371 | if (content[2 * i] == null && content[2 * i + 1] != null) { | ||
372 | @SuppressWarnings("unchecked") | ||
373 | Node<K, V> subNode = (Node<K, V>) content[2 * i + 1]; | ||
374 | builder.append("\n"); | ||
375 | subNode.prettyPrint(builder, depth + 1, i); | ||
376 | } | ||
377 | } | ||
378 | } | ||
379 | |||
380 | @SuppressWarnings({ "unchecked" }) | ||
381 | @Override | ||
382 | public void checkIntegrity(ContinousHashProvider<? super K> hashProvider, V defaultValue, int depth) { | ||
383 | // check for orphan nodes | ||
384 | if (depth > 0) { | ||
385 | int orphaned = isOrphaned(); | ||
386 | if (orphaned >= 0) { | ||
387 | throw new IllegalStateException("Orphaned node! " + orphaned + ": " + content[2 * orphaned]); | ||
388 | } | ||
389 | } | ||
390 | // check the place of data | ||
391 | for (int i = 0; i < FACTOR; i++) { | ||
392 | if (this.content[2 * i] != null) { | ||
393 | K key = (K) this.content[2 * i]; | ||
394 | V value = (V) this.content[2 * i + 1]; | ||
395 | |||
396 | if (value == defaultValue) { | ||
397 | throw new IllegalStateException("Node contains default value!"); | ||
398 | } | ||
399 | int hashCode = hashProvider.getHash(key, hashDepth(depth)); | ||
400 | int shiftDepth = shiftDepth(depth); | ||
401 | int selectedHashFragment = hashFragment(hashCode, shiftDepth); | ||
402 | if (i != selectedHashFragment) { | ||
403 | throw new IllegalStateException("Key " + key + " with hash code " + hashCode | ||
404 | + " is in bad place! Fragment=" + selectedHashFragment + ", Place=" + i); | ||
405 | } | ||
406 | } | ||
407 | } | ||
408 | // check subnodes | ||
409 | for (int i = 0; i < FACTOR; i++) { | ||
410 | if (this.content[2 * i + 1] != null && this.content[2 * i] == null) { | ||
411 | Node<K, V> subNode = (Node<K, V>) this.content[2 * i + 1]; | ||
412 | subNode.checkIntegrity(hashProvider, defaultValue, depth + 1); | ||
413 | } | ||
414 | } | ||
415 | // check the hash | ||
416 | int oldHash = this.cachedHash; | ||
417 | updateHash(); | ||
418 | int newHash = this.cachedHash; | ||
419 | if (oldHash != newHash) { | ||
420 | throw new IllegalStateException("Hash code was not up to date! (old=" + oldHash + ",new=" + newHash + ")"); | ||
421 | } | ||
422 | } | ||
423 | |||
424 | protected void updateHash() { | ||
425 | this.cachedHash = Arrays.hashCode(content); | ||
426 | } | ||
427 | |||
428 | @Override | ||
429 | public int hashCode() { | ||
430 | return this.cachedHash; | ||
431 | } | ||
432 | |||
433 | @Override | ||
434 | public boolean equals(Object obj) { | ||
435 | if (this == obj) | ||
436 | return true; | ||
437 | if (obj == null) | ||
438 | return false; | ||
439 | if (obj instanceof MutableNode<?, ?>) { | ||
440 | MutableNode<?, ?> other = (MutableNode<?, ?>) obj; | ||
441 | return Arrays.deepEquals(this.content, other.content); | ||
442 | } else if (obj instanceof ImmutableNode<?, ?>) { | ||
443 | ImmutableNode<?, ?> other = (ImmutableNode<?, ?>) obj; | ||
444 | return ImmutableNode.compareImmutableMutable(other, this); | ||
445 | } else { | ||
446 | return false; | ||
447 | } | ||
448 | } | ||
449 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/Node.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/Node.java new file mode 100644 index 00000000..d40f980a --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/Node.java | |||
@@ -0,0 +1,85 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.internal; | ||
2 | |||
3 | import java.util.Map; | ||
4 | |||
5 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
6 | |||
7 | public abstract class Node<K,V>{ | ||
8 | public static final int BRANCHING_FACTOR_BITS = 5; | ||
9 | public static final int FACTOR = 1<<BRANCHING_FACTOR_BITS; | ||
10 | protected static final int NUMBER_OF_FACTORS = Integer.SIZE / BRANCHING_FACTOR_BITS; | ||
11 | protected static final int FACTOR_MASK = FACTOR-1; | ||
12 | public static final int EFFECTIVE_BITS = BRANCHING_FACTOR_BITS * NUMBER_OF_FACTORS; | ||
13 | |||
14 | /** | ||
15 | * Calculates the index for the continuous hash. | ||
16 | * @param depth The depth of the node in the tree. | ||
17 | * @return The index of the continuous hash. | ||
18 | */ | ||
19 | protected static int hashDepth(int depth) { | ||
20 | return depth/NUMBER_OF_FACTORS; | ||
21 | } | ||
22 | |||
23 | /** | ||
24 | * Calculates the which segment of a single hash should be used. | ||
25 | * @param depth The depth of the node in the tree. | ||
26 | * @return The segment of a hash code. | ||
27 | */ | ||
28 | protected static int shiftDepth(int depth) { | ||
29 | return depth%NUMBER_OF_FACTORS; | ||
30 | } | ||
31 | /** | ||
32 | * Selects a segments from a complete hash for a given depth. | ||
33 | * @param hash A complete hash. | ||
34 | * @param shiftDepth The index of the segment. | ||
35 | * @return The segment as an integer. | ||
36 | */ | ||
37 | protected static int hashFragment(int hash, int shiftDepth) { | ||
38 | if(shiftDepth<0 || Node.NUMBER_OF_FACTORS<shiftDepth) throw new IllegalArgumentException("Invalid shift depth! valid intervall=[0;5], input="+shiftDepth); | ||
39 | return (hash >>> shiftDepth*BRANCHING_FACTOR_BITS) & FACTOR_MASK; | ||
40 | } | ||
41 | |||
42 | /** | ||
43 | * Returns the hash code for a given depth. It may calculate new hash code, or reuse a hash code calculated for depth-1. | ||
44 | * @param key The key. | ||
45 | * @param hash Hash code for depth-1 | ||
46 | * @param depth The depth. | ||
47 | * @return The new hash code. | ||
48 | */ | ||
49 | protected int newHash(final ContinousHashProvider<? super K> hashProvider, K key, int hash, int depth) { | ||
50 | final int hashDepth = hashDepth(depth); | ||
51 | if(hashDepth>=ContinousHashProvider.MAX_PRACTICAL_DEPTH) { | ||
52 | throw new IllegalArgumentException("Key "+key+" have the clashing hashcode over the practical depth limitation ("+ContinousHashProvider.MAX_PRACTICAL_DEPTH+")!"); | ||
53 | } | ||
54 | return depth%NUMBER_OF_FACTORS == 0? | ||
55 | hashProvider.getHash(key, hashDepth) : | ||
56 | hash; | ||
57 | } | ||
58 | |||
59 | |||
60 | public abstract V getValue(K key, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth); | ||
61 | public abstract Node<K,V> putValue(K key, V value, OldValueBox<V> old, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth); | ||
62 | public abstract long getSize(); | ||
63 | |||
64 | abstract MutableNode<K, V> toMutable(); | ||
65 | public abstract ImmutableNode<K, V> toImmutable( | ||
66 | Map<Node<K, V>,ImmutableNode<K, V>> cache); | ||
67 | protected abstract MutableNode<K, V> isMutable(); | ||
68 | /** | ||
69 | * Moves a {@link MapCursor} to its next position. | ||
70 | * @param cursor the cursor | ||
71 | * @return Whether there was a next value to move on. | ||
72 | */ | ||
73 | abstract boolean moveToNext(MapCursor<K,V> cursor); | ||
74 | |||
75 | ///////// FOR printing | ||
76 | public abstract void prettyPrint(StringBuilder builder, int depth, int code); | ||
77 | @Override | ||
78 | public String toString() { | ||
79 | StringBuilder stringBuilder = new StringBuilder(); | ||
80 | prettyPrint(stringBuilder, 0, -1); | ||
81 | return stringBuilder.toString(); | ||
82 | } | ||
83 | public void checkIntegrity(ContinousHashProvider<? super K> hashProvider, V defaultValue, int depth) {} | ||
84 | |||
85 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/OldValueBox.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/OldValueBox.java new file mode 100644 index 00000000..23502857 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/OldValueBox.java | |||
@@ -0,0 +1,19 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.internal; | ||
2 | |||
3 | public class OldValueBox<V>{ | ||
4 | V oldValue; | ||
5 | boolean isSet = false; | ||
6 | |||
7 | public V getOldValue() { | ||
8 | if(!isSet) throw new IllegalStateException(); | ||
9 | isSet = false; | ||
10 | return oldValue; | ||
11 | } | ||
12 | |||
13 | public void setOldValue(V ouldValue) { | ||
14 | if(isSet) throw new IllegalStateException(); | ||
15 | this.oldValue = ouldValue; | ||
16 | isSet = true; | ||
17 | } | ||
18 | |||
19 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/VersionedMapImpl.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/VersionedMapImpl.java new file mode 100644 index 00000000..de41e602 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/VersionedMapImpl.java | |||
@@ -0,0 +1,171 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.internal; | ||
2 | |||
3 | import java.util.Iterator; | ||
4 | import java.util.LinkedList; | ||
5 | import java.util.List; | ||
6 | |||
7 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
8 | import org.eclipse.viatra.solver.data.map.Cursor; | ||
9 | import org.eclipse.viatra.solver.data.map.DiffCursor; | ||
10 | import org.eclipse.viatra.solver.data.map.VersionedMap; | ||
11 | import org.eclipse.viatra.solver.data.map.VersionedMapStoreImpl; | ||
12 | |||
13 | /** | ||
14 | * Not threadSafe in itself | ||
15 | * @author Oszkar Semerath | ||
16 | * | ||
17 | * @param <K> | ||
18 | * @param <V> | ||
19 | */ | ||
20 | public class VersionedMapImpl<K,V> implements VersionedMap<K,V>{ | ||
21 | protected final VersionedMapStoreImpl<K,V> store; | ||
22 | |||
23 | protected final ContinousHashProvider<K> hashProvider; | ||
24 | protected final V defaultValue; | ||
25 | protected Node<K,V> root; | ||
26 | |||
27 | private OldValueBox<V> oldValueBox = new OldValueBox<>(); | ||
28 | |||
29 | public VersionedMapImpl( | ||
30 | VersionedMapStoreImpl<K,V> store, | ||
31 | ContinousHashProvider<K> hashProvider, | ||
32 | V defaultValue) | ||
33 | { | ||
34 | this.store = store; | ||
35 | this.hashProvider = hashProvider; | ||
36 | this.defaultValue = defaultValue; | ||
37 | this.root = null; | ||
38 | } | ||
39 | public VersionedMapImpl( | ||
40 | VersionedMapStoreImpl<K,V> store, | ||
41 | ContinousHashProvider<K> hashProvider, | ||
42 | V defaultValue, Node<K,V> data) | ||
43 | { | ||
44 | this.store = store; | ||
45 | this.hashProvider = hashProvider; | ||
46 | this.defaultValue = defaultValue; | ||
47 | this.root = data; | ||
48 | } | ||
49 | |||
50 | public V getDefaultValue() { | ||
51 | return defaultValue; | ||
52 | } | ||
53 | public ContinousHashProvider<K> getHashProvider() { | ||
54 | return hashProvider; | ||
55 | } | ||
56 | @Override | ||
57 | public V put(K key, V value) { | ||
58 | if(root!=null) { | ||
59 | root = root.putValue(key, value, oldValueBox, hashProvider, defaultValue, hashProvider.getHash(key, 0), 0); | ||
60 | return oldValueBox.getOldValue(); | ||
61 | } else { | ||
62 | root = MutableNode.initialize(key, value, hashProvider, defaultValue); | ||
63 | return defaultValue; | ||
64 | } | ||
65 | } | ||
66 | |||
67 | @Override | ||
68 | public void putAll(Cursor<K, V> cursor) { | ||
69 | if(cursor.getDependingMaps().contains(this)) { | ||
70 | List<K> keys = new LinkedList<>(); | ||
71 | List<V> values = new LinkedList<>(); | ||
72 | while(cursor.move()) { | ||
73 | keys.add(cursor.getKey()); | ||
74 | values.add(cursor.getValue()); | ||
75 | } | ||
76 | Iterator<K> keyIterator = keys.iterator(); | ||
77 | Iterator<V> valueIterator = values.iterator(); | ||
78 | while(keyIterator.hasNext()) { | ||
79 | this.put(keyIterator.next(), valueIterator.next()); | ||
80 | } | ||
81 | } else { | ||
82 | while(cursor.move()) { | ||
83 | this.put(cursor.getKey(), cursor.getValue()); | ||
84 | } | ||
85 | } | ||
86 | } | ||
87 | |||
88 | @Override | ||
89 | public V get(K key) { | ||
90 | if(root!=null) { | ||
91 | return root.getValue(key, hashProvider, defaultValue, hashProvider.getHash(key, 0), 0); | ||
92 | } else { | ||
93 | return defaultValue; | ||
94 | } | ||
95 | } | ||
96 | @Override | ||
97 | public long getSize() { | ||
98 | if(root == null) { | ||
99 | return 0; | ||
100 | } else { | ||
101 | return root.getSize(); | ||
102 | } | ||
103 | } | ||
104 | |||
105 | @Override | ||
106 | public Cursor<K, V> getAll() { | ||
107 | return new MapCursor<>(this.root,this); | ||
108 | } | ||
109 | @Override | ||
110 | public DiffCursor<K, V> getDiffCursor(long toVersion) { | ||
111 | Cursor<K, V> fromCursor = this.getAll(); | ||
112 | VersionedMap<K, V> toMap = this.store.createMap(toVersion); | ||
113 | Cursor<K, V> toCursor = toMap.getAll(); | ||
114 | return new MapDiffCursor<>(this.hashProvider,this.defaultValue, fromCursor, toCursor); | ||
115 | |||
116 | } | ||
117 | |||
118 | |||
119 | @Override | ||
120 | public long commit() { | ||
121 | return this.store.commit(root,this); | ||
122 | } | ||
123 | public void setRoot(Node<K, V> root) { | ||
124 | this.root = root; | ||
125 | } | ||
126 | |||
127 | @Override | ||
128 | public void restore(long state) { | ||
129 | root = this.store.revert(state); | ||
130 | } | ||
131 | |||
132 | @Override | ||
133 | public int hashCode() { | ||
134 | final int prime = 31; | ||
135 | int result = 1; | ||
136 | result = prime * result + ((root == null) ? 0 : root.hashCode()); | ||
137 | return result; | ||
138 | } | ||
139 | |||
140 | @Override | ||
141 | public boolean equals(Object obj) { | ||
142 | if (this == obj) | ||
143 | return true; | ||
144 | if (obj == null) | ||
145 | return false; | ||
146 | if (getClass() != obj.getClass()) | ||
147 | return false; | ||
148 | VersionedMapImpl<?,?> other = (VersionedMapImpl<?,?>) obj; | ||
149 | if (root == null) { | ||
150 | if (other.root != null) | ||
151 | return false; | ||
152 | } else if (!root.equals(other.root)) | ||
153 | return false; | ||
154 | return true; | ||
155 | } | ||
156 | public void prettyPrint() { | ||
157 | StringBuilder s = new StringBuilder(); | ||
158 | if(this.root != null) { | ||
159 | this.root.prettyPrint(s, 0, -1); | ||
160 | System.out.println(s.toString()); | ||
161 | } else { | ||
162 | System.out.println("empty tree"); | ||
163 | } | ||
164 | } | ||
165 | public void checkIntegrity() { | ||
166 | if(this.root != null) { | ||
167 | this.root.checkIntegrity(hashProvider, defaultValue, 0); | ||
168 | } | ||
169 | } | ||
170 | |||
171 | } | ||