aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store/src/main/java/tools
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-09-05 20:33:31 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-09-05 20:33:31 +0200
commite5f14a3e9d92194c3f972f90a2f78c9c3dacaef4 (patch)
treeff1ebefb45348be48a23a89fa12b4817bc52bf51 /subprojects/store/src/main/java/tools
parentfeat(web): control server settings with env vars (diff)
parentrestructured DSE framework, failing build (diff)
downloadrefinery-e5f14a3e9d92194c3f972f90a2f78c9c3dacaef4.tar.gz
refinery-e5f14a3e9d92194c3f972f90a2f78c9c3dacaef4.tar.zst
refinery-e5f14a3e9d92194c3f972f90a2f78c9c3dacaef4.zip
Merge remote-tracking branch 'OszkarSemerath/datastructure' into partial-interpretation
Diffstat (limited to 'subprojects/store/src/main/java/tools')
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/AnyVersionedMap.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/ContinuousHashProvider.java (renamed from subprojects/store/src/main/java/tools/refinery/store/map/ContinousHashProvider.java)14
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/IteratorAsCursor.java64
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/Version.java26
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java18
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java4
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java18
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactory.java24
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java30
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java132
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java23
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java229
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java90
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java150
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaBasedVersionedMapStoreFactory.java38
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaDiffCursor.java147
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapDelta.java20
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapTransaction.java41
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaArrayStore.java36
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaMapStore.java53
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaStore.java29
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java237
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapStoreDeltaImpl.java94
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/ImmutableNode.java (renamed from subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java)163
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/InOrderMapCursor.java146
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapCursor.java (renamed from subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java)54
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapDiffCursor.java264
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MutableNode.java (renamed from subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java)254
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/Node.java131
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/OldValueBox.java (renamed from subprojects/store/src/main/java/tools/refinery/store/map/internal/OldValueBox.java)6
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/StateBasedVersionedMapStoreFactory.java43
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStateImpl.java (renamed from subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java)62
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateConfiguration.java (renamed from subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreConfiguration.java)25
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateImpl.java119
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java3
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/Model.java10
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java4
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java8
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java127
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java34
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/IndexedVersionedInterpretation.java6
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java66
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java18
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java37
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelVersion.java29
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/NullaryVersionedInterpretation.java6
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/UnaryVersionedInterpretation.java6
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java36
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/Morphism.java11
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/ObjectCode.java11
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculator.java10
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculatorFactory.java15
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java23
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderBuilder.java45
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderResult.java9
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderStoreAdapter.java17
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java21
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java39
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java71
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java74
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java96
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java193
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java115
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java81
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java89
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairingPermutations.java57
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java33
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java64
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java166
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/TrivialNodePairing.java36
70 files changed, 3524 insertions, 931 deletions
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/AnyVersionedMap.java b/subprojects/store/src/main/java/tools/refinery/store/map/AnyVersionedMap.java
index 25fc91e6..01099eb0 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/AnyVersionedMap.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/AnyVersionedMap.java
@@ -42,4 +42,9 @@ public sealed interface AnyVersionedMap extends Versioned permits VersionedMap {
42 @SuppressWarnings("squid:S1133") 42 @SuppressWarnings("squid:S1133")
43 @Deprecated(since = "0.0.0") 43 @Deprecated(since = "0.0.0")
44 boolean equals(Object obj); 44 boolean equals(Object obj);
45
46 /**
47 * Checks the integrity of the map, and throws an exception if an inconsistency is detected.
48 */
49 void checkIntegrity();
45} 50}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/ContinousHashProvider.java b/subprojects/store/src/main/java/tools/refinery/store/map/ContinuousHashProvider.java
index 8e451230..abc044d0 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/ContinousHashProvider.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/ContinuousHashProvider.java
@@ -5,17 +5,17 @@
5 */ 5 */
6package tools.refinery.store.map; 6package tools.refinery.store.map;
7 7
8import tools.refinery.store.map.internal.Node; 8import tools.refinery.store.map.internal.state.Node;
9 9
10/** 10/**
11 * A class representing an equivalence relation for a type {@code K} with a 11 * A class representing an equivalence relation for a type {@code K} with a
12 * continuous hash function. 12 * continuous hash function.
13 * 13 *
14 * @author Oszkar Semerath 14 * @author Oszkar Semerath
15 * 15 *
16 * @param <K> Target java type. 16 * @param <K> Target java type.
17 */ 17 */
18public interface ContinousHashProvider<K> { 18public interface ContinuousHashProvider<K> {
19 public static final int EFFECTIVE_BITS = Node.EFFECTIVE_BITS; 19 public static final int EFFECTIVE_BITS = Node.EFFECTIVE_BITS;
20 public static final int EFFECTIVE_BIT_MASK = (1 << (EFFECTIVE_BITS)) - 1; 20 public static final int EFFECTIVE_BIT_MASK = (1 << (EFFECTIVE_BITS)) - 1;
21 21
@@ -38,9 +38,9 @@ public interface ContinousHashProvider<K> {
38 * {@link #EFFECTIVE_BITS} 38 * {@link #EFFECTIVE_BITS}
39 * </ul> 39 * </ul>
40 * Check {@link #equals} for further details. 40 * Check {@link #equals} for further details.
41 * 41 *
42 * @param key The target data object. 42 * @param key The target data object.
43 * @param index The depth of the the hash code. Needs to be non-negative. 43 * @param index The depth of the hash code. Needs to be non-negative.
44 * @return A hash code. 44 * @return A hash code.
45 */ 45 */
46 public int getHash(K key, int index); 46 public int getHash(K key, int index);
@@ -53,7 +53,7 @@ public interface ContinousHashProvider<K> {
53 if (key1.equals(key2)) { 53 if (key1.equals(key2)) {
54 return 0; 54 return 0;
55 } else { 55 } else {
56 for (int i = 0; i < ContinousHashProvider.MAX_PRACTICAL_DEPTH; i++) { 56 for (int i = 0; i < ContinuousHashProvider.MAX_PRACTICAL_DEPTH; i++) {
57 int hash1 = getEffectiveHash(key1, i); 57 int hash1 = getEffectiveHash(key1, i);
58 int hash2 = getEffectiveHash(key2, i); 58 int hash2 = getEffectiveHash(key2, i);
59 for(int j = 0; j<Integer.SIZE/Node.BRANCHING_FACTOR_BITS; j++) { 59 for(int j = 0; j<Integer.SIZE/Node.BRANCHING_FACTOR_BITS; j++) {
@@ -68,7 +68,7 @@ public interface ContinousHashProvider<K> {
68 } 68 }
69 throw new IllegalArgumentException("Two different keys (" + key1 + " and " + key2 69 throw new IllegalArgumentException("Two different keys (" + key1 + " and " + key2
70 + ") have the same hashcode over the practical depth limitation (" 70 + ") have the same hashcode over the practical depth limitation ("
71 + ContinousHashProvider.MAX_PRACTICAL_DEPTH + ")!"); 71 + ContinuousHashProvider.MAX_PRACTICAL_DEPTH + ")!");
72 } 72 }
73 } 73 }
74} 74}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/IteratorAsCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/IteratorAsCursor.java
new file mode 100644
index 00000000..29b03edf
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/IteratorAsCursor.java
@@ -0,0 +1,64 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map;
7
8import java.util.Iterator;
9import java.util.Map;
10import java.util.Map.Entry;
11import java.util.Set;
12
13public class IteratorAsCursor<K, V> implements Cursor<K, V> {
14 final Iterator<Entry<K, V>> iterator;
15 final VersionedMap<K, V> source;
16
17 private boolean terminated;
18 private K key;
19 private V value;
20
21 public IteratorAsCursor(VersionedMap<K, V> source, Map<K, V> current) {
22 this.iterator = current.entrySet().iterator();
23 this.source = source;
24 }
25
26 @Override
27 public K getKey() {
28 return key;
29 }
30
31 @Override
32 public V getValue() {
33 return value;
34 }
35
36 @Override
37 public boolean isTerminated() {
38 return terminated;
39 }
40
41 @Override
42 public boolean move() {
43 terminated = !iterator.hasNext();
44 if (terminated) {
45 this.key = null;
46 this.value = null;
47 } else {
48 Entry<K, V> next = iterator.next();
49 this.key = next.getKey();
50 this.value = next.getValue();
51 }
52 return !terminated;
53 }
54
55 @Override
56 public boolean isDirty() {
57 return false;
58 }
59
60 @Override
61 public Set<AnyVersionedMap> getDependingMaps() {
62 return Set.of(this.source);
63 }
64}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/Version.java b/subprojects/store/src/main/java/tools/refinery/store/map/Version.java
new file mode 100644
index 00000000..fa2734e4
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/Version.java
@@ -0,0 +1,26 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map;
7
8/**
9 * Interface denoting versions of {@link Versioned}.
10 */
11public interface Version {
12 /**
13 * Hashcode should be updated in accordance with equals.
14 * @return a hashcode of the object.
15 */
16 int hashCode();
17
18 /**
19 * Equivalence of two {@link Version}. This equivalence must satisfy the following constraint (in addition to the
20 * constraints of {@link Object#equals(Object)}: if {@code v1} and {@code v2} are {@link Version}s, and {@code v1
21 * .equals(v2)}, then {@code versioned.restore(v1)} must be {@code equals} to {@code versioned.restore(v2)}.
22 * @param o the other object.
23 * @return weather the two versions are equals.
24 */
25 boolean equals(Object o);
26}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java b/subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java
index 55720db3..da12b0a9 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java
@@ -5,8 +5,20 @@
5 */ 5 */
6package tools.refinery.store.map; 6package tools.refinery.store.map;
7 7
8/**
9 * Object that can save and restore its state.
10 */
8public interface Versioned { 11public interface Versioned {
9 public long commit(); 12 /**
10 //maybe revert()? 13 * Saves the state of the object.
11 public void restore(long state); 14 * @return an object that marks the version of the object at the time the function was called.
15 */
16 Version commit();
17
18 /**
19 * Restores the state of the object.
20 * @param state a {@link Version} object that marks the version. The state must be a {@link Version} object
21 * returned by a previous {@link #commit()}!
22 */
23 void restore(Version state);
12} 24}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java
index 9bbde24d..28194b58 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java
@@ -6,6 +6,8 @@
6package tools.refinery.store.map; 6package tools.refinery.store.map;
7 7
8public non-sealed interface VersionedMap<K, V> extends AnyVersionedMap { 8public non-sealed interface VersionedMap<K, V> extends AnyVersionedMap {
9 V getDefaultValue();
10
9 V get(K key); 11 V get(K key);
10 12
11 Cursor<K, V> getAll(); 13 Cursor<K, V> getAll();
@@ -14,5 +16,5 @@ public non-sealed interface VersionedMap<K, V> extends AnyVersionedMap {
14 16
15 void putAll(Cursor<K, V> cursor); 17 void putAll(Cursor<K, V> cursor);
16 18
17 DiffCursor<K, V> getDiffCursor(long state); 19 DiffCursor<K, V> getDiffCursor(Version state);
18} 20}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java
index 5aafa338..55cf08a5 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java
@@ -5,15 +5,17 @@
5 */ 5 */
6package tools.refinery.store.map; 6package tools.refinery.store.map;
7 7
8import java.util.Set; 8import tools.refinery.store.map.internal.VersionedMapStoreFactoryBuilderImpl;
9 9
10public interface VersionedMapStore<K, V> { 10public interface VersionedMapStore<K, V> {
11
12 public VersionedMap<K, V> createMap();
13 11
14 public VersionedMap<K, V> createMap(long state); 12 VersionedMap<K, V> createMap();
15
16 public Set<Long> getStates();
17 13
18 public DiffCursor<K,V> getDiffCursor(long fromState, long toState); 14 VersionedMap<K, V> createMap(Version state);
19} \ No newline at end of file 15
16 DiffCursor<K,V> getDiffCursor(Version fromState, Version toState);
17
18 static <K,V> VersionedMapStoreFactoryBuilder<K,V> builder() {
19 return new VersionedMapStoreFactoryBuilderImpl<>();
20 }
21}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactory.java
new file mode 100644
index 00000000..baf6ab50
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactory.java
@@ -0,0 +1,24 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map;
7
8import java.util.List;
9
10public interface VersionedMapStoreFactory<K,V> {
11 /**
12 * Constructs a new instance of {@link VersionedMap}.
13 * @return The new instance.
14 */
15 VersionedMapStore<K,V> createOne();
16
17 /**
18 * Constructs a group of {@link VersionedMap}s with the same configuration. If possible, the stores share
19 * resources with each other.
20 * @param amount The amount of new instances to be created.
21 * @return A list of new stores with the given number of elements.
22 */
23 List<VersionedMapStore<K, V>> createGroup(int amount);
24}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java
new file mode 100644
index 00000000..0ac196f2
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map;
7
8public interface VersionedMapStoreFactoryBuilder<K,V> {
9 enum StoreStrategy {
10 STATE, DELTA
11 }
12
13 enum DeltaTransactionStrategy {
14 LIST, SET
15 }
16
17 enum SharingStrategy {
18 NO_NODE_CACHE, SHARED_NODE_CACHE, SHARED_NODE_CACHE_IN_GROUP
19 }
20
21 VersionedMapStoreFactoryBuilder<K,V> defaultValue(V defaultValue);
22 VersionedMapStoreFactoryBuilder<K,V> strategy(StoreStrategy strategy);
23 VersionedMapStoreFactoryBuilder<K,V> versionFreeing(boolean enabled);
24 VersionedMapStoreFactoryBuilder<K,V> stateBasedImmutableWhenCommitting(boolean transformToImmutable);
25 VersionedMapStoreFactoryBuilder<K,V> stateBasedSharingStrategy(SharingStrategy sharingStrategy);
26 VersionedMapStoreFactoryBuilder<K,V> stateBasedHashProvider(ContinuousHashProvider<K> hashProvider);
27 VersionedMapStoreFactoryBuilder<K,V> deltaTransactionStrategy(DeltaTransactionStrategy deltaStrategy);
28
29 VersionedMapStoreFactory<K,V> build();
30}
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
deleted file mode 100644
index aade4aeb..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java
+++ /dev/null
@@ -1,132 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map;
7
8import tools.refinery.store.map.internal.ImmutableNode;
9import tools.refinery.store.map.internal.MapDiffCursor;
10import tools.refinery.store.map.internal.Node;
11import tools.refinery.store.map.internal.VersionedMapImpl;
12
13import java.util.*;
14
15public class VersionedMapStoreImpl<K, V> implements VersionedMapStore<K, V> {
16 // Configuration
17 private final boolean immutableWhenCommitting;
18
19 // Static data
20 protected final ContinousHashProvider<K> hashProvider;
21 protected final V defaultValue;
22
23 // Dynamic data
24 protected final Map<Long, ImmutableNode<K, V>> states = new HashMap<>();
25 protected final Map<Node<K, V>, ImmutableNode<K, V>> nodeCache;
26 protected long nextID = 0;
27
28 public VersionedMapStoreImpl(ContinousHashProvider<K> hashProvider, V defaultValue,
29 VersionedMapStoreConfiguration config) {
30 this.immutableWhenCommitting = config.isImmutableWhenCommitting();
31 this.hashProvider = hashProvider;
32 this.defaultValue = defaultValue;
33 if (config.isSharedNodeCacheInStore()) {
34 nodeCache = new HashMap<>();
35 } else {
36 nodeCache = null;
37 }
38 }
39
40 private VersionedMapStoreImpl(ContinousHashProvider<K> hashProvider, V defaultValue,
41 Map<Node<K, V>, ImmutableNode<K, V>> nodeCache, VersionedMapStoreConfiguration config) {
42 this.immutableWhenCommitting = config.isImmutableWhenCommitting();
43 this.hashProvider = hashProvider;
44 this.defaultValue = defaultValue;
45 this.nodeCache = nodeCache;
46 }
47
48 public VersionedMapStoreImpl(ContinousHashProvider<K> hashProvider, V defaultValue) {
49 this(hashProvider, defaultValue, new VersionedMapStoreConfiguration());
50 }
51
52 public static <K, V> List<VersionedMapStore<K, V>> createSharedVersionedMapStores(int amount,
53 ContinousHashProvider<K> hashProvider, V defaultValue,
54 VersionedMapStoreConfiguration config) {
55 List<VersionedMapStore<K, V>> result = new ArrayList<>(amount);
56 if (config.isSharedNodeCacheInStoreGroups()) {
57 Map<Node<K, V>, ImmutableNode<K, V>> nodeCache;
58 if (config.isSharedNodeCacheInStore()) {
59 nodeCache = new HashMap<>();
60 } else {
61 nodeCache = null;
62 }
63 for (int i = 0; i < amount; i++) {
64 result.add(new VersionedMapStoreImpl<>(hashProvider, defaultValue, nodeCache, config));
65 }
66 } else {
67 for (int i = 0; i < amount; i++) {
68 result.add(new VersionedMapStoreImpl<>(hashProvider, defaultValue, config));
69 }
70 }
71 return result;
72 }
73
74 public static <K, V> List<VersionedMapStore<K, V>> createSharedVersionedMapStores(int amount,
75 ContinousHashProvider<K> hashProvider, V defaultValue) {
76 return createSharedVersionedMapStores(amount, hashProvider, defaultValue, new VersionedMapStoreConfiguration());
77 }
78
79 @Override
80 public synchronized Set<Long> getStates() {
81 return new HashSet<>(states.keySet());
82 }
83
84 @Override
85 public VersionedMap<K, V> createMap() {
86 return new VersionedMapImpl<>(this, hashProvider, defaultValue);
87 }
88
89 @Override
90 public VersionedMap<K, V> createMap(long state) {
91 ImmutableNode<K, V> data = revert(state);
92 return new VersionedMapImpl<>(this, hashProvider, defaultValue, data);
93 }
94
95 public synchronized ImmutableNode<K, V> revert(long state) {
96 if (states.containsKey(state)) {
97 return states.get(state);
98 } else {
99 ArrayList<Long> existingKeys = new ArrayList<>(states.keySet());
100 Collections.sort(existingKeys);
101 throw new IllegalArgumentException("Store does not contain state " + state + "! Avaliable states: "
102 + Arrays.toString(existingKeys.toArray()));
103 }
104 }
105
106 public synchronized long commit(Node<K, V> data, VersionedMapImpl<K, V> mapToUpdateRoot) {
107 ImmutableNode<K, V> immutable;
108 if (data != null) {
109 immutable = data.toImmutable(this.nodeCache);
110 } else {
111 immutable = null;
112 }
113
114 if (nextID == Long.MAX_VALUE)
115 throw new IllegalStateException("Map store run out of Id-s");
116 long id = nextID++;
117 this.states.put(id, immutable);
118 if (this.immutableWhenCommitting) {
119 mapToUpdateRoot.setRoot(immutable);
120 }
121 return id;
122 }
123
124 @Override
125 public DiffCursor<K, V> getDiffCursor(long fromState, long toState) {
126 VersionedMap<K, V> map1 = createMap(fromState);
127 VersionedMap<K, V> map2 = createMap(toState);
128 Cursor<K, V> cursor1 = map1.getAll();
129 Cursor<K, V> cursor2 = map2.getAll();
130 return new MapDiffCursor<>(this.hashProvider, this.defaultValue, cursor1, cursor2);
131 }
132}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java
deleted file mode 100644
index 61b3d1b8..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java
+++ /dev/null
@@ -1,23 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal;
7
8enum HashClash {
9 /**
10 * Not stuck.
11 */
12 NONE,
13
14 /**
15 * Clashed, next we should return the key of cursor 1.
16 */
17 STUCK_CURSOR_1,
18
19 /**
20 * Clashed, next we should return the key of cursor 2.
21 */
22 STUCK_CURSOR_2
23}
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
deleted file mode 100644
index d31f1a05..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java
+++ /dev/null
@@ -1,229 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal;
7
8import tools.refinery.store.map.AnyVersionedMap;
9import tools.refinery.store.map.ContinousHashProvider;
10import tools.refinery.store.map.Cursor;
11import tools.refinery.store.map.DiffCursor;
12
13import java.util.Set;
14import java.util.stream.Collectors;
15import java.util.stream.Stream;
16
17/**
18 * A cursor representing the difference between two states of a map.
19 *
20 * @author Oszkar Semerath
21 *
22 */
23public class MapDiffCursor<K, V> implements DiffCursor<K, V>, Cursor<K, V> {
24 /**
25 * Default nodeId representing missing elements.
26 */
27 private final V defaultValue;
28 private final MapCursor<K, V> cursor1;
29 private final MapCursor<K, V> cursor2;
30 private final ContinousHashProvider<? super K> hashProvider;
31
32 // Values
33 private K key;
34 private V fromValue;
35 private V toValue;
36
37 // State
38 /**
39 * Positive number if cursor 1 is behind, negative number if cursor 2 is behind,
40 * and 0 if they are at the same position.
41 */
42 private int cursorRelation;
43 private HashClash hashClash = HashClash.NONE;
44
45 public MapDiffCursor(ContinousHashProvider<? super K> hashProvider, V defaultValue, Cursor<K, V> cursor1,
46 Cursor<K, V> cursor2) {
47 super();
48 this.hashProvider = hashProvider;
49 this.defaultValue = defaultValue;
50 this.cursor1 = (MapCursor<K, V>) cursor1;
51 this.cursor2 = (MapCursor<K, V>) cursor2;
52 }
53
54 @Override
55 public K getKey() {
56 return key;
57 }
58
59 @Override
60 public V getFromValue() {
61 return fromValue;
62 }
63
64 @Override
65 public V getToValue() {
66 return toValue;
67 }
68
69 @Override
70 public V getValue() {
71 return getToValue();
72 }
73
74 public boolean isTerminated() {
75 return cursor1.isTerminated() && cursor2.isTerminated();
76 }
77
78 @Override
79 public boolean isDirty() {
80 return this.cursor1.isDirty() || this.cursor2.isDirty();
81 }
82
83 @Override
84 public Set<AnyVersionedMap> getDependingMaps() {
85 return Stream.concat(cursor1.getDependingMaps().stream(), cursor2.getDependingMaps().stream())
86 .map(AnyVersionedMap.class::cast)
87 .collect(Collectors.toUnmodifiableSet());
88 }
89
90 protected void updateState() {
91 if (!isTerminated()) {
92 this.cursorRelation = MapCursor.compare(cursor1, cursor2);
93 if (cursorRelation > 0 || cursor2.isTerminated()) {
94 this.key = cursor1.getKey();
95 this.fromValue = cursor1.getValue();
96 this.toValue = defaultValue;
97 } else if (cursorRelation < 0 || cursor1.isTerminated()) {
98 this.key = cursor2.getKey();
99 this.fromValue = defaultValue;
100 this.toValue = cursor1.getValue();
101 } else {
102 // cursor1 = cursor2
103 if (cursor1.getKey().equals(cursor2.getKey())) {
104 this.key = cursor1.getKey();
105 this.fromValue = cursor1.getValue();
106 this.toValue = defaultValue;
107 } else {
108 resolveHashClashWithFirstEntry();
109 }
110 }
111 }
112 }
113
114 protected void resolveHashClashWithFirstEntry() {
115 int compareResult = this.hashProvider.compare(cursor1.key, cursor2.key);
116 if (compareResult < 0) {
117 this.hashClash = HashClash.STUCK_CURSOR_2;
118 this.cursorRelation = 0;
119 this.key = cursor1.key;
120 this.fromValue = cursor1.value;
121 this.toValue = defaultValue;
122 } else if (compareResult > 0) {
123 this.hashClash = HashClash.STUCK_CURSOR_1;
124 this.cursorRelation = 0;
125 this.key = cursor2.key;
126 this.fromValue = defaultValue;
127 this.toValue = cursor2.value;
128 } else {
129 throw new IllegalArgumentException("Inconsistent compare result for diffcursor");
130 }
131 }
132
133 protected boolean isInHashClash() {
134 return this.hashClash != HashClash.NONE;
135 }
136
137 protected void resolveHashClashWithSecondEntry() {
138 switch (this.hashClash) {
139 case STUCK_CURSOR_1:
140 this.hashClash = HashClash.NONE;
141 this.cursorRelation = 0;
142 this.key = cursor1.key;
143 this.fromValue = cursor1.value;
144 this.toValue = defaultValue;
145 break;
146 case STUCK_CURSOR_2:
147 this.hashClash = HashClash.NONE;
148 this.cursorRelation = 0;
149 this.key = cursor2.key;
150 this.fromValue = defaultValue;
151 this.toValue = cursor2.value;
152 break;
153 default:
154 throw new IllegalArgumentException("Inconsistent compare result for diffcursor");
155 }
156 }
157
158 protected boolean sameValues() {
159 if (this.fromValue == null) {
160 return this.toValue == null;
161 } else {
162 return this.fromValue.equals(this.toValue);
163 }
164 }
165
166 protected boolean moveOne() {
167 if (isTerminated()) {
168 return false;
169 }
170 if (this.cursorRelation > 0 || cursor2.isTerminated()) {
171 return cursor1.move();
172 } else if (this.cursorRelation < 0 || cursor1.isTerminated()) {
173 return cursor2.move();
174 } else {
175 boolean moved1 = cursor1.move();
176 boolean moved2 = cursor2.move();
177 return moved1 && moved2;
178 }
179 }
180
181 private boolean skipNode() {
182 if (isTerminated()) {
183 throw new IllegalStateException("DiffCursor tries to skip when terminated!");
184 }
185 boolean update1 = cursor1.skipCurrentNode();
186 boolean update2 = cursor2.skipCurrentNode();
187 updateState();
188 return update1 && update2;
189 }
190
191 protected boolean moveToConsistentState() {
192 if (!isTerminated()) {
193 boolean changed;
194 boolean lastResult = true;
195 do {
196 changed = false;
197 if (MapCursor.sameSubnode(cursor1, cursor2)) {
198 lastResult = skipNode();
199 changed = true;
200 }
201 if (sameValues()) {
202 lastResult = moveOne();
203 changed = true;
204 }
205 updateState();
206 } while (changed && !isTerminated());
207 return lastResult;
208 } else {
209 return false;
210 }
211 }
212
213 public boolean move() {
214 if (!isTerminated()) {
215 if (isInHashClash()) {
216 this.resolveHashClashWithSecondEntry();
217 return true;
218 } else {
219 if (moveOne()) {
220 return moveToConsistentState();
221 } else {
222 return false;
223 }
224 }
225
226 } else
227 return false;
228 }
229}
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
deleted file mode 100644
index 958d645f..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java
+++ /dev/null
@@ -1,90 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal;
7
8import java.util.Map;
9
10import tools.refinery.store.map.ContinousHashProvider;
11
12public abstract class Node<K,V>{
13 public static final int BRANCHING_FACTOR_BITS = 5;
14 public static final int FACTOR = 1<<BRANCHING_FACTOR_BITS;
15 protected static final int NUMBER_OF_FACTORS = Integer.SIZE / BRANCHING_FACTOR_BITS;
16 protected static final int FACTOR_MASK = FACTOR-1;
17 public static final int EFFECTIVE_BITS = BRANCHING_FACTOR_BITS * NUMBER_OF_FACTORS;
18
19 /**
20 * Calculates the index for the continuous hash.
21 * @param depth The depth of the node in the tree.
22 * @return The index of the continuous hash.
23 */
24 protected static int hashDepth(int depth) {
25 return depth/NUMBER_OF_FACTORS;
26 }
27
28 /**
29 * Calculates the which segment of a single hash should be used.
30 * @param depth The depth of the node in the tree.
31 * @return The segment of a hash code.
32 */
33 protected static int shiftDepth(int depth) {
34 return depth%NUMBER_OF_FACTORS;
35 }
36 /**
37 * Selects a segments from a complete hash for a given depth.
38 * @param hash A complete hash.
39 * @param shiftDepth The index of the segment.
40 * @return The segment as an integer.
41 */
42 protected static int hashFragment(int hash, int shiftDepth) {
43 if(shiftDepth<0 || Node.NUMBER_OF_FACTORS<shiftDepth) throw new IllegalArgumentException("Invalid shift depth! valid intervall=[0;5], input="+shiftDepth);
44 return (hash >>> shiftDepth*BRANCHING_FACTOR_BITS) & FACTOR_MASK;
45 }
46
47 /**
48 * Returns the hash code for a given depth. It may calculate new hash code, or reuse a hash code calculated for depth-1.
49 * @param key The key.
50 * @param hash Hash code for depth-1
51 * @param depth The depth.
52 * @return The new hash code.
53 */
54 protected int newHash(final ContinousHashProvider<? super K> hashProvider, K key, int hash, int depth) {
55 final int hashDepth = hashDepth(depth);
56 if(hashDepth>=ContinousHashProvider.MAX_PRACTICAL_DEPTH) {
57 throw new IllegalArgumentException("Key "+key+" have the clashing hashcode over the practical depth limitation ("+ContinousHashProvider.MAX_PRACTICAL_DEPTH+")!");
58 }
59 return depth%NUMBER_OF_FACTORS == 0?
60 hashProvider.getHash(key, hashDepth) :
61 hash;
62 }
63
64
65 public abstract V getValue(K key, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth);
66 public abstract Node<K,V> putValue(K key, V value, OldValueBox<V> old, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth);
67 public abstract long getSize();
68
69 abstract MutableNode<K, V> toMutable();
70 public abstract ImmutableNode<K, V> toImmutable(
71 Map<Node<K, V>,ImmutableNode<K, V>> cache);
72 protected abstract MutableNode<K, V> isMutable();
73 /**
74 * Moves a {@link MapCursor} to its next position.
75 * @param cursor the cursor
76 * @return Whether there was a next nodeId to move on.
77 */
78 abstract boolean moveToNext(MapCursor<K,V> cursor);
79
80 ///////// FOR printing
81 public abstract void prettyPrint(StringBuilder builder, int depth, int code);
82 @Override
83 public String toString() {
84 StringBuilder stringBuilder = new StringBuilder();
85 prettyPrint(stringBuilder, 0, -1);
86 return stringBuilder.toString();
87 }
88 public void checkIntegrity(ContinousHashProvider<? super K> hashProvider, V defaultValue, int depth) {}
89
90}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java
new file mode 100644
index 00000000..9f419ce1
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java
@@ -0,0 +1,150 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal;
7
8import tools.refinery.store.map.ContinuousHashProvider;
9import tools.refinery.store.map.VersionedMapStoreFactory;
10import tools.refinery.store.map.VersionedMapStoreFactoryBuilder;
11import tools.refinery.store.map.internal.delta.DeltaBasedVersionedMapStoreFactory;
12import tools.refinery.store.map.internal.state.StateBasedVersionedMapStoreFactory;
13
14public class VersionedMapStoreFactoryBuilderImpl<K, V> implements VersionedMapStoreFactoryBuilder<K, V> {
15
16 private boolean defaultSet = false;
17 private V defaultValue;
18 private StoreStrategy strategy = null;
19 private Boolean transformToImmutable = null;
20 private SharingStrategy sharingStrategy = null;
21 private Boolean enableVersionFreeing = null;
22 private ContinuousHashProvider<K> continuousHashProvider = null;
23 private DeltaTransactionStrategy deltaTransactionStrategy = null;
24
25 private StoreStrategy checkStrategy() {
26 StoreStrategy currentStrategy = strategy;
27 currentStrategy = mergeStrategies(currentStrategy, transformToImmutable, StoreStrategy.STATE);
28 currentStrategy = mergeStrategies(currentStrategy, sharingStrategy, StoreStrategy.STATE);
29 currentStrategy = mergeStrategies(currentStrategy, continuousHashProvider, StoreStrategy.STATE);
30 currentStrategy = mergeStrategies(currentStrategy, deltaTransactionStrategy, StoreStrategy.DELTA);
31 return currentStrategy;
32 }
33
34 private StoreStrategy mergeStrategies(StoreStrategy old, StoreStrategy newStrategy) {
35 if (old != null && newStrategy != null && old != newStrategy) {
36 throw new IllegalArgumentException("Mixed strategy parametrization in VersionedMap builder!");
37 }
38
39 if (old != null) {
40 return old;
41 } else {
42 return newStrategy;
43 }
44 }
45
46 private StoreStrategy mergeStrategies(StoreStrategy old, Object parameter, StoreStrategy newStrategy) {
47 if (parameter != null) {
48 return mergeStrategies(old, newStrategy);
49 } else {
50 return old;
51 }
52 }
53
54 @Override
55 public VersionedMapStoreFactoryBuilder<K, V> defaultValue(V defaultValue) {
56 this.defaultSet = true;
57 this.defaultValue = defaultValue;
58 return this;
59 }
60
61 @Override
62 public VersionedMapStoreFactoryBuilder<K, V> strategy(StoreStrategy strategy) {
63 this.strategy = strategy;
64 checkStrategy();
65 return this;
66 }
67
68 @Override
69 public VersionedMapStoreFactoryBuilder<K, V> versionFreeing(boolean enabled) {
70 this.enableVersionFreeing = enabled;
71 checkStrategy();
72 return this;
73 }
74
75 @Override
76 public VersionedMapStoreFactoryBuilder<K, V> stateBasedImmutableWhenCommitting(boolean transformToImmutable) {
77 this.transformToImmutable = transformToImmutable;
78 checkStrategy();
79 return this;
80 }
81
82 @Override
83 public VersionedMapStoreFactoryBuilder<K, V> stateBasedSharingStrategy(SharingStrategy sharingStrategy) {
84 this.sharingStrategy = sharingStrategy;
85 checkStrategy();
86 return this;
87 }
88
89 @Override
90 public VersionedMapStoreFactoryBuilder<K, V> stateBasedHashProvider(ContinuousHashProvider<K> hashProvider) {
91 this.continuousHashProvider = hashProvider;
92 checkStrategy();
93 return this;
94 }
95
96 @Override
97 public VersionedMapStoreFactoryBuilder<K, V> deltaTransactionStrategy(DeltaTransactionStrategy deltaTransactionStrategy) {
98 this.deltaTransactionStrategy = deltaTransactionStrategy;
99 checkStrategy();
100 return this;
101 }
102
103 private <T> T getOrDefault(T value, T defaultValue) {
104 if(value != null) {
105 return value;
106 } else {
107 return defaultValue;
108 }
109 }
110
111 @Override
112 public VersionedMapStoreFactory<K, V> build() {
113 if (!defaultSet) {
114 throw new IllegalArgumentException("Default value is missing!");
115 }
116 var strategyToUse = checkStrategy();
117 if (strategyToUse == null) {
118 return new DeltaBasedVersionedMapStoreFactory<>(defaultValue,
119 getOrDefault(deltaTransactionStrategy, DeltaTransactionStrategy.LIST));
120 }
121 return switch (strategyToUse) {
122 case STATE -> {
123 if(continuousHashProvider == null) {
124 throw new IllegalArgumentException("Continuous hash provider is missing!");
125 }
126 yield new StateBasedVersionedMapStoreFactory<>(defaultValue,
127 getOrDefault(transformToImmutable,true),
128 getOrDefault(sharingStrategy, SharingStrategy.SHARED_NODE_CACHE_IN_GROUP),
129 getOrDefault(enableVersionFreeing, true),
130 continuousHashProvider);
131 }
132 case DELTA -> new DeltaBasedVersionedMapStoreFactory<>(defaultValue,
133 getOrDefault(deltaTransactionStrategy, DeltaTransactionStrategy.LIST));
134 };
135 }
136
137 @Override
138 public String toString() {
139 return "VersionedMapStoreFactoryBuilderImpl{" +
140 "defaultSet=" + defaultSet +
141 ", defaultValue=" + defaultValue +
142 ", strategy=" + strategy +
143 ", transformToImmutable=" + transformToImmutable +
144 ", sharingStrategy=" + sharingStrategy +
145 ", enableVersionFreeing=" + enableVersionFreeing +
146 ", continuousHashProvider=" + continuousHashProvider +
147 ", deltaTransactionStrategy=" + deltaTransactionStrategy +
148 '}';
149 }
150}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaBasedVersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaBasedVersionedMapStoreFactory.java
new file mode 100644
index 00000000..cedcdc0b
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaBasedVersionedMapStoreFactory.java
@@ -0,0 +1,38 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8import tools.refinery.store.map.VersionedMapStore;
9import tools.refinery.store.map.VersionedMapStoreFactory;
10import tools.refinery.store.map.VersionedMapStoreFactoryBuilder;
11
12import java.util.ArrayList;
13import java.util.List;
14
15public class DeltaBasedVersionedMapStoreFactory<K, V> implements VersionedMapStoreFactory<K, V> {
16 private final V defaultValue;
17 private final boolean summarizeChanges;
18
19 public DeltaBasedVersionedMapStoreFactory(V defaultValue,
20 VersionedMapStoreFactoryBuilder.DeltaTransactionStrategy deltaTransactionStrategy) {
21 this.defaultValue = defaultValue;
22 this.summarizeChanges = deltaTransactionStrategy == VersionedMapStoreFactoryBuilder.DeltaTransactionStrategy.SET;
23 }
24
25 @Override
26 public VersionedMapStore<K, V> createOne() {
27 return new VersionedMapStoreDeltaImpl<>(summarizeChanges, defaultValue);
28 }
29
30 @Override
31 public List<VersionedMapStore<K, V>> createGroup(int amount) {
32 List<VersionedMapStore<K, V>> result = new ArrayList<>(amount);
33 for(int i=0; i<amount; i++) {
34 result.add(new VersionedMapStoreDeltaImpl<>(summarizeChanges,defaultValue));
35 }
36 return result;
37 }
38}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaDiffCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaDiffCursor.java
new file mode 100644
index 00000000..ce10b246
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaDiffCursor.java
@@ -0,0 +1,147 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8import tools.refinery.store.map.AnyVersionedMap;
9import tools.refinery.store.map.DiffCursor;
10
11import java.util.Collections;
12import java.util.List;
13import java.util.Set;
14
15public class DeltaDiffCursor<K, V> implements DiffCursor<K, V> {
16 final List<MapDelta<K, V>[]> backwardTransactions;
17 final List<MapDelta<K, V>[]> forwardTransactions;
18
19 boolean started;
20 /**
21 * Denotes the direction of traversal. False means backwards, true means
22 * forward.
23 */
24 boolean direction;
25 int listIndex;
26 int arrayIndex;
27
28 public DeltaDiffCursor(List<MapDelta<K, V>[]> backwardTransactions, List<MapDelta<K, V>[]> forwardTransactions) {
29 this.backwardTransactions = backwardTransactions;
30 this.forwardTransactions = forwardTransactions;
31
32 if (!backwardTransactions.isEmpty()) {
33 direction = false;
34 listIndex = 0;
35 arrayIndex = backwardTransactions.get(listIndex).length - 1;
36 } else if (!forwardTransactions.isEmpty()) {
37 direction = true;
38 listIndex = forwardTransactions.size() - 1;
39 arrayIndex = 0;
40 } else {
41 direction = true;
42 listIndex = -1;
43 }
44 started = false;
45 }
46
47 protected MapDelta<K, V> getCurrentDelta() {
48 final List<MapDelta<K, V>[]> list;
49 if (!direction) {
50 list = this.backwardTransactions;
51 } else {
52 list = this.forwardTransactions;
53 }
54 return list.get(listIndex)[arrayIndex];
55 }
56
57 @Override
58 public K getKey() {
59 return getCurrentDelta().getKey();
60 }
61
62 @Override
63 public V getValue() {
64 return getToValue();
65 }
66
67 @Override
68 public boolean isTerminated() {
69 return this.direction && listIndex == -1;
70 }
71
72
73 @Override
74 public boolean move() {
75 if(!started) {
76 started = true;
77 return !isTerminated();
78 } else if (isTerminated()) {
79 return false;
80 } else {
81 if (this.direction) {
82 if (arrayIndex+1 < forwardTransactions.get(listIndex).length) {
83 arrayIndex++;
84 return true;
85 } else {
86 if (listIndex-1 >= 0) {
87 listIndex--;
88 arrayIndex = 0;
89 return true;
90 } else {
91 listIndex = -1;
92 return false;
93 }
94 }
95 } else {
96 if (arrayIndex > 0) {
97 arrayIndex--;
98 return true;
99 } else {
100 if (listIndex+1 < backwardTransactions.size()) {
101 listIndex++;
102 this.arrayIndex = backwardTransactions.get(listIndex).length - 1;
103 return true;
104 } else {
105 this.direction = true;
106 if (!this.forwardTransactions.isEmpty()) {
107 listIndex = forwardTransactions.size() - 1;
108 arrayIndex = 0;
109 return true;
110 } else {
111 listIndex = -1;
112 return false;
113 }
114 }
115 }
116 }
117 }
118 }
119
120 @Override
121 public boolean isDirty() {
122 return false;
123 }
124
125 @Override
126 public Set<AnyVersionedMap> getDependingMaps() {
127 return Collections.emptySet();
128 }
129
130 @Override
131 public V getFromValue() {
132 if(this.direction) {
133 return getCurrentDelta().getOldValue();
134 } else {
135 return getCurrentDelta().getNewValue();
136 }
137 }
138
139 @Override
140 public V getToValue() {
141 if(this.direction) {
142 return getCurrentDelta().getNewValue();
143 } else {
144 return getCurrentDelta().getOldValue();
145 }
146 }
147}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapDelta.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapDelta.java
new file mode 100644
index 00000000..0c0cc906
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapDelta.java
@@ -0,0 +1,20 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8public record MapDelta<K, V>(K key, V oldValue, V newValue) {
9 public K getKey() {
10 return key;
11 }
12
13 public V getOldValue() {
14 return oldValue;
15 }
16
17 public V getNewValue() {
18 return newValue;
19 }
20}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapTransaction.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapTransaction.java
new file mode 100644
index 00000000..6f3fa6d7
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapTransaction.java
@@ -0,0 +1,41 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8import tools.refinery.store.map.Version;
9
10import java.util.Arrays;
11import java.util.Objects;
12
13public record MapTransaction<K, V>(MapDelta<K, V>[] deltas, MapTransaction<K, V> parent, int depth) implements Version {
14
15 @Override
16 public int hashCode() {
17 final int prime = 31;
18 int result = 1;
19 result = prime * result + Arrays.hashCode(deltas);
20 result = prime * result + Objects.hash(parent, depth);
21 return result;
22 }
23
24 @Override
25 public boolean equals(Object obj) {
26 if (this == obj)
27 return true;
28 if (obj == null)
29 return false;
30 if (getClass() != obj.getClass())
31 return false;
32 @SuppressWarnings("unchecked")
33 MapTransaction<K, V> other = (MapTransaction<K, V>) obj;
34 return depth == other.depth && Objects.equals(parent, other.parent) && Arrays.equals(deltas, other.deltas);
35 }
36
37 @Override
38 public String toString() {
39 return "MapTransaction " + depth + " " + Arrays.toString(deltas);
40 }
41}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaArrayStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaArrayStore.java
new file mode 100644
index 00000000..1f6a9000
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaArrayStore.java
@@ -0,0 +1,36 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8import java.util.ArrayList;
9import java.util.List;
10
11public class UncommittedDeltaArrayStore<K, V> implements UncommittedDeltaStore<K, V> {
12 final List<MapDelta<K, V>> uncommittedOldValues = new ArrayList<>();
13
14 @Override
15 public void processChange(K key, V oldValue, V newValue) {
16 uncommittedOldValues.add(new MapDelta<>(key, oldValue, newValue));
17 }
18
19 @Override
20 public MapDelta<K, V>[] extractDeltas() {
21 if (uncommittedOldValues.isEmpty()) {
22 return null;
23 } else {
24 @SuppressWarnings("unchecked")
25 MapDelta<K, V>[] result = uncommittedOldValues.toArray(new MapDelta[0]);
26 return result;
27 }
28 }
29
30 @Override
31 public MapDelta<K, V>[] extractAndDeleteDeltas() {
32 MapDelta<K, V>[] res = extractDeltas();
33 this.uncommittedOldValues.clear();
34 return res;
35 }
36}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaMapStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaMapStore.java
new file mode 100644
index 00000000..644884a6
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaMapStore.java
@@ -0,0 +1,53 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8import java.util.*;
9import java.util.Map.Entry;
10
11import tools.refinery.store.map.VersionedMap;
12
13public class UncommittedDeltaMapStore<K, V> implements UncommittedDeltaStore<K, V> {
14 final VersionedMap<K, V> source;
15 final Map<K, V> uncommittedOldValues = new HashMap<>();
16
17 public UncommittedDeltaMapStore(VersionedMap<K, V> source) {
18 this.source = source;
19 }
20
21 @Override
22 public void processChange(K key, V oldValue, V newValue) {
23 if(!uncommittedOldValues.containsKey(key)) {
24 this.uncommittedOldValues.put(key,oldValue);
25 }
26 }
27
28 @Override
29 public MapDelta<K, V>[] extractDeltas() {
30 if (uncommittedOldValues.isEmpty()) {
31 return null;
32 } else {
33 @SuppressWarnings("unchecked")
34 MapDelta<K,V>[] deltas = new MapDelta[uncommittedOldValues.size()];
35 int i = 0;
36 for (Entry<K, V> entry : uncommittedOldValues.entrySet()) {
37 final K key = entry.getKey();
38 final V oldValue = entry.getValue();
39 final V newValue = source.get(key);
40 deltas[i++] = new MapDelta<>(key, oldValue, newValue);
41 }
42
43 return deltas;
44 }
45 }
46
47 @Override
48 public MapDelta<K, V>[] extractAndDeleteDeltas() {
49 MapDelta<K, V>[] res = extractDeltas();
50 this.uncommittedOldValues.clear();
51 return res;
52 }
53}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaStore.java
new file mode 100644
index 00000000..ecd33c5f
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaStore.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8public interface UncommittedDeltaStore<K, V> {
9 void processChange(K key, V oldValue, V newValue);
10
11 MapDelta<K, V>[] extractDeltas();
12
13 MapDelta<K, V>[] extractAndDeleteDeltas();
14
15 default void checkIntegrity() {
16 MapDelta<K, V>[] extractedDeltas = extractDeltas();
17 if(extractedDeltas != null) {
18 for(var uncommittedOldValue : extractedDeltas) {
19 if(uncommittedOldValue == null) {
20 throw new IllegalArgumentException("Null entry in deltas!");
21 }
22 if(uncommittedOldValue.getKey() == null) {
23 throw new IllegalStateException("Null key in deltas!");
24 }
25 }
26 }
27 }
28
29}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java
new file mode 100644
index 00000000..d530ae87
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java
@@ -0,0 +1,237 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8import tools.refinery.store.map.*;
9
10import java.util.*;
11
12public class VersionedMapDeltaImpl<K, V> implements VersionedMap<K, V> {
13 protected final VersionedMapStoreDeltaImpl<K, V> store;
14
15 final Map<K, V> current;
16
17 final UncommittedDeltaStore<K, V> uncommittedStore;
18 MapTransaction<K, V> previous;
19
20 protected final V defaultValue;
21
22 public VersionedMapDeltaImpl(VersionedMapStoreDeltaImpl<K, V> store, boolean summarizeChanges, V defaultValue) {
23 this.store = store;
24 this.defaultValue = defaultValue;
25
26 current = new HashMap<>();
27 if (summarizeChanges) {
28 this.uncommittedStore = new UncommittedDeltaMapStore<>(this);
29 } else {
30 this.uncommittedStore = new UncommittedDeltaArrayStore<>();
31 }
32 }
33
34 @Override
35 public V getDefaultValue() {
36 return defaultValue;
37 }
38
39 @Override
40 public Version commit() {
41 MapDelta<K, V>[] deltas = uncommittedStore.extractAndDeleteDeltas();
42 final MapTransaction<K,V> committedTransaction = this.store.appendTransaction(deltas, previous);
43 this.previous = committedTransaction;
44 return committedTransaction;
45 }
46
47 @Override
48 public void restore(Version state) {
49 // 1. restore uncommitted states
50 MapDelta<K, V>[] uncommitted = this.uncommittedStore.extractAndDeleteDeltas();
51 if (uncommitted != null) {
52 backward(uncommitted);
53 }
54
55 // 2. get common ancestor
56 final MapTransaction<K,V> parent;
57 List<MapDelta<K, V>[]> forward = new ArrayList<>();
58 if (this.previous == null) {
59 parent = this.store.getPath(state, forward);
60 this.forward(forward);
61 } else {
62 List<MapDelta<K, V>[]> backward = new ArrayList<>();
63 parent = this.store.getPath(this.previous, state, backward, forward);
64 this.backward(backward);
65 this.forward(forward);
66 }
67 this.previous = parent;
68 }
69
70 protected void forward(List<MapDelta<K, V>[]> changes) {
71 for (int i = changes.size() - 1; i >= 0; i--) {
72 forward(changes.get(i));
73 }
74 }
75
76 protected void backward(List<MapDelta<K, V>[]> changes) {
77 //Currently, this loop statement is faster.
78 //noinspection ForLoopReplaceableByForEach
79 for (int i = 0; i < changes.size(); i++) {
80 backward(changes.get(i));
81 }
82 }
83
84 protected void forward(MapDelta<K, V>[] changes) {
85 //Currently, this loop statement is faster.
86 //noinspection ForLoopReplaceableByForEach
87 for (int i = 0; i < changes.length; i++) {
88 final MapDelta<K, V> change = changes[i];
89 K key = change.getKey();
90 V newValue = change.getNewValue();
91
92 if(newValue == defaultValue) {
93 current.remove(key);
94 } else {
95 current.put(key,newValue);
96 }
97 }
98 }
99
100 protected void backward(MapDelta<K, V>[] changes) {
101 for (int i = changes.length - 1; i >= 0; i--) {
102 final MapDelta<K, V> change = changes[i];
103 K key = change.getKey();
104 V oldValue = change.oldValue();
105
106 if(oldValue == defaultValue) {
107 current.remove(key);
108 } else {
109 current.put(key,oldValue);
110 }
111 }
112 }
113
114 @Override
115 public V get(K key) {
116 return current.getOrDefault(key, defaultValue);
117 }
118
119 @Override
120 public Cursor<K, V> getAll() {
121 return new IteratorAsCursor<>(this, current);
122 }
123
124 @Override
125 public V put(K key, V value) {
126 final V oldValue;
127 if (Objects.equals(value, defaultValue)) {
128 final V res = current.remove(key);
129 if (res == null) {
130 // no changes: default > default
131 oldValue = defaultValue;
132 } else {
133 oldValue = res;
134 }
135 } else {
136 final var mapValue = current.put(key, value);
137 if (mapValue == null) {
138 oldValue = defaultValue;
139 } else {
140 oldValue = mapValue;
141 }
142 }
143 if(!Objects.equals(oldValue,value)) {
144 uncommittedStore.processChange(key, oldValue, value);
145 }
146 return oldValue;
147 }
148
149 @Override
150 public void putAll(Cursor<K, V> cursor) {
151 if (cursor.getDependingMaps().contains(this)) {
152 List<K> keys = new ArrayList<>();
153 List<V> values = new ArrayList<>();
154 while (cursor.move()) {
155 keys.add(cursor.getKey());
156 values.add(cursor.getValue());
157 }
158 for (int i = 0; i < keys.size(); i++) {
159 this.put(keys.get(i), values.get(i));
160 }
161 } else {
162 while (cursor.move()) {
163 this.put(cursor.getKey(), cursor.getValue());
164 }
165 }
166 }
167
168 @Override
169 public long getSize() {
170 return current.size();
171 }
172
173 @Override
174 public DiffCursor<K, V> getDiffCursor(Version state) {
175 MapDelta<K, V>[] backward = this.uncommittedStore.extractDeltas();
176 List<MapDelta<K, V>[]> backwardTransactions = new ArrayList<>();
177 List<MapDelta<K, V>[]> forwardTransactions = new ArrayList<>();
178
179 if (backward != null) {
180 backwardTransactions.add(backward);
181 }
182
183 if (this.previous != null) {
184 store.getPath(this.previous, state, backwardTransactions, forwardTransactions);
185 } else {
186 store.getPath(state, forwardTransactions);
187 }
188
189 return new DeltaDiffCursor<>(backwardTransactions, forwardTransactions);
190 }
191
192 @Override
193 public int contentHashCode(ContentHashCode mode) {
194 return this.current.hashCode();
195 }
196
197 @Override
198 public boolean contentEquals(AnyVersionedMap other) {
199 if (other instanceof VersionedMapDeltaImpl<?, ?> versioned) {
200 if (versioned == this) {
201 return true;
202 } else {
203 return Objects.equals(this.defaultValue, versioned.defaultValue) && Objects.equals(this.current, versioned.current);
204 }
205 } else {
206 throw new UnsupportedOperationException("Comparing different map implementations is ineffective.");
207 }
208 }
209
210 @Override
211 public void checkIntegrity() {
212 this.uncommittedStore.checkIntegrity();
213
214 for (var entry : this.current.entrySet()) {
215 var value = entry.getValue();
216 if (value == this.defaultValue) {
217 throw new IllegalStateException("Default value stored in map!");
218 } else if (value == null) {
219 throw new IllegalStateException("null value stored in map!");
220 }
221 }
222 MapTransaction<K,V> transaction = this.previous;
223 while(transaction != null) {
224 MapTransaction<K,V> parent = transaction.parent();
225 if(parent != null) {
226 if(parent.depth() != transaction.depth()-1) {
227 throw new IllegalStateException("Parent depths are inconsistent!");
228 }
229 } else {
230 if(transaction.depth() != 0) {
231 throw new IllegalArgumentException("Root depth is not 0!");
232 }
233 }
234 transaction = transaction.parent();
235 }
236 }
237}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapStoreDeltaImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapStoreDeltaImpl.java
new file mode 100644
index 00000000..ed169409
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapStoreDeltaImpl.java
@@ -0,0 +1,94 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.delta;
7
8import tools.refinery.store.map.DiffCursor;
9import tools.refinery.store.map.Version;
10import tools.refinery.store.map.VersionedMap;
11import tools.refinery.store.map.VersionedMapStore;
12
13import java.util.*;
14
15public class VersionedMapStoreDeltaImpl<K, V> implements VersionedMapStore<K, V> {
16 // Configuration
17 protected final boolean summarizeChanges;
18
19 // Static data
20 protected final V defaultValue;
21
22 public VersionedMapStoreDeltaImpl(boolean summarizeChanges, V defaultValue) {
23 this.summarizeChanges = summarizeChanges;
24 this.defaultValue = defaultValue;
25 }
26
27 @Override
28 public VersionedMap<K, V> createMap() {
29 return new VersionedMapDeltaImpl<>(this, this.summarizeChanges, this.defaultValue);
30 }
31
32 @Override
33 public VersionedMap<K, V> createMap(Version state) {
34 VersionedMapDeltaImpl<K, V> result = new VersionedMapDeltaImpl<>(this, this.summarizeChanges, this.defaultValue);
35 result.restore(state);
36 return result;
37 }
38
39 public MapTransaction<K, V> appendTransaction(MapDelta<K, V>[] deltas, MapTransaction<K, V> previous) {
40 if (deltas == null) {
41 return previous;
42 } else {
43 final int depth;
44 if(previous != null) {
45 depth = previous.depth()+1;
46 } else {
47 depth = 0;
48 }
49 return new MapTransaction<>(deltas, previous, depth);
50 }
51 }
52
53 @SuppressWarnings("unchecked")
54 private MapTransaction<K, V> getState(Version state) {
55 return (MapTransaction<K, V>) state;
56 }
57
58 public MapTransaction<K, V> getPath(Version to, List<MapDelta<K, V>[]> forwardTransactions) {
59 final MapTransaction<K, V> target = getState(to);
60 MapTransaction<K, V> toTransaction = target;
61 while (toTransaction != null) {
62 forwardTransactions.add(toTransaction.deltas());
63 toTransaction = toTransaction.parent();
64 }
65 return target;
66 }
67
68 public MapTransaction<K, V> getPath(Version from, Version to,
69 List<MapDelta<K, V>[]> backwardTransactions,
70 List<MapDelta<K, V>[]> forwardTransactions) {
71 MapTransaction<K, V> fromTransaction = getState(from);
72 final MapTransaction<K, V> target = getState(to);
73 MapTransaction<K, V> toTransaction = target;
74
75 while (fromTransaction != toTransaction) {
76 if (fromTransaction == null || (toTransaction != null && fromTransaction.depth() < toTransaction.depth())) {
77 forwardTransactions.add(toTransaction.deltas());
78 toTransaction = toTransaction.parent();
79 } else {
80 backwardTransactions.add(fromTransaction.deltas());
81 fromTransaction = fromTransaction.parent();
82 }
83 }
84 return target;
85 }
86
87 @Override
88 public DiffCursor<K, V> getDiffCursor(Version fromState, Version toState) {
89 List<MapDelta<K, V>[]> backwardTransactions = new ArrayList<>();
90 List<MapDelta<K, V>[]> forwardTransactions = new ArrayList<>();
91 getPath(fromState, toState, backwardTransactions, forwardTransactions);
92 return new DeltaDiffCursor<>(backwardTransactions, forwardTransactions);
93 }
94}
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/state/ImmutableNode.java
index 03dffc15..5b1d8b77 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/state/ImmutableNode.java
@@ -1,16 +1,17 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal.state;
7 7
8import java.util.Arrays; 8import java.util.Arrays;
9import java.util.Map; 9import java.util.Map;
10 10
11import tools.refinery.store.map.ContinousHashProvider; 11import tools.refinery.store.map.ContinuousHashProvider;
12import tools.refinery.store.map.Version;
12 13
13public class ImmutableNode<K, V> extends Node<K, V> { 14public class ImmutableNode<K, V> extends Node<K, V> implements Version {
14 /** 15 /**
15 * Bitmap defining the stored key and values. 16 * Bitmap defining the stored key and values.
16 */ 17 */
@@ -20,7 +21,7 @@ public class ImmutableNode<K, V> extends Node<K, V> {
20 */ 21 */
21 final int nodeMap; 22 final int nodeMap;
22 /** 23 /**
23 * Stores Keys, Values, and subnodes. Structure: (K,V)*,NODE; NODES are stored 24 * Stores Keys, Values, and sub-nodes. Structure: (K,V)*,NODE; NODES are stored
24 * backwards. 25 * backwards.
25 */ 26 */
26 final Object[] content; 27 final Object[] content;
@@ -47,8 +48,7 @@ public class ImmutableNode<K, V> extends Node<K, V> {
47 * available. 48 * available.
48 * @return an immutable version of the input node. 49 * @return an immutable version of the input node.
49 */ 50 */
50 static <K, V> ImmutableNode<K, V> constructImmutable(MutableNode<K, V> node, 51 static <K, V> ImmutableNode<K, V> constructImmutable(MutableNode<K, V> node, Map<Node<K, V>, ImmutableNode<K, V>> cache) {
51 Map<Node<K, V>, ImmutableNode<K, V>> cache) {
52 // 1. try to return from cache 52 // 1. try to return from cache
53 if (cache != null) { 53 if (cache != null) {
54 ImmutableNode<K, V> cachedResult = cache.get(node); 54 ImmutableNode<K, V> cachedResult = cache.get(node);
@@ -71,25 +71,24 @@ public class ImmutableNode<K, V> extends Node<K, V> {
71 int resultDataMap = 0; 71 int resultDataMap = 0;
72 int resultNodeMap = 0; 72 int resultNodeMap = 0;
73 final Object[] resultContent = new Object[size]; 73 final Object[] resultContent = new Object[size];
74 int bitposition = 1; 74 int bitPosition = 1;
75 for (int i = 0; i < FACTOR; i++) { 75 for (int i = 0; i < FACTOR; i++) {
76 Object key = node.content[i * 2]; 76 Object key = node.content[i * 2];
77 if (key != null) { 77 if (key != null) {
78 resultDataMap |= bitposition; 78 resultDataMap |= bitPosition;
79 resultContent[datas * 2] = key; 79 resultContent[datas * 2] = key;
80 resultContent[datas * 2 + 1] = node.content[i * 2 + 1]; 80 resultContent[datas * 2 + 1] = node.content[i * 2 + 1];
81 datas++; 81 datas++;
82 } else { 82 } else {
83 @SuppressWarnings("unchecked") 83 @SuppressWarnings("unchecked") var subnode = (Node<K, V>) node.content[i * 2 + 1];
84 var subnode = (Node<K, V>) node.content[i * 2 + 1];
85 if (subnode != null) { 84 if (subnode != null) {
86 ImmutableNode<K, V> immutableSubnode = subnode.toImmutable(cache); 85 ImmutableNode<K, V> immutableSubNode = subnode.toImmutable(cache);
87 resultNodeMap |= bitposition; 86 resultNodeMap |= bitPosition;
88 resultContent[size - 1 - nodes] = immutableSubnode; 87 resultContent[size - 1 - nodes] = immutableSubNode;
89 nodes++; 88 nodes++;
90 } 89 }
91 } 90 }
92 bitposition <<= 1; 91 bitPosition <<= 1;
93 } 92 }
94 final int resultHash = node.hashCode(); 93 final int resultHash = node.hashCode();
95 var newImmutable = new ImmutableNode<K, V>(resultDataMap, resultNodeMap, resultContent, resultHash); 94 var newImmutable = new ImmutableNode<K, V>(resultDataMap, resultNodeMap, resultContent, resultHash);
@@ -106,17 +105,15 @@ public class ImmutableNode<K, V> extends Node<K, V> {
106 } 105 }
107 106
108 @Override 107 @Override
109 public V getValue(K key, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) { 108 public V getValue(K key, ContinuousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) {
110 int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); 109 int selectedHashFragment = hashFragment(hash, shiftDepth(depth));
111 int bitposition = 1 << selectedHashFragment; 110 int bitposition = 1 << selectedHashFragment;
112 // If the key is stored as a data 111 // If the key is stored as a data
113 if ((dataMap & bitposition) != 0) { 112 if ((dataMap & bitposition) != 0) {
114 int keyIndex = 2 * index(dataMap, bitposition); 113 int keyIndex = 2 * index(dataMap, bitposition);
115 @SuppressWarnings("unchecked") 114 @SuppressWarnings("unchecked") K keyCandidate = (K) content[keyIndex];
116 K keyCandidate = (K) content[keyIndex];
117 if (keyCandidate.equals(key)) { 115 if (keyCandidate.equals(key)) {
118 @SuppressWarnings("unchecked") 116 @SuppressWarnings("unchecked") V value = (V) content[keyIndex + 1];
119 V value = (V) content[keyIndex + 1];
120 return value; 117 return value;
121 } else { 118 } else {
122 return defaultValue; 119 return defaultValue;
@@ -125,9 +122,8 @@ public class ImmutableNode<K, V> extends Node<K, V> {
125 // the key is stored as a node 122 // the key is stored as a node
126 else if ((nodeMap & bitposition) != 0) { 123 else if ((nodeMap & bitposition) != 0) {
127 int keyIndex = content.length - 1 - index(nodeMap, bitposition); 124 int keyIndex = content.length - 1 - index(nodeMap, bitposition);
128 @SuppressWarnings("unchecked") 125 @SuppressWarnings("unchecked") var subNode = (ImmutableNode<K, V>) content[keyIndex];
129 var subNode = (ImmutableNode<K, V>) content[keyIndex]; 126 int newDepth = incrementDepth(depth);
130 int newDepth = depth + 1;
131 int newHash = newHash(hashProvider, key, hash, newDepth); 127 int newHash = newHash(hashProvider, key, hash, newDepth);
132 return subNode.getValue(key, hashProvider, defaultValue, newHash, newDepth); 128 return subNode.getValue(key, hashProvider, defaultValue, newHash, newDepth);
133 } 129 }
@@ -138,44 +134,41 @@ public class ImmutableNode<K, V> extends Node<K, V> {
138 } 134 }
139 135
140 @Override 136 @Override
141 public Node<K, V> putValue(K key, V value, OldValueBox<V> oldValue, ContinousHashProvider<? super K> hashProvider, 137 public Node<K, V> putValue(K key, V value, OldValueBox<V> oldValue, ContinuousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) {
142 V defaultValue, int hash, int depth) {
143 int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); 138 int selectedHashFragment = hashFragment(hash, shiftDepth(depth));
144 int bitposition = 1 << selectedHashFragment; 139 int bitPosition = 1 << selectedHashFragment;
145 if ((dataMap & bitposition) != 0) { 140 if ((dataMap & bitPosition) != 0) {
146 int keyIndex = 2 * index(dataMap, bitposition); 141 int keyIndex = 2 * index(dataMap, bitPosition);
147 @SuppressWarnings("unchecked") 142 @SuppressWarnings("unchecked") K keyCandidate = (K) content[keyIndex];
148 K keyCandidate = (K) content[keyIndex];
149 if (keyCandidate.equals(key)) { 143 if (keyCandidate.equals(key)) {
150 if (value == defaultValue) { 144 if (value == defaultValue) {
151 // delete 145 // delete
152 MutableNode<K, V> mutable = this.toMutable(); 146 MutableNode<K, V> mutable = this.toMutable();
153 return mutable.removeEntry(selectedHashFragment, oldValue); 147 return mutable.removeEntry(selectedHashFragment, oldValue);
154 } else if (value == content[keyIndex + 1]) { 148 } else if (value == content[keyIndex + 1]) {
155 // dont change 149 // don't change
156 oldValue.setOldValue(value); 150 oldValue.setOldValue(value);
157 return this; 151 return this;
158 } else { 152 } else {
159 // update existing nodeId 153 // update existing value
160 MutableNode<K, V> mutable = this.toMutable(); 154 MutableNode<K, V> mutable = this.toMutable();
161 return mutable.updateValue(value, oldValue, selectedHashFragment); 155 return mutable.updateValue(value, oldValue, selectedHashFragment);
162 } 156 }
163 } else { 157 } else {
164 if (value == defaultValue) { 158 if (value == defaultValue) {
165 // dont change 159 // don't change
166 oldValue.setOldValue(defaultValue); 160 oldValue.setOldValue(defaultValue);
167 return this; 161 return this;
168 } else { 162 } else {
169 // add new key + nodeId 163 // add new key + value
170 MutableNode<K, V> mutable = this.toMutable(); 164 MutableNode<K, V> mutable = this.toMutable();
171 return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth); 165 return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth);
172 } 166 }
173 } 167 }
174 } else if ((nodeMap & bitposition) != 0) { 168 } else if ((nodeMap & bitPosition) != 0) {
175 int keyIndex = content.length - 1 - index(nodeMap, bitposition); 169 int keyIndex = content.length - 1 - index(nodeMap, bitPosition);
176 @SuppressWarnings("unchecked") 170 @SuppressWarnings("unchecked") var subNode = (ImmutableNode<K, V>) content[keyIndex];
177 var subNode = (ImmutableNode<K, V>) content[keyIndex]; 171 int newDepth = incrementDepth(depth);
178 int newDepth = depth + 1;
179 int newHash = newHash(hashProvider, key, hash, newDepth); 172 int newHash = newHash(hashProvider, key, hash, newDepth);
180 var newsubNode = subNode.putValue(key, value, oldValue, hashProvider, defaultValue, newHash, newDepth); 173 var newsubNode = subNode.putValue(key, value, oldValue, hashProvider, defaultValue, newHash, newDepth);
181 174
@@ -184,10 +177,11 @@ public class ImmutableNode<K, V> extends Node<K, V> {
184 return this; 177 return this;
185 } else { 178 } else {
186 MutableNode<K, V> mutable = toMutable(); 179 MutableNode<K, V> mutable = toMutable();
187 return mutable.updateWithSubNode(selectedHashFragment, newsubNode, value.equals(defaultValue)); 180 return mutable.updateWithSubNode(selectedHashFragment, newsubNode,
181 (value == null && defaultValue == null) || (value != null && value.equals(defaultValue)));
188 } 182 }
189 } else { 183 } else {
190 // add new key + nodeId 184 // add new key + value
191 MutableNode<K, V> mutable = this.toMutable(); 185 MutableNode<K, V> mutable = this.toMutable();
192 return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth); 186 return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth);
193 } 187 }
@@ -197,8 +191,7 @@ public class ImmutableNode<K, V> extends Node<K, V> {
197 public long getSize() { 191 public long getSize() {
198 int result = Integer.bitCount(this.dataMap); 192 int result = Integer.bitCount(this.dataMap);
199 for (int subnodeIndex = 0; subnodeIndex < Integer.bitCount(this.nodeMap); subnodeIndex++) { 193 for (int subnodeIndex = 0; subnodeIndex < Integer.bitCount(this.nodeMap); subnodeIndex++) {
200 @SuppressWarnings("unchecked") 194 @SuppressWarnings("unchecked") var subnode = (ImmutableNode<K, V>) this.content[this.content.length - 1 - subnodeIndex];
201 var subnode = (ImmutableNode<K, V>) this.content[this.content.length - 1 - subnodeIndex];
202 result += subnode.getSize(); 195 result += subnode.getSize();
203 } 196 }
204 return result; 197 return result;
@@ -238,6 +231,9 @@ public class ImmutableNode<K, V> extends Node<K, V> {
238 231
239 // 2. look inside the subnodes 232 // 2. look inside the subnodes
240 int nodes = Integer.bitCount(this.nodeMap); 233 int nodes = Integer.bitCount(this.nodeMap);
234 if(cursor.nodeIndexStack.peek()==null) {
235 throw new IllegalStateException("Cursor moved to the next state when the state is empty.");
236 }
241 int newNodeIndex = cursor.nodeIndexStack.peek() + 1; 237 int newNodeIndex = cursor.nodeIndexStack.peek() + 1;
242 if (newNodeIndex < nodes) { 238 if (newNodeIndex < nodes) {
243 // 2.1 found next subnode, move down to the subnode 239 // 2.1 found next subnode, move down to the subnode
@@ -264,10 +260,51 @@ public class ImmutableNode<K, V> extends Node<K, V> {
264 } 260 }
265 261
266 @Override 262 @Override
267 public void prettyPrint(StringBuilder builder, int depth, int code) { 263 @SuppressWarnings("unchecked")
268 for (int i = 0; i < depth; i++) { 264 boolean moveToNextInorder(InOrderMapCursor<K, V> cursor) {
269 builder.append("\t"); 265 if(cursor.nodeIndexStack.peek()==null) {
266 throw new IllegalStateException("Cursor moved to the next state when the state is empty.");
270 } 267 }
268
269 int position = cursor.nodeIndexStack.peek();
270 for (int index = position + 1; index < FACTOR; index++) {
271 final int mask = 1<<index;
272 if((this.dataMap & mask) != 0) {
273 // data found
274 cursor.nodeIndexStack.pop();
275 cursor.nodeIndexStack.push(index);
276
277 cursor.key = (K) this.content[2 * index(dataMap, mask)];
278 cursor.value = (V) this.content[2 * index(dataMap, mask) +1];
279 return true;
280 } else if((this.nodeMap & mask) != 0) {
281 // node found
282 Node<K,V> subnode = (Node<K, V>) this.content[this.content.length - 1 - index(nodeMap, mask)];
283 cursor.nodeIndexStack.pop();
284 cursor.nodeIndexStack.push(index);
285 cursor.nodeIndexStack.push(InOrderMapCursor.INDEX_START);
286 cursor.nodeStack.push(subnode);
287
288 return subnode.moveToNextInorder(cursor);
289 }
290 }
291
292 // nothing found
293 cursor.nodeStack.pop();
294 cursor.nodeIndexStack.pop();
295 if (!cursor.nodeStack.isEmpty()) {
296 Node<K, V> supernode = cursor.nodeStack.peek();
297 return supernode.moveToNextInorder(cursor);
298 } else {
299 cursor.key = null;
300 cursor.value = null;
301 return false;
302 }
303 }
304
305 @Override
306 public void prettyPrint(StringBuilder builder, int depth, int code) {
307 builder.append("\t".repeat(Math.max(0, depth)));
271 if (code >= 0) { 308 if (code >= 0) {
272 builder.append(code); 309 builder.append(code);
273 builder.append(":"); 310 builder.append(":");
@@ -294,17 +331,16 @@ public class ImmutableNode<K, V> extends Node<K, V> {
294 int nodeMask = 1; 331 int nodeMask = 1;
295 for (int i = 0; i < FACTOR; i++) { 332 for (int i = 0; i < FACTOR; i++) {
296 if ((nodeMask & nodeMap) != 0) { 333 if ((nodeMask & nodeMap) != 0) {
297 @SuppressWarnings("unchecked") 334 @SuppressWarnings("unchecked") Node<K, V> subNode = (Node<K, V>) content[content.length - 1 - index(nodeMap, nodeMask)];
298 Node<K, V> subNode = (Node<K, V>) content[content.length - 1 - index(nodeMap, nodeMask)];
299 builder.append("\n"); 335 builder.append("\n");
300 subNode.prettyPrint(builder, depth + 1, i); 336 subNode.prettyPrint(builder, incrementDepth(depth), i);
301 } 337 }
302 nodeMask <<= 1; 338 nodeMask <<= 1;
303 } 339 }
304 } 340 }
305 341
306 @Override 342 @Override
307 public void checkIntegrity(ContinousHashProvider<? super K> hashProvider, V defaultValue, int depth) { 343 public void checkIntegrity(ContinuousHashProvider<? super K> hashProvider, V defaultValue, int depth) {
308 if (depth > 0) { 344 if (depth > 0) {
309 boolean orphaned = Integer.bitCount(dataMap) == 1 && nodeMap == 0; 345 boolean orphaned = Integer.bitCount(dataMap) == 1 && nodeMap == 0;
310 if (orphaned) { 346 if (orphaned) {
@@ -315,12 +351,11 @@ public class ImmutableNode<K, V> extends Node<K, V> {
315 351
316 // check subnodes 352 // check subnodes
317 for (int i = 0; i < Integer.bitCount(nodeMap); i++) { 353 for (int i = 0; i < Integer.bitCount(nodeMap); i++) {
318 @SuppressWarnings("unchecked") 354 @SuppressWarnings("unchecked") var subnode = (Node<K, V>) this.content[this.content.length - 1 - i];
319 var subnode = (Node<K, V>) this.content[this.content.length - 1 - i];
320 if (!(subnode instanceof ImmutableNode<?, ?>)) { 355 if (!(subnode instanceof ImmutableNode<?, ?>)) {
321 throw new IllegalStateException("Immutable node contains mutable subnodes!"); 356 throw new IllegalStateException("Immutable node contains mutable subnodes!");
322 } else { 357 } else {
323 subnode.checkIntegrity(hashProvider, defaultValue, depth + 1); 358 subnode.checkIntegrity(hashProvider, defaultValue, incrementDepth(depth));
324 } 359 }
325 } 360 }
326 } 361 }
@@ -332,13 +367,10 @@ public class ImmutableNode<K, V> extends Node<K, V> {
332 367
333 @Override 368 @Override
334 public boolean equals(Object obj) { 369 public boolean equals(Object obj) {
335 if (this == obj) 370 if (this == obj) return true;
336 return true; 371 if (obj == null) return false;
337 if (obj == null)
338 return false;
339 if (obj instanceof ImmutableNode<?, ?> other) { 372 if (obj instanceof ImmutableNode<?, ?> other) {
340 return precalculatedHash == other.precalculatedHash && dataMap == other.dataMap && nodeMap == other.nodeMap 373 return precalculatedHash == other.precalculatedHash && dataMap == other.dataMap && nodeMap == other.nodeMap && Arrays.deepEquals(content, other.content);
341 && Arrays.deepEquals(content, other.content);
342 } else if (obj instanceof MutableNode<?, ?> mutableObj) { 374 } else if (obj instanceof MutableNode<?, ?> mutableObj) {
343 return ImmutableNode.compareImmutableMutable(this, mutableObj); 375 return ImmutableNode.compareImmutableMutable(this, mutableObj);
344 } else { 376 } else {
@@ -356,19 +388,17 @@ public class ImmutableNode<K, V> extends Node<K, V> {
356 if (key != null) { 388 if (key != null) {
357 // Check whether a new Key-Value pair can fit into the immutable container 389 // Check whether a new Key-Value pair can fit into the immutable container
358 if (datas * 2 + nodes + 2 <= immutableLength) { 390 if (datas * 2 + nodes + 2 <= immutableLength) {
359 if (!immutable.content[datas * 2].equals(key) 391 if (!immutable.content[datas * 2].equals(key) || !immutable.content[datas * 2 + 1].equals(mutable.content[i * 2 + 1])) {
360 || !immutable.content[datas * 2 + 1].equals(mutable.content[i * 2 + 1])) {
361 return false; 392 return false;
362 } 393 }
363 } else 394 } else return false;
364 return false;
365 datas++; 395 datas++;
366 } else { 396 } else {
367 var mutableSubnode = (Node<?, ?>) mutable.content[i * 2 + 1]; 397 var mutableSubnode = (Node<?, ?>) mutable.content[i * 2 + 1];
368 if (mutableSubnode != null) { 398 if (mutableSubnode != null) {
369 if (datas * 2 + nodes + 1 <= immutableLength) { 399 if (datas * 2 + nodes + 1 <= immutableLength) {
370 Object immutableSubnode = immutable.content[immutableLength - 1 - nodes]; 400 Object immutableSubNode = immutable.content[immutableLength - 1 - nodes];
371 if (!mutableSubnode.equals(immutableSubnode)) { 401 if (!mutableSubnode.equals(immutableSubNode)) {
372 return false; 402 return false;
373 } 403 }
374 nodes++; 404 nodes++;
@@ -378,6 +408,7 @@ public class ImmutableNode<K, V> extends Node<K, V> {
378 } 408 }
379 } 409 }
380 } 410 }
381 return true; 411
412 return datas * 2 + nodes == immutable.content.length;
382 } 413 }
383} 414}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/InOrderMapCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/InOrderMapCursor.java
new file mode 100644
index 00000000..7cc91004
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/InOrderMapCursor.java
@@ -0,0 +1,146 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.state;
7
8import tools.refinery.store.map.AnyVersionedMap;
9import tools.refinery.store.map.ContentHashCode;
10import tools.refinery.store.map.Cursor;
11import tools.refinery.store.map.VersionedMap;
12
13import java.util.*;
14
15public class InOrderMapCursor<K, V> implements Cursor<K, V> {
16 // Constants
17 static final int INDEX_START = -1;
18
19 // Tree stack
20 ArrayDeque<Node<K, V>> nodeStack;
21 ArrayDeque<Integer> nodeIndexStack;
22
23
24 // Values
25 K key;
26 V value;
27
28 // Hash code for checking concurrent modifications
29 final VersionedMap<K, V> map;
30 final int creationHash;
31
32 public InOrderMapCursor(VersionedMapStateImpl<K, V> map) {
33 // Initializing tree stack
34 super();
35 this.nodeStack = new ArrayDeque<>();
36 this.nodeIndexStack = new ArrayDeque<>();
37 if (map.root != null) {
38 this.nodeStack.add(map.root);
39 this.nodeIndexStack.push(INDEX_START);
40 }
41
42 // Initializing cache
43 this.key = null;
44 this.value = null;
45
46 // Initializing state
47 this.map = map;
48 this.creationHash = map.contentHashCode(ContentHashCode.APPROXIMATE_FAST);
49 }
50
51 public K getKey() {
52 return key;
53 }
54
55 public V getValue() {
56 return value;
57 }
58
59 public boolean isTerminated() {
60 return this.nodeStack.isEmpty();
61 }
62
63 public boolean move() {
64 if (isDirty()) {
65 throw new ConcurrentModificationException();
66 }
67 if (!isTerminated()) {
68 var node = this.nodeStack.peek();
69 if (node == null) {
70 throw new IllegalStateException("Cursor is not terminated but the current node is missing");
71 }
72 boolean result = node.moveToNextInorder(this);
73 if (this.nodeIndexStack.size() != this.nodeStack.size()) {
74 throw new IllegalArgumentException("Node stack is corrupted by illegal moves!");
75 }
76 return result;
77 }
78 return false;
79 }
80
81 public boolean skipCurrentNode() {
82 nodeStack.pop();
83 nodeIndexStack.pop();
84 return move();
85 }
86
87 @Override
88 public boolean isDirty() {
89 return this.map.contentHashCode(ContentHashCode.APPROXIMATE_FAST) != this.creationHash;
90 }
91
92 @Override
93 public Set<AnyVersionedMap> getDependingMaps() {
94 return Set.of(this.map);
95 }
96
97 public static <K, V> boolean sameSubNode(InOrderMapCursor<K, V> cursor1, InOrderMapCursor<K, V> cursor2) {
98 Node<K, V> nodeOfCursor1 = cursor1.nodeStack.peek();
99 Node<K, V> nodeOfCursor2 = cursor2.nodeStack.peek();
100 return Objects.equals(nodeOfCursor1, nodeOfCursor2);
101 }
102
103 /**
104 * Compares the state of two cursors started on two {@link VersionedMap} of the same
105 * {@link tools.refinery.store.map.VersionedMapStore}.
106 * @param <K> Key type
107 * @param <V> Value type
108 * @param cursor1 first cursor
109 * @param cursor2 second cursor
110 * @return Positive number if cursor 1 is behind, negative number if cursor 2 is behind, and 0 if they are at the
111 * same position.
112 */
113 public static <K, V> int comparePosition(InOrderMapCursor<K, V> cursor1, InOrderMapCursor<K, V> cursor2) {
114 // If the state does not determine the order, then compare @nodeIndexStack.
115 Iterator<Integer> nodeIndexStack1 = cursor1.nodeIndexStack.descendingIterator();
116 Iterator<Integer> nodeIndexStack2 = cursor2.nodeIndexStack.descendingIterator();
117
118 while(nodeIndexStack1.hasNext() && nodeIndexStack2.hasNext()){
119 final int index1 = nodeIndexStack1.next();
120 final int index2 = nodeIndexStack2.next();
121 if(index1 < index2) {
122 return 1;
123 } else if(index1 > index2) {
124 return -1;
125 }
126 }
127
128 return 0;
129 }
130
131 /**
132 * Compares the depth of two cursors started on @{@link VersionedMap} of the same
133 * {@link tools.refinery.store.map.VersionedMapStore}.
134 * @param <K> Key type
135 * @param <V> Value type
136 * @param cursor1 first cursor
137 * @param cursor2 second cursor
138 * @return Positive number if cursor 1 is deeper, negative number if cursor 2 is deeper, and 0 if they are at the
139 * same depth.
140 */
141 public static <K, V> int compareDepth(InOrderMapCursor<K, V> cursor1, InOrderMapCursor<K, V> cursor2) {
142 int d1 = cursor1.nodeIndexStack.size();
143 int d2 = cursor2.nodeIndexStack.size();
144 return Integer.compare(d1, d2);
145 }
146}
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/state/MapCursor.java
index f34ec7bb..0db50d04 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/state/MapCursor.java
@@ -1,9 +1,9 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal.state;
7 7
8import tools.refinery.store.map.AnyVersionedMap; 8import tools.refinery.store.map.AnyVersionedMap;
9import tools.refinery.store.map.ContentHashCode; 9import tools.refinery.store.map.ContentHashCode;
@@ -12,7 +12,6 @@ import tools.refinery.store.map.VersionedMap;
12 12
13import java.util.ArrayDeque; 13import java.util.ArrayDeque;
14import java.util.ConcurrentModificationException; 14import java.util.ConcurrentModificationException;
15import java.util.Iterator;
16import java.util.Set; 15import java.util.Set;
17 16
18public class MapCursor<K, V> implements Cursor<K, V> { 17public class MapCursor<K, V> implements Cursor<K, V> {
@@ -84,13 +83,6 @@ public class MapCursor<K, V> implements Cursor<K, V> {
84 return false; 83 return false;
85 } 84 }
86 85
87 public boolean skipCurrentNode() {
88 nodeStack.pop();
89 nodeIndexStack.pop();
90 dataIndex = INDEX_FINISH;
91 return move();
92 }
93
94 @Override 86 @Override
95 public boolean isDirty() { 87 public boolean isDirty() {
96 return this.map.contentHashCode(ContentHashCode.APPROXIMATE_FAST) != this.creationHash; 88 return this.map.contentHashCode(ContentHashCode.APPROXIMATE_FAST) != this.creationHash;
@@ -100,46 +92,4 @@ public class MapCursor<K, V> implements Cursor<K, V> {
100 public Set<AnyVersionedMap> getDependingMaps() { 92 public Set<AnyVersionedMap> getDependingMaps() {
101 return Set.of(this.map); 93 return Set.of(this.map);
102 } 94 }
103
104 public static <K, V> boolean sameSubnode(MapCursor<K, V> cursor1, MapCursor<K, V> cursor2) {
105 Node<K, V> nodeOfCursor1 = cursor1.nodeStack.peek();
106 Node<K, V> nodeOfCursor2 = cursor2.nodeStack.peek();
107 if (nodeOfCursor1 != null && nodeOfCursor2 != null) {
108 return nodeOfCursor1.equals(nodeOfCursor2);
109 } else {
110 return false;
111 }
112 }
113
114 /**
115 * @param <K>
116 * @param <V>
117 * @param cursor1
118 * @param cursor2
119 * @return Positive number if cursor 1 is behind, negative number if cursor 2 is behind, and 0 if they are at the
120 * same position.
121 */
122 public static <K, V> int compare(MapCursor<K, V> cursor1, MapCursor<K, V> cursor2) {
123 // two cursors are equally deep
124 Iterator<Integer> stack1 = cursor1.nodeIndexStack.descendingIterator();
125 Iterator<Integer> stack2 = cursor2.nodeIndexStack.descendingIterator();
126 if (stack1.hasNext()) {
127 if (!stack2.hasNext()) {
128 // stack 2 has no more element, thus stack 1 is deeper
129 return 1;
130 }
131 int val1 = stack1.next();
132 int val2 = stack2.next();
133 if (val1 < val2) {
134 return -1;
135 } else if (val2 < val1) {
136 return 1;
137 }
138 }
139 if (stack2.hasNext()) {
140 // stack 2 has more element, thus stack 2 is deeper
141 return 1;
142 }
143 return Integer.compare(cursor1.dataIndex, cursor2.dataIndex);
144 }
145} 95}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapDiffCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapDiffCursor.java
new file mode 100644
index 00000000..083bf8cf
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapDiffCursor.java
@@ -0,0 +1,264 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.state;
7
8import tools.refinery.store.map.AnyVersionedMap;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.map.DiffCursor;
11
12import java.util.Objects;
13import java.util.Set;
14import java.util.stream.Collectors;
15import java.util.stream.Stream;
16
17/**
18 * A cursor representing the difference between two states of a map.
19 *
20 * @author Oszkar Semerath
21 */
22public class MapDiffCursor<K, V> implements DiffCursor<K, V>, Cursor<K, V> {
23 private enum State {
24 /**
25 * initialized state.
26 */
27 INIT,
28 /**
29 * Unstable state.
30 */
31 MOVING_MOVING_SAME_KEY_SAME_VALUE,
32 /**
33 * Both cursors are moving, and they are on the same sub-node.
34 */
35 MOVING_MOVING_SAME_NODE,
36 /**
37 * Both cursors are moving, cursor 1 is behind.
38 */
39 MOVING_MOVING_BEHIND1,
40 /**
41 * Both cursors are moving, cursor 2 is behind.
42 */
43 MOVING_MOVING_BEHIND2,
44 /**
45 * Both cursors are moving, cursor 1 is on the same key as cursor 2, values are different
46 */
47 MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE,
48 /**
49 * Cursor 1 is moving, Cursor 2 is terminated.
50 */
51 MOVING_TERMINATED,
52 /**
53 * Cursor 1 is terminated , Cursor 2 is moving.
54 */
55 TERMINATED_MOVING,
56 /**
57 * Both cursors are terminated.
58 */
59 TERMINATED_TERMINATED,
60 /**
61 * Both Cursors are moving, and they are on an incomparable position.
62 * It is resolved by showing Cursor 1.
63 */
64 MOVING_MOVING_HASH1,
65 /**
66 * Both Cursors are moving, and they are on an incomparable position.
67 * It is resolved by showing Cursor 2.
68 */
69 MOVING_MOVING_HASH2
70 }
71
72 /**
73 * Default nodeId representing missing elements.
74 */
75 private final V defaultValue;
76 private final InOrderMapCursor<K, V> cursor1;
77 private final InOrderMapCursor<K, V> cursor2;
78
79 // State
80 State state = State.INIT;
81
82 // Values
83 private K key;
84 private V fromValue;
85 private V toValue;
86
87
88 public MapDiffCursor(V defaultValue, InOrderMapCursor<K, V> cursor1, InOrderMapCursor<K, V> cursor2) {
89 super();
90 this.defaultValue = defaultValue;
91 this.cursor1 = cursor1;
92 this.cursor2 = cursor2;
93 }
94
95 @Override
96 public K getKey() {
97 return key;
98 }
99
100 @Override
101 public V getFromValue() {
102 return fromValue;
103 }
104
105 @Override
106 public V getToValue() {
107 return toValue;
108 }
109
110 @Override
111 public V getValue() {
112 return getToValue();
113 }
114
115 public boolean isTerminated() {
116 return this.state == State.TERMINATED_TERMINATED;
117 }
118
119 @Override
120 public boolean isDirty() {
121 return this.cursor1.isDirty() || this.cursor2.isDirty();
122 }
123
124 @Override
125 public Set<AnyVersionedMap> getDependingMaps() {
126 return Stream.concat(cursor1.getDependingMaps().stream(), cursor2.getDependingMaps().stream()).map(AnyVersionedMap.class::cast).collect(Collectors.toUnmodifiableSet());
127 }
128
129 private boolean isInStableState() {
130 return this.state != State.MOVING_MOVING_SAME_KEY_SAME_VALUE
131 && this.state != State.MOVING_MOVING_SAME_NODE && this.state != State.INIT;
132 }
133
134 private boolean updateAndReturnWithResult() {
135 return switch (this.state) {
136 case INIT -> throw new IllegalStateException("DiffCursor terminated without starting!");
137 case MOVING_MOVING_SAME_KEY_SAME_VALUE, MOVING_MOVING_SAME_NODE ->
138 throw new IllegalStateException("DiffCursor terminated in unstable state!");
139 case MOVING_MOVING_BEHIND1, MOVING_TERMINATED, MOVING_MOVING_HASH1 -> {
140 this.key = this.cursor1.getKey();
141 this.fromValue = this.cursor1.getValue();
142 this.toValue = this.defaultValue;
143 yield true;
144 }
145 case MOVING_MOVING_BEHIND2, TERMINATED_MOVING, MOVING_MOVING_HASH2 -> {
146 this.key = this.cursor2.getKey();
147 this.fromValue = this.defaultValue;
148 this.toValue = cursor2.getValue();
149 yield true;
150 }
151 case MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE -> {
152 this.key = this.cursor1.getKey();
153 this.fromValue = this.cursor1.getValue();
154 this.toValue = this.cursor2.getValue();
155 yield true;
156 }
157 case TERMINATED_TERMINATED -> {
158 this.key = null;
159 this.fromValue = null;
160 this.toValue = null;
161 yield false;
162 }
163 };
164 }
165
166 public boolean move() {
167 do {
168 this.state = moveOne(this.state);
169 } while (!isInStableState());
170 return updateAndReturnWithResult();
171 }
172
173 private State moveOne(State currentState) {
174 return switch (currentState) {
175 case INIT, MOVING_MOVING_HASH2, MOVING_MOVING_SAME_KEY_SAME_VALUE, MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE -> {
176 boolean cursor1Moved = cursor1.move();
177 boolean cursor2Moved = cursor2.move();
178 yield recalculateStateAfterCursorMovement(cursor1Moved, cursor2Moved);
179 }
180 case MOVING_MOVING_SAME_NODE -> {
181 boolean cursor1Moved = cursor1.skipCurrentNode();
182 boolean cursor2Moved = cursor2.skipCurrentNode();
183 yield recalculateStateAfterCursorMovement(cursor1Moved, cursor2Moved);
184 }
185 case MOVING_MOVING_BEHIND1 -> {
186 boolean cursorMoved = cursor1.move();
187 if (cursorMoved) {
188 yield recalculateStateBasedOnCursorRelation();
189 } else {
190 yield State.TERMINATED_MOVING;
191 }
192 }
193 case MOVING_MOVING_BEHIND2 -> {
194 boolean cursorMoved = cursor2.move();
195 if (cursorMoved) {
196 yield recalculateStateBasedOnCursorRelation();
197 } else {
198 yield State.MOVING_TERMINATED;
199 }
200 }
201 case TERMINATED_MOVING -> {
202 boolean cursorMoved = cursor2.move();
203 if (cursorMoved) {
204 yield State.TERMINATED_MOVING;
205 } else {
206 yield State.TERMINATED_TERMINATED;
207 }
208 }
209 case MOVING_TERMINATED -> {
210 boolean cursorMoved = cursor1.move();
211 if (cursorMoved) {
212 yield State.MOVING_TERMINATED;
213 } else {
214 yield State.TERMINATED_TERMINATED;
215 }
216 }
217 case MOVING_MOVING_HASH1 -> State.MOVING_MOVING_HASH2;
218 case TERMINATED_TERMINATED -> throw new IllegalStateException("Trying to move while terminated!");
219 };
220 }
221
222 private State recalculateStateAfterCursorMovement(boolean cursor1Moved, boolean cursor2Moved) {
223 if (cursor1Moved && cursor2Moved) {
224 return recalculateStateBasedOnCursorRelation();
225 } else if (cursor1Moved) {
226 return State.MOVING_TERMINATED;
227 } else if (cursor2Moved) {
228 return State.TERMINATED_MOVING;
229 } else {
230 return State.TERMINATED_TERMINATED;
231 }
232 }
233
234 private State recalculateStateBasedOnCursorRelation() {
235 final int relation = InOrderMapCursor.comparePosition(cursor1, cursor2);
236
237 if (relation > 0) {
238 return State.MOVING_MOVING_BEHIND1;
239 } else if (relation < 0) {
240 return State.MOVING_MOVING_BEHIND2;
241 }
242
243 if (InOrderMapCursor.sameSubNode(cursor1, cursor2)) {
244 return State.MOVING_MOVING_SAME_NODE;
245 } else if (Objects.equals(cursor1.getKey(), cursor2.getKey())) {
246 if (Objects.equals(cursor1.getValue(), cursor2.getValue())) {
247 return State.MOVING_MOVING_SAME_KEY_SAME_VALUE;
248 } else {
249 return State.MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE;
250 }
251 }
252
253 final int depth = InOrderMapCursor.compareDepth(cursor1, cursor2);
254
255 if (depth > 0) {
256 return State.MOVING_MOVING_BEHIND1;
257 } else if (depth < 0) {
258 return State.MOVING_MOVING_BEHIND2;
259 } else {
260 return State.MOVING_MOVING_HASH1;
261 }
262
263 }
264}
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/state/MutableNode.java
index 1129ee5a..408ed62c 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/state/MutableNode.java
@@ -1,26 +1,26 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal.state;
7 7
8import tools.refinery.store.map.ContinousHashProvider; 8import tools.refinery.store.map.ContinuousHashProvider;
9 9
10import java.util.Arrays; 10import java.util.Arrays;
11import java.util.Map; 11import java.util.Map;
12 12
13public class MutableNode<K, V> extends Node<K, V> { 13public class MutableNode<K, V> extends Node<K, V> {
14 int cachedHash; 14 int cachedHash;
15 protected boolean cachedHashValid;
15 protected Object[] content; 16 protected Object[] content;
16 17
17 protected MutableNode() { 18 protected MutableNode() {
18 this.content = new Object[2 * FACTOR]; 19 this.content = new Object[2 * FACTOR];
19 updateHash(); 20 invalidateHash();
20 } 21 }
21 22
22 public static <K, V> MutableNode<K, V> initialize(K key, V value, ContinousHashProvider<? super K> hashProvider, 23 public static <K, V> MutableNode<K, V> initialize(K key, V value, ContinuousHashProvider<? super K> hashProvider, V defaultValue) {
23 V defaultValue) {
24 if (value == defaultValue) { 24 if (value == defaultValue) {
25 return null; 25 return null;
26 } else { 26 } else {
@@ -29,7 +29,7 @@ public class MutableNode<K, V> extends Node<K, V> {
29 MutableNode<K, V> res = new MutableNode<>(); 29 MutableNode<K, V> res = new MutableNode<>();
30 res.content[2 * fragment] = key; 30 res.content[2 * fragment] = key;
31 res.content[2 * fragment + 1] = value; 31 res.content[2 * fragment + 1] = value;
32 res.updateHash(); 32 res.invalidateHash();
33 return res; 33 return res;
34 } 34 }
35 } 35 }
@@ -37,44 +37,41 @@ public class MutableNode<K, V> extends Node<K, V> {
37 /** 37 /**
38 * Constructs a {@link MutableNode} as a copy of an {@link ImmutableNode} 38 * Constructs a {@link MutableNode} as a copy of an {@link ImmutableNode}
39 * 39 *
40 * @param node 40 * @param node to be transformed
41 */ 41 */
42 protected MutableNode(ImmutableNode<K, V> node) { 42 protected MutableNode(ImmutableNode<K, V> node) {
43 this.content = new Object[2 * FACTOR]; 43 this.content = new Object[2 * FACTOR];
44 int dataUsed = 0; 44 int dataUsed = 0;
45 int nodeUsed = 0; 45 int nodeUsed = 0;
46 for (int i = 0; i < FACTOR; i++) { 46 for (int i = 0; i < FACTOR; i++) {
47 int bitposition = 1 << i; 47 int bitPosition = 1 << i;
48 if ((node.dataMap & bitposition) != 0) { 48 if ((node.dataMap & bitPosition) != 0) {
49 content[2 * i] = node.content[dataUsed * 2]; 49 content[2 * i] = node.content[dataUsed * 2];
50 content[2 * i + 1] = node.content[dataUsed * 2 + 1]; 50 content[2 * i + 1] = node.content[dataUsed * 2 + 1];
51 dataUsed++; 51 dataUsed++;
52 } else if ((node.nodeMap & bitposition) != 0) { 52 } else if ((node.nodeMap & bitPosition) != 0) {
53 content[2 * i + 1] = node.content[node.content.length - 1 - nodeUsed]; 53 content[2 * i + 1] = node.content[node.content.length - 1 - nodeUsed];
54 nodeUsed++; 54 nodeUsed++;
55 } 55 }
56 } 56 }
57 this.cachedHash = node.hashCode(); 57 this.cachedHashValid = false;
58 } 58 }
59 59
60 @Override 60 @Override
61 public V getValue(K key, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) { 61 public V getValue(K key, ContinuousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) {
62 int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); 62 int selectedHashFragment = hashFragment(hash, shiftDepth(depth));
63 @SuppressWarnings("unchecked") 63 @SuppressWarnings("unchecked") K keyCandidate = (K) this.content[2 * selectedHashFragment];
64 K keyCandidate = (K) this.content[2 * selectedHashFragment];
65 if (keyCandidate != null) { 64 if (keyCandidate != null) {
66 if (keyCandidate.equals(key)) { 65 if (keyCandidate.equals(key)) {
67 @SuppressWarnings("unchecked") 66 @SuppressWarnings("unchecked") V value = (V) this.content[2 * selectedHashFragment + 1];
68 V value = (V) this.content[2 * selectedHashFragment + 1];
69 return value; 67 return value;
70 } else { 68 } else {
71 return defaultValue; 69 return defaultValue;
72 } 70 }
73 } else { 71 } else {
74 @SuppressWarnings("unchecked") 72 @SuppressWarnings("unchecked") var nodeCandidate = (Node<K, V>) content[2 * selectedHashFragment + 1];
75 var nodeCandidate = (Node<K, V>) content[2 * selectedHashFragment + 1];
76 if (nodeCandidate != null) { 73 if (nodeCandidate != null) {
77 int newDepth = depth + 1; 74 int newDepth = incrementDepth(depth);
78 int newHash = newHash(hashProvider, key, hash, newDepth); 75 int newHash = newHash(hashProvider, key, hash, newDepth);
79 return nodeCandidate.getValue(key, hashProvider, defaultValue, newHash, newDepth); 76 return nodeCandidate.getValue(key, hashProvider, defaultValue, newHash, newDepth);
80 } else { 77 } else {
@@ -84,13 +81,11 @@ public class MutableNode<K, V> extends Node<K, V> {
84 } 81 }
85 82
86 @Override 83 @Override
87 public Node<K, V> putValue(K key, V value, OldValueBox<V> oldValueBox, ContinousHashProvider<? super K> hashProvider, 84 public Node<K, V> putValue(K key, V value, OldValueBox<V> oldValueBox, ContinuousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) {
88 V defaultValue, int hash, int depth) {
89 int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); 85 int selectedHashFragment = hashFragment(hash, shiftDepth(depth));
90 @SuppressWarnings("unchecked") 86 @SuppressWarnings("unchecked") K keyCandidate = (K) content[2 * selectedHashFragment];
91 K keyCandidate = (K) content[2 * selectedHashFragment];
92 if (keyCandidate != null) { 87 if (keyCandidate != null) {
93 // If has key 88 // If it has key
94 if (keyCandidate.equals(key)) { 89 if (keyCandidate.equals(key)) {
95 // The key is equals to an existing key -> update entry 90 // The key is equals to an existing key -> update entry
96 if (value == defaultValue) { 91 if (value == defaultValue) {
@@ -111,25 +106,22 @@ public class MutableNode<K, V> extends Node<K, V> {
111 return moveDownAndSplit(hashProvider, key, value, keyCandidate, hash, depth, selectedHashFragment); 106 return moveDownAndSplit(hashProvider, key, value, keyCandidate, hash, depth, selectedHashFragment);
112 } 107 }
113 } 108 }
109 }
110 // If it does not have key, check for value
111 @SuppressWarnings("unchecked") var nodeCandidate = (Node<K, V>) content[2 * selectedHashFragment + 1];
112 if (nodeCandidate != null) {
113 // If it has value, it is a sub-node -> update that
114 int newDepth = incrementDepth(depth);
115 var newNode = nodeCandidate.putValue(key, value, oldValueBox, hashProvider, defaultValue, newHash(hashProvider, key, hash, newDepth), newDepth);
116 return updateWithSubNode(selectedHashFragment, newNode, (value == null && defaultValue == null) || (value != null && value.equals(defaultValue)));
114 } else { 117 } else {
115 // If it does not have key, check for nodeId 118 // If it does not have value, put it in the empty place
116 @SuppressWarnings("unchecked") 119 if (value == defaultValue) {
117 var nodeCandidate = (Node<K, V>) content[2 * selectedHashFragment + 1]; 120 // don't need to add new key-value pair
118 if (nodeCandidate != null) { 121 oldValueBox.setOldValue(defaultValue);
119 // If it has nodeId, it is a subnode -> upate that 122 return this;
120 var newNode = nodeCandidate.putValue(key, value, oldValueBox, hashProvider, defaultValue,
121 newHash(hashProvider, key, hash, depth + 1), depth + 1);
122 return updateWithSubNode(selectedHashFragment, newNode, value.equals(defaultValue));
123 } else { 123 } else {
124 // If it does not have nodeId, put it in the empty place 124 return addEntry(key, value, oldValueBox, selectedHashFragment, defaultValue);
125 if (value == defaultValue) {
126 // dont need to add new key-nodeId pair
127 oldValueBox.setOldValue(defaultValue);
128 return this;
129 } else {
130 return addEntry(key, value, oldValueBox, selectedHashFragment, defaultValue);
131 }
132
133 } 125 }
134 } 126 }
135 } 127 }
@@ -138,30 +130,31 @@ public class MutableNode<K, V> extends Node<K, V> {
138 content[2 * selectedHashFragment] = key; 130 content[2 * selectedHashFragment] = key;
139 oldValueBox.setOldValue(defaultValue); 131 oldValueBox.setOldValue(defaultValue);
140 content[2 * selectedHashFragment + 1] = value; 132 content[2 * selectedHashFragment + 1] = value;
141 updateHash(); 133 invalidateHash();
142 return this; 134 return this;
143 } 135 }
144 136
145 /** 137 /**
146 * Updates an entry in a selected hash-fragment to a non-default nodeId. 138 * Updates an entry in a selected hash-fragment to a non-default value.
147 * 139 *
148 * @param value 140 * @param value new value
149 * @param selectedHashFragment 141 * @param selectedHashFragment position of the value
150 * @return 142 * @return updated node
151 */ 143 */
152 @SuppressWarnings("unchecked") 144 @SuppressWarnings("unchecked")
153 Node<K, V> updateValue(V value, OldValueBox<V> oldValue, int selectedHashFragment) { 145 Node<K, V> updateValue(V value, OldValueBox<V> oldValue, int selectedHashFragment) {
154 oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]); 146 oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]);
155 content[2 * selectedHashFragment + 1] = value; 147 content[2 * selectedHashFragment + 1] = value;
156 updateHash(); 148 invalidateHash();
157 return this; 149 return this;
158 } 150 }
159 151
160 /** 152 /**
153 * Updates an entry in a selected hash-fragment with a subtree.
161 * 154 *
162 * @param selectedHashFragment 155 * @param selectedHashFragment position of the value
163 * @param newNode 156 * @param newNode the subtree
164 * @return 157 * @return updated node
165 */ 158 */
166 Node<K, V> updateWithSubNode(int selectedHashFragment, Node<K, V> newNode, boolean deletionHappened) { 159 Node<K, V> updateWithSubNode(int selectedHashFragment, Node<K, V> newNode, boolean deletionHappened) {
167 if (deletionHappened) { 160 if (deletionHappened) {
@@ -169,7 +162,7 @@ public class MutableNode<K, V> extends Node<K, V> {
169 // Check whether this node become empty 162 // Check whether this node become empty
170 content[2 * selectedHashFragment + 1] = null; // i.e. the new node 163 content[2 * selectedHashFragment + 1] = null; // i.e. the new node
171 if (hasContent()) { 164 if (hasContent()) {
172 updateHash(); 165 invalidateHash();
173 return this; 166 return this;
174 } else { 167 } else {
175 return null; 168 return null;
@@ -180,10 +173,10 @@ public class MutableNode<K, V> extends Node<K, V> {
180 if (immutableNewNode != null) { 173 if (immutableNewNode != null) {
181 int orphaned = immutableNewNode.isOrphaned(); 174 int orphaned = immutableNewNode.isOrphaned();
182 if (orphaned >= 0) { 175 if (orphaned >= 0) {
183 // orphan subnode data is replaced with data 176 // orphan sub-node data is replaced with data
184 content[2 * selectedHashFragment] = immutableNewNode.content[orphaned * 2]; 177 content[2 * selectedHashFragment] = immutableNewNode.content[orphaned * 2];
185 content[2 * selectedHashFragment + 1] = immutableNewNode.content[orphaned * 2 + 1]; 178 content[2 * selectedHashFragment + 1] = immutableNewNode.content[orphaned * 2 + 1];
186 updateHash(); 179 invalidateHash();
187 return this; 180 return this;
188 } 181 }
189 } 182 }
@@ -191,15 +184,13 @@ public class MutableNode<K, V> extends Node<K, V> {
191 } 184 }
192 // normal behaviour 185 // normal behaviour
193 content[2 * selectedHashFragment + 1] = newNode; 186 content[2 * selectedHashFragment + 1] = newNode;
194 updateHash(); 187 invalidateHash();
195 return this; 188 return this;
196
197 } 189 }
198 190
199 private boolean hasContent() { 191 private boolean hasContent() {
200 for (Object element : this.content) { 192 for (Object element : this.content) {
201 if (element != null) 193 if (element != null) return true;
202 return true;
203 } 194 }
204 return false; 195 return false;
205 } 196 }
@@ -226,27 +217,24 @@ public class MutableNode<K, V> extends Node<K, V> {
226 } 217 }
227 218
228 @SuppressWarnings("unchecked") 219 @SuppressWarnings("unchecked")
229 private Node<K, V> moveDownAndSplit(ContinousHashProvider<? super K> hashProvider, K newKey, V newValue, 220 private Node<K, V> moveDownAndSplit(ContinuousHashProvider<? super K> hashProvider, K newKey, V newValue, K previousKey, int hashOfNewKey, int depth, int selectedHashFragmentOfCurrentDepth) {
230 K previousKey, int hashOfNewKey, int depth, int selectedHashFragmentOfCurrentDepth) {
231 V previousValue = (V) content[2 * selectedHashFragmentOfCurrentDepth + 1]; 221 V previousValue = (V) content[2 * selectedHashFragmentOfCurrentDepth + 1];
232 222
233 MutableNode<K, V> newSubNode = newNodeWithTwoEntries(hashProvider, previousKey, previousValue, 223 MutableNode<K, V> newSubNode = newNodeWithTwoEntries(hashProvider, previousKey, previousValue, hashProvider.getHash(previousKey, hashDepth(depth)), newKey, newValue, hashOfNewKey, incrementDepth(depth));
234 hashProvider.getHash(previousKey, hashDepth(depth)), newKey, newValue, hashOfNewKey, depth + 1);
235 224
236 content[2 * selectedHashFragmentOfCurrentDepth] = null; 225 content[2 * selectedHashFragmentOfCurrentDepth] = null;
237 content[2 * selectedHashFragmentOfCurrentDepth + 1] = newSubNode; 226 content[2 * selectedHashFragmentOfCurrentDepth + 1] = newSubNode;
238 updateHash(); 227 invalidateHash();
239 return this; 228 return this;
240 } 229 }
241 230
242 // Pass everything as parameters for performance. 231 // Pass everything as parameters for performance.
243 @SuppressWarnings("squid:S107") 232 @SuppressWarnings("squid:S107")
244 private MutableNode<K, V> newNodeWithTwoEntries(ContinousHashProvider<? super K> hashProvider, K key1, V value1, 233 private MutableNode<K, V> newNodeWithTwoEntries(ContinuousHashProvider<? super K> hashProvider, K key1, V value1, int oldHash1, K key2, V value2, int oldHash2, int newDepth) {
245 int oldHash1, K key2, V value2, int oldHash2, int newdepth) { 234 int newHash1 = newHash(hashProvider, key1, oldHash1, newDepth);
246 int newHash1 = newHash(hashProvider, key1, oldHash1, newdepth); 235 int newHash2 = newHash(hashProvider, key2, oldHash2, newDepth);
247 int newHash2 = newHash(hashProvider, key2, oldHash2, newdepth); 236 int newFragment1 = hashFragment(newHash1, shiftDepth(newDepth));
248 int newFragment1 = hashFragment(newHash1, shiftDepth(newdepth)); 237 int newFragment2 = hashFragment(newHash2, shiftDepth(newDepth));
249 int newFragment2 = hashFragment(newHash2, shiftDepth(newdepth));
250 238
251 MutableNode<K, V> subNode = new MutableNode<>(); 239 MutableNode<K, V> subNode = new MutableNode<>();
252 if (newFragment1 != newFragment2) { 240 if (newFragment1 != newFragment2) {
@@ -256,11 +244,10 @@ public class MutableNode<K, V> extends Node<K, V> {
256 subNode.content[newFragment2 * 2] = key2; 244 subNode.content[newFragment2 * 2] = key2;
257 subNode.content[newFragment2 * 2 + 1] = value2; 245 subNode.content[newFragment2 * 2 + 1] = value2;
258 } else { 246 } else {
259 MutableNode<K, V> subSubNode = newNodeWithTwoEntries(hashProvider, key1, value1, newHash1, key2, value2, 247 MutableNode<K, V> subSubNode = newNodeWithTwoEntries(hashProvider, key1, value1, newHash1, key2, value2, newHash2, incrementDepth(newDepth));
260 newHash2, newdepth + 1);
261 subNode.content[newFragment1 * 2 + 1] = subSubNode; 248 subNode.content[newFragment1 * 2 + 1] = subSubNode;
262 } 249 }
263 subNode.updateHash(); 250 subNode.invalidateHash();
264 return subNode; 251 return subNode;
265 } 252 }
266 253
@@ -270,7 +257,7 @@ public class MutableNode<K, V> extends Node<K, V> {
270 oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]); 257 oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]);
271 content[2 * selectedHashFragment + 1] = null; 258 content[2 * selectedHashFragment + 1] = null;
272 if (hasContent()) { 259 if (hasContent()) {
273 updateHash(); 260 invalidateHash();
274 return this; 261 return this;
275 } else { 262 } else {
276 return null; 263 return null;
@@ -321,10 +308,13 @@ public class MutableNode<K, V> extends Node<K, V> {
321 cursor.dataIndex = MapCursor.INDEX_FINISH; 308 cursor.dataIndex = MapCursor.INDEX_FINISH;
322 } 309 }
323 310
324 // 2. look inside the subnodes 311 // 2. look inside the sub-nodes
312 if(cursor.nodeIndexStack.peek()==null) {
313 throw new IllegalStateException("Cursor moved to the next state when the state is empty.");
314 }
325 for (int index = cursor.nodeIndexStack.peek() + 1; index < FACTOR; index++) { 315 for (int index = cursor.nodeIndexStack.peek() + 1; index < FACTOR; index++) {
326 if (this.content[index * 2] == null && this.content[index * 2 + 1] != null) { 316 if (this.content[index * 2] == null && this.content[index * 2 + 1] != null) {
327 // 2.1 found next subnode, move down to the subnode 317 // 2.1 found next sub-node, move down to the sub-node
328 Node<K, V> subnode = (Node<K, V>) this.content[index * 2 + 1]; 318 Node<K, V> subnode = (Node<K, V>) this.content[index * 2 + 1];
329 319
330 cursor.dataIndex = MapCursor.INDEX_START; 320 cursor.dataIndex = MapCursor.INDEX_START;
@@ -336,7 +326,7 @@ public class MutableNode<K, V> extends Node<K, V> {
336 return subnode.moveToNext(cursor); 326 return subnode.moveToNext(cursor);
337 } 327 }
338 } 328 }
339 // 3. no subnode found, move up 329 // 3. no sub-node found, move up
340 cursor.nodeStack.pop(); 330 cursor.nodeStack.pop();
341 cursor.nodeIndexStack.pop(); 331 cursor.nodeIndexStack.pop();
342 if (!cursor.nodeStack.isEmpty()) { 332 if (!cursor.nodeStack.isEmpty()) {
@@ -350,10 +340,51 @@ public class MutableNode<K, V> extends Node<K, V> {
350 } 340 }
351 341
352 @Override 342 @Override
353 public void prettyPrint(StringBuilder builder, int depth, int code) { 343 @SuppressWarnings("unchecked")
354 for (int i = 0; i < depth; i++) { 344 boolean moveToNextInorder(InOrderMapCursor<K,V> cursor) {
355 builder.append("\t"); 345 if(cursor.nodeIndexStack.peek()==null || cursor.nodeStack.peek()==null) {
346 throw new IllegalStateException("Cursor moved to the next state when the state is empty.");
356 } 347 }
348
349 int position = cursor.nodeIndexStack.peek();
350
351 for (int index = position + 1; index < FACTOR; index++) {
352 // data found
353 if (this.content[index * 2] != null) {
354 cursor.nodeIndexStack.pop();
355 cursor.nodeIndexStack.push(index);
356
357 cursor.key = (K) this.content[index * 2];
358 cursor.value = (V) this.content[index * 2 + 1];
359 return true;
360 } else if (this.content[index * 2 +1] != null) {
361 // sub-node found
362 Node<K,V> subnode = (Node<K, V>) this.content[index * 2 +1];
363 cursor.nodeIndexStack.pop();
364 cursor.nodeIndexStack.push(index);
365 cursor.nodeIndexStack.push(InOrderMapCursor.INDEX_START);
366 cursor.nodeStack.push(subnode);
367
368 return subnode.moveToNextInorder(cursor);
369 }
370 }
371
372 // nothing found
373 cursor.nodeStack.pop();
374 cursor.nodeIndexStack.pop();
375 if (!cursor.nodeStack.isEmpty()) {
376 Node<K, V> supernode = cursor.nodeStack.peek();
377 return supernode.moveToNextInorder(cursor);
378 } else {
379 cursor.key = null;
380 cursor.value = null;
381 return false;
382 }
383 }
384
385 @Override
386 public void prettyPrint(StringBuilder builder, int depth, int code) {
387 builder.append("\t".repeat(Math.max(0, depth)));
357 if (code >= 0) { 388 if (code >= 0) {
358 builder.append(code); 389 builder.append(code);
359 builder.append(":"); 390 builder.append(":");
@@ -376,19 +407,18 @@ public class MutableNode<K, V> extends Node<K, V> {
376 } 407 }
377 } 408 }
378 builder.append(")"); 409 builder.append(")");
379 // print subnodes 410 // print sub-nodes
380 for (int i = 0; i < FACTOR; i++) { 411 for (int i = 0; i < FACTOR; i++) {
381 if (content[2 * i] == null && content[2 * i + 1] != null) { 412 if (content[2 * i] == null && content[2 * i + 1] != null) {
382 @SuppressWarnings("unchecked") 413 @SuppressWarnings("unchecked") Node<K, V> subNode = (Node<K, V>) content[2 * i + 1];
383 Node<K, V> subNode = (Node<K, V>) content[2 * i + 1];
384 builder.append("\n"); 414 builder.append("\n");
385 subNode.prettyPrint(builder, depth + 1, i); 415 subNode.prettyPrint(builder, incrementDepth(depth), i);
386 } 416 }
387 } 417 }
388 } 418 }
389 419
390 @Override 420 @Override
391 public void checkIntegrity(ContinousHashProvider<? super K> hashProvider, V defaultValue, int depth) { 421 public void checkIntegrity(ContinuousHashProvider<? super K> hashProvider, V defaultValue, int depth) {
392 // check for orphan nodes 422 // check for orphan nodes
393 if (depth > 0) { 423 if (depth > 0) {
394 int orphaned = isOrphaned(); 424 int orphaned = isOrphaned();
@@ -399,57 +429,67 @@ public class MutableNode<K, V> extends Node<K, V> {
399 // check the place of data 429 // check the place of data
400 for (int i = 0; i < FACTOR; i++) { 430 for (int i = 0; i < FACTOR; i++) {
401 if (this.content[2 * i] != null) { 431 if (this.content[2 * i] != null) {
402 @SuppressWarnings("unchecked") 432 @SuppressWarnings("unchecked") K key = (K) this.content[2 * i];
403 K key = (K) this.content[2 * i]; 433 @SuppressWarnings("unchecked") V value = (V) this.content[2 * i + 1];
404 @SuppressWarnings("unchecked")
405 V value = (V) this.content[2 * i + 1];
406 434
407 if (value == defaultValue) { 435 if (value == defaultValue) {
408 throw new IllegalStateException("Node contains default nodeId!"); 436 throw new IllegalStateException("Node contains default value!");
409 } 437 }
410 int hashCode = hashProvider.getHash(key, hashDepth(depth)); 438 int hashCode = hashProvider.getHash(key, hashDepth(depth));
411 int shiftDepth = shiftDepth(depth); 439 int shiftDepth = shiftDepth(depth);
412 int selectedHashFragment = hashFragment(hashCode, shiftDepth); 440 int selectedHashFragment = hashFragment(hashCode, shiftDepth);
413 if (i != selectedHashFragment) { 441 if (i != selectedHashFragment) {
414 throw new IllegalStateException("Key " + key + " with hash code " + hashCode 442 throw new IllegalStateException("Key " + key + " with hash code " + hashCode + " is in bad place! Fragment=" + selectedHashFragment + ", Place=" + i);
415 + " is in bad place! Fragment=" + selectedHashFragment + ", Place=" + i);
416 } 443 }
417 } 444 }
418 } 445 }
419 // check subnodes 446 // check sub-nodes
420 for (int i = 0; i < FACTOR; i++) { 447 for (int i = 0; i < FACTOR; i++) {
421 if (this.content[2 * i + 1] != null && this.content[2 * i] == null) { 448 if (this.content[2 * i + 1] != null && this.content[2 * i] == null) {
422 @SuppressWarnings("unchecked") 449 @SuppressWarnings("unchecked") var subNode = (Node<K, V>) this.content[2 * i + 1];
423 var subNode = (Node<K, V>) this.content[2 * i + 1]; 450 subNode.checkIntegrity(hashProvider, defaultValue, incrementDepth(depth));
424 subNode.checkIntegrity(hashProvider, defaultValue, depth + 1);
425 } 451 }
426 } 452 }
427 // check the hash 453 // check the hash
428 int oldHash = this.cachedHash; 454 if (cachedHashValid) {
429 updateHash(); 455 int oldHash = this.cachedHash;
430 int newHash = this.cachedHash; 456 invalidateHash();
431 if (oldHash != newHash) { 457 int newHash = hashCode();
432 throw new IllegalStateException("Hash code was not up to date! (old=" + oldHash + ",new=" + newHash + ")"); 458 if (oldHash != newHash) {
459 throw new IllegalStateException("Hash code was not up to date! (old=" + oldHash + ",new=" + newHash + ")");
460 }
433 } 461 }
434 } 462 }
435 463
436 protected void updateHash() { 464 protected void invalidateHash() {
437 this.cachedHash = Arrays.hashCode(content); 465 this.cachedHashValid = false;
438 } 466 }
439 467
440 @Override 468 @Override
441 public int hashCode() { 469 public int hashCode() {
470 if (!this.cachedHashValid) {
471 this.cachedHash = Arrays.hashCode(content);
472 this.cachedHashValid = true;
473 }
442 return this.cachedHash; 474 return this.cachedHash;
443 } 475 }
444 476
445 @Override 477 @Override
446 public boolean equals(Object obj) { 478 public boolean equals(Object obj) {
447 if (this == obj) 479 if (this == obj) return true;
448 return true; 480 if (obj == null) return false;
449 if (obj == null)
450 return false;
451 if (obj instanceof MutableNode<?, ?> mutableObj) { 481 if (obj instanceof MutableNode<?, ?> mutableObj) {
452 return Arrays.deepEquals(this.content, mutableObj.content); 482 if (obj.hashCode() != this.hashCode()) {
483 return false;
484 } else {
485 for (int i = 0; i < FACTOR * 2; i++) {
486 Object thisContent = this.content[i];
487 if (thisContent != null && !thisContent.equals(mutableObj.content[i])) {
488 return false;
489 }
490 }
491 return true;
492 }
453 } else if (obj instanceof ImmutableNode<?, ?> immutableObj) { 493 } else if (obj instanceof ImmutableNode<?, ?> immutableObj) {
454 return ImmutableNode.compareImmutableMutable(immutableObj, this); 494 return ImmutableNode.compareImmutableMutable(immutableObj, this);
455 } else { 495 } else {
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/Node.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/Node.java
new file mode 100644
index 00000000..0ecf2712
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/Node.java
@@ -0,0 +1,131 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.state;
7
8import java.util.Map;
9
10import tools.refinery.store.map.ContinuousHashProvider;
11
12public abstract class Node<K, V> {
13 public static final int BRANCHING_FACTOR_BITS = 5;
14 public static final int FACTOR = 1 << BRANCHING_FACTOR_BITS;
15 protected static final int NUMBER_OF_FACTORS = Integer.SIZE / BRANCHING_FACTOR_BITS;
16 protected static final int FACTOR_MASK = FACTOR - 1;
17 public static final int EFFECTIVE_BITS = BRANCHING_FACTOR_BITS * NUMBER_OF_FACTORS;
18 public static final int FACTOR_SELECTION_BITS = 32 - Integer.numberOfLeadingZeros(NUMBER_OF_FACTORS);
19 public static final int FACTOR_SELECTION_MASK = (1 << FACTOR_SELECTION_BITS) - 1;
20 public static final int INCREMENT_BIG_STEP = (1 << FACTOR_SELECTION_BITS) - NUMBER_OF_FACTORS;
21
22 /**
23 * Increments the depth of the search in the tree. The depth parameter has two
24 * components: the least few bits selects the fragment of the hashcode, the
25 * other part selects the continuous hash.
26 *
27 * @param depth parameter encoding the fragment and the depth
28 * @return new depth.
29 */
30 protected int incrementDepth(int depth) {
31 int newDepth = depth + 1;
32 if ((newDepth & FACTOR_SELECTION_MASK) == NUMBER_OF_FACTORS) {
33 newDepth += INCREMENT_BIG_STEP;
34 }
35 return newDepth;
36 }
37
38 /**
39 * Calculates the index for the continuous hash.
40 *
41 * @param depth The depth of the node in the tree.
42 * @return The index of the continuous hash.
43 */
44 protected static int hashDepth(int depth) {
45 return depth >> FACTOR_SELECTION_BITS;
46 }
47
48 /**
49 * Calculates the which segment of a single hash should be used.
50 *
51 * @param depth The depth of the node in the tree.
52 * @return The segment of a hash code.
53 */
54 protected static int shiftDepth(int depth) {
55 return depth & FACTOR_SELECTION_MASK;
56 }
57
58 /**
59 * Selects a segments from a complete hash for a given depth.
60 *
61 * @param hash A complete hash.
62 * @param shiftDepth The index of the segment.
63 * @return The segment as an integer.
64 */
65 protected static int hashFragment(int hash, int shiftDepth) {
66 if (shiftDepth < 0 || Node.NUMBER_OF_FACTORS < shiftDepth)
67 throw new IllegalArgumentException("Invalid shift depth! valid interval=[0;5], input=" + shiftDepth);
68 return (hash >>> shiftDepth * BRANCHING_FACTOR_BITS) & FACTOR_MASK;
69 }
70
71 /**
72 * Returns the hash code for a given depth. It may calculate new hash code, or
73 * reuse a hash code calculated for depth-1.
74 *
75 * @param key The key.
76 * @param hash Hash code for depth-1
77 * @param depth The depth.
78 * @return The new hash code.
79 */
80 protected int newHash(final ContinuousHashProvider<? super K> hashProvider, K key, int hash, int depth) {
81 final int shiftDepth = shiftDepth(depth);
82 if (shiftDepth == 0) {
83 final int hashDepth = hashDepth(depth);
84 if (hashDepth >= ContinuousHashProvider.MAX_PRACTICAL_DEPTH) {
85 throw new IllegalArgumentException(
86 "Key " + key + " have the clashing hashcode over the practical depth limitation ("
87 + ContinuousHashProvider.MAX_PRACTICAL_DEPTH + ")!");
88 }
89 return hashProvider.getHash(key, hashDepth);
90 } else {
91 return hash;
92 }
93 }
94
95 public abstract V getValue(K key, ContinuousHashProvider<? super K> hashProvider, V defaultValue, int hash,
96 int depth);
97
98 public abstract Node<K, V> putValue(K key, V value, OldValueBox<V> old,
99 ContinuousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth);
100
101 public abstract long getSize();
102
103 abstract MutableNode<K, V> toMutable();
104
105 public abstract ImmutableNode<K, V> toImmutable(Map<Node<K, V>, ImmutableNode<K, V>> cache);
106
107 protected abstract MutableNode<K, V> isMutable();
108
109 /**
110 * Moves a {@link MapCursor} to its next position.
111 *
112 * @param cursor the cursor
113 * @return Whether there was a next value to move on.
114 */
115 abstract boolean moveToNext(MapCursor<K, V> cursor);
116 abstract boolean moveToNextInorder(InOrderMapCursor<K, V> cursor);
117
118 ///////// FOR printing
119 public abstract void prettyPrint(StringBuilder builder, int depth, int code);
120
121
122 @Override
123 public String toString() {
124 StringBuilder stringBuilder = new StringBuilder();
125 prettyPrint(stringBuilder, 0, -1);
126 return stringBuilder.toString();
127 }
128
129 public void checkIntegrity(ContinuousHashProvider<? super K> hashProvider, V defaultValue, int depth) {
130 }
131}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/OldValueBox.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/OldValueBox.java
index 354af51d..1b0a4b0c 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/OldValueBox.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/OldValueBox.java
@@ -1,9 +1,9 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal.state;
7 7
8public class OldValueBox<V>{ 8public class OldValueBox<V>{
9 V oldValue; 9 V oldValue;
@@ -20,5 +20,5 @@ public class OldValueBox<V>{
20 this.oldValue = ouldValue; 20 this.oldValue = ouldValue;
21 isSet = true; 21 isSet = true;
22 } 22 }
23 23
24} 24}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/StateBasedVersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/StateBasedVersionedMapStoreFactory.java
new file mode 100644
index 00000000..ccc791a8
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/StateBasedVersionedMapStoreFactory.java
@@ -0,0 +1,43 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.state;
7
8import tools.refinery.store.map.*;
9
10import java.util.List;
11
12public class StateBasedVersionedMapStoreFactory<K, V> implements VersionedMapStoreFactory<K, V> {
13 private final V defaultValue;
14 private final ContinuousHashProvider<K> continuousHashProvider;
15 private final VersionedMapStoreStateConfiguration config;
16
17 public StateBasedVersionedMapStoreFactory(V defaultValue, Boolean transformToImmutable,
18 VersionedMapStoreFactoryBuilder.SharingStrategy sharingStrategy,
19 boolean versionFreeingEnabled,
20 ContinuousHashProvider<K> continuousHashProvider) {
21 this.defaultValue = defaultValue;
22 this.continuousHashProvider = continuousHashProvider;
23
24 this.config = new VersionedMapStoreStateConfiguration(
25 transformToImmutable,
26 sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE
27 || sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE_IN_GROUP,
28 sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE_IN_GROUP,
29 versionFreeingEnabled);
30 }
31
32 @Override
33 public VersionedMapStore<K, V> createOne() {
34 return new VersionedMapStoreStateImpl<>(continuousHashProvider, defaultValue, config);
35
36 }
37
38 @Override
39 public List<VersionedMapStore<K, V>> createGroup(int amount) {
40 return VersionedMapStoreStateImpl.createSharedVersionedMapStores(amount, continuousHashProvider, defaultValue,
41 config);
42 }
43}
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/state/VersionedMapStateImpl.java
index 7abece0d..57eeccf6 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/state/VersionedMapStateImpl.java
@@ -1,9 +1,9 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal.state;
7 7
8import tools.refinery.store.map.*; 8import tools.refinery.store.map.*;
9 9
@@ -19,19 +19,18 @@ import java.util.Objects;
19 * @param <V> 19 * @param <V>
20 * @author Oszkar Semerath 20 * @author Oszkar Semerath
21 */ 21 */
22public class VersionedMapImpl<K, V> implements VersionedMap<K, V> { 22public class VersionedMapStateImpl<K, V> implements VersionedMap<K, V> {
23 protected final VersionedMapStoreImpl<K, V> store; 23 protected final VersionedMapStoreStateImpl<K, V> store;
24
25 protected final ContinousHashProvider<K> hashProvider;
26 24
25 protected final ContinuousHashProvider<K> hashProvider;
27 protected final V defaultValue; 26 protected final V defaultValue;
28 protected Node<K, V> root; 27 protected Node<K, V> root;
29 28
30 private final OldValueBox<V> oldValueBox = new OldValueBox<>(); 29 private final OldValueBox<V> oldValueBox = new OldValueBox<>();
31 30
32 public VersionedMapImpl( 31 public VersionedMapStateImpl(
33 VersionedMapStoreImpl<K, V> store, 32 VersionedMapStoreStateImpl<K, V> store,
34 ContinousHashProvider<K> hashProvider, 33 ContinuousHashProvider<K> hashProvider,
35 V defaultValue) { 34 V defaultValue) {
36 this.store = store; 35 this.store = store;
37 this.hashProvider = hashProvider; 36 this.hashProvider = hashProvider;
@@ -39,9 +38,9 @@ public class VersionedMapImpl<K, V> implements VersionedMap<K, V> {
39 this.root = null; 38 this.root = null;
40 } 39 }
41 40
42 public VersionedMapImpl( 41 public VersionedMapStateImpl(
43 VersionedMapStoreImpl<K, V> store, 42 VersionedMapStoreStateImpl<K, V> store,
44 ContinousHashProvider<K> hashProvider, 43 ContinuousHashProvider<K> hashProvider,
45 V defaultValue, Node<K, V> data) { 44 V defaultValue, Node<K, V> data) {
46 this.store = store; 45 this.store = store;
47 this.hashProvider = hashProvider; 46 this.hashProvider = hashProvider;
@@ -49,11 +48,12 @@ public class VersionedMapImpl<K, V> implements VersionedMap<K, V> {
49 this.root = data; 48 this.root = data;
50 } 49 }
51 50
51 @Override
52 public V getDefaultValue() { 52 public V getDefaultValue() {
53 return defaultValue; 53 return defaultValue;
54 } 54 }
55 55
56 public ContinousHashProvider<K> getHashProvider() { 56 public ContinuousHashProvider<K> getHashProvider() {
57 return hashProvider; 57 return hashProvider;
58 } 58 }
59 59
@@ -80,7 +80,9 @@ public class VersionedMapImpl<K, V> implements VersionedMap<K, V> {
80 Iterator<K> keyIterator = keys.iterator(); 80 Iterator<K> keyIterator = keys.iterator();
81 Iterator<V> valueIterator = values.iterator(); 81 Iterator<V> valueIterator = values.iterator();
82 while (keyIterator.hasNext()) { 82 while (keyIterator.hasNext()) {
83 this.put(keyIterator.next(), valueIterator.next()); 83 var key = keyIterator.next();
84 var value = valueIterator.next();
85 this.put(key,value);
84 } 86 }
85 } else { 87 } else {
86 while (cursor.move()) { 88 while (cursor.move()) {
@@ -113,17 +115,16 @@ public class VersionedMapImpl<K, V> implements VersionedMap<K, V> {
113 } 115 }
114 116
115 @Override 117 @Override
116 public DiffCursor<K, V> getDiffCursor(long toVersion) { 118 public DiffCursor<K, V> getDiffCursor(Version toVersion) {
117 Cursor<K, V> fromCursor = this.getAll(); 119 InOrderMapCursor<K, V> fromCursor = new InOrderMapCursor<>(this);
118 VersionedMap<K, V> toMap = this.store.createMap(toVersion); 120 VersionedMapStateImpl<K, V> toMap = (VersionedMapStateImpl<K, V>) this.store.createMap(toVersion);
119 Cursor<K, V> toCursor = toMap.getAll(); 121 InOrderMapCursor<K, V> toCursor = new InOrderMapCursor<>(toMap);
120 return new MapDiffCursor<>(this.hashProvider, this.defaultValue, fromCursor, toCursor); 122 return new MapDiffCursor<>(this.defaultValue, fromCursor, toCursor);
121
122 } 123 }
123 124
124 125
125 @Override 126 @Override
126 public long commit() { 127 public Version commit() {
127 return this.store.commit(root, this); 128 return this.store.commit(root, this);
128 } 129 }
129 130
@@ -132,20 +133,21 @@ public class VersionedMapImpl<K, V> implements VersionedMap<K, V> {
132 } 133 }
133 134
134 @Override 135 @Override
135 public void restore(long state) { 136 public void restore(Version state) {
136 root = this.store.revert(state); 137 root = this.store.revert(state);
137 } 138 }
138 139
139 public void prettyPrint() { 140 public String prettyPrint() {
140 StringBuilder s = new StringBuilder();
141 if (this.root != null) { 141 if (this.root != null) {
142 StringBuilder s = new StringBuilder();
142 this.root.prettyPrint(s, 0, -1); 143 this.root.prettyPrint(s, 0, -1);
143 System.out.println(s.toString()); 144 return s.toString();
144 } else { 145 } else {
145 System.out.println("empty tree"); 146 return "empty tree";
146 } 147 }
147 } 148 }
148 149
150 @Override
149 public void checkIntegrity() { 151 public void checkIntegrity() {
150 if (this.root != null) { 152 if (this.root != null) {
151 this.root.checkIntegrity(hashProvider, defaultValue, 0); 153 this.root.checkIntegrity(hashProvider, defaultValue, 0);
@@ -155,11 +157,15 @@ public class VersionedMapImpl<K, V> implements VersionedMap<K, V> {
155 @Override 157 @Override
156 public int contentHashCode(ContentHashCode mode) { 158 public int contentHashCode(ContentHashCode mode) {
157 // Calculating the root hashCode is always fast, because {@link Node} caches its hashCode. 159 // Calculating the root hashCode is always fast, because {@link Node} caches its hashCode.
158 return Objects.hashCode(root); 160 if(root == null) {
161 return 0;
162 } else {
163 return root.hashCode();
164 }
159 } 165 }
160 166
161 @Override 167 @Override
162 public boolean contentEquals(AnyVersionedMap other) { 168 public boolean contentEquals(AnyVersionedMap other) {
163 return other instanceof VersionedMapImpl<?, ?> otherImpl && Objects.equals(root, otherImpl.root); 169 return other instanceof VersionedMapStateImpl<?, ?> otherImpl && Objects.equals(root, otherImpl.root);
164 } 170 }
165} 171}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreConfiguration.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateConfiguration.java
index b00cd961..6650f565 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreConfiguration.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateConfiguration.java
@@ -1,21 +1,25 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.map; 6package tools.refinery.store.map.internal.state;
7 7
8public class VersionedMapStoreConfiguration { 8import tools.refinery.store.map.ContinuousHashProvider;
9import tools.refinery.store.map.VersionedMapStore;
9 10
10 public VersionedMapStoreConfiguration() { 11public class VersionedMapStoreStateConfiguration {
12
13 public VersionedMapStoreStateConfiguration() {
11 14
12 } 15 }
13 public VersionedMapStoreConfiguration(boolean immutableWhenCommitting, boolean sharedNodeCacheInStore, 16 public VersionedMapStoreStateConfiguration(boolean immutableWhenCommitting, boolean sharedNodeCacheInStore,
14 boolean sharedNodeCacheInStoreGroups) { 17 boolean sharedNodeCacheInStoreGroups, boolean versionFreeingEnabled) {
15 super(); 18 super();
16 this.immutableWhenCommitting = immutableWhenCommitting; 19 this.immutableWhenCommitting = immutableWhenCommitting;
17 this.sharedNodeCacheInStore = sharedNodeCacheInStore; 20 this.sharedNodeCacheInStore = sharedNodeCacheInStore;
18 this.sharedNodeCacheInStoreGroups = sharedNodeCacheInStoreGroups; 21 this.sharedNodeCacheInStoreGroups = sharedNodeCacheInStoreGroups;
22 this.versionFreeingEnabled = versionFreeingEnabled;
19 } 23 }
20 24
21 /** 25 /**
@@ -42,12 +46,17 @@ public class VersionedMapStoreConfiguration {
42 46
43 /** 47 /**
44 * If true, all sub-nodes are cached within a group of 48 * If true, all sub-nodes are cached within a group of
45 * {@link VersionedMapStoreImpl#createSharedVersionedMapStores(int, ContinousHashProvider, Object, VersionedMapStoreConfiguration)}. 49 * {@link VersionedMapStoreStateImpl#createSharedVersionedMapStores(int, ContinuousHashProvider, Object, VersionedMapStoreStateConfiguration)}.
46 * If {@link VersionedMapStoreConfiguration#sharedNodeCacheInStore} is 50 * If {@link VersionedMapStoreStateConfiguration#sharedNodeCacheInStore} is
47 * <code>false</code>, then it has currently no impact. 51 * <code>false</code>, then it has currently no impact.
48 */ 52 */
49 private boolean sharedNodeCacheInStoreGroups = true; 53 private boolean sharedNodeCacheInStoreGroups = true;
50 public boolean isSharedNodeCacheInStoreGroups() { 54 public boolean isSharedNodeCacheInStoreGroups() {
51 return sharedNodeCacheInStoreGroups; 55 return sharedNodeCacheInStoreGroups;
52 } 56 }
57
58 private boolean versionFreeingEnabled = true;
59 public boolean isVersionFreeingEnabled() {
60 return versionFreeingEnabled;
61 }
53} 62}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateImpl.java
new file mode 100644
index 00000000..8ff3f8e7
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateImpl.java
@@ -0,0 +1,119 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map.internal.state;
7
8import tools.refinery.store.map.*;
9
10import java.util.*;
11
12public class VersionedMapStoreStateImpl<K, V> implements VersionedMapStore<K, V> {
13 // Configuration
14 private final boolean immutableWhenCommitting;
15
16 // Static data
17 protected final ContinuousHashProvider<K> hashProvider;
18 protected final V defaultValue;
19
20 protected final Map<Node<K, V>, ImmutableNode<K, V>> nodeCache;
21
22 public VersionedMapStoreStateImpl(ContinuousHashProvider<K> hashProvider, V defaultValue,
23 VersionedMapStoreStateConfiguration config) {
24 this.immutableWhenCommitting = config.isImmutableWhenCommitting();
25 this.hashProvider = hashProvider;
26 this.defaultValue = defaultValue;
27 if (config.isSharedNodeCacheInStore()) {
28 nodeCache = createNoteCache(config);
29 } else {
30 nodeCache = null;
31 }
32 }
33
34 private VersionedMapStoreStateImpl(ContinuousHashProvider<K> hashProvider, V defaultValue,
35 Map<Node<K, V>, ImmutableNode<K, V>> nodeCache, VersionedMapStoreStateConfiguration config) {
36 this.immutableWhenCommitting = config.isImmutableWhenCommitting();
37 this.hashProvider = hashProvider;
38 this.defaultValue = defaultValue;
39 this.nodeCache = nodeCache;
40 }
41
42 public VersionedMapStoreStateImpl(ContinuousHashProvider<K> hashProvider, V defaultValue) {
43 this(hashProvider, defaultValue, new VersionedMapStoreStateConfiguration());
44 }
45
46 public static <K, V> List<VersionedMapStore<K, V>> createSharedVersionedMapStores(int amount,
47 ContinuousHashProvider<K> hashProvider, V defaultValue,
48 VersionedMapStoreStateConfiguration config) {
49 List<VersionedMapStore<K, V>> result = new ArrayList<>(amount);
50 if (config.isSharedNodeCacheInStoreGroups()) {
51 Map<Node<K, V>, ImmutableNode<K, V>> nodeCache;
52 if (config.isSharedNodeCacheInStore()) {
53 nodeCache = createNoteCache(config);
54 } else {
55 nodeCache = null;
56 }
57 for (int i = 0; i < amount; i++) {
58 result.add(new VersionedMapStoreStateImpl<>(hashProvider, defaultValue, nodeCache, config));
59 }
60 } else {
61 for (int i = 0; i < amount; i++) {
62 result.add(new VersionedMapStoreStateImpl<>(hashProvider, defaultValue, config));
63 }
64 }
65 return result;
66 }
67
68 private static <K,V> Map<K,V> createNoteCache(VersionedMapStoreStateConfiguration config) {
69 if(config.isVersionFreeingEnabled()) {
70 return new WeakHashMap<>();
71 } else {
72 return new HashMap<>();
73 }
74 }
75
76 public static <K, V> List<VersionedMapStore<K, V>> createSharedVersionedMapStores(int amount,
77 ContinuousHashProvider<K> hashProvider, V defaultValue) {
78 return createSharedVersionedMapStores(amount, hashProvider, defaultValue, new VersionedMapStoreStateConfiguration());
79 }
80
81 @Override
82 public VersionedMap<K, V> createMap() {
83 return new VersionedMapStateImpl<>(this, hashProvider, defaultValue);
84 }
85
86 @Override
87 public VersionedMap<K, V> createMap(Version state) {
88 ImmutableNode<K, V> data = revert(state);
89 return new VersionedMapStateImpl<>(this, hashProvider, defaultValue, data);
90 }
91
92 @SuppressWarnings("unchecked")
93 public synchronized ImmutableNode<K, V> revert(Version state) {
94 return (ImmutableNode<K, V>) state;
95 }
96
97 public synchronized Version commit(Node<K, V> data, VersionedMapStateImpl<K, V> mapToUpdateRoot) {
98 ImmutableNode<K, V> immutable;
99 if (data != null) {
100 immutable = data.toImmutable(this.nodeCache);
101 } else {
102 immutable = null;
103 }
104
105 if (this.immutableWhenCommitting) {
106 mapToUpdateRoot.setRoot(immutable);
107 }
108 return immutable;
109 }
110
111 @Override
112 public DiffCursor<K, V> getDiffCursor(Version fromState, Version toState) {
113 VersionedMapStateImpl<K, V> map1 = (VersionedMapStateImpl<K, V>) createMap(fromState);
114 VersionedMapStateImpl<K, V> map2 = (VersionedMapStateImpl<K, V>) createMap(toState);
115 InOrderMapCursor<K, V> cursor1 = new InOrderMapCursor<>(map1);
116 InOrderMapCursor<K, V> cursor2 = new InOrderMapCursor<>(map2);
117 return new MapDiffCursor<>(this.defaultValue, cursor1, cursor2);
118 }
119}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java
index ea670cbc..1b15e4cf 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java
@@ -7,6 +7,7 @@ package tools.refinery.store.model;
7 7
8import tools.refinery.store.map.Cursor; 8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.DiffCursor; 9import tools.refinery.store.map.DiffCursor;
10import tools.refinery.store.map.Version;
10import tools.refinery.store.representation.Symbol; 11import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.tuple.Tuple; 12import tools.refinery.store.tuple.Tuple;
12 13
@@ -24,7 +25,7 @@ public non-sealed interface Interpretation<T> extends AnyInterpretation {
24 25
25 void putAll(Cursor<Tuple, T> cursor); 26 void putAll(Cursor<Tuple, T> cursor);
26 27
27 DiffCursor<Tuple, T> getDiffCursor(long to); 28 DiffCursor<Tuple, T> getDiffCursor(Version to);
28 29
29 void addListener(InterpretationListener<T> listener, boolean alsoWhenRestoring); 30 void addListener(InterpretationListener<T> listener, boolean alsoWhenRestoring);
30 31
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/Model.java b/subprojects/store/src/main/java/tools/refinery/store/model/Model.java
index d58d91c3..e2ab72e7 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/Model.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/Model.java
@@ -6,18 +6,20 @@
6package tools.refinery.store.model; 6package tools.refinery.store.model;
7 7
8import tools.refinery.store.adapter.ModelAdapter; 8import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.map.Version;
9import tools.refinery.store.map.Versioned; 10import tools.refinery.store.map.Versioned;
11import tools.refinery.store.model.internal.VersionedInterpretation;
10import tools.refinery.store.representation.AnySymbol; 12import tools.refinery.store.representation.AnySymbol;
11import tools.refinery.store.representation.Symbol; 13import tools.refinery.store.representation.Symbol;
12 14
15import java.util.Map;
13import java.util.Optional; 16import java.util.Optional;
14 17
15public interface Model extends Versioned { 18public interface Model extends Versioned {
16 long NO_STATE_ID = -1; 19 Version NO_STATE_ID = null;
17
18 ModelStore getStore(); 20 ModelStore getStore();
19 21
20 long getState(); 22 Version getState();
21 23
22 boolean hasUncommittedChanges(); 24 boolean hasUncommittedChanges();
23 25
@@ -27,7 +29,7 @@ public interface Model extends Versioned {
27 29
28 <T> Interpretation<T> getInterpretation(Symbol<T> symbol); 30 <T> Interpretation<T> getInterpretation(Symbol<T> symbol);
29 31
30 ModelDiffCursor getDiffCursor(long to); 32 ModelDiffCursor getDiffCursor(Version to);
31 33
32 <T extends ModelAdapter> Optional<T> tryGetAdapter(Class<? extends T> adapterType); 34 <T extends ModelAdapter> Optional<T> tryGetAdapter(Class<? extends T> adapterType);
33 35
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java b/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java
index a9ad8cfd..703ee10a 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java
@@ -5,6 +5,8 @@
5 */ 5 */
6package tools.refinery.store.model; 6package tools.refinery.store.model;
7 7
8import tools.refinery.store.map.Version;
9
8public interface ModelListener { 10public interface ModelListener {
9 default void beforeCommit() { 11 default void beforeCommit() {
10 } 12 }
@@ -12,7 +14,7 @@ public interface ModelListener {
12 default void afterCommit() { 14 default void afterCommit() {
13 } 15 }
14 16
15 default void beforeRestore(long state) { 17 default void beforeRestore(Version state) {
16 } 18 }
17 19
18 default void afterRestore() { 20 default void afterRestore() {
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java
index b10eb8a4..89382b3a 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java
@@ -6,23 +6,21 @@
6package tools.refinery.store.model; 6package tools.refinery.store.model;
7 7
8import tools.refinery.store.adapter.ModelStoreAdapter; 8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.map.Version;
9import tools.refinery.store.model.internal.ModelStoreBuilderImpl; 10import tools.refinery.store.model.internal.ModelStoreBuilderImpl;
10import tools.refinery.store.representation.AnySymbol; 11import tools.refinery.store.representation.AnySymbol;
11 12
12import java.util.Collection; 13import java.util.Collection;
13import java.util.Optional; 14import java.util.Optional;
14import java.util.Set;
15 15
16public interface ModelStore { 16public interface ModelStore {
17 Collection<AnySymbol> getSymbols(); 17 Collection<AnySymbol> getSymbols();
18 18
19 Model createEmptyModel(); 19 Model createEmptyModel();
20 20
21 Model createModelForState(long state); 21 Model createModelForState(Version state);
22 22
23 Set<Long> getStates(); 23 ModelDiffCursor getDiffCursor(Version from, Version to);
24
25 ModelDiffCursor getDiffCursor(long from, long to);
26 24
27 <T extends ModelStoreAdapter> Optional<T> tryGetAdapter(Class<? extends T> adapterType); 25 <T extends ModelStoreAdapter> Optional<T> tryGetAdapter(Class<? extends T> adapterType);
28 26
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java b/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java
index fe1c2ab5..3c94541f 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java
@@ -5,41 +5,45 @@
5 */ 5 */
6package tools.refinery.store.model; 6package tools.refinery.store.model;
7 7
8import tools.refinery.store.map.ContinousHashProvider; 8import tools.refinery.store.map.ContinuousHashProvider;
9import tools.refinery.store.tuple.Tuple; 9import tools.refinery.store.tuple.Tuple;
10import tools.refinery.store.tuple.Tuple1;
11import tools.refinery.store.tuple.Tuple2;
10 12
11public class TupleHashProvider implements ContinousHashProvider<Tuple> { 13public class TupleHashProvider implements ContinuousHashProvider<Tuple> {
12 protected static final int[] primes = new int[] { 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 14 protected static final int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
13 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 15 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
14 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 16 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283,
15 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 17 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421,
16 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 18 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563,
17 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 19 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683,
18 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 20 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829,
19 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, 21 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983,
20 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 22 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097,
21 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 23 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231,
22 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, 24 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373,
23 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543, 1549, 25 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493,
24 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 26 1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619,
25 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 27 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759,
26 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 28 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907,
27 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, 2089, 29 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053,
28 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243, 30 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203,
29 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 31 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339,
30 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 32 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, 2459,
31 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 33 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633,
32 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, 2791, 34 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741,
33 2797, 2801, 2803, 2819, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, 3203, 3209, 3217, 35 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169,
34 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 36 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319,
35 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 37 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, 3457, 3461, 3463,
36 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 38 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593,
37 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, 39 3607, 3613, 3617, 3623, 3631, 3637, 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733,
38 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911 }; 40 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889,
41 3907, 3911 };
39 42
40 public static final long LARGEST_PRIME_30_BITS = 1073741789; 43 protected static final long LARGEST_PRIME_30_BITS_LONG = 1073741789;
41 44 protected static final int LARGEST_PRIME_30_BITS_INTEGER = 1073741789;
42 public static final int MAX_MODEL_SIZE = (int) LARGEST_PRIME_30_BITS; 45 protected static final int LARGEST_BINARY_INDEX_1 = LARGEST_PRIME_30_BITS_INTEGER / 4;
46 public static final int MAX_MODEL_SIZE = LARGEST_PRIME_30_BITS_INTEGER;
43 47
44 public static final TupleHashProvider INSTANCE = new TupleHashProvider(); 48 public static final TupleHashProvider INSTANCE = new TupleHashProvider();
45 49
@@ -52,15 +56,68 @@ public class TupleHashProvider implements ContinousHashProvider<Tuple> {
52 56
53 @Override 57 @Override
54 public int getHash(Tuple key, int index) { 58 public int getHash(Tuple key, int index) {
55 if (index >= primes.length) { 59 if(key instanceof Tuple1 t1) {
56 throw new IllegalArgumentException("Not enough prime numbers to support index"); 60 return t1.value0();
61 } else if(key instanceof Tuple2 t2){
62 if(index == 0) {
63 return murmur3T2(t2.value0(), t2.value1());
64 } else if(index == 1) {
65 return lagrangeT2I0Quick(t2);
66 } else if(index == 2) {
67 return lagrangeT2I1Quick(t2);
68 } else {
69 return lagrangeTXIX(key, index-1);
70 }
71 } else {
72 return lagrangeTXIX(key, index);
57 } 73 }
74
75 }
76
77 private static int lagrangeT2I0Quick(Tuple2 t2) {
78 int result = 2 * t2.value0() + t2.value1();
79 if (result > LARGEST_PRIME_30_BITS_INTEGER) {
80 return result % LARGEST_PRIME_30_BITS_INTEGER;
81 } else
82 return result;
83 }
84 private static int lagrangeT2I1Quick(Tuple2 t2) {
85 int value0 = t2.value0();
86 int value1 = t2.value1();
87 if(value0 < LARGEST_BINARY_INDEX_1 && value1 < LARGEST_BINARY_INDEX_1) {
88 return 3* value0 + value1;
89 } else {
90 return lagrangeTXIX(t2, 1);
91 }
92 }
93
94 private static int lagrangeTXIX(Tuple key, int index) {
58 long accumulator = 0; 95 long accumulator = 0;
59 final int prime = primes[index]; 96 final int prime = primes[index];
60 for (int i = 0; i < key.getSize(); i++) { 97 for (int i = 0; i < key.getSize(); i++) {
61 accumulator = (prime * accumulator + key.get(i)) % MAX_MODEL_SIZE; 98 accumulator = (prime * accumulator + key.get(i)) % LARGEST_PRIME_30_BITS_LONG;
62 } 99 }
63 100
64 return (int) accumulator; 101 return (int) accumulator;
65 } 102 }
103
104 private static int murmur3T2(int v0, int v1)
105 {
106 int h = 0;
107
108 h = murmur32Scramble(v0, h);
109 h = murmur32Scramble(v1, h);
110
111 return h;
112 }
113
114 private static int murmur32Scramble(int k, int h) {
115 k *= 0xcc9e2d51;
116 k = (k << 15) | (k >>> 17);
117 k *= 0x1b873593;
118 h ^= k;
119 h = (h << 13) | (h >>> 19);
120 h = h * 5 + 0xe6546b64;
121 return h;
122 }
66} 123}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java b/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java
deleted file mode 100644
index 14116a90..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java
+++ /dev/null
@@ -1,34 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.model;
7
8import tools.refinery.store.map.ContinousHashProvider;
9import tools.refinery.store.tuple.Tuple;
10
11public class TupleHashProviderBitMagic implements ContinousHashProvider<Tuple> {
12
13 @Override
14 public int getHash(Tuple key, int index) {
15 if(key.getSize() == 1) {
16 return key.get(0);
17 }
18
19 int result = 0;
20 final int startBitIndex = index*30;
21 final int finalBitIndex = startBitIndex+30;
22 final int arity = key.getSize();
23
24 for(int i = startBitIndex; i<=finalBitIndex; i++) {
25 final int selectedKey = key.get(i%arity);
26 final int selectedPosition = 1<<(i/arity);
27 if((selectedKey&selectedPosition) != 0) {
28 result |= 1<<(i%30);
29 }
30 }
31
32 return result;
33 }
34}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/IndexedVersionedInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/IndexedVersionedInterpretation.java
index e0a371de..0be16f77 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/IndexedVersionedInterpretation.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/IndexedVersionedInterpretation.java
@@ -7,7 +7,6 @@ package tools.refinery.store.model.internal;
7 7
8import tools.refinery.store.map.Cursor; 8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.VersionedMap; 9import tools.refinery.store.map.VersionedMap;
10import tools.refinery.store.map.VersionedMapStore;
11import tools.refinery.store.representation.Symbol; 10import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.tuple.Tuple; 11import tools.refinery.store.tuple.Tuple;
13 12
@@ -16,9 +15,8 @@ import java.util.Objects;
16class IndexedVersionedInterpretation<T> extends VersionedInterpretation<T> { 15class IndexedVersionedInterpretation<T> extends VersionedInterpretation<T> {
17 private final BaseIndexer<T> indexer; 16 private final BaseIndexer<T> indexer;
18 17
19 public IndexedVersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMapStore<Tuple, T> store, 18 public IndexedVersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMap<Tuple, T> map) {
20 VersionedMap<Tuple, T> map) { 19 super(model, symbol, map);
21 super(model, symbol, store, map);
22 indexer = new BaseIndexer<>(symbol.arity(), map); 20 indexer = new BaseIndexer<>(symbol.arity(), map);
23 } 21 }
24 22
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java
index c5475a1a..2b12d5a6 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java
@@ -8,6 +8,7 @@ package tools.refinery.store.model.internal;
8import tools.refinery.store.adapter.AdapterUtils; 8import tools.refinery.store.adapter.AdapterUtils;
9import tools.refinery.store.adapter.ModelAdapter; 9import tools.refinery.store.adapter.ModelAdapter;
10import tools.refinery.store.map.DiffCursor; 10import tools.refinery.store.map.DiffCursor;
11import tools.refinery.store.map.Version;
11import tools.refinery.store.model.*; 12import tools.refinery.store.model.*;
12import tools.refinery.store.representation.AnySymbol; 13import tools.refinery.store.representation.AnySymbol;
13import tools.refinery.store.representation.Symbol; 14import tools.refinery.store.representation.Symbol;
@@ -17,21 +18,21 @@ import java.util.*;
17 18
18public class ModelImpl implements Model { 19public class ModelImpl implements Model {
19 private final ModelStore store; 20 private final ModelStore store;
20 private long state; 21 private Version state;
21 private Map<? extends AnySymbol, ? extends VersionedInterpretation<?>> interpretations; 22 private LinkedHashMap<? extends AnySymbol, ? extends VersionedInterpretation<?>> interpretations;
22 private final List<ModelAdapter> adapters; 23 private final List<ModelAdapter> adapters;
23 private final List<ModelListener> listeners = new ArrayList<>(); 24 private final List<ModelListener> listeners = new ArrayList<>();
24 private boolean uncommittedChanges; 25 private boolean uncommittedChanges;
25 private ModelAction pendingAction = ModelAction.NONE; 26 private ModelAction pendingAction = ModelAction.NONE;
26 private long restoringToState = NO_STATE_ID; 27 private Version restoringToState = null;
27 28
28 ModelImpl(ModelStore store, long state, int adapterCount) { 29 ModelImpl(ModelStore store, Version state, int adapterCount) {
29 this.store = store; 30 this.store = store;
30 this.state = state; 31 this.state = state;
31 adapters = new ArrayList<>(adapterCount); 32 adapters = new ArrayList<>(adapterCount);
32 } 33 }
33 34
34 void setInterpretations(Map<? extends AnySymbol, ? extends VersionedInterpretation<?>> interpretations) { 35 void setInterpretations(LinkedHashMap<? extends AnySymbol, ? extends VersionedInterpretation<?>> interpretations) {
35 this.interpretations = interpretations; 36 this.interpretations = interpretations;
36 } 37 }
37 38
@@ -41,7 +42,7 @@ public class ModelImpl implements Model {
41 } 42 }
42 43
43 @Override 44 @Override
44 public long getState() { 45 public Version getState() {
45 return state; 46 return state;
46 } 47 }
47 48
@@ -57,7 +58,7 @@ public class ModelImpl implements Model {
57 } 58 }
58 59
59 @Override 60 @Override
60 public ModelDiffCursor getDiffCursor(long to) { 61 public ModelDiffCursor getDiffCursor(Version to) {
61 var diffCursors = new HashMap<AnySymbol, DiffCursor<Tuple, ?>>(interpretations.size()); 62 var diffCursors = new HashMap<AnySymbol, DiffCursor<Tuple, ?>>(interpretations.size());
62 for (var entry : interpretations.entrySet()) { 63 for (var entry : interpretations.entrySet()) {
63 diffCursors.put(entry.getKey(), entry.getValue().getDiffCursor(to)); 64 diffCursors.put(entry.getKey(), entry.getValue().getDiffCursor(to));
@@ -65,7 +66,7 @@ public class ModelImpl implements Model {
65 return new ModelDiffCursor(diffCursors); 66 return new ModelDiffCursor(diffCursors);
66 } 67 }
67 68
68 private void setState(long state) { 69 private void setState(Version state) {
69 this.state = state; 70 this.state = state;
70 uncommittedChanges = false; 71 uncommittedChanges = false;
71 } 72 }
@@ -82,11 +83,11 @@ public class ModelImpl implements Model {
82 } 83 }
83 84
84 private boolean hasPendingAction() { 85 private boolean hasPendingAction() {
85 return pendingAction != ModelAction.NONE || restoringToState != NO_STATE_ID; 86 return pendingAction != ModelAction.NONE || restoringToState != null;
86 } 87 }
87 88
88 @Override 89 @Override
89 public long commit() { 90 public Version commit() {
90 if (hasPendingAction()) { 91 if (hasPendingAction()) {
91 throw pendingActionError("commit"); 92 throw pendingActionError("commit");
92 } 93 }
@@ -94,43 +95,40 @@ public class ModelImpl implements Model {
94 try { 95 try {
95 int listenerCount = listeners.size(); 96 int listenerCount = listeners.size();
96 int i = listenerCount; 97 int i = listenerCount;
97 long version = 0; 98
99 // Before commit message to listeners
98 while (i > 0) { 100 while (i > 0) {
99 i--; 101 i--;
100 listeners.get(i).beforeCommit(); 102 listeners.get(i).beforeCommit();
101 } 103 }
102 boolean versionSet = false; 104
103 for (var interpretation : interpretations.values()) { 105 // Doing the commit on the interpretations
104 long newVersion = interpretation.commit(); 106 Version[] interpretationVersions = new Version[interpretations.size()];
105 if (versionSet) { 107 int j = 0;
106 if (version != newVersion) { 108 for (var interpretationEntry : interpretations.entrySet()) {
107 throw new IllegalStateException("Interpretations in model have different versions (%d and %d)" 109 interpretationVersions[j++] = interpretationEntry.getValue().commit();
108 .formatted(version, newVersion));
109 }
110 } else {
111 version = newVersion;
112 versionSet = true;
113 }
114 } 110 }
115 setState(version); 111 ModelVersion modelVersion = new ModelVersion(interpretationVersions);
112 setState(modelVersion);
113
114 // After commit message to listeners
116 while (i < listenerCount) { 115 while (i < listenerCount) {
117 listeners.get(i).afterCommit(); 116 listeners.get(i).afterCommit();
118 i++; 117 i++;
119 } 118 }
120 return version; 119
120 return modelVersion;
121 } finally { 121 } finally {
122 pendingAction = ModelAction.NONE; 122 pendingAction = ModelAction.NONE;
123 } 123 }
124 } 124 }
125 125
126 @Override 126 @Override
127 public void restore(long version) { 127 public void restore(Version version) {
128 if (hasPendingAction()) { 128 if (hasPendingAction()) {
129 throw pendingActionError("restore to %d".formatted(version)); 129 throw pendingActionError("restore to %s".formatted(version));
130 }
131 if (!store.getStates().contains(version)) {
132 throw new IllegalArgumentException("Store does not contain state %d".formatted(version));
133 } 130 }
131
134 pendingAction = ModelAction.RESTORE; 132 pendingAction = ModelAction.RESTORE;
135 restoringToState = version; 133 restoringToState = version;
136 try { 134 try {
@@ -140,9 +138,11 @@ public class ModelImpl implements Model {
140 i--; 138 i--;
141 listeners.get(i).beforeRestore(version); 139 listeners.get(i).beforeRestore(version);
142 } 140 }
141 int j = 0;
143 for (var interpretation : interpretations.values()) { 142 for (var interpretation : interpretations.values()) {
144 interpretation.restore(version); 143 interpretation.restore(ModelVersion.getInternalVersion(version, j++));
145 } 144 }
145
146 setState(version); 146 setState(version);
147 while (i < listenerCount) { 147 while (i < listenerCount) {
148 listeners.get(i).afterRestore(); 148 listeners.get(i).afterRestore();
@@ -150,7 +150,7 @@ public class ModelImpl implements Model {
150 } 150 }
151 } finally { 151 } finally {
152 pendingAction = ModelAction.NONE; 152 pendingAction = ModelAction.NONE;
153 restoringToState = NO_STATE_ID; 153 restoringToState = null;
154 } 154 }
155 } 155 }
156 156
@@ -159,7 +159,7 @@ public class ModelImpl implements Model {
159 case NONE -> throw new IllegalArgumentException("Trying to throw pending action error when there is no " + 159 case NONE -> throw new IllegalArgumentException("Trying to throw pending action error when there is no " +
160 "pending action"); 160 "pending action");
161 case COMMIT -> "commit"; 161 case COMMIT -> "commit";
162 case RESTORE -> "restore to %d".formatted(restoringToState); 162 case RESTORE -> "restore to %s".formatted(restoringToState);
163 }; 163 };
164 return new IllegalStateException("Cannot %s due to pending %s".formatted(currentActionName, pendingActionName)); 164 return new IllegalStateException("Cannot %s due to pending %s".formatted(currentActionName, pendingActionName));
165 } 165 }
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java
index 5c688178..2dde7a4c 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java
@@ -8,11 +8,11 @@ package tools.refinery.store.model.internal;
8import tools.refinery.store.adapter.AdapterUtils; 8import tools.refinery.store.adapter.AdapterUtils;
9import tools.refinery.store.adapter.ModelAdapterBuilder; 9import tools.refinery.store.adapter.ModelAdapterBuilder;
10import tools.refinery.store.map.VersionedMapStore; 10import tools.refinery.store.map.VersionedMapStore;
11import tools.refinery.store.map.VersionedMapStoreImpl; 11import tools.refinery.store.map.VersionedMapStoreFactory;
12import tools.refinery.store.map.VersionedMapStoreFactoryBuilder;
12import tools.refinery.store.model.ModelStore; 13import tools.refinery.store.model.ModelStore;
13import tools.refinery.store.model.ModelStoreBuilder; 14import tools.refinery.store.model.ModelStoreBuilder;
14import tools.refinery.store.model.ModelStoreConfiguration; 15import tools.refinery.store.model.ModelStoreConfiguration;
15import tools.refinery.store.model.TupleHashProvider;
16import tools.refinery.store.representation.AnySymbol; 16import tools.refinery.store.representation.AnySymbol;
17import tools.refinery.store.representation.Symbol; 17import tools.refinery.store.representation.Symbol;
18import tools.refinery.store.tuple.Tuple; 18import tools.refinery.store.tuple.Tuple;
@@ -20,8 +20,8 @@ import tools.refinery.store.tuple.Tuple;
20import java.util.*; 20import java.util.*;
21 21
22public class ModelStoreBuilderImpl implements ModelStoreBuilder { 22public class ModelStoreBuilderImpl implements ModelStoreBuilder {
23 private final Set<AnySymbol> allSymbols = new HashSet<>(); 23 private final LinkedHashSet<AnySymbol> allSymbols = new LinkedHashSet<>();
24 private final Map<SymbolEquivalenceClass<?>, List<AnySymbol>> equivalenceClasses = new HashMap<>(); 24 private final LinkedHashMap<SymbolEquivalenceClass<?>, List<AnySymbol>> equivalenceClasses = new LinkedHashMap<>();
25 private final List<ModelAdapterBuilder> adapters = new ArrayList<>(); 25 private final List<ModelAdapterBuilder> adapters = new ArrayList<>();
26 26
27 @Override 27 @Override
@@ -71,7 +71,7 @@ public class ModelStoreBuilderImpl implements ModelStoreBuilder {
71 for (int i = adapters.size() - 1; i >= 0; i--) { 71 for (int i = adapters.size() - 1; i >= 0; i--) {
72 adapters.get(i).configure(this); 72 adapters.get(i).configure(this);
73 } 73 }
74 var stores = new HashMap<AnySymbol, VersionedMapStore<Tuple, ?>>(allSymbols.size()); 74 var stores = new LinkedHashMap<AnySymbol, VersionedMapStore<Tuple, ?>>(allSymbols.size());
75 for (var entry : equivalenceClasses.entrySet()) { 75 for (var entry : equivalenceClasses.entrySet()) {
76 createStores(stores, entry.getKey(), entry.getValue()); 76 createStores(stores, entry.getKey(), entry.getValue());
77 } 77 }
@@ -86,8 +86,12 @@ public class ModelStoreBuilderImpl implements ModelStoreBuilder {
86 private <T> void createStores(Map<AnySymbol, VersionedMapStore<Tuple, ?>> stores, 86 private <T> void createStores(Map<AnySymbol, VersionedMapStore<Tuple, ?>> stores,
87 SymbolEquivalenceClass<T> equivalenceClass, List<AnySymbol> symbols) { 87 SymbolEquivalenceClass<T> equivalenceClass, List<AnySymbol> symbols) {
88 int size = symbols.size(); 88 int size = symbols.size();
89 var storeGroup = VersionedMapStoreImpl.createSharedVersionedMapStores(size, TupleHashProvider.INSTANCE, 89 VersionedMapStoreFactory<Tuple,T> mapFactory = VersionedMapStore
90 equivalenceClass.defaultValue()); 90 .<Tuple,T>builder()
91 .strategy(VersionedMapStoreFactoryBuilder.StoreStrategy.DELTA)
92 .defaultValue(equivalenceClass.defaultValue())
93 .build();
94 var storeGroup = mapFactory.createGroup(size);
91 for (int i = 0; i < size; i++) { 95 for (int i = 0; i < size; i++) {
92 stores.put(symbols.get(i), storeGroup.get(i)); 96 stores.put(symbols.get(i), storeGroup.get(i));
93 } 97 }
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java
index 60b735e6..a320a618 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java
@@ -8,8 +8,8 @@ package tools.refinery.store.model.internal;
8import tools.refinery.store.adapter.AdapterUtils; 8import tools.refinery.store.adapter.AdapterUtils;
9import tools.refinery.store.adapter.ModelStoreAdapter; 9import tools.refinery.store.adapter.ModelStoreAdapter;
10import tools.refinery.store.map.DiffCursor; 10import tools.refinery.store.map.DiffCursor;
11import tools.refinery.store.map.Version;
11import tools.refinery.store.map.VersionedMapStore; 12import tools.refinery.store.map.VersionedMapStore;
12import tools.refinery.store.model.Model;
13import tools.refinery.store.model.ModelDiffCursor; 13import tools.refinery.store.model.ModelDiffCursor;
14import tools.refinery.store.model.ModelStore; 14import tools.refinery.store.model.ModelStore;
15import tools.refinery.store.representation.AnySymbol; 15import tools.refinery.store.representation.AnySymbol;
@@ -18,10 +18,10 @@ import tools.refinery.store.tuple.Tuple;
18import java.util.*; 18import java.util.*;
19 19
20public class ModelStoreImpl implements ModelStore { 20public class ModelStoreImpl implements ModelStore {
21 private final Map<? extends AnySymbol, ? extends VersionedMapStore<Tuple, ?>> stores; 21 private final LinkedHashMap<? extends AnySymbol, ? extends VersionedMapStore<Tuple, ?>> stores;
22 private final List<ModelStoreAdapter> adapters; 22 private final List<ModelStoreAdapter> adapters;
23 23
24 ModelStoreImpl(Map<? extends AnySymbol, ? extends VersionedMapStore<Tuple, ?>> stores, int adapterCount) { 24 ModelStoreImpl(LinkedHashMap<? extends AnySymbol, ? extends VersionedMapStore<Tuple, ?>> stores, int adapterCount) {
25 this.stores = stores; 25 this.stores = stores;
26 adapters = new ArrayList<>(adapterCount); 26 adapters = new ArrayList<>(adapterCount);
27 } 27 }
@@ -31,14 +31,14 @@ public class ModelStoreImpl implements ModelStore {
31 return Collections.unmodifiableCollection(stores.keySet()); 31 return Collections.unmodifiableCollection(stores.keySet());
32 } 32 }
33 33
34 private ModelImpl createModelWithoutInterpretations(long state) { 34 private ModelImpl createModelWithoutInterpretations(Version state) {
35 return new ModelImpl(this, state, adapters.size()); 35 return new ModelImpl(this, state, adapters.size());
36 } 36 }
37 37
38 @Override 38 @Override
39 public ModelImpl createEmptyModel() { 39 public ModelImpl createEmptyModel() {
40 var model = createModelWithoutInterpretations(Model.NO_STATE_ID); 40 var model = createModelWithoutInterpretations(null);
41 var interpretations = new HashMap<AnySymbol, VersionedInterpretation<?>>(stores.size()); 41 var interpretations = new LinkedHashMap<AnySymbol, VersionedInterpretation<?>>(stores.size());
42 for (var entry : this.stores.entrySet()) { 42 for (var entry : this.stores.entrySet()) {
43 var symbol = entry.getKey(); 43 var symbol = entry.getKey();
44 interpretations.put(symbol, VersionedInterpretation.of(model, symbol, entry.getValue())); 44 interpretations.put(symbol, VersionedInterpretation.of(model, symbol, entry.getValue()));
@@ -49,13 +49,21 @@ public class ModelStoreImpl implements ModelStore {
49 } 49 }
50 50
51 @Override 51 @Override
52 public synchronized ModelImpl createModelForState(long state) { 52 public synchronized ModelImpl createModelForState(Version state) {
53 var model = createModelWithoutInterpretations(state); 53 var model = createModelWithoutInterpretations(state);
54 var interpretations = new HashMap<AnySymbol, VersionedInterpretation<?>>(stores.size()); 54 var interpretations = new LinkedHashMap<AnySymbol, VersionedInterpretation<?>>(stores.size());
55
56 int i=0;
55 for (var entry : this.stores.entrySet()) { 57 for (var entry : this.stores.entrySet()) {
56 var symbol = entry.getKey(); 58 var symbol = entry.getKey();
57 interpretations.put(symbol, VersionedInterpretation.of(model, symbol, entry.getValue(), state)); 59 interpretations.put(symbol,
60 VersionedInterpretation.of(
61 model,
62 symbol,
63 entry.getValue(),
64 ModelVersion.getInternalVersion(state,i++)));
58 } 65 }
66
59 model.setInterpretations(interpretations); 67 model.setInterpretations(interpretations);
60 adaptModel(model); 68 adaptModel(model);
61 return model; 69 return model;
@@ -69,16 +77,7 @@ public class ModelStoreImpl implements ModelStore {
69 } 77 }
70 78
71 @Override 79 @Override
72 public synchronized Set<Long> getStates() { 80 public synchronized ModelDiffCursor getDiffCursor(Version from, Version to) {
73 var iterator = stores.values().iterator();
74 if (iterator.hasNext()) {
75 return Set.copyOf(iterator.next().getStates());
76 }
77 return Set.of(0L);
78 }
79
80 @Override
81 public synchronized ModelDiffCursor getDiffCursor(long from, long to) {
82 var diffCursors = new HashMap<AnySymbol, DiffCursor<?, ?>>(); 81 var diffCursors = new HashMap<AnySymbol, DiffCursor<?, ?>>();
83 for (var entry : stores.entrySet()) { 82 for (var entry : stores.entrySet()) {
84 var representation = entry.getKey(); 83 var representation = entry.getKey();
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelVersion.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelVersion.java
new file mode 100644
index 00000000..c3e52084
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelVersion.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.model.internal;
7
8import tools.refinery.store.map.Version;
9
10import java.util.Arrays;
11
12public class ModelVersion implements Version {
13 final Version[] mapVersions;
14
15 public ModelVersion(Version[] mapVersions) {
16 this.mapVersions = mapVersions;
17 }
18
19 public static Version getInternalVersion(Version modelVersion, int interpretationIndex) {
20 return ((ModelVersion) modelVersion).mapVersions[interpretationIndex];
21 }
22
23 @Override
24 public String toString() {
25 return "ModelVersion{" +
26 "mapVersions=" + Arrays.toString(mapVersions) +
27 '}';
28 }
29}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/NullaryVersionedInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/NullaryVersionedInterpretation.java
index 96639a8e..4a8e6752 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/NullaryVersionedInterpretation.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/NullaryVersionedInterpretation.java
@@ -7,14 +7,12 @@ package tools.refinery.store.model.internal;
7 7
8import tools.refinery.store.map.Cursor; 8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.VersionedMap; 9import tools.refinery.store.map.VersionedMap;
10import tools.refinery.store.map.VersionedMapStore;
11import tools.refinery.store.representation.Symbol; 10import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.tuple.Tuple; 11import tools.refinery.store.tuple.Tuple;
13 12
14class NullaryVersionedInterpretation<T> extends VersionedInterpretation<T> { 13class NullaryVersionedInterpretation<T> extends VersionedInterpretation<T> {
15 public NullaryVersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMapStore<Tuple, T> store, 14 public NullaryVersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMap<Tuple, T> map) {
16 VersionedMap<Tuple, T> map) { 15 super(model, symbol, map);
17 super(model, symbol, store, map);
18 } 16 }
19 17
20 @Override 18 @Override
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/UnaryVersionedInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/UnaryVersionedInterpretation.java
index 4ec04358..75946680 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/UnaryVersionedInterpretation.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/UnaryVersionedInterpretation.java
@@ -8,16 +8,14 @@ package tools.refinery.store.model.internal;
8import tools.refinery.store.map.Cursor; 8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.Cursors; 9import tools.refinery.store.map.Cursors;
10import tools.refinery.store.map.VersionedMap; 10import tools.refinery.store.map.VersionedMap;
11import tools.refinery.store.map.VersionedMapStore;
12import tools.refinery.store.representation.Symbol; 11import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.tuple.Tuple; 12import tools.refinery.store.tuple.Tuple;
14 13
15import java.util.Objects; 14import java.util.Objects;
16 15
17class UnaryVersionedInterpretation<T> extends VersionedInterpretation<T> { 16class UnaryVersionedInterpretation<T> extends VersionedInterpretation<T> {
18 public UnaryVersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMapStore<Tuple, T> store, 17 public UnaryVersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMap<Tuple, T> map) {
19 VersionedMap<Tuple, T> map) { 18 super(model, symbol, map);
20 super(model, symbol, store, map);
21 } 19 }
22 20
23 private void validateSlot(int slot) { 21 private void validateSlot(int slot) {
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 877028cc..71df3962 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
@@ -5,15 +5,10 @@
5 */ 5 */
6package tools.refinery.store.model.internal; 6package tools.refinery.store.model.internal;
7 7
8import tools.refinery.store.map.Cursor; 8import tools.refinery.store.map.*;
9import tools.refinery.store.map.DiffCursor;
10import tools.refinery.store.map.VersionedMap;
11import tools.refinery.store.map.VersionedMapStore;
12import tools.refinery.store.map.internal.MapDiffCursor;
13import tools.refinery.store.model.Interpretation; 9import tools.refinery.store.model.Interpretation;
14import tools.refinery.store.model.InterpretationListener; 10import tools.refinery.store.model.InterpretationListener;
15import tools.refinery.store.model.Model; 11import tools.refinery.store.model.Model;
16import tools.refinery.store.model.TupleHashProvider;
17import tools.refinery.store.representation.AnySymbol; 12import tools.refinery.store.representation.AnySymbol;
18import tools.refinery.store.representation.Symbol; 13import tools.refinery.store.representation.Symbol;
19import tools.refinery.store.tuple.Tuple; 14import tools.refinery.store.tuple.Tuple;
@@ -24,16 +19,13 @@ import java.util.List;
24public abstract class VersionedInterpretation<T> implements Interpretation<T> { 19public abstract class VersionedInterpretation<T> implements Interpretation<T> {
25 private final ModelImpl model; 20 private final ModelImpl model;
26 private final Symbol<T> symbol; 21 private final Symbol<T> symbol;
27 private final VersionedMapStore<Tuple, T> store;
28 private final VersionedMap<Tuple, T> map; 22 private final VersionedMap<Tuple, T> map;
29 private final List<InterpretationListener<T>> listeners = new ArrayList<>(); 23 private final List<InterpretationListener<T>> listeners = new ArrayList<>();
30 private final List<InterpretationListener<T>> restoreListeners = new ArrayList<>(); 24 private final List<InterpretationListener<T>> restoreListeners = new ArrayList<>();
31 25
32 protected VersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMapStore<Tuple, T> store, 26 protected VersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMap<Tuple, T> map) {
33 VersionedMap<Tuple, T> map) {
34 this.model = model; 27 this.model = model;
35 this.symbol = symbol; 28 this.symbol = symbol;
36 this.store = store;
37 this.map = map; 29 this.map = map;
38 } 30 }
39 31
@@ -116,13 +108,11 @@ public abstract class VersionedInterpretation<T> implements Interpretation<T> {
116 } 108 }
117 109
118 @Override 110 @Override
119 public DiffCursor<Tuple, T> getDiffCursor(long to) { 111 public DiffCursor<Tuple, T> getDiffCursor(Version to) {
120 var fromCursor = getAll(); 112 return map.getDiffCursor(to);
121 var toCursor = store.createMap(to).getAll();
122 return new MapDiffCursor<>(TupleHashProvider.INSTANCE, symbol.defaultValue(), fromCursor, toCursor);
123 } 113 }
124 114
125 public long commit() { 115 Version commit() {
126 return map.commit(); 116 return map.commit();
127 } 117 }
128 118
@@ -130,7 +120,7 @@ public abstract class VersionedInterpretation<T> implements Interpretation<T> {
130 return !restoreListeners.isEmpty(); 120 return !restoreListeners.isEmpty();
131 } 121 }
132 122
133 public void restore(long state) { 123 public void restore(Version state) {
134 if (shouldNotifyRestoreListeners()) { 124 if (shouldNotifyRestoreListeners()) {
135 var diffCursor = getDiffCursor(state); 125 var diffCursor = getDiffCursor(state);
136 while (diffCursor.move()) { 126 while (diffCursor.move()) {
@@ -158,23 +148,23 @@ public abstract class VersionedInterpretation<T> implements Interpretation<T> {
158 @SuppressWarnings("unchecked") 148 @SuppressWarnings("unchecked")
159 var typedSymbol = (Symbol<T>) symbol; 149 var typedSymbol = (Symbol<T>) symbol;
160 var map = store.createMap(); 150 var map = store.createMap();
161 return of(model, typedSymbol, store, map); 151 return of(model, typedSymbol, map);
162 } 152 }
163 153
164 static <T> VersionedInterpretation<T> of(ModelImpl model, AnySymbol symbol, VersionedMapStore<Tuple, T> store, 154 static <T> VersionedInterpretation<T> of(ModelImpl model, AnySymbol symbol, VersionedMapStore<Tuple, T> store,
165 long state) { 155 Version state) {
166 @SuppressWarnings("unchecked") 156 @SuppressWarnings("unchecked")
167 var typedSymbol = (Symbol<T>) symbol; 157 var typedSymbol = (Symbol<T>) symbol;
168 var map = store.createMap(state); 158 var map = store.createMap(state);
169 return of(model, typedSymbol, store, map); 159 return of(model, typedSymbol, map);
170 } 160 }
171 161
172 private static <T> VersionedInterpretation<T> of(ModelImpl model, Symbol<T> typedSymbol, 162 private static <T> VersionedInterpretation<T> of(ModelImpl model, Symbol<T> typedSymbol,
173 VersionedMapStore<Tuple, T> store, VersionedMap<Tuple, T> map) { 163 VersionedMap<Tuple, T> map) {
174 return switch (typedSymbol.arity()) { 164 return switch (typedSymbol.arity()) {
175 case 0 -> new NullaryVersionedInterpretation<>(model, typedSymbol, store, map); 165 case 0 -> new NullaryVersionedInterpretation<>(model, typedSymbol, map);
176 case 1 -> new UnaryVersionedInterpretation<>(model, typedSymbol, store, map); 166 case 1 -> new UnaryVersionedInterpretation<>(model, typedSymbol, map);
177 default -> new IndexedVersionedInterpretation<>(model, typedSymbol, store, map); 167 default -> new IndexedVersionedInterpretation<>(model, typedSymbol, map);
178 }; 168 };
179 } 169 }
180} 170}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/Morphism.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/Morphism.java
new file mode 100644
index 00000000..d4ac3b58
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/Morphism.java
@@ -0,0 +1,11 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8public interface Morphism {
9 int get(int object);
10 int getSize();
11}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/ObjectCode.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/ObjectCode.java
new file mode 100644
index 00000000..840253ea
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/ObjectCode.java
@@ -0,0 +1,11 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8public interface ObjectCode {
9 long get(int object);
10 int getSize();
11}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculator.java
new file mode 100644
index 00000000..b7f1d81a
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculator.java
@@ -0,0 +1,10 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8public interface StateCodeCalculator {
9 StateCoderResult calculateCodes();
10}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculatorFactory.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculatorFactory.java
new file mode 100644
index 00000000..04e17a13
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculatorFactory.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8import org.eclipse.collections.api.set.primitive.IntSet;
9import tools.refinery.store.model.Interpretation;
10
11import java.util.List;
12
13public interface StateCodeCalculatorFactory {
14 StateCodeCalculator create(List<? extends Interpretation<?>> interpretations, IntSet individuals);
15}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java
new file mode 100644
index 00000000..cb73d27e
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java
@@ -0,0 +1,23 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.statecoding.internal.StateCoderBuilderImpl;
10
11public interface StateCoderAdapter extends ModelAdapter {
12 StateCoderResult calculateStateCode();
13 default int calculateModelCode() {
14 return calculateStateCode().modelCode();
15 }
16 default ObjectCode calculateObjectCode() {
17 return calculateStateCode().objectCode();
18 }
19
20 static StateCoderBuilder builder() {
21 return new StateCoderBuilderImpl();
22 }
23}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderBuilder.java
new file mode 100644
index 00000000..54650825
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderBuilder.java
@@ -0,0 +1,45 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8import tools.refinery.store.adapter.ModelAdapterBuilder;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.representation.AnySymbol;
11import tools.refinery.store.tuple.Tuple1;
12
13import java.util.Arrays;
14import java.util.Collection;
15import java.util.List;
16
17public interface StateCoderBuilder extends ModelAdapterBuilder {
18 StateCoderBuilder exclude(AnySymbol symbol);
19 default StateCoderBuilder excludeAll(Collection<? extends AnySymbol> symbols) {
20 for(var symbol : symbols) {
21 exclude(symbol);
22 }
23 return this;
24 }
25 default StateCoderBuilder excludeAll(AnySymbol... symbols) {
26 return excludeAll(List.of(symbols));
27 }
28
29 StateCoderBuilder individual(Tuple1 tuple);
30 default StateCoderBuilder individual(Collection<Tuple1> tuple1s) {
31 for(Tuple1 tuple : tuple1s){
32 individual(tuple);
33 }
34 return this;
35 }
36 default StateCoderBuilder individuals(Tuple1... tuple1s) {
37 return individual(Arrays.stream(tuple1s).toList());
38 }
39
40 StateCoderBuilder stateCodeCalculatorFactory(StateCodeCalculatorFactory codeCalculatorFactory);
41 StateCoderBuilder stateEquivalenceChecker(StateEquivalenceChecker stateEquivalenceChecker);
42
43 @Override
44 StateCoderStoreAdapter build(ModelStore store);
45}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderResult.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderResult.java
new file mode 100644
index 00000000..d2aff836
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderResult.java
@@ -0,0 +1,9 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8public record StateCoderResult(int modelCode, ObjectCode objectCode) {
9}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderStoreAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderStoreAdapter.java
new file mode 100644
index 00000000..c6509521
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderStoreAdapter.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.map.Version;
10import tools.refinery.store.model.Model;
11
12public interface StateCoderStoreAdapter extends ModelStoreAdapter {
13 StateEquivalenceChecker.EquivalenceResult checkEquivalence(Version v1, Version v2);
14
15 @Override
16 StateCoderAdapter createModelAdapter(Model model);
17}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java
new file mode 100644
index 00000000..0f0023ed
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding;
7
8import org.eclipse.collections.api.set.primitive.IntSet;
9import tools.refinery.store.model.AnyInterpretation;
10
11import java.util.List;
12
13public interface StateEquivalenceChecker {
14 enum EquivalenceResult {
15 ISOMORPHIC, UNKNOWN, DIFFERENT
16 }
17
18 EquivalenceResult constructMorphism(
19 IntSet individuals, List<? extends AnyInterpretation> interpretations1, ObjectCode code1,
20 List<? extends AnyInterpretation> interpretations2, ObjectCode code2);
21}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java
new file mode 100644
index 00000000..a2471916
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java
@@ -0,0 +1,39 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.internal;
7
8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.statecoding.StateCodeCalculator;
11import tools.refinery.store.statecoding.StateCoderAdapter;
12import tools.refinery.store.statecoding.StateCoderResult;
13
14public class StateCoderAdapterImpl implements StateCoderAdapter {
15 final ModelStoreAdapter storeAdapter;
16 final Model model;
17 final StateCodeCalculator calculator;
18
19 StateCoderAdapterImpl(ModelStoreAdapter storeAdapter, StateCodeCalculator calculator, Model model) {
20 this.storeAdapter = storeAdapter;
21 this.model = model;
22 this.calculator = calculator;
23 }
24
25 @Override
26 public Model getModel() {
27 return model;
28 }
29
30 @Override
31 public ModelStoreAdapter getStoreAdapter() {
32 return storeAdapter;
33 }
34
35 @Override
36 public StateCoderResult calculateStateCode() {
37 return calculator.calculateCodes();
38 }
39}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java
new file mode 100644
index 00000000..eed591e7
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java
@@ -0,0 +1,71 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.internal;
7
8import org.eclipse.collections.api.factory.primitive.IntSets;
9import org.eclipse.collections.api.set.primitive.MutableIntSet;
10import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
11import tools.refinery.store.model.ModelStore;
12import tools.refinery.store.representation.AnySymbol;
13import tools.refinery.store.representation.Symbol;
14import tools.refinery.store.statecoding.StateCodeCalculatorFactory;
15import tools.refinery.store.statecoding.StateCoderBuilder;
16import tools.refinery.store.statecoding.StateCoderStoreAdapter;
17import tools.refinery.store.statecoding.StateEquivalenceChecker;
18import tools.refinery.store.statecoding.neighbourhood.NeighbourhoodCalculator;
19import tools.refinery.store.statecoding.stateequivalence.StateEquivalenceCheckerImpl;
20import tools.refinery.store.tuple.Tuple1;
21
22import java.util.HashSet;
23import java.util.LinkedHashSet;
24import java.util.Set;
25
26public class StateCoderBuilderImpl extends AbstractModelAdapterBuilder<StateCoderStoreAdapter>
27 implements StateCoderBuilder {
28 private final Set<AnySymbol> excluded = new HashSet<>();
29 private final MutableIntSet individuals = IntSets.mutable.empty();
30 private StateCodeCalculatorFactory calculator = NeighbourhoodCalculator::new;
31 private StateEquivalenceChecker checker = new StateEquivalenceCheckerImpl();
32
33 @Override
34 public StateCoderBuilder exclude(AnySymbol symbol) {
35 checkNotConfigured();
36 excluded.add(symbol);
37 return this;
38 }
39
40 @Override
41 public StateCoderBuilder individual(Tuple1 tuple) {
42 checkNotConfigured();
43 individuals.add(tuple.get(0));
44 return this;
45 }
46
47 @Override
48 public StateCoderBuilder stateEquivalenceChecker(StateEquivalenceChecker stateEquivalenceChecker) {
49 checkNotConfigured();
50 this.checker = stateEquivalenceChecker;
51 return this;
52 }
53
54 @Override
55 public StateCoderBuilder stateCodeCalculatorFactory(StateCodeCalculatorFactory codeCalculatorFactory) {
56 checkNotConfigured();
57 this.calculator = codeCalculatorFactory;
58 return this;
59 }
60
61 @Override
62 protected StateCoderStoreAdapter doBuild(ModelStore store) {
63 Set<Symbol<?>> symbols = new LinkedHashSet<>();
64 for (AnySymbol symbol : store.getSymbols()) {
65 if (!excluded.contains(symbol) && (symbol instanceof Symbol<?> typed)) {
66 symbols.add(typed);
67 }
68 }
69 return new StateCoderStoreAdapterImpl(store, calculator, checker, symbols, individuals);
70 }
71}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java
new file mode 100644
index 00000000..89586bfb
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java
@@ -0,0 +1,74 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.internal;
7
8import org.eclipse.collections.api.set.primitive.IntSet;
9import tools.refinery.store.map.Version;
10import tools.refinery.store.model.Model;
11import tools.refinery.store.model.ModelStore;
12import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.statecoding.StateCodeCalculatorFactory;
14import tools.refinery.store.statecoding.StateCoderAdapter;
15import tools.refinery.store.statecoding.StateCoderStoreAdapter;
16import tools.refinery.store.statecoding.StateEquivalenceChecker;
17
18import java.util.Collection;
19import java.util.Objects;
20
21public class StateCoderStoreAdapterImpl implements StateCoderStoreAdapter {
22 final ModelStore store;
23 final Collection<Symbol<?>> symbols;
24 final IntSet individuals;
25
26 final StateEquivalenceChecker equivalenceChecker;
27 final StateCodeCalculatorFactory codeCalculatorFactory;
28
29 StateCoderStoreAdapterImpl(ModelStore store,
30 StateCodeCalculatorFactory codeCalculatorFactory,
31 StateEquivalenceChecker equivalenceChecker,
32 Collection<Symbol<?>> symbols,
33 IntSet individuals)
34 {
35 this.codeCalculatorFactory = codeCalculatorFactory;
36 this.equivalenceChecker = equivalenceChecker;
37 this.store = store;
38 this.symbols = symbols;
39 this.individuals = individuals;
40 }
41
42 @Override
43 public ModelStore getStore() {
44 return store;
45 }
46
47 @Override
48 public StateEquivalenceChecker.EquivalenceResult checkEquivalence(Version v1, Version v2) {
49 if (Objects.equals(v1, v2)) {
50 return StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC;
51 }
52 var model1 = this.getStore().createModelForState(v1);
53 var model2 = this.getStore().createModelForState(v2);
54
55 var s1 = model1.getAdapter(StateCoderAdapter.class).calculateStateCode();
56 var s2 = model2.getAdapter(StateCoderAdapter.class).calculateStateCode();
57
58 if (s1.modelCode() != s2.modelCode()) {
59 return StateEquivalenceChecker.EquivalenceResult.DIFFERENT;
60 }
61
62 var i1 = symbols.stream().map(model1::getInterpretation).toList();
63 var i2 = symbols.stream().map(model2::getInterpretation).toList();
64
65 return equivalenceChecker.constructMorphism(individuals, i1, s1.objectCode(), i2, s2.objectCode());
66 }
67
68 @Override
69 public StateCoderAdapter createModelAdapter(Model model) {
70 var interpretations = symbols.stream().map(model::getInterpretation).toList();
71 var coder = codeCalculatorFactory.create(interpretations, individuals);
72 return new StateCoderAdapterImpl(this, coder, model);
73 }
74}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java
new file mode 100644
index 00000000..5d390da2
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java
@@ -0,0 +1,96 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.neighbourhood;
7
8import org.eclipse.collections.api.factory.primitive.IntLongMaps;
9import org.eclipse.collections.api.map.primitive.MutableIntLongMap;
10import org.eclipse.collections.api.set.primitive.IntSet;
11import tools.refinery.store.model.AnyInterpretation;
12import tools.refinery.store.model.Interpretation;
13import tools.refinery.store.statecoding.ObjectCode;
14import tools.refinery.store.tuple.Tuple;
15import tools.refinery.store.tuple.Tuple0;
16
17import java.util.*;
18
19public abstract class AbstractNeighbourhoodCalculator {
20 protected final List<AnyInterpretation> nullImpactValues;
21 protected final LinkedHashMap<AnyInterpretation, long[]> impactValues;
22 protected final MutableIntLongMap individualHashValues = IntLongMaps.mutable.empty();
23
24 protected static final long PRIME = 31;
25
26 protected AbstractNeighbourhoodCalculator(List<? extends AnyInterpretation> interpretations, IntSet individuals) {
27 this.nullImpactValues = new ArrayList<>();
28 this.impactValues = new LinkedHashMap<>();
29 // Random isn't used for cryptographical purposes but just to assign distinguishable identifiers to symbols.
30 @SuppressWarnings("squid:S2245")
31 Random random = new Random(1);
32
33 var individualsInOrder = individuals.toSortedList(Integer::compare);
34 for(int i = 0; i<individualsInOrder.size(); i++) {
35 individualHashValues.put(individualsInOrder.get(i), random.nextLong());
36 }
37
38 for (AnyInterpretation interpretation : interpretations) {
39 int arity = interpretation.getSymbol().arity();
40 if (arity == 0) {
41 nullImpactValues.add(interpretation);
42 } else {
43 long[] impact = new long[arity];
44 for (int i = 0; i < arity; i++) {
45 impact[i] = random.nextInt();
46 }
47 impactValues.put(interpretation, impact);
48 }
49 }
50 }
51
52 protected void initializeWithIndividuals(ObjectCodeImpl previous) {
53 for (var entry : individualHashValues.keyValuesView()) {
54 previous.set(entry.getOne(), entry.getTwo());
55 }
56 }
57
58 protected long getTupleHash1(Tuple tuple, Object value, ObjectCode objectCodeImpl) {
59 long result = Objects.hashCode(value);
60 result = result * PRIME + objectCodeImpl.get(tuple.get(0));
61 return result;
62 }
63
64 protected long getTupleHash2(Tuple tuple, Object value, ObjectCode objectCodeImpl) {
65 long result = Objects.hashCode(value);
66 result = result * PRIME + objectCodeImpl.get(tuple.get(0));
67 result = result * PRIME + objectCodeImpl.get(tuple.get(1));
68 if (tuple.get(0) == tuple.get(1)) {
69 result += PRIME;
70 result *= PRIME;
71 }
72 return result;
73 }
74
75 protected long getTupleHashN(Tuple tuple, Object value, ObjectCode objectCodeImpl) {
76 long result = Objects.hashCode(value);
77 for (int i = 0; i < tuple.getSize(); i++) {
78 result = result * PRIME + objectCodeImpl.get(tuple.get(i));
79 }
80 return result;
81 }
82
83 protected void addHash(ObjectCodeImpl objectCodeImpl, int o, long impact, long tupleHash) {
84 long x = tupleHash * impact;
85 objectCodeImpl.set(o, objectCodeImpl.get(o) + x);
86 }
87
88 protected long calculateModelCode(long lastSum) {
89 long result = 0;
90 for (var nullImpactValue : nullImpactValues) {
91 result = result * PRIME + Objects.hashCode(((Interpretation<?>) nullImpactValue).get(Tuple0.INSTANCE));
92 }
93 result += lastSum;
94 return result;
95 }
96}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java
new file mode 100644
index 00000000..c188a839
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java
@@ -0,0 +1,193 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.neighbourhood;
7
8import org.eclipse.collections.api.factory.primitive.LongIntMaps;
9import org.eclipse.collections.api.map.primitive.LongIntMap;
10import org.eclipse.collections.api.map.primitive.MutableLongIntMap;
11import org.eclipse.collections.api.set.primitive.IntSet;
12import tools.refinery.store.map.Cursor;
13import tools.refinery.store.model.AnyInterpretation;
14import tools.refinery.store.model.Interpretation;
15import tools.refinery.store.statecoding.StateCodeCalculator;
16import tools.refinery.store.statecoding.StateCoderResult;
17import tools.refinery.store.tuple.Tuple;
18
19import java.util.List;
20
21public class LazyNeighbourhoodCalculator extends AbstractNeighbourhoodCalculator implements StateCodeCalculator {
22 public LazyNeighbourhoodCalculator(List<? extends AnyInterpretation> interpretations, IntSet individuals) {
23 super(interpretations, individuals);
24 }
25
26 public StateCoderResult calculateCodes() {
27 ObjectCodeImpl previousObjectCode = new ObjectCodeImpl();
28 MutableLongIntMap prevHash2Amount = LongIntMaps.mutable.empty();
29
30 long lastSum;
31 // All hash code is 0, except to the individuals.
32 int lastSize = 1;
33 boolean first = true;
34
35 boolean grows;
36 int rounds = 0;
37 do {
38 final ObjectCodeImpl nextObjectCode;
39 if (first) {
40 nextObjectCode = new ObjectCodeImpl();
41 initializeWithIndividuals(nextObjectCode);
42 } else {
43 nextObjectCode = new ObjectCodeImpl(previousObjectCode);
44 }
45 constructNextObjectCodes(previousObjectCode, nextObjectCode, prevHash2Amount);
46
47 MutableLongIntMap nextHash2Amount = LongIntMaps.mutable.empty();
48 lastSum = calculateLastSum(previousObjectCode, nextObjectCode, prevHash2Amount, nextHash2Amount);
49
50 int nextSize = nextHash2Amount.size();
51 grows = nextSize > lastSize;
52 lastSize = nextSize;
53 first = false;
54
55 previousObjectCode = nextObjectCode;
56 prevHash2Amount = nextHash2Amount;
57 } while (grows && rounds++ < 4/*&& lastSize < previousObjectCode.getSize()*/);
58
59 long result = calculateModelCode(lastSum);
60
61 return new StateCoderResult((int) result, previousObjectCode);
62 }
63
64 private long calculateLastSum(ObjectCodeImpl previous, ObjectCodeImpl next, LongIntMap hash2Amount,
65 MutableLongIntMap nextHash2Amount) {
66 long lastSum = 0;
67 for (int i = 0; i < next.getSize(); i++) {
68 final long hash;
69 if (isUnique(hash2Amount, previous, i)) {
70 hash = previous.get(i);
71 next.set(i, hash);
72 } else {
73 hash = next.get(i);
74 }
75
76 final int amount = nextHash2Amount.get(hash);
77 nextHash2Amount.put(hash, amount + 1);
78
79 final long shifted1 = hash >>> 8;
80 final long shifted2 = hash << 8;
81 final long shifted3 = hash >> 2;
82 lastSum += shifted1 * shifted3 + shifted2;
83 }
84 return lastSum;
85 }
86
87 private void constructNextObjectCodes(ObjectCodeImpl previous, ObjectCodeImpl next, LongIntMap hash2Amount) {
88 for (var impactValueEntry : this.impactValues.entrySet()) {
89 Interpretation<?> interpretation = (Interpretation<?>) impactValueEntry.getKey();
90 var cursor = interpretation.getAll();
91 int arity = interpretation.getSymbol().arity();
92 long[] impactValue = impactValueEntry.getValue();
93
94 if (arity == 1) {
95 while (cursor.move()) {
96 lazyImpactCalculation1(hash2Amount, previous, next, impactValue, cursor);
97 }
98 } else if (arity == 2) {
99 while (cursor.move()) {
100 lazyImpactCalculation2(hash2Amount, previous, next, impactValue, cursor);
101 }
102 } else {
103 while (cursor.move()) {
104 lazyImpactCalculationN(hash2Amount, previous, next, impactValue, cursor);
105 }
106 }
107 }
108 }
109
110 private boolean isUnique(LongIntMap hash2Amount, ObjectCodeImpl objectCodeImpl, int object) {
111 final long hash = objectCodeImpl.get(object);
112 if (hash == 0) {
113 return false;
114 }
115 final int amount = hash2Amount.get(hash);
116 return amount == 1;
117 }
118
119 private void lazyImpactCalculation1(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next,
120 long[] impactValues, Cursor<Tuple, ?> cursor) {
121
122 Tuple tuple = cursor.getKey();
123 int o = tuple.get(0);
124
125 if (isUnique(hash2Amount, previous, o)) {
126 next.ensureSize(o);
127 } else {
128 Object value = cursor.getValue();
129 long tupleHash = getTupleHash1(tuple, value, previous);
130
131 addHash(next, o, impactValues[0], tupleHash);
132 }
133 }
134
135 private void lazyImpactCalculation2(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next,
136 long[] impactValues, Cursor<Tuple, ?> cursor) {
137 final Tuple tuple = cursor.getKey();
138 final int o1 = tuple.get(0);
139 final int o2 = tuple.get(1);
140
141 final boolean u1 = isUnique(hash2Amount, previous, o1);
142 final boolean u2 = isUnique(hash2Amount, previous, o2);
143
144 if (u1 && u2) {
145 next.ensureSize(o1);
146 next.ensureSize(o2);
147 } else {
148 Object value = cursor.getValue();
149 long tupleHash = getTupleHash2(tuple, value, previous);
150
151 if (!u1) {
152 addHash(next, o1, impactValues[0], tupleHash);
153 next.ensureSize(o2);
154 }
155 if (!u2) {
156 next.ensureSize(o1);
157 addHash(next, o2, impactValues[1], tupleHash);
158 }
159 }
160 }
161
162 private void lazyImpactCalculationN(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next,
163 long[] impactValues, Cursor<Tuple, ?> cursor) {
164 final Tuple tuple = cursor.getKey();
165
166 final boolean[] uniques = new boolean[tuple.getSize()];
167 boolean allUnique = true;
168 for (int i = 0; i < tuple.getSize(); i++) {
169 final boolean isUnique = isUnique(hash2Amount, previous, tuple.get(i));
170 uniques[i] = isUnique;
171 allUnique &= isUnique;
172 }
173
174 if (allUnique) {
175 for (int i = 0; i < tuple.getSize(); i++) {
176 next.ensureSize(tuple.get(i));
177 }
178 } else {
179 Object value = cursor.getValue();
180 long tupleHash = getTupleHashN(tuple, value, previous);
181
182 for (int i = 0; i < tuple.getSize(); i++) {
183 int o = tuple.get(i);
184 if (!uniques[i]) {
185 addHash(next, o, impactValues[i], tupleHash);
186 } else {
187 next.ensureSize(o);
188 }
189 }
190 }
191 }
192
193}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java
new file mode 100644
index 00000000..785fda7a
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java
@@ -0,0 +1,115 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.neighbourhood;
7
8import org.eclipse.collections.api.set.primitive.IntSet;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.model.Interpretation;
11import tools.refinery.store.statecoding.ObjectCode;
12import tools.refinery.store.statecoding.StateCodeCalculator;
13import tools.refinery.store.statecoding.StateCoderResult;
14import tools.refinery.store.tuple.Tuple;
15import tools.refinery.store.tuple.Tuple0;
16
17import java.util.List;
18import java.util.Objects;
19
20public class NeighbourhoodCalculator extends AbstractNeighbourhoodCalculator implements StateCodeCalculator {
21 public NeighbourhoodCalculator(List<? extends Interpretation<?>> interpretations, IntSet individuals) {
22 super(interpretations, individuals);
23 }
24
25 public StateCoderResult calculateCodes() {
26 ObjectCodeImpl previousObjectCode = new ObjectCodeImpl();
27 initializeWithIndividuals(previousObjectCode);
28
29 int rounds = 0;
30 do {
31 final ObjectCodeImpl nextObjectCode = rounds == 0 ? new ObjectCodeImpl() :
32 new ObjectCodeImpl(previousObjectCode.getSize());
33
34 constructNextObjectCodes(previousObjectCode, nextObjectCode);
35 previousObjectCode = nextObjectCode;
36 rounds++;
37 } while (rounds <= 7 && rounds <= previousObjectCode.getEffectiveSize());
38
39 long result = calculateLastSum(previousObjectCode);
40 return new StateCoderResult((int) result, previousObjectCode);
41 }
42
43 private long calculateLastSum(ObjectCode codes) {
44 long result = 0;
45 for (var nullImpactValue : nullImpactValues) {
46 result = result * 31 + Objects.hashCode(((Interpretation<?>) nullImpactValue).get(Tuple0.INSTANCE));
47 }
48
49 for (int i = 0; i < codes.getSize(); i++) {
50 final long hash = codes.get(i);
51 result += hash*PRIME;
52 }
53
54 return result;
55 }
56
57 private void constructNextObjectCodes(ObjectCodeImpl previous, ObjectCodeImpl next) {
58 for (var impactValueEntry : this.impactValues.entrySet()) {
59 Interpretation<?> interpretation = (Interpretation<?>) impactValueEntry.getKey();
60 var cursor = interpretation.getAll();
61 int arity = interpretation.getSymbol().arity();
62 long[] impactValue = impactValueEntry.getValue();
63
64 if (arity == 1) {
65 while (cursor.move()) {
66 impactCalculation1(previous, next, impactValue, cursor);
67 }
68 } else if (arity == 2) {
69 while (cursor.move()) {
70 impactCalculation2(previous, next, impactValue, cursor);
71 }
72 } else {
73 while (cursor.move()) {
74 impactCalculationN(previous, next, impactValue, cursor);
75 }
76 }
77 }
78 }
79
80
81 private void impactCalculation1(ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues,
82 Cursor<Tuple, ?> cursor) {
83
84 Tuple tuple = cursor.getKey();
85 int o = tuple.get(0);
86 Object value = cursor.getValue();
87 long tupleHash = getTupleHash1(tuple, value, previous);
88 addHash(next, o, impactValues[0], tupleHash);
89 }
90
91 private void impactCalculation2(ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues,
92 Cursor<Tuple, ?> cursor) {
93 final Tuple tuple = cursor.getKey();
94 final int o1 = tuple.get(0);
95 final int o2 = tuple.get(1);
96
97 Object value = cursor.getValue();
98 long tupleHash = getTupleHash2(tuple, value, previous);
99
100 addHash(next, o1, impactValues[0], tupleHash);
101 addHash(next, o2, impactValues[1], tupleHash);
102 }
103
104 private void impactCalculationN(ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues,
105 Cursor<Tuple, ?> cursor) {
106 final Tuple tuple = cursor.getKey();
107
108 Object value = cursor.getValue();
109 long tupleHash = getTupleHashN(tuple, value, previous);
110
111 for (int i = 0; i < tuple.getSize(); i++) {
112 addHash(next, tuple.get(i), impactValues[i], tupleHash);
113 }
114 }
115}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java
new file mode 100644
index 00000000..0cd7ff58
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java
@@ -0,0 +1,81 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.neighbourhood;
7
8import tools.refinery.store.statecoding.ObjectCode;
9
10import java.util.Arrays;
11
12public class ObjectCodeImpl implements ObjectCode {
13 private long[] vector;
14 private int size;
15 private int effectiveSize;
16
17 public ObjectCodeImpl() {
18 vector = new long[10];
19 size = 0;
20 effectiveSize = 0;
21 }
22
23 public ObjectCodeImpl(int size) {
24 this.vector = new long[size];
25 this.size = size;
26 effectiveSize = 0;
27 }
28
29 public ObjectCodeImpl(ObjectCodeImpl copy) {
30 this.vector = Arrays.copyOf(copy.vector, copy.size);
31 this.size = copy.size;
32 effectiveSize = copy.effectiveSize;
33 }
34
35 public void ensureSize(int object) {
36 if (object >= size) {
37 size = object + 1;
38 }
39
40 if (object >= vector.length) {
41 int newLength = vector.length * 2;
42 while (object >= newLength) {
43 newLength *= 2;
44 }
45
46 long[] newVector = new long[newLength];
47 System.arraycopy(vector, 0, newVector, 0, vector.length);
48 this.vector = newVector;
49 }
50 }
51
52 public long get(int object) {
53 if (object < vector.length) {
54 return vector[object];
55 } else {
56 return 0;
57 }
58 }
59
60 public void set(int object, long value) {
61 ensureSize(object);
62 final long valueToPut = value == 0 ? 1 : value;
63 if (vector[object] == 0) effectiveSize++;
64 vector[object] = valueToPut;
65 }
66
67 public int getSize() {
68 return this.size;
69 }
70
71 public int getEffectiveSize() {
72 return this.effectiveSize;
73 }
74
75 @Override
76 public String toString() {
77 return "ObjectCodeImpl{" +
78 "vector=" + Arrays.toString(Arrays.copyOf(vector, this.size)) +
79 '}';
80 }
81}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java
new file mode 100644
index 00000000..0682e1a4
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java
@@ -0,0 +1,89 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.stateequivalence;
7
8import org.eclipse.collections.api.factory.primitive.IntIntMaps;
9import org.eclipse.collections.api.map.primitive.IntIntMap;
10import org.eclipse.collections.api.set.primitive.IntSet;
11
12import java.util.*;
13
14public class CombinationNodePairing implements NodePairing {
15 private final int[] left;
16 private final int[] right;
17
18 CombinationNodePairing(IntSet left, IntSet right) {
19 this.left = left.toArray();
20 this.right = right.toArray();
21
22 Arrays.sort(this.left);
23 Arrays.sort(this.right);
24 }
25
26 @Override
27 public int size() {
28 return left.length;
29 }
30
31 private static final int LIMIT = 5;
32 // Enum-based singleton used to delay generating all permutations until they are first needed.
33 @SuppressWarnings("squid:S6548")
34 private enum PermutationsHolder {
35 INSTANCE;
36
37 final CombinationNodePairingPermutations permutations = new CombinationNodePairingPermutations(LIMIT);
38 }
39
40 @Override
41 public List<IntIntMap> permutations() {
42 int limit = this.size();
43 Iterable<Integer> interval = () -> new IntervalIterator(limit);
44
45 if (isComplete()) {
46 final List<int[]> p = PermutationsHolder.INSTANCE.permutations.getPermutations(this.size() - 1);
47 return p.stream().map(x -> constructPermutationMap(interval, x)).toList();
48 } else {
49 return List.of(constructTrivialMap(interval));
50 }
51 }
52
53 private IntIntMap constructTrivialMap(Iterable<Integer> interval) {
54 return IntIntMaps.immutable.from(interval, l -> left[l], r -> right[r]);
55 }
56
57 private IntIntMap constructPermutationMap(Iterable<Integer> interval, int[] permutation) {
58 return IntIntMaps.immutable.from(interval, l -> left[l], r -> right[permutation[r]]);
59 }
60
61 @Override
62 public boolean isComplete() {
63 return this.size() <= LIMIT;
64 }
65
66 private static class IntervalIterator implements Iterator<Integer> {
67 private final int limit;
68 private int value = 0;
69
70 private IntervalIterator(int max) {
71 this.limit = max;
72 }
73
74 @Override
75 public boolean hasNext() {
76 return value < limit;
77 }
78
79 @Override
80 public Integer next() {
81 if (value >= limit) {
82 throw new NoSuchElementException("End of interval");
83 }
84 int next = value;
85 value++;
86 return next;
87 }
88 }
89}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairingPermutations.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairingPermutations.java
new file mode 100644
index 00000000..eacd3a2a
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairingPermutations.java
@@ -0,0 +1,57 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.stateequivalence;
7
8import java.util.ArrayList;
9import java.util.List;
10
11class CombinationNodePairingPermutations {
12 private final List<List<int[]>> permutations = new ArrayList<>();
13
14 public CombinationNodePairingPermutations(int max) {
15 initializePermutations(max);
16 }
17
18 public List<int[]> getPermutations(int max) {
19 if (max >= permutations.size()) {
20 throw new IllegalArgumentException("Only permutations up to %d elements are supported".formatted(max));
21 }
22 return permutations.get(max);
23 }
24
25 /**
26 * Generates and stores permutations up to a given size. If the number would be more than a limit, it provides a
27 * single permutation only.
28 *
29 * @param max The max number in the permutation
30 * @return A complete list of permutations of numbers 0...max, or a single permutation.
31 */
32 private List<int[]> initializePermutations(int max) {
33 if (max < permutations.size()) {
34 return permutations.get(max);
35 }
36 if (max == 0) {
37 List<int[]> result = new ArrayList<>();
38 result.add(new int[1]);
39 permutations.add(result);
40 return result;
41 }
42 List<int[]> result = new ArrayList<>();
43 List<int[]> previousPermutations = initializePermutations(max - 1);
44 for (var permutation : previousPermutations) {
45 for (int pos = 0; pos <= max; pos++) {
46 int[] newPermutation = new int[max + 1];
47 System.arraycopy(permutation, 0, newPermutation, 0, pos);
48 newPermutation[pos] = max;
49 if (max - (pos + 1) >= 0)
50 System.arraycopy(permutation, pos + 1, newPermutation, pos + 1 + 1, max - (pos + 1));
51 result.add(newPermutation);
52 }
53 }
54 permutations.add(result);
55 return result;
56 }
57}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java
new file mode 100644
index 00000000..f45f0d2e
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java
@@ -0,0 +1,33 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.stateequivalence;
7
8import org.eclipse.collections.api.map.primitive.IntIntMap;
9import org.eclipse.collections.api.set.primitive.IntSet;
10
11import java.util.List;
12
13public interface NodePairing {
14 int size();
15
16 List<IntIntMap> permutations();
17
18 boolean isComplete();
19
20 static NodePairing constructNodePairing(IntSet left, IntSet right){
21 if(left.size() != right.size()) {
22 return null;
23 }
24
25 if(left.size() == 1) {
26 int leftValue = left.intIterator().next();
27 int rightValue = right.intIterator().next();
28 return new TrivialNodePairing(leftValue, rightValue);
29 } else {
30 return new CombinationNodePairing(left,right);
31 }
32 }
33}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java
new file mode 100644
index 00000000..bc4d723a
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java
@@ -0,0 +1,64 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.stateequivalence;
7
8import org.eclipse.collections.api.map.primitive.IntIntMap;
9import tools.refinery.store.statecoding.Morphism;
10
11import java.util.List;
12
13public class PermutationMorphism implements Morphism {
14 private final IntIntMap object2PermutationGroup;
15 private final List<? extends List<? extends IntIntMap>> permutationsGroups;
16 private final int[] selection;
17 private boolean hasNext;
18
19 PermutationMorphism(IntIntMap object2PermutationGroup,
20 List<? extends List<? extends IntIntMap>> permutationsGroups) {
21 this.object2PermutationGroup = object2PermutationGroup;
22 this.permutationsGroups = permutationsGroups;
23
24 this.selection = new int[this.permutationsGroups.size()];
25 this.hasNext = true;
26 }
27
28 public boolean next() {
29 return next(0);
30 }
31
32 private boolean next(int position) {
33 if (position >= permutationsGroups.size()) {
34 this.hasNext = false;
35 return false;
36 }
37 if (selection[position] + 1 < permutationsGroups.get(position).size()) {
38 selection[position] = selection[position] + 1;
39 return true;
40 } else {
41 selection[position] = 0;
42 return next(position + 1);
43 }
44 }
45
46 @Override
47 public int get(int object) {
48 if(!hasNext) {
49 throw new IllegalArgumentException("No next permutation!");
50 }
51
52 final int groupIndex = object2PermutationGroup.get(object);
53 final var selectedGroup = permutationsGroups.get(groupIndex);
54 final int permutationIndex = selection[groupIndex];
55 final var selectedPermutation = selectedGroup.get(permutationIndex);
56
57 return selectedPermutation.get(object);
58 }
59
60 @Override
61 public int getSize() {
62 return object2PermutationGroup.size();
63 }
64}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java
new file mode 100644
index 00000000..5a62d8a0
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java
@@ -0,0 +1,166 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.stateequivalence;
7
8import org.eclipse.collections.api.factory.primitive.IntIntMaps;
9import org.eclipse.collections.api.factory.primitive.IntSets;
10import org.eclipse.collections.api.factory.primitive.LongObjectMaps;
11import org.eclipse.collections.api.map.primitive.IntIntMap;
12import org.eclipse.collections.api.map.primitive.MutableIntIntMap;
13import org.eclipse.collections.api.map.primitive.MutableLongObjectMap;
14import org.eclipse.collections.api.set.primitive.IntSet;
15import org.eclipse.collections.api.set.primitive.MutableIntSet;
16import tools.refinery.store.model.AnyInterpretation;
17import tools.refinery.store.model.Interpretation;
18import tools.refinery.store.statecoding.Morphism;
19import tools.refinery.store.statecoding.ObjectCode;
20import tools.refinery.store.statecoding.StateEquivalenceChecker;
21import tools.refinery.store.tuple.Tuple;
22
23import java.util.ArrayList;
24import java.util.List;
25import java.util.Objects;
26
27public class StateEquivalenceCheckerImpl implements StateEquivalenceChecker {
28 public static final int LIMIT = 1000;
29
30 @Override
31 public EquivalenceResult constructMorphism(IntSet individuals,
32 List<? extends AnyInterpretation> interpretations1,
33 ObjectCode code1,
34 List<? extends AnyInterpretation> interpretations2,
35 ObjectCode code2) {
36 MutableIntIntMap object2PermutationGroup = IntIntMaps.mutable.empty();
37 List<List<IntIntMap>> permutationsGroups = new ArrayList<>();
38
39 final EquivalenceResult permutations = constructPermutationNavigation(individuals,
40 indexByHash(code1, individuals), indexByHash(code2, individuals),
41 object2PermutationGroup, permutationsGroups);
42
43 if (permutations == EquivalenceResult.DIFFERENT) {
44 return EquivalenceResult.DIFFERENT;
45 }
46
47 boolean hasNext;
48 PermutationMorphism morphism = new PermutationMorphism(object2PermutationGroup, permutationsGroups);
49 int tried = 0;
50 do {
51 if (testMorphism(interpretations1, interpretations2, morphism)) {
52 return permutations;
53 }
54
55 if (tried >= LIMIT) {
56 return EquivalenceResult.UNKNOWN;
57 }
58
59 hasNext = morphism.next();
60 tried++;
61 } while (hasNext);
62
63 if (permutations == EquivalenceResult.UNKNOWN) {
64 return EquivalenceResult.UNKNOWN;
65 } else {
66 return EquivalenceResult.DIFFERENT;
67 }
68 }
69
70 private MutableLongObjectMap<MutableIntSet> indexByHash(ObjectCode code, IntSet individuals) {
71 MutableLongObjectMap<MutableIntSet> result = LongObjectMaps.mutable.empty();
72 for (int o = 0; o < code.getSize(); o++) {
73 if (!individuals.contains(o)) {
74 long hash = code.get(o);
75 if (hash != 0) {
76 var equivalenceClass = result.get(hash);
77 if (equivalenceClass == null) {
78 equivalenceClass = IntSets.mutable.empty();
79 result.put(hash, equivalenceClass);
80 }
81 equivalenceClass.add(o);
82 }
83 }
84 }
85 return result;
86 }
87
88 private EquivalenceResult constructPermutationNavigation(
89 IntSet individuals, MutableLongObjectMap<MutableIntSet> map1, MutableLongObjectMap<MutableIntSet> map2,
90 MutableIntIntMap object2OptionIndex, List<List<IntIntMap>> listOfOptions) {
91 if (map1.size() != map2.size()) {
92 return EquivalenceResult.DIFFERENT;
93 }
94
95 var iterator = map1.keySet().longIterator();
96
97 boolean allComplete = true;
98
99 while (iterator.hasNext()) {
100 long hash = iterator.next();
101 var set1 = map1.get(hash);
102 var set2 = map2.get(hash);
103 if (set2 == null) {
104 return EquivalenceResult.DIFFERENT;
105 }
106
107 var pairing = NodePairing.constructNodePairing(set1, set2);
108 if (pairing == null) {
109 return EquivalenceResult.DIFFERENT;
110 }
111
112 allComplete &= pairing.isComplete();
113
114 final int optionIndex = listOfOptions.size();
115 set1.forEach(key -> object2OptionIndex.put(key, optionIndex));
116 listOfOptions.add(pairing.permutations());
117 }
118
119 individuals.forEach(o -> listOfOptions.add(o, List.of(IntIntMaps.immutable.of(o, o))));
120
121 if (allComplete) {
122 return EquivalenceResult.ISOMORPHIC;
123 } else {
124 return EquivalenceResult.UNKNOWN;
125 }
126 }
127
128 private boolean testMorphism(List<? extends AnyInterpretation> s, List<? extends AnyInterpretation> t,
129 Morphism m) {
130 for (int interpretationIndex = 0; interpretationIndex < s.size(); interpretationIndex++) {
131 var sI = s.get(interpretationIndex);
132 var tI = t.get(interpretationIndex);
133
134 var cursor = ((Interpretation<?>) sI).getAll();
135 while (cursor.move()) {
136 final Tuple sTuple = cursor.getKey();
137 final Object sValue = cursor.getValue();
138
139 final Tuple tTuple = apply(sTuple, m);
140 final Object tValue = ((Interpretation<?>) tI).get(tTuple);
141
142 if (!Objects.equals(sValue, tValue)) {
143 return false;
144 }
145 }
146 }
147 return true;
148 }
149
150 private Tuple apply(Tuple t, Morphism m) {
151 final int arity = t.getSize();
152 if (arity == 0) {
153 return Tuple.of();
154 } else if (arity == 1) {
155 return Tuple.of(m.get(t.get(0)));
156 } else if (arity == 2) {
157 return Tuple.of(m.get(t.get(0)), m.get(t.get(1)));
158 } else {
159 int[] newTupleIndices = new int[arity];
160 for (int i = 0; i < arity; i++) {
161 newTupleIndices[i] = m.get(t.get(i));
162 }
163 return Tuple.of(newTupleIndices);
164 }
165 }
166}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/TrivialNodePairing.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/TrivialNodePairing.java
new file mode 100644
index 00000000..f5eadfb9
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/TrivialNodePairing.java
@@ -0,0 +1,36 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.stateequivalence;
7
8import org.eclipse.collections.api.factory.primitive.IntIntMaps;
9import org.eclipse.collections.api.map.primitive.IntIntMap;
10
11import java.util.List;
12
13public class TrivialNodePairing implements NodePairing {
14 final int left;
15 final int right;
16
17 TrivialNodePairing(int left, int right) {
18 this.left = left;
19 this.right = right;
20 }
21
22 @Override
23 public int size() {
24 return 1;
25 }
26
27 @Override
28 public List<IntIntMap> permutations() {
29 return List.of(IntIntMaps.immutable.of(left,right));
30 }
31
32 @Override
33 public boolean isComplete() {
34 return true;
35 }
36}