diff options
author | 2023-09-05 20:33:31 +0200 | |
---|---|---|
committer | 2023-09-05 20:33:31 +0200 | |
commit | e5f14a3e9d92194c3f972f90a2f78c9c3dacaef4 (patch) | |
tree | ff1ebefb45348be48a23a89fa12b4817bc52bf51 /subprojects/store/src/main/java/tools | |
parent | feat(web): control server settings with env vars (diff) | |
parent | restructured DSE framework, failing build (diff) | |
download | refinery-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')
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 | */ |
6 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map; |
7 | 7 | ||
8 | import tools.refinery.store.map.internal.Node; | 8 | import 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 | */ |
18 | public interface ContinousHashProvider<K> { | 18 | public 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 | */ | ||
6 | package tools.refinery.store.map; | ||
7 | |||
8 | import java.util.Iterator; | ||
9 | import java.util.Map; | ||
10 | import java.util.Map.Entry; | ||
11 | import java.util.Set; | ||
12 | |||
13 | public 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 | */ | ||
6 | package tools.refinery.store.map; | ||
7 | |||
8 | /** | ||
9 | * Interface denoting versions of {@link Versioned}. | ||
10 | */ | ||
11 | public 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 | */ |
6 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map; |
7 | 7 | ||
8 | /** | ||
9 | * Object that can save and restore its state. | ||
10 | */ | ||
8 | public interface Versioned { | 11 | public 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 @@ | |||
6 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map; |
7 | 7 | ||
8 | public non-sealed interface VersionedMap<K, V> extends AnyVersionedMap { | 8 | public 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 | */ |
6 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map; |
7 | 7 | ||
8 | import java.util.Set; | 8 | import tools.refinery.store.map.internal.VersionedMapStoreFactoryBuilderImpl; |
9 | 9 | ||
10 | public interface VersionedMapStore<K, V> { | 10 | public 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 | */ | ||
6 | package tools.refinery.store.map; | ||
7 | |||
8 | import java.util.List; | ||
9 | |||
10 | public 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 | */ | ||
6 | package tools.refinery.store.map; | ||
7 | |||
8 | public 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 | */ | ||
6 | package tools.refinery.store.map; | ||
7 | |||
8 | import tools.refinery.store.map.internal.ImmutableNode; | ||
9 | import tools.refinery.store.map.internal.MapDiffCursor; | ||
10 | import tools.refinery.store.map.internal.Node; | ||
11 | import tools.refinery.store.map.internal.VersionedMapImpl; | ||
12 | |||
13 | import java.util.*; | ||
14 | |||
15 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal; | ||
7 | |||
8 | enum 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 | */ | ||
6 | package tools.refinery.store.map.internal; | ||
7 | |||
8 | import tools.refinery.store.map.AnyVersionedMap; | ||
9 | import tools.refinery.store.map.ContinousHashProvider; | ||
10 | import tools.refinery.store.map.Cursor; | ||
11 | import tools.refinery.store.map.DiffCursor; | ||
12 | |||
13 | import java.util.Set; | ||
14 | import java.util.stream.Collectors; | ||
15 | import 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 | */ | ||
23 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal; | ||
7 | |||
8 | import java.util.Map; | ||
9 | |||
10 | import tools.refinery.store.map.ContinousHashProvider; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal; | ||
7 | |||
8 | import tools.refinery.store.map.ContinuousHashProvider; | ||
9 | import tools.refinery.store.map.VersionedMapStoreFactory; | ||
10 | import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; | ||
11 | import tools.refinery.store.map.internal.delta.DeltaBasedVersionedMapStoreFactory; | ||
12 | import tools.refinery.store.map.internal.state.StateBasedVersionedMapStoreFactory; | ||
13 | |||
14 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal.delta; | ||
7 | |||
8 | import tools.refinery.store.map.VersionedMapStore; | ||
9 | import tools.refinery.store.map.VersionedMapStoreFactory; | ||
10 | import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; | ||
11 | |||
12 | import java.util.ArrayList; | ||
13 | import java.util.List; | ||
14 | |||
15 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal.delta; | ||
7 | |||
8 | import tools.refinery.store.map.AnyVersionedMap; | ||
9 | import tools.refinery.store.map.DiffCursor; | ||
10 | |||
11 | import java.util.Collections; | ||
12 | import java.util.List; | ||
13 | import java.util.Set; | ||
14 | |||
15 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal.delta; | ||
7 | |||
8 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal.delta; | ||
7 | |||
8 | import tools.refinery.store.map.Version; | ||
9 | |||
10 | import java.util.Arrays; | ||
11 | import java.util.Objects; | ||
12 | |||
13 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal.delta; | ||
7 | |||
8 | import java.util.ArrayList; | ||
9 | import java.util.List; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal.delta; | ||
7 | |||
8 | import java.util.*; | ||
9 | import java.util.Map.Entry; | ||
10 | |||
11 | import tools.refinery.store.map.VersionedMap; | ||
12 | |||
13 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal.delta; | ||
7 | |||
8 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal.delta; | ||
7 | |||
8 | import tools.refinery.store.map.*; | ||
9 | |||
10 | import java.util.*; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal.delta; | ||
7 | |||
8 | import tools.refinery.store.map.DiffCursor; | ||
9 | import tools.refinery.store.map.Version; | ||
10 | import tools.refinery.store.map.VersionedMap; | ||
11 | import tools.refinery.store.map.VersionedMapStore; | ||
12 | |||
13 | import java.util.*; | ||
14 | |||
15 | public 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 | */ |
6 | package tools.refinery.store.map.internal; | 6 | package tools.refinery.store.map.internal.state; |
7 | 7 | ||
8 | import java.util.Arrays; | 8 | import java.util.Arrays; |
9 | import java.util.Map; | 9 | import java.util.Map; |
10 | 10 | ||
11 | import tools.refinery.store.map.ContinousHashProvider; | 11 | import tools.refinery.store.map.ContinuousHashProvider; |
12 | import tools.refinery.store.map.Version; | ||
12 | 13 | ||
13 | public class ImmutableNode<K, V> extends Node<K, V> { | 14 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal.state; | ||
7 | |||
8 | import tools.refinery.store.map.AnyVersionedMap; | ||
9 | import tools.refinery.store.map.ContentHashCode; | ||
10 | import tools.refinery.store.map.Cursor; | ||
11 | import tools.refinery.store.map.VersionedMap; | ||
12 | |||
13 | import java.util.*; | ||
14 | |||
15 | public 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 | */ |
6 | package tools.refinery.store.map.internal; | 6 | package tools.refinery.store.map.internal.state; |
7 | 7 | ||
8 | import tools.refinery.store.map.AnyVersionedMap; | 8 | import tools.refinery.store.map.AnyVersionedMap; |
9 | import tools.refinery.store.map.ContentHashCode; | 9 | import tools.refinery.store.map.ContentHashCode; |
@@ -12,7 +12,6 @@ import tools.refinery.store.map.VersionedMap; | |||
12 | 12 | ||
13 | import java.util.ArrayDeque; | 13 | import java.util.ArrayDeque; |
14 | import java.util.ConcurrentModificationException; | 14 | import java.util.ConcurrentModificationException; |
15 | import java.util.Iterator; | ||
16 | import java.util.Set; | 15 | import java.util.Set; |
17 | 16 | ||
18 | public class MapCursor<K, V> implements Cursor<K, V> { | 17 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal.state; | ||
7 | |||
8 | import tools.refinery.store.map.AnyVersionedMap; | ||
9 | import tools.refinery.store.map.Cursor; | ||
10 | import tools.refinery.store.map.DiffCursor; | ||
11 | |||
12 | import java.util.Objects; | ||
13 | import java.util.Set; | ||
14 | import java.util.stream.Collectors; | ||
15 | import 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 | public 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 | */ |
6 | package tools.refinery.store.map.internal; | 6 | package tools.refinery.store.map.internal.state; |
7 | 7 | ||
8 | import tools.refinery.store.map.ContinousHashProvider; | 8 | import tools.refinery.store.map.ContinuousHashProvider; |
9 | 9 | ||
10 | import java.util.Arrays; | 10 | import java.util.Arrays; |
11 | import java.util.Map; | 11 | import java.util.Map; |
12 | 12 | ||
13 | public class MutableNode<K, V> extends Node<K, V> { | 13 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal.state; | ||
7 | |||
8 | import java.util.Map; | ||
9 | |||
10 | import tools.refinery.store.map.ContinuousHashProvider; | ||
11 | |||
12 | public 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 | */ |
6 | package tools.refinery.store.map.internal; | 6 | package tools.refinery.store.map.internal.state; |
7 | 7 | ||
8 | public class OldValueBox<V>{ | 8 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal.state; | ||
7 | |||
8 | import tools.refinery.store.map.*; | ||
9 | |||
10 | import java.util.List; | ||
11 | |||
12 | public 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 | */ |
6 | package tools.refinery.store.map.internal; | 6 | package tools.refinery.store.map.internal.state; |
7 | 7 | ||
8 | import tools.refinery.store.map.*; | 8 | import 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 | */ |
22 | public class VersionedMapImpl<K, V> implements VersionedMap<K, V> { | 22 | public 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 | */ |
6 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map.internal.state; |
7 | 7 | ||
8 | public class VersionedMapStoreConfiguration { | 8 | import tools.refinery.store.map.ContinuousHashProvider; |
9 | import tools.refinery.store.map.VersionedMapStore; | ||
9 | 10 | ||
10 | public VersionedMapStoreConfiguration() { | 11 | public 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 | */ | ||
6 | package tools.refinery.store.map.internal.state; | ||
7 | |||
8 | import tools.refinery.store.map.*; | ||
9 | |||
10 | import java.util.*; | ||
11 | |||
12 | public 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 | ||
8 | import tools.refinery.store.map.Cursor; | 8 | import tools.refinery.store.map.Cursor; |
9 | import tools.refinery.store.map.DiffCursor; | 9 | import tools.refinery.store.map.DiffCursor; |
10 | import tools.refinery.store.map.Version; | ||
10 | import tools.refinery.store.representation.Symbol; | 11 | import tools.refinery.store.representation.Symbol; |
11 | import tools.refinery.store.tuple.Tuple; | 12 | import 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 @@ | |||
6 | package tools.refinery.store.model; | 6 | package tools.refinery.store.model; |
7 | 7 | ||
8 | import tools.refinery.store.adapter.ModelAdapter; | 8 | import tools.refinery.store.adapter.ModelAdapter; |
9 | import tools.refinery.store.map.Version; | ||
9 | import tools.refinery.store.map.Versioned; | 10 | import tools.refinery.store.map.Versioned; |
11 | import tools.refinery.store.model.internal.VersionedInterpretation; | ||
10 | import tools.refinery.store.representation.AnySymbol; | 12 | import tools.refinery.store.representation.AnySymbol; |
11 | import tools.refinery.store.representation.Symbol; | 13 | import tools.refinery.store.representation.Symbol; |
12 | 14 | ||
15 | import java.util.Map; | ||
13 | import java.util.Optional; | 16 | import java.util.Optional; |
14 | 17 | ||
15 | public interface Model extends Versioned { | 18 | public 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 | */ |
6 | package tools.refinery.store.model; | 6 | package tools.refinery.store.model; |
7 | 7 | ||
8 | import tools.refinery.store.map.Version; | ||
9 | |||
8 | public interface ModelListener { | 10 | public 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 @@ | |||
6 | package tools.refinery.store.model; | 6 | package tools.refinery.store.model; |
7 | 7 | ||
8 | import tools.refinery.store.adapter.ModelStoreAdapter; | 8 | import tools.refinery.store.adapter.ModelStoreAdapter; |
9 | import tools.refinery.store.map.Version; | ||
9 | import tools.refinery.store.model.internal.ModelStoreBuilderImpl; | 10 | import tools.refinery.store.model.internal.ModelStoreBuilderImpl; |
10 | import tools.refinery.store.representation.AnySymbol; | 11 | import tools.refinery.store.representation.AnySymbol; |
11 | 12 | ||
12 | import java.util.Collection; | 13 | import java.util.Collection; |
13 | import java.util.Optional; | 14 | import java.util.Optional; |
14 | import java.util.Set; | ||
15 | 15 | ||
16 | public interface ModelStore { | 16 | public 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 | */ |
6 | package tools.refinery.store.model; | 6 | package tools.refinery.store.model; |
7 | 7 | ||
8 | import tools.refinery.store.map.ContinousHashProvider; | 8 | import tools.refinery.store.map.ContinuousHashProvider; |
9 | import tools.refinery.store.tuple.Tuple; | 9 | import tools.refinery.store.tuple.Tuple; |
10 | import tools.refinery.store.tuple.Tuple1; | ||
11 | import tools.refinery.store.tuple.Tuple2; | ||
10 | 12 | ||
11 | public class TupleHashProvider implements ContinousHashProvider<Tuple> { | 13 | public 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 | */ | ||
6 | package tools.refinery.store.model; | ||
7 | |||
8 | import tools.refinery.store.map.ContinousHashProvider; | ||
9 | import tools.refinery.store.tuple.Tuple; | ||
10 | |||
11 | public 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 | ||
8 | import tools.refinery.store.map.Cursor; | 8 | import tools.refinery.store.map.Cursor; |
9 | import tools.refinery.store.map.VersionedMap; | 9 | import tools.refinery.store.map.VersionedMap; |
10 | import tools.refinery.store.map.VersionedMapStore; | ||
11 | import tools.refinery.store.representation.Symbol; | 10 | import tools.refinery.store.representation.Symbol; |
12 | import tools.refinery.store.tuple.Tuple; | 11 | import tools.refinery.store.tuple.Tuple; |
13 | 12 | ||
@@ -16,9 +15,8 @@ import java.util.Objects; | |||
16 | class IndexedVersionedInterpretation<T> extends VersionedInterpretation<T> { | 15 | class 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; | |||
8 | import tools.refinery.store.adapter.AdapterUtils; | 8 | import tools.refinery.store.adapter.AdapterUtils; |
9 | import tools.refinery.store.adapter.ModelAdapter; | 9 | import tools.refinery.store.adapter.ModelAdapter; |
10 | import tools.refinery.store.map.DiffCursor; | 10 | import tools.refinery.store.map.DiffCursor; |
11 | import tools.refinery.store.map.Version; | ||
11 | import tools.refinery.store.model.*; | 12 | import tools.refinery.store.model.*; |
12 | import tools.refinery.store.representation.AnySymbol; | 13 | import tools.refinery.store.representation.AnySymbol; |
13 | import tools.refinery.store.representation.Symbol; | 14 | import tools.refinery.store.representation.Symbol; |
@@ -17,21 +18,21 @@ import java.util.*; | |||
17 | 18 | ||
18 | public class ModelImpl implements Model { | 19 | public 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; | |||
8 | import tools.refinery.store.adapter.AdapterUtils; | 8 | import tools.refinery.store.adapter.AdapterUtils; |
9 | import tools.refinery.store.adapter.ModelAdapterBuilder; | 9 | import tools.refinery.store.adapter.ModelAdapterBuilder; |
10 | import tools.refinery.store.map.VersionedMapStore; | 10 | import tools.refinery.store.map.VersionedMapStore; |
11 | import tools.refinery.store.map.VersionedMapStoreImpl; | 11 | import tools.refinery.store.map.VersionedMapStoreFactory; |
12 | import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; | ||
12 | import tools.refinery.store.model.ModelStore; | 13 | import tools.refinery.store.model.ModelStore; |
13 | import tools.refinery.store.model.ModelStoreBuilder; | 14 | import tools.refinery.store.model.ModelStoreBuilder; |
14 | import tools.refinery.store.model.ModelStoreConfiguration; | 15 | import tools.refinery.store.model.ModelStoreConfiguration; |
15 | import tools.refinery.store.model.TupleHashProvider; | ||
16 | import tools.refinery.store.representation.AnySymbol; | 16 | import tools.refinery.store.representation.AnySymbol; |
17 | import tools.refinery.store.representation.Symbol; | 17 | import tools.refinery.store.representation.Symbol; |
18 | import tools.refinery.store.tuple.Tuple; | 18 | import tools.refinery.store.tuple.Tuple; |
@@ -20,8 +20,8 @@ import tools.refinery.store.tuple.Tuple; | |||
20 | import java.util.*; | 20 | import java.util.*; |
21 | 21 | ||
22 | public class ModelStoreBuilderImpl implements ModelStoreBuilder { | 22 | public 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; | |||
8 | import tools.refinery.store.adapter.AdapterUtils; | 8 | import tools.refinery.store.adapter.AdapterUtils; |
9 | import tools.refinery.store.adapter.ModelStoreAdapter; | 9 | import tools.refinery.store.adapter.ModelStoreAdapter; |
10 | import tools.refinery.store.map.DiffCursor; | 10 | import tools.refinery.store.map.DiffCursor; |
11 | import tools.refinery.store.map.Version; | ||
11 | import tools.refinery.store.map.VersionedMapStore; | 12 | import tools.refinery.store.map.VersionedMapStore; |
12 | import tools.refinery.store.model.Model; | ||
13 | import tools.refinery.store.model.ModelDiffCursor; | 13 | import tools.refinery.store.model.ModelDiffCursor; |
14 | import tools.refinery.store.model.ModelStore; | 14 | import tools.refinery.store.model.ModelStore; |
15 | import tools.refinery.store.representation.AnySymbol; | 15 | import tools.refinery.store.representation.AnySymbol; |
@@ -18,10 +18,10 @@ import tools.refinery.store.tuple.Tuple; | |||
18 | import java.util.*; | 18 | import java.util.*; |
19 | 19 | ||
20 | public class ModelStoreImpl implements ModelStore { | 20 | public 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 | */ | ||
6 | package tools.refinery.store.model.internal; | ||
7 | |||
8 | import tools.refinery.store.map.Version; | ||
9 | |||
10 | import java.util.Arrays; | ||
11 | |||
12 | public 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 | ||
8 | import tools.refinery.store.map.Cursor; | 8 | import tools.refinery.store.map.Cursor; |
9 | import tools.refinery.store.map.VersionedMap; | 9 | import tools.refinery.store.map.VersionedMap; |
10 | import tools.refinery.store.map.VersionedMapStore; | ||
11 | import tools.refinery.store.representation.Symbol; | 10 | import tools.refinery.store.representation.Symbol; |
12 | import tools.refinery.store.tuple.Tuple; | 11 | import tools.refinery.store.tuple.Tuple; |
13 | 12 | ||
14 | class NullaryVersionedInterpretation<T> extends VersionedInterpretation<T> { | 13 | class 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; | |||
8 | import tools.refinery.store.map.Cursor; | 8 | import tools.refinery.store.map.Cursor; |
9 | import tools.refinery.store.map.Cursors; | 9 | import tools.refinery.store.map.Cursors; |
10 | import tools.refinery.store.map.VersionedMap; | 10 | import tools.refinery.store.map.VersionedMap; |
11 | import tools.refinery.store.map.VersionedMapStore; | ||
12 | import tools.refinery.store.representation.Symbol; | 11 | import tools.refinery.store.representation.Symbol; |
13 | import tools.refinery.store.tuple.Tuple; | 12 | import tools.refinery.store.tuple.Tuple; |
14 | 13 | ||
15 | import java.util.Objects; | 14 | import java.util.Objects; |
16 | 15 | ||
17 | class UnaryVersionedInterpretation<T> extends VersionedInterpretation<T> { | 16 | class 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 | */ |
6 | package tools.refinery.store.model.internal; | 6 | package tools.refinery.store.model.internal; |
7 | 7 | ||
8 | import tools.refinery.store.map.Cursor; | 8 | import tools.refinery.store.map.*; |
9 | import tools.refinery.store.map.DiffCursor; | ||
10 | import tools.refinery.store.map.VersionedMap; | ||
11 | import tools.refinery.store.map.VersionedMapStore; | ||
12 | import tools.refinery.store.map.internal.MapDiffCursor; | ||
13 | import tools.refinery.store.model.Interpretation; | 9 | import tools.refinery.store.model.Interpretation; |
14 | import tools.refinery.store.model.InterpretationListener; | 10 | import tools.refinery.store.model.InterpretationListener; |
15 | import tools.refinery.store.model.Model; | 11 | import tools.refinery.store.model.Model; |
16 | import tools.refinery.store.model.TupleHashProvider; | ||
17 | import tools.refinery.store.representation.AnySymbol; | 12 | import tools.refinery.store.representation.AnySymbol; |
18 | import tools.refinery.store.representation.Symbol; | 13 | import tools.refinery.store.representation.Symbol; |
19 | import tools.refinery.store.tuple.Tuple; | 14 | import tools.refinery.store.tuple.Tuple; |
@@ -24,16 +19,13 @@ import java.util.List; | |||
24 | public abstract class VersionedInterpretation<T> implements Interpretation<T> { | 19 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding; | ||
7 | |||
8 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding; | ||
7 | |||
8 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding; | ||
7 | |||
8 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding; | ||
7 | |||
8 | import org.eclipse.collections.api.set.primitive.IntSet; | ||
9 | import tools.refinery.store.model.Interpretation; | ||
10 | |||
11 | import java.util.List; | ||
12 | |||
13 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelAdapter; | ||
9 | import tools.refinery.store.statecoding.internal.StateCoderBuilderImpl; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelAdapterBuilder; | ||
9 | import tools.refinery.store.model.ModelStore; | ||
10 | import tools.refinery.store.representation.AnySymbol; | ||
11 | import tools.refinery.store.tuple.Tuple1; | ||
12 | |||
13 | import java.util.Arrays; | ||
14 | import java.util.Collection; | ||
15 | import java.util.List; | ||
16 | |||
17 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding; | ||
7 | |||
8 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelStoreAdapter; | ||
9 | import tools.refinery.store.map.Version; | ||
10 | import tools.refinery.store.model.Model; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding; | ||
7 | |||
8 | import org.eclipse.collections.api.set.primitive.IntSet; | ||
9 | import tools.refinery.store.model.AnyInterpretation; | ||
10 | |||
11 | import java.util.List; | ||
12 | |||
13 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding.internal; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelStoreAdapter; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.statecoding.StateCodeCalculator; | ||
11 | import tools.refinery.store.statecoding.StateCoderAdapter; | ||
12 | import tools.refinery.store.statecoding.StateCoderResult; | ||
13 | |||
14 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding.internal; | ||
7 | |||
8 | import org.eclipse.collections.api.factory.primitive.IntSets; | ||
9 | import org.eclipse.collections.api.set.primitive.MutableIntSet; | ||
10 | import tools.refinery.store.adapter.AbstractModelAdapterBuilder; | ||
11 | import tools.refinery.store.model.ModelStore; | ||
12 | import tools.refinery.store.representation.AnySymbol; | ||
13 | import tools.refinery.store.representation.Symbol; | ||
14 | import tools.refinery.store.statecoding.StateCodeCalculatorFactory; | ||
15 | import tools.refinery.store.statecoding.StateCoderBuilder; | ||
16 | import tools.refinery.store.statecoding.StateCoderStoreAdapter; | ||
17 | import tools.refinery.store.statecoding.StateEquivalenceChecker; | ||
18 | import tools.refinery.store.statecoding.neighbourhood.NeighbourhoodCalculator; | ||
19 | import tools.refinery.store.statecoding.stateequivalence.StateEquivalenceCheckerImpl; | ||
20 | import tools.refinery.store.tuple.Tuple1; | ||
21 | |||
22 | import java.util.HashSet; | ||
23 | import java.util.LinkedHashSet; | ||
24 | import java.util.Set; | ||
25 | |||
26 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding.internal; | ||
7 | |||
8 | import org.eclipse.collections.api.set.primitive.IntSet; | ||
9 | import tools.refinery.store.map.Version; | ||
10 | import tools.refinery.store.model.Model; | ||
11 | import tools.refinery.store.model.ModelStore; | ||
12 | import tools.refinery.store.representation.Symbol; | ||
13 | import tools.refinery.store.statecoding.StateCodeCalculatorFactory; | ||
14 | import tools.refinery.store.statecoding.StateCoderAdapter; | ||
15 | import tools.refinery.store.statecoding.StateCoderStoreAdapter; | ||
16 | import tools.refinery.store.statecoding.StateEquivalenceChecker; | ||
17 | |||
18 | import java.util.Collection; | ||
19 | import java.util.Objects; | ||
20 | |||
21 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding.neighbourhood; | ||
7 | |||
8 | import org.eclipse.collections.api.factory.primitive.IntLongMaps; | ||
9 | import org.eclipse.collections.api.map.primitive.MutableIntLongMap; | ||
10 | import org.eclipse.collections.api.set.primitive.IntSet; | ||
11 | import tools.refinery.store.model.AnyInterpretation; | ||
12 | import tools.refinery.store.model.Interpretation; | ||
13 | import tools.refinery.store.statecoding.ObjectCode; | ||
14 | import tools.refinery.store.tuple.Tuple; | ||
15 | import tools.refinery.store.tuple.Tuple0; | ||
16 | |||
17 | import java.util.*; | ||
18 | |||
19 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding.neighbourhood; | ||
7 | |||
8 | import org.eclipse.collections.api.factory.primitive.LongIntMaps; | ||
9 | import org.eclipse.collections.api.map.primitive.LongIntMap; | ||
10 | import org.eclipse.collections.api.map.primitive.MutableLongIntMap; | ||
11 | import org.eclipse.collections.api.set.primitive.IntSet; | ||
12 | import tools.refinery.store.map.Cursor; | ||
13 | import tools.refinery.store.model.AnyInterpretation; | ||
14 | import tools.refinery.store.model.Interpretation; | ||
15 | import tools.refinery.store.statecoding.StateCodeCalculator; | ||
16 | import tools.refinery.store.statecoding.StateCoderResult; | ||
17 | import tools.refinery.store.tuple.Tuple; | ||
18 | |||
19 | import java.util.List; | ||
20 | |||
21 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding.neighbourhood; | ||
7 | |||
8 | import org.eclipse.collections.api.set.primitive.IntSet; | ||
9 | import tools.refinery.store.map.Cursor; | ||
10 | import tools.refinery.store.model.Interpretation; | ||
11 | import tools.refinery.store.statecoding.ObjectCode; | ||
12 | import tools.refinery.store.statecoding.StateCodeCalculator; | ||
13 | import tools.refinery.store.statecoding.StateCoderResult; | ||
14 | import tools.refinery.store.tuple.Tuple; | ||
15 | import tools.refinery.store.tuple.Tuple0; | ||
16 | |||
17 | import java.util.List; | ||
18 | import java.util.Objects; | ||
19 | |||
20 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding.neighbourhood; | ||
7 | |||
8 | import tools.refinery.store.statecoding.ObjectCode; | ||
9 | |||
10 | import java.util.Arrays; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding.stateequivalence; | ||
7 | |||
8 | import org.eclipse.collections.api.factory.primitive.IntIntMaps; | ||
9 | import org.eclipse.collections.api.map.primitive.IntIntMap; | ||
10 | import org.eclipse.collections.api.set.primitive.IntSet; | ||
11 | |||
12 | import java.util.*; | ||
13 | |||
14 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding.stateequivalence; | ||
7 | |||
8 | import java.util.ArrayList; | ||
9 | import java.util.List; | ||
10 | |||
11 | class 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 | */ | ||
6 | package tools.refinery.store.statecoding.stateequivalence; | ||
7 | |||
8 | import org.eclipse.collections.api.map.primitive.IntIntMap; | ||
9 | import org.eclipse.collections.api.set.primitive.IntSet; | ||
10 | |||
11 | import java.util.List; | ||
12 | |||
13 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding.stateequivalence; | ||
7 | |||
8 | import org.eclipse.collections.api.map.primitive.IntIntMap; | ||
9 | import tools.refinery.store.statecoding.Morphism; | ||
10 | |||
11 | import java.util.List; | ||
12 | |||
13 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding.stateequivalence; | ||
7 | |||
8 | import org.eclipse.collections.api.factory.primitive.IntIntMaps; | ||
9 | import org.eclipse.collections.api.factory.primitive.IntSets; | ||
10 | import org.eclipse.collections.api.factory.primitive.LongObjectMaps; | ||
11 | import org.eclipse.collections.api.map.primitive.IntIntMap; | ||
12 | import org.eclipse.collections.api.map.primitive.MutableIntIntMap; | ||
13 | import org.eclipse.collections.api.map.primitive.MutableLongObjectMap; | ||
14 | import org.eclipse.collections.api.set.primitive.IntSet; | ||
15 | import org.eclipse.collections.api.set.primitive.MutableIntSet; | ||
16 | import tools.refinery.store.model.AnyInterpretation; | ||
17 | import tools.refinery.store.model.Interpretation; | ||
18 | import tools.refinery.store.statecoding.Morphism; | ||
19 | import tools.refinery.store.statecoding.ObjectCode; | ||
20 | import tools.refinery.store.statecoding.StateEquivalenceChecker; | ||
21 | import tools.refinery.store.tuple.Tuple; | ||
22 | |||
23 | import java.util.ArrayList; | ||
24 | import java.util.List; | ||
25 | import java.util.Objects; | ||
26 | |||
27 | public 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 | */ | ||
6 | package tools.refinery.store.statecoding.stateequivalence; | ||
7 | |||
8 | import org.eclipse.collections.api.factory.primitive.IntIntMaps; | ||
9 | import org.eclipse.collections.api.map.primitive.IntIntMap; | ||
10 | |||
11 | import java.util.List; | ||
12 | |||
13 | public 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 | } | ||