diff options
Diffstat (limited to 'store')
71 files changed, 5824 insertions, 0 deletions
diff --git a/store/build.gradle b/store/build.gradle new file mode 100644 index 00000000..6861b732 --- /dev/null +++ b/store/build.gradle | |||
@@ -0,0 +1,9 @@ | |||
1 | apply plugin: 'java-library' | ||
2 | apply from: "${rootDir}/gradle/java-common.gradle" | ||
3 | apply from: "${rootDir}/gradle/junit.gradle" | ||
4 | apply from: "${rootDir}/gradle/jmh.gradle" | ||
5 | |||
6 | dependencies { | ||
7 | implementation "org.eclipse.viatra:viatra-query-runtime:${viatraVersion}" | ||
8 | implementation "org.eclipse.emf:org.eclipse.emf.ecore:${ecoreVersion}" | ||
9 | } | ||
diff --git a/store/src/jmh/java/org/eclipse/viatra/solver/data/map/benchmarks/ImmutablePutBenchmark.java b/store/src/jmh/java/org/eclipse/viatra/solver/data/map/benchmarks/ImmutablePutBenchmark.java new file mode 100644 index 00000000..f0af443f --- /dev/null +++ b/store/src/jmh/java/org/eclipse/viatra/solver/data/map/benchmarks/ImmutablePutBenchmark.java | |||
@@ -0,0 +1,77 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.benchmarks; | ||
2 | |||
3 | import java.util.ArrayList; | ||
4 | import java.util.HashMap; | ||
5 | import java.util.concurrent.TimeUnit; | ||
6 | |||
7 | import org.openjdk.jmh.annotations.Benchmark; | ||
8 | import org.openjdk.jmh.annotations.BenchmarkMode; | ||
9 | import org.openjdk.jmh.annotations.Fork; | ||
10 | import org.openjdk.jmh.annotations.Measurement; | ||
11 | import org.openjdk.jmh.annotations.Mode; | ||
12 | import org.openjdk.jmh.annotations.OutputTimeUnit; | ||
13 | import org.openjdk.jmh.annotations.Warmup; | ||
14 | import org.openjdk.jmh.infra.Blackhole; | ||
15 | |||
16 | @Fork(1) | ||
17 | @BenchmarkMode(Mode.AverageTime) | ||
18 | @OutputTimeUnit(TimeUnit.MILLISECONDS) | ||
19 | @Measurement(time = 1, timeUnit = TimeUnit.SECONDS) | ||
20 | @Warmup(time = 1, timeUnit = TimeUnit.SECONDS) | ||
21 | public class ImmutablePutBenchmark { | ||
22 | @Benchmark | ||
23 | public void immutablePutBenchmark(ImmutablePutExecutionPlan executionPlan, Blackhole blackhole) { | ||
24 | var sut = executionPlan.createSut(); | ||
25 | for (int i = 0; i < executionPlan.nPut; i++) { | ||
26 | sut.put(executionPlan.nextKey(), executionPlan.nextValue()); | ||
27 | } | ||
28 | blackhole.consume(sut); | ||
29 | } | ||
30 | |||
31 | @Benchmark | ||
32 | public void immutablePutAndCommitBenchmark(ImmutablePutExecutionPlan executionPlan, Blackhole blackhole) { | ||
33 | var sut = executionPlan.createSut(); | ||
34 | for (int i = 0; i < executionPlan.nPut; i++) { | ||
35 | sut.put(executionPlan.nextKey(), executionPlan.nextValue()); | ||
36 | if (i % 10 == 0) { | ||
37 | blackhole.consume(sut.commit()); | ||
38 | } | ||
39 | } | ||
40 | blackhole.consume(sut); | ||
41 | } | ||
42 | |||
43 | @Benchmark | ||
44 | public void baselinePutBenchmark(ImmutablePutExecutionPlan executionPlan, Blackhole blackhole) { | ||
45 | var sut = new HashMap<Integer, String>(); | ||
46 | for (int i = 0; i < executionPlan.nPut; i++) { | ||
47 | var key = executionPlan.nextKey(); | ||
48 | var value = executionPlan.nextValue(); | ||
49 | if (executionPlan.isDefault(value)) { | ||
50 | sut.remove(key); | ||
51 | } else { | ||
52 | sut.put(key, value); | ||
53 | } | ||
54 | } | ||
55 | blackhole.consume(sut); | ||
56 | } | ||
57 | |||
58 | @Benchmark | ||
59 | public void baselinePutAndCommitBenchmark(ImmutablePutExecutionPlan executionPlan, Blackhole blackhole) { | ||
60 | var sut = new HashMap<Integer, String>(); | ||
61 | var store = new ArrayList<HashMap<Integer, String>>(); | ||
62 | for (int i = 0; i < executionPlan.nPut; i++) { | ||
63 | var key = executionPlan.nextKey(); | ||
64 | var value = executionPlan.nextValue(); | ||
65 | if (executionPlan.isDefault(value)) { | ||
66 | sut.remove(key); | ||
67 | } else { | ||
68 | sut.put(key, value); | ||
69 | } | ||
70 | if (i % 10 == 0) { | ||
71 | store.add(new HashMap<>(sut)); | ||
72 | } | ||
73 | } | ||
74 | blackhole.consume(sut); | ||
75 | blackhole.consume(store); | ||
76 | } | ||
77 | } | ||
diff --git a/store/src/jmh/java/org/eclipse/viatra/solver/data/map/benchmarks/ImmutablePutExecutionPlan.java b/store/src/jmh/java/org/eclipse/viatra/solver/data/map/benchmarks/ImmutablePutExecutionPlan.java new file mode 100644 index 00000000..6cac74af --- /dev/null +++ b/store/src/jmh/java/org/eclipse/viatra/solver/data/map/benchmarks/ImmutablePutExecutionPlan.java | |||
@@ -0,0 +1,56 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.benchmarks; | ||
2 | |||
3 | import java.util.Random; | ||
4 | |||
5 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
6 | import org.eclipse.viatra.solver.data.map.VersionedMapStore; | ||
7 | import org.eclipse.viatra.solver.data.map.VersionedMapStoreImpl; | ||
8 | import org.eclipse.viatra.solver.data.map.internal.VersionedMapImpl; | ||
9 | import org.eclipse.viatra.solver.data.map.tests.utils.MapTestEnvironment; | ||
10 | import org.openjdk.jmh.annotations.Level; | ||
11 | import org.openjdk.jmh.annotations.Param; | ||
12 | import org.openjdk.jmh.annotations.Scope; | ||
13 | import org.openjdk.jmh.annotations.Setup; | ||
14 | import org.openjdk.jmh.annotations.State; | ||
15 | |||
16 | @State(Scope.Benchmark) | ||
17 | public class ImmutablePutExecutionPlan { | ||
18 | |||
19 | @Param({ "100", "10000" }) | ||
20 | public int nPut; | ||
21 | |||
22 | @Param({ "32", "1000", "100000" }) | ||
23 | public int nKeys; | ||
24 | |||
25 | @Param({ "2", "3" }) | ||
26 | public int nValues; | ||
27 | |||
28 | private Random random; | ||
29 | |||
30 | private String[] values; | ||
31 | |||
32 | private ContinousHashProvider<Integer> hashProvider = MapTestEnvironment.prepareHashProvider(false); | ||
33 | |||
34 | @Setup(Level.Trial) | ||
35 | public void setUpTrial() { | ||
36 | random = new Random(); | ||
37 | values = MapTestEnvironment.prepareValues(nValues); | ||
38 | } | ||
39 | |||
40 | public VersionedMapImpl<Integer, String> createSut() { | ||
41 | VersionedMapStore<Integer, String> store = new VersionedMapStoreImpl<Integer, String>(hashProvider, values[0]); | ||
42 | return (VersionedMapImpl<Integer, String>) store.createMap(); | ||
43 | } | ||
44 | |||
45 | public Integer nextKey() { | ||
46 | return random.nextInt(nKeys); | ||
47 | } | ||
48 | |||
49 | public boolean isDefault(String value) { | ||
50 | return value == values[0]; | ||
51 | } | ||
52 | |||
53 | public String nextValue() { | ||
54 | return values[random.nextInt(nValues)]; | ||
55 | } | ||
56 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/ContinousHashProvider.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/ContinousHashProvider.java new file mode 100644 index 00000000..dd64f901 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/ContinousHashProvider.java | |||
@@ -0,0 +1,69 @@ | |||
1 | package org.eclipse.viatra.solver.data.map; | ||
2 | |||
3 | import org.eclipse.viatra.solver.data.map.internal.Node; | ||
4 | |||
5 | /** | ||
6 | * A class representing an equivalence relation for a type {@code K} with a | ||
7 | * continuous hash function. | ||
8 | * | ||
9 | * @author Oszkar Semerath | ||
10 | * | ||
11 | * @param <K> Target java type. | ||
12 | */ | ||
13 | public interface ContinousHashProvider<K> { | ||
14 | public static final int EFFECTIVE_BITS = Node.EFFECTIVE_BITS; | ||
15 | public static final int EFFECTIVE_BIT_MASK = (1 << (EFFECTIVE_BITS)) - 1; | ||
16 | |||
17 | /** | ||
18 | * Maximal practical depth for differentiating keys. If two keys have the same | ||
19 | * hash code until that depth, the algorithm can stop. | ||
20 | */ | ||
21 | public static final int MAX_PRACTICAL_DEPTH = 500; | ||
22 | |||
23 | /** | ||
24 | * Provides a hash code for a object {@code key} with a given {@code index}. It | ||
25 | * has the following contracts: | ||
26 | * <ul> | ||
27 | * <li>If {@link #equals}{@code (key1,key2)}, then | ||
28 | * {@code getHash(key1, index) == getHash(key2, index)} for all values of | ||
29 | * {@code index}.</li> | ||
30 | * <li>If {@code getHash(key1,index) == getHash(key2, index)} for all values of | ||
31 | * {@code index}, then {@link #equals}{@code (key1, key2)}</li> | ||
32 | * <li>In current implementation, we use only the least significant | ||
33 | * {@link #EFFECTIVE_BITS} | ||
34 | * </ul> | ||
35 | * Check {@link #equals} for further details. | ||
36 | * | ||
37 | * @param key The target data object. | ||
38 | * @param index The depth of the the hash code. Needs to be non-negative. | ||
39 | * @return A hash code. | ||
40 | */ | ||
41 | public int getHash(K key, int index); | ||
42 | |||
43 | public default int getEffectiveHash(K key, int index) { | ||
44 | return getHash(key, index) & EFFECTIVE_BIT_MASK; | ||
45 | } | ||
46 | |||
47 | public default int compare(K key1, K key2) { | ||
48 | if (key1.equals(key2)) { | ||
49 | return 0; | ||
50 | } else { | ||
51 | for (int i = 0; i < ContinousHashProvider.MAX_PRACTICAL_DEPTH; i++) { | ||
52 | int hash1 = getEffectiveHash(key1, i); | ||
53 | int hash2 = getEffectiveHash(key2, i); | ||
54 | for(int j = 0; j<Integer.SIZE/Node.BRANCHING_FACTOR_BITS; j++) { | ||
55 | final int factorMask = (1<<Node.BRANCHING_FACTOR_BITS)-1; | ||
56 | int hashFragment1 = (hash1>>>j*Node.BRANCHING_FACTOR_BITS) & factorMask; | ||
57 | int hashFragment2 = (hash2>>>j*Node.BRANCHING_FACTOR_BITS) & factorMask; | ||
58 | var result = Integer.compare(hashFragment1, hashFragment2); | ||
59 | if (result != 0) { | ||
60 | return result; | ||
61 | } | ||
62 | } | ||
63 | } | ||
64 | throw new IllegalArgumentException("Two different keys (" + key1 + " and " + key2 | ||
65 | + ") have the same hashcode over the practical depth limitation (" | ||
66 | + ContinousHashProvider.MAX_PRACTICAL_DEPTH + ")!"); | ||
67 | } | ||
68 | } | ||
69 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/Cursor.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/Cursor.java new file mode 100644 index 00000000..e45b1f20 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/Cursor.java | |||
@@ -0,0 +1,14 @@ | |||
1 | package org.eclipse.viatra.solver.data.map; | ||
2 | |||
3 | import java.util.List; | ||
4 | |||
5 | public interface Cursor<K,V> { | ||
6 | public K getKey(); | ||
7 | public V getValue(); | ||
8 | public boolean isTerminated(); | ||
9 | public boolean move(); | ||
10 | public boolean isDirty(); | ||
11 | |||
12 | @SuppressWarnings("squid:S1452") | ||
13 | public List<VersionedMap<?,?>> getDependingMaps(); | ||
14 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/CursorAsIterator.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/CursorAsIterator.java new file mode 100644 index 00000000..b29b3119 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/CursorAsIterator.java | |||
@@ -0,0 +1,57 @@ | |||
1 | package org.eclipse.viatra.solver.data.map; | ||
2 | |||
3 | import java.util.Iterator; | ||
4 | import java.util.NoSuchElementException; | ||
5 | import java.util.function.BiFunction; | ||
6 | import java.util.function.BiPredicate; | ||
7 | |||
8 | public class CursorAsIterator<K,V,D> implements Iterator<D> { | ||
9 | private final Cursor<K, V> internal; | ||
10 | private final BiFunction<K, V, D> entryTransformation; | ||
11 | private final BiPredicate<K,V> filtering; | ||
12 | |||
13 | D lastValidElement; | ||
14 | |||
15 | public CursorAsIterator(Cursor<K, V> internal, BiFunction<K, V, D> entryTransformation, BiPredicate<K,V> filtering) { | ||
16 | this.internal = internal; | ||
17 | this.entryTransformation = entryTransformation; | ||
18 | this.filtering = filtering; | ||
19 | |||
20 | moveToNext(); | ||
21 | } | ||
22 | public CursorAsIterator(Cursor<K, V> internal, BiFunction<K, V, D> entryTransformation) { | ||
23 | this.internal = internal; | ||
24 | this.entryTransformation = entryTransformation; | ||
25 | this.filtering = ((k,v)->true); | ||
26 | |||
27 | moveToNext(); | ||
28 | } | ||
29 | |||
30 | private void moveToNext() { | ||
31 | internal.move(); | ||
32 | while(!internal.isTerminated() && !filtering.test(internal.getKey(), internal.getValue())) { | ||
33 | internal.move(); | ||
34 | } | ||
35 | if(!internal.isTerminated()) { | ||
36 | lastValidElement = entryTransformation.apply(internal.getKey(), internal.getValue()); | ||
37 | } | ||
38 | } | ||
39 | |||
40 | |||
41 | @Override | ||
42 | public boolean hasNext() { | ||
43 | return !internal.isTerminated(); | ||
44 | } | ||
45 | @Override | ||
46 | public D next() { | ||
47 | if(hasNext()) { | ||
48 | D last = lastValidElement; | ||
49 | moveToNext(); | ||
50 | return last; | ||
51 | } else { | ||
52 | throw new NoSuchElementException(); | ||
53 | } | ||
54 | |||
55 | } | ||
56 | |||
57 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/DiffCursor.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/DiffCursor.java new file mode 100644 index 00000000..f0af1436 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/DiffCursor.java | |||
@@ -0,0 +1,6 @@ | |||
1 | package org.eclipse.viatra.solver.data.map; | ||
2 | |||
3 | public interface DiffCursor<K, V> extends Cursor<K,V> { | ||
4 | public V getFromValue(); | ||
5 | public V getToValue(); | ||
6 | } \ No newline at end of file | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/MapAsIterable.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/MapAsIterable.java new file mode 100644 index 00000000..22b5e6c1 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/MapAsIterable.java | |||
@@ -0,0 +1,26 @@ | |||
1 | package org.eclipse.viatra.solver.data.map; | ||
2 | |||
3 | import java.util.Iterator; | ||
4 | import java.util.function.BiFunction; | ||
5 | import java.util.function.BiPredicate; | ||
6 | |||
7 | public class MapAsIterable<K,V,D> implements Iterable<D> { | ||
8 | private final VersionedMap<K, V> internal; | ||
9 | private final BiFunction<K, V, D> entryTransformation; | ||
10 | private final BiPredicate<K,V> filtering; | ||
11 | |||
12 | public MapAsIterable(VersionedMap<K, V> internal, BiFunction<K, V, D> entryTransformation, BiPredicate<K,V> filtering) { | ||
13 | this.internal = internal; | ||
14 | this.entryTransformation = entryTransformation; | ||
15 | this.filtering = filtering; | ||
16 | } | ||
17 | public MapAsIterable(VersionedMap<K, V> internal, BiFunction<K, V, D> entryTransformation) { | ||
18 | this.internal = internal; | ||
19 | this.entryTransformation = entryTransformation; | ||
20 | this.filtering = ((k,v)->true); | ||
21 | } | ||
22 | @Override | ||
23 | public Iterator<D> iterator() { | ||
24 | return new CursorAsIterator<>(internal.getAll(), entryTransformation, filtering); | ||
25 | } | ||
26 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/Versioned.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/Versioned.java new file mode 100644 index 00000000..e46be237 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/Versioned.java | |||
@@ -0,0 +1,7 @@ | |||
1 | package org.eclipse.viatra.solver.data.map; | ||
2 | |||
3 | public interface Versioned { | ||
4 | public long commit(); | ||
5 | //maybe revert()? | ||
6 | public void restore(long state); | ||
7 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/VersionedMap.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/VersionedMap.java new file mode 100644 index 00000000..3a35b9f0 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/VersionedMap.java | |||
@@ -0,0 +1,13 @@ | |||
1 | package org.eclipse.viatra.solver.data.map; | ||
2 | |||
3 | public interface VersionedMap<K,V> extends Versioned{ | ||
4 | public V get(K key); | ||
5 | public Cursor<K,V> getAll(); | ||
6 | |||
7 | public V put(K key, V value); | ||
8 | public void putAll(Cursor<K,V> cursor); | ||
9 | |||
10 | public long getSize(); | ||
11 | |||
12 | public DiffCursor<K,V> getDiffCursor(long state); | ||
13 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/VersionedMapStore.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/VersionedMapStore.java new file mode 100644 index 00000000..0ff0773f --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/VersionedMapStore.java | |||
@@ -0,0 +1,14 @@ | |||
1 | package org.eclipse.viatra.solver.data.map; | ||
2 | |||
3 | import java.util.Set; | ||
4 | |||
5 | public interface VersionedMapStore<K, V> { | ||
6 | |||
7 | public VersionedMap<K, V> createMap(); | ||
8 | |||
9 | public VersionedMap<K, V> createMap(long state); | ||
10 | |||
11 | public Set<Long> getStates(); | ||
12 | |||
13 | public DiffCursor<K,V> getDiffCursor(long fromState, long toState); | ||
14 | } \ No newline at end of file | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/VersionedMapStoreConfiguration.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/VersionedMapStoreConfiguration.java new file mode 100644 index 00000000..be768e98 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/VersionedMapStoreConfiguration.java | |||
@@ -0,0 +1,48 @@ | |||
1 | package org.eclipse.viatra.solver.data.map; | ||
2 | |||
3 | public class VersionedMapStoreConfiguration { | ||
4 | |||
5 | public VersionedMapStoreConfiguration() { | ||
6 | |||
7 | } | ||
8 | public VersionedMapStoreConfiguration(boolean immutableWhenCommiting, boolean sharedNodeCacheInStore, | ||
9 | boolean sharedNodeCacheInStoreGroups) { | ||
10 | super(); | ||
11 | this.immutableWhenCommiting = immutableWhenCommiting; | ||
12 | this.sharedNodeCacheInStore = sharedNodeCacheInStore; | ||
13 | this.sharedNodeCacheInStoreGroups = sharedNodeCacheInStoreGroups; | ||
14 | } | ||
15 | |||
16 | /** | ||
17 | * If true root is replaced with immutable node when committed. Frees up memory | ||
18 | * by releasing immutable nodes, but it may decrease performance by recreating | ||
19 | * immutable nodes upon changes (some evidence). | ||
20 | */ | ||
21 | private boolean immutableWhenCommiting = true; | ||
22 | public boolean isImmutableWhenCommiting() { | ||
23 | return immutableWhenCommiting; | ||
24 | } | ||
25 | |||
26 | /** | ||
27 | * If true, all subnodes are cached within a {@link VersionedMapStore}. It | ||
28 | * decreases the memory requirements. It may increase performance by discovering | ||
29 | * existing immutable copy of a node (some evidence). Additional overhead may | ||
30 | * decrease performance (no example found). The option permits the efficient | ||
31 | * implementation of version deletion. | ||
32 | */ | ||
33 | private boolean sharedNodeCacheInStore = true; | ||
34 | public boolean isSharedNodeCacheInStore() { | ||
35 | return sharedNodeCacheInStore; | ||
36 | } | ||
37 | |||
38 | /** | ||
39 | * If true, all subnodes are cached within a group of | ||
40 | * {@link VersionedMapStoreImpl#createSharedVersionedMapStores(int, ContinousHashProvider, Object, VersionedMapStoreConfiguration)}. | ||
41 | * If {@link VersionedMapStoreConfiguration#sharedNodeCacheInStore} is | ||
42 | * <code>false</code>, then it has currently no impact. | ||
43 | */ | ||
44 | private boolean sharedNodeCacheInStoreGroups = true; | ||
45 | public boolean isSharedNodeCacheInStoreGroups() { | ||
46 | return sharedNodeCacheInStoreGroups; | ||
47 | } | ||
48 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/VersionedMapStoreImpl.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/VersionedMapStoreImpl.java new file mode 100644 index 00000000..83d0e8cd --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/VersionedMapStoreImpl.java | |||
@@ -0,0 +1,135 @@ | |||
1 | package org.eclipse.viatra.solver.data.map; | ||
2 | |||
3 | import java.util.ArrayList; | ||
4 | import java.util.Arrays; | ||
5 | import java.util.Collections; | ||
6 | import java.util.HashMap; | ||
7 | import java.util.HashSet; | ||
8 | import java.util.List; | ||
9 | import java.util.Map; | ||
10 | import java.util.Set; | ||
11 | |||
12 | import org.eclipse.viatra.solver.data.map.internal.ImmutableNode; | ||
13 | import org.eclipse.viatra.solver.data.map.internal.MapDiffCursor; | ||
14 | import org.eclipse.viatra.solver.data.map.internal.Node; | ||
15 | import org.eclipse.viatra.solver.data.map.internal.VersionedMapImpl; | ||
16 | |||
17 | public class VersionedMapStoreImpl<K, V> implements VersionedMapStore<K, V> { | ||
18 | // Configuration | ||
19 | private final boolean immutableWhenCommiting; | ||
20 | |||
21 | // Static data | ||
22 | protected final ContinousHashProvider<K> hashProvider; | ||
23 | protected final V defaultValue; | ||
24 | |||
25 | // Dynamic data | ||
26 | protected final Map<Long, ImmutableNode<K, V>> states = new HashMap<>(); | ||
27 | protected final Map<Node<K, V>, ImmutableNode<K, V>> nodeCache; | ||
28 | protected long nextID = 0; | ||
29 | |||
30 | public VersionedMapStoreImpl(ContinousHashProvider<K> hashProvider, V defaultValue, | ||
31 | VersionedMapStoreConfiguration config) { | ||
32 | this.immutableWhenCommiting = config.isImmutableWhenCommiting(); | ||
33 | this.hashProvider = hashProvider; | ||
34 | this.defaultValue = defaultValue; | ||
35 | if (config.isSharedNodeCacheInStore()) { | ||
36 | nodeCache = new HashMap<>(); | ||
37 | } else { | ||
38 | nodeCache = null; | ||
39 | } | ||
40 | } | ||
41 | |||
42 | private VersionedMapStoreImpl(ContinousHashProvider<K> hashProvider, V defaultValue, | ||
43 | Map<Node<K, V>, ImmutableNode<K, V>> nodeCache, VersionedMapStoreConfiguration config) { | ||
44 | this.immutableWhenCommiting = config.isImmutableWhenCommiting(); | ||
45 | this.hashProvider = hashProvider; | ||
46 | this.defaultValue = defaultValue; | ||
47 | this.nodeCache = nodeCache; | ||
48 | } | ||
49 | |||
50 | public VersionedMapStoreImpl(ContinousHashProvider<K> hashProvider, V defaultValue) { | ||
51 | this(hashProvider, defaultValue, new VersionedMapStoreConfiguration()); | ||
52 | } | ||
53 | |||
54 | public static <K, V> List<VersionedMapStore<K, V>> createSharedVersionedMapStores(int amount, | ||
55 | ContinousHashProvider<K> hashProvider, V defaultValue, | ||
56 | VersionedMapStoreConfiguration config) { | ||
57 | List<VersionedMapStore<K, V>> result = new ArrayList<>(amount); | ||
58 | if (config.isSharedNodeCacheInStoreGroups()) { | ||
59 | Map<Node<K, V>, ImmutableNode<K, V>> nodeCache; | ||
60 | if (config.isSharedNodeCacheInStore()) { | ||
61 | nodeCache = new HashMap<>(); | ||
62 | } else { | ||
63 | nodeCache = null; | ||
64 | } | ||
65 | for (int i = 0; i < amount; i++) { | ||
66 | result.add(new VersionedMapStoreImpl<>(hashProvider, defaultValue, nodeCache, config)); | ||
67 | } | ||
68 | } else { | ||
69 | for (int i = 0; i < amount; i++) { | ||
70 | result.add(new VersionedMapStoreImpl<>(hashProvider, defaultValue, config)); | ||
71 | } | ||
72 | } | ||
73 | return result; | ||
74 | } | ||
75 | |||
76 | public static <K, V> List<VersionedMapStore<K, V>> createSharedVersionedMapStores(int amount, | ||
77 | ContinousHashProvider<K> hashProvider, V defaultValue) { | ||
78 | return createSharedVersionedMapStores(amount, hashProvider, defaultValue, new VersionedMapStoreConfiguration()); | ||
79 | } | ||
80 | |||
81 | @Override | ||
82 | public synchronized Set<Long> getStates() { | ||
83 | return new HashSet<>(states.keySet()); | ||
84 | } | ||
85 | |||
86 | @Override | ||
87 | public VersionedMap<K, V> createMap() { | ||
88 | return new VersionedMapImpl<>(this, hashProvider, defaultValue); | ||
89 | } | ||
90 | |||
91 | @Override | ||
92 | public VersionedMap<K, V> createMap(long state) { | ||
93 | ImmutableNode<K, V> data = revert(state); | ||
94 | return new VersionedMapImpl<>(this, hashProvider, defaultValue, data); | ||
95 | } | ||
96 | |||
97 | |||
98 | public synchronized ImmutableNode<K, V> revert(long state) { | ||
99 | if (states.containsKey(state)) { | ||
100 | return states.get(state); | ||
101 | } else { | ||
102 | ArrayList<Long> existingKeys = new ArrayList<>(states.keySet()); | ||
103 | Collections.sort(existingKeys); | ||
104 | throw new IllegalArgumentException("Store does not contain state " + state + "! Avaliable states: " | ||
105 | + Arrays.toString(existingKeys.toArray())); | ||
106 | } | ||
107 | } | ||
108 | |||
109 | public synchronized long commit(Node<K, V> data, VersionedMapImpl<K, V> mapToUpdateRoot) { | ||
110 | ImmutableNode<K, V> immutable; | ||
111 | if (data != null) { | ||
112 | immutable = data.toImmutable(this.nodeCache); | ||
113 | } else { | ||
114 | immutable = null; | ||
115 | } | ||
116 | |||
117 | if (nextID == Long.MAX_VALUE) | ||
118 | throw new IllegalStateException("Map store run out of Id-s"); | ||
119 | long id = nextID++; | ||
120 | this.states.put(id, immutable); | ||
121 | if (this.immutableWhenCommiting) { | ||
122 | mapToUpdateRoot.setRoot(immutable); | ||
123 | } | ||
124 | return id; | ||
125 | } | ||
126 | |||
127 | @Override | ||
128 | public DiffCursor<K, V> getDiffCursor(long fromState, long toState) { | ||
129 | VersionedMap<K, V> map1 = createMap(fromState); | ||
130 | VersionedMap<K, V> map2 = createMap(toState); | ||
131 | Cursor<K, V> cursor1 = map1.getAll(); | ||
132 | Cursor<K, V> cursor2 = map2.getAll(); | ||
133 | return new MapDiffCursor<>(this.hashProvider, this.defaultValue, cursor1, cursor2); | ||
134 | } | ||
135 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/ImmutableNode.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/ImmutableNode.java new file mode 100644 index 00000000..04a9b19a --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/ImmutableNode.java | |||
@@ -0,0 +1,379 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.internal; | ||
2 | |||
3 | import java.util.Arrays; | ||
4 | import java.util.Map; | ||
5 | |||
6 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
7 | |||
8 | public class ImmutableNode<K, V> extends Node<K, V> { | ||
9 | /** | ||
10 | * Bitmap defining the stored key and values. | ||
11 | */ | ||
12 | final int dataMap; | ||
13 | /** | ||
14 | * Bitmap defining the positions of further nodes. | ||
15 | */ | ||
16 | final int nodeMap; | ||
17 | /** | ||
18 | * Stores Keys, Values, and subnodes. Structure: (K,V)*,NODE; NODES are stored backwards. | ||
19 | */ | ||
20 | final Object[] content; | ||
21 | |||
22 | /** | ||
23 | * Hash code derived from immutable hash code | ||
24 | */ | ||
25 | final int precalculatedHash; | ||
26 | |||
27 | private ImmutableNode(int dataMap, int nodeMap, Object[] content, int precalculatedHash) { | ||
28 | super(); | ||
29 | this.dataMap = dataMap; | ||
30 | this.nodeMap = nodeMap; | ||
31 | this.content = content; | ||
32 | this.precalculatedHash = precalculatedHash; | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Constructor that copies a mutable node to an immutable. | ||
37 | * | ||
38 | * @param node A mutable node. | ||
39 | * @param cache A cache of existing immutable nodes. It can be used to search | ||
40 | * and place reference immutable nodes. It can be null, if no cache | ||
41 | * available. | ||
42 | * @return an immutable version of the input node. | ||
43 | */ | ||
44 | @SuppressWarnings("unchecked") | ||
45 | static <K,V> ImmutableNode<K,V> constructImmutable(MutableNode<K,V> node, Map<Node<K, V>, ImmutableNode<K, V>> cache) { | ||
46 | // 1. try to return from cache | ||
47 | if(cache != null) { | ||
48 | ImmutableNode<K, V> cachedResult = cache.get(node); | ||
49 | if(cachedResult != null) { | ||
50 | // 1.1 Already cached, return from cache. | ||
51 | return cachedResult; | ||
52 | } | ||
53 | } | ||
54 | |||
55 | // 2. otherwise construct a new ImmutableNode | ||
56 | int size = 0; | ||
57 | for(int i = 0; i<node.content.length; i++) { | ||
58 | if(node.content[i]!=null) { | ||
59 | size++; | ||
60 | } | ||
61 | } | ||
62 | |||
63 | int datas = 0; | ||
64 | int nodes = 0; | ||
65 | int resultDataMap = 0; | ||
66 | int resultNodeMap = 0; | ||
67 | final Object[] resultContent = new Object[size]; | ||
68 | int bitposition = 1; | ||
69 | for(int i = 0; i<FACTOR; i++) { | ||
70 | Object key = node.content[i*2]; | ||
71 | if(key != null) { | ||
72 | resultDataMap |= bitposition; | ||
73 | resultContent[datas*2] = key; | ||
74 | resultContent[datas*2+1] = node.content[i*2+1]; | ||
75 | datas++; | ||
76 | } else { | ||
77 | Node<K,V> subnode = (Node<K, V>) node.content[i*2+1]; | ||
78 | if(subnode != null) { | ||
79 | ImmutableNode<K, V> immutableSubnode = subnode.toImmutable(cache); | ||
80 | resultNodeMap |=bitposition; | ||
81 | resultContent[size-1-nodes] = immutableSubnode; | ||
82 | nodes++; | ||
83 | } | ||
84 | } | ||
85 | bitposition<<=1; | ||
86 | } | ||
87 | final int resultHash = node.hashCode(); | ||
88 | ImmutableNode<K,V> newImmutable = new ImmutableNode<>(resultDataMap, resultNodeMap, resultContent, resultHash); | ||
89 | |||
90 | // 3. save new immutable. | ||
91 | if(cache != null) { | ||
92 | cache.put(newImmutable, newImmutable); | ||
93 | } | ||
94 | return newImmutable; | ||
95 | } | ||
96 | |||
97 | private int index(int bitmap, int bitpos) { | ||
98 | return Integer.bitCount(bitmap & (bitpos-1)); | ||
99 | } | ||
100 | |||
101 | @SuppressWarnings("unchecked") | ||
102 | @Override | ||
103 | public V getValue(K key, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) { | ||
104 | int selectedHashFragment = hashFragment(hash,shiftDepth(depth)); | ||
105 | int bitposition = 1 << selectedHashFragment; | ||
106 | // If the key is stored as a data | ||
107 | if((dataMap & bitposition) != 0) { | ||
108 | int keyIndex = 2*index(dataMap, bitposition); | ||
109 | K keyCandidate = (K) content[keyIndex]; | ||
110 | if(keyCandidate.equals(key)) { | ||
111 | return (V) content[keyIndex+1]; | ||
112 | } else { | ||
113 | return defaultValue; | ||
114 | } | ||
115 | } | ||
116 | // the key is stored as a node | ||
117 | else if((nodeMap & bitposition) != 0) { | ||
118 | int keyIndex = content.length-1-index(nodeMap, bitposition); | ||
119 | ImmutableNode<K,V> subNode = (ImmutableNode<K,V>) content[keyIndex]; | ||
120 | int newDepth = depth+1; | ||
121 | int newHash = newHash(hashProvider, key, hash, newDepth); | ||
122 | return subNode.getValue(key, hashProvider, defaultValue, newHash, newDepth); | ||
123 | } | ||
124 | // the key is not stored at all | ||
125 | else { | ||
126 | return defaultValue; | ||
127 | } | ||
128 | } | ||
129 | |||
130 | @SuppressWarnings("unchecked") | ||
131 | @Override | ||
132 | public Node<K,V> putValue(K key, V value, OldValueBox<V> oldValue, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) { | ||
133 | int selectedHashFragment = hashFragment(hash,shiftDepth(depth)); | ||
134 | int bitposition = 1 << selectedHashFragment; | ||
135 | if((dataMap & bitposition) != 0) { | ||
136 | int keyIndex = 2*index(dataMap, bitposition); | ||
137 | K keyCandidate = (K) content[keyIndex]; | ||
138 | if(keyCandidate.equals(key)) { | ||
139 | if(value == defaultValue) { | ||
140 | // delete | ||
141 | MutableNode<K, V> mutable = this.toMutable(); | ||
142 | return mutable.removeEntry(selectedHashFragment,oldValue); | ||
143 | } else if(value == content[keyIndex+1]) { | ||
144 | // dont change | ||
145 | oldValue.setOldValue(value); | ||
146 | return this; | ||
147 | } else { | ||
148 | // update existing value | ||
149 | MutableNode<K, V> mutable = this.toMutable(); | ||
150 | return mutable.updateValue(value, oldValue, selectedHashFragment); | ||
151 | } | ||
152 | } else { | ||
153 | if(value == defaultValue) { | ||
154 | // dont change | ||
155 | oldValue.setOldValue(defaultValue); | ||
156 | return this; | ||
157 | } else { | ||
158 | // add new key + value | ||
159 | MutableNode<K, V> mutable = this.toMutable(); | ||
160 | return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth); | ||
161 | } | ||
162 | } | ||
163 | } else if((nodeMap & bitposition)!=0) { | ||
164 | |||
165 | int keyIndex = content.length-1-index(nodeMap, bitposition); | ||
166 | ImmutableNode<K,V> subNode = (ImmutableNode<K,V>) content[keyIndex]; | ||
167 | int newDepth = depth+1; | ||
168 | int newHash = newHash(hashProvider, key, hash, newDepth); | ||
169 | Node<K,V> newsubNode = subNode.putValue(key, value, oldValue, hashProvider, defaultValue, newHash, newDepth); | ||
170 | |||
171 | if(subNode == newsubNode) { | ||
172 | // nothing changed | ||
173 | return this; | ||
174 | } else { | ||
175 | MutableNode<K, V> mutable = toMutable(); | ||
176 | return mutable.updateWithSubNode(selectedHashFragment, newsubNode, value.equals(defaultValue)); | ||
177 | } | ||
178 | } else { | ||
179 | // add new key + value | ||
180 | MutableNode<K, V> mutable = this.toMutable(); | ||
181 | return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth); | ||
182 | } | ||
183 | } | ||
184 | |||
185 | |||
186 | @SuppressWarnings("unchecked") | ||
187 | @Override | ||
188 | public long getSize() { | ||
189 | int result = Integer.bitCount(this.dataMap); | ||
190 | for(int subnodeIndex = 0; subnodeIndex < Integer.bitCount(this.nodeMap); subnodeIndex++) { | ||
191 | ImmutableNode<K,V> subnode = | ||
192 | (ImmutableNode<K, V>) this.content[this.content.length-1-subnodeIndex]; | ||
193 | result += subnode.getSize(); | ||
194 | } | ||
195 | return result; | ||
196 | } | ||
197 | |||
198 | @Override | ||
199 | protected MutableNode<K,V> toMutable() { | ||
200 | return new MutableNode<>(this); | ||
201 | } | ||
202 | |||
203 | @Override | ||
204 | public ImmutableNode<K, V> toImmutable( | ||
205 | Map<Node<K, V>, ImmutableNode<K, V>> cache) { | ||
206 | return this; | ||
207 | } | ||
208 | |||
209 | @Override | ||
210 | protected MutableNode<K, V> isMutable() { | ||
211 | return null; | ||
212 | } | ||
213 | |||
214 | @SuppressWarnings("unchecked") | ||
215 | @Override | ||
216 | boolean moveToNext(MapCursor<K, V> cursor) { | ||
217 | // 1. try to move to data | ||
218 | int datas = Integer.bitCount(this.dataMap); | ||
219 | if(cursor.dataIndex != MapCursor.INDEX_FINISH) { | ||
220 | int newDataIndex = cursor.dataIndex + 1; | ||
221 | if(newDataIndex < datas) { | ||
222 | cursor.dataIndex = newDataIndex; | ||
223 | cursor.key = (K) this.content[newDataIndex*2]; | ||
224 | cursor.value = (V) this.content[newDataIndex*2+1]; | ||
225 | return true; | ||
226 | } else { | ||
227 | cursor.dataIndex = MapCursor.INDEX_FINISH; | ||
228 | } | ||
229 | } | ||
230 | |||
231 | // 2. look inside the subnodes | ||
232 | int nodes = Integer.bitCount(this.nodeMap); | ||
233 | int newNodeIndex = cursor.nodeIndexStack.peek() + 1; | ||
234 | if(newNodeIndex < nodes) { | ||
235 | // 2.1 found next subnode, move down to the subnode | ||
236 | Node<K, V> subnode = (Node<K, V>) this.content[this.content.length-1-newNodeIndex]; | ||
237 | cursor.dataIndex = MapCursor.INDEX_START; | ||
238 | cursor.nodeIndexStack.pop(); | ||
239 | cursor.nodeIndexStack.push(newNodeIndex); | ||
240 | cursor.nodeIndexStack.push(MapCursor.INDEX_START); | ||
241 | cursor.nodeStack.push(subnode); | ||
242 | return subnode.moveToNext(cursor); | ||
243 | } else { | ||
244 | // 3. no subnode found, move up | ||
245 | cursor.nodeStack.pop(); | ||
246 | cursor.nodeIndexStack.pop(); | ||
247 | if(!cursor.nodeStack.isEmpty()) { | ||
248 | Node<K, V> supernode = cursor.nodeStack.peek(); | ||
249 | return supernode.moveToNext(cursor); | ||
250 | } else { | ||
251 | cursor.key = null; | ||
252 | cursor.value = null; | ||
253 | return false; | ||
254 | } | ||
255 | } | ||
256 | } | ||
257 | |||
258 | @Override | ||
259 | public void prettyPrint(StringBuilder builder, int depth, int code) { | ||
260 | for(int i = 0; i<depth; i++) { | ||
261 | builder.append("\t"); | ||
262 | } | ||
263 | if(code>=0) { | ||
264 | builder.append(code); | ||
265 | builder.append(":"); | ||
266 | } | ||
267 | builder.append("Immutable("); | ||
268 | boolean hadContent = false; | ||
269 | int dataMask = 1; | ||
270 | for(int i = 0; i<FACTOR; i++) { | ||
271 | if((dataMask & dataMap) != 0) { | ||
272 | if(hadContent) { | ||
273 | builder.append(","); | ||
274 | } | ||
275 | builder.append(i); | ||
276 | builder.append(":["); | ||
277 | builder.append(content[2*index(dataMap, dataMask)].toString()); | ||
278 | builder.append("]->["); | ||
279 | builder.append(content[2*index(dataMap, dataMask)+1].toString()); | ||
280 | builder.append("]"); | ||
281 | hadContent = true; | ||
282 | } | ||
283 | dataMask<<=1; | ||
284 | } | ||
285 | builder.append(")"); | ||
286 | int nodeMask = 1; | ||
287 | for(int i = 0; i<FACTOR; i++) { | ||
288 | if((nodeMask & nodeMap)!=0) { | ||
289 | @SuppressWarnings("unchecked") | ||
290 | Node<K,V> subNode = (Node<K, V>) content[content.length-1-index(nodeMap, nodeMask)]; | ||
291 | builder.append("\n"); | ||
292 | subNode.prettyPrint(builder, depth+1, i); | ||
293 | } | ||
294 | nodeMask<<=1; | ||
295 | } | ||
296 | } | ||
297 | |||
298 | @SuppressWarnings("unchecked") | ||
299 | @Override | ||
300 | public void checkIntegrity(ContinousHashProvider<? super K> hashProvider, V defaultValue, int depth) { | ||
301 | if(depth>0) { | ||
302 | boolean orphaned = Integer.bitCount(dataMap) == 1 && nodeMap == 0; | ||
303 | if(orphaned) { | ||
304 | throw new IllegalStateException("Orphaned node! " + dataMap + ": " + content[0]); | ||
305 | } | ||
306 | } | ||
307 | // check the place of data | ||
308 | |||
309 | // check subnodes | ||
310 | for(int i = 0; i<Integer.bitCount(nodeMap); i++) { | ||
311 | Node<K,V> subnode = (Node<K, V>) this.content[this.content.length-1-i]; | ||
312 | if(! (subnode instanceof ImmutableNode<?,?>)) { | ||
313 | throw new IllegalStateException("Immutable node contains mutable subnodes!"); | ||
314 | } else { | ||
315 | subnode.checkIntegrity(hashProvider, defaultValue, depth+1); | ||
316 | } | ||
317 | } | ||
318 | } | ||
319 | |||
320 | @Override | ||
321 | public int hashCode() { | ||
322 | return this.precalculatedHash; | ||
323 | } | ||
324 | |||
325 | @Override | ||
326 | public boolean equals(Object obj) { | ||
327 | if (this == obj) | ||
328 | return true; | ||
329 | if (obj == null) | ||
330 | return false; | ||
331 | if (obj instanceof ImmutableNode<?,?>) { | ||
332 | ImmutableNode<?,?> other = (ImmutableNode<?,?>) obj; | ||
333 | if (precalculatedHash != other.precalculatedHash || dataMap != other.dataMap || nodeMap != other.nodeMap || !Arrays.deepEquals(content, other.content)) | ||
334 | return false; | ||
335 | else return true; | ||
336 | } else if(obj instanceof MutableNode<?,?>) { | ||
337 | return ImmutableNode.compareImmutableMutable(this, (MutableNode<?, ?>) obj); | ||
338 | } else { | ||
339 | return false; | ||
340 | } | ||
341 | } | ||
342 | public static boolean compareImmutableMutable( | ||
343 | ImmutableNode<?, ?> immutable, | ||
344 | MutableNode<?, ?> mutable) | ||
345 | { | ||
346 | int datas = 0; | ||
347 | int nodes = 0; | ||
348 | final int immutableLength = immutable.content.length; | ||
349 | for(int i = 0; i<FACTOR; i++) { | ||
350 | Object key = mutable.content[i*2]; | ||
351 | // For each key candidate | ||
352 | if(key != null) { | ||
353 | // Check whether a new Key-Value pair can fit into the immutable container | ||
354 | if(datas*2+nodes+2 <= immutableLength) { | ||
355 | if( !immutable.content[datas*2].equals(key) || | ||
356 | !immutable.content[datas*2+1].equals(mutable.content[i*2+1])) | ||
357 | { | ||
358 | return false; | ||
359 | } | ||
360 | } else return false; | ||
361 | datas++; | ||
362 | } else { | ||
363 | Node<?,?> mutableSubnode = (Node<?, ?>) mutable.content[i*2+1]; | ||
364 | if(mutableSubnode != null) { | ||
365 | if(datas*2+nodes+1 <= immutableLength) { | ||
366 | Object immutableSubnode = immutable.content[immutableLength-1-nodes]; | ||
367 | if(!mutableSubnode.equals(immutableSubnode)) { | ||
368 | return false; | ||
369 | } | ||
370 | nodes++; | ||
371 | } else { | ||
372 | return false; | ||
373 | } | ||
374 | } | ||
375 | } | ||
376 | } | ||
377 | return true; | ||
378 | } | ||
379 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MapCursor.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MapCursor.java new file mode 100644 index 00000000..cc5a3982 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MapCursor.java | |||
@@ -0,0 +1,131 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.internal; | ||
2 | |||
3 | import java.util.ArrayDeque; | ||
4 | import java.util.ConcurrentModificationException; | ||
5 | import java.util.Iterator; | ||
6 | import java.util.List; | ||
7 | |||
8 | import org.eclipse.viatra.solver.data.map.Cursor; | ||
9 | import org.eclipse.viatra.solver.data.map.VersionedMap; | ||
10 | |||
11 | public class MapCursor<K,V> implements Cursor<K,V> { | ||
12 | // Constants | ||
13 | static final int INDEX_START = -1; | ||
14 | static final int INDEX_FINISH = -2; | ||
15 | |||
16 | // Tree stack | ||
17 | ArrayDeque<Node<K,V>> nodeStack; | ||
18 | ArrayDeque<Integer> nodeIndexStack; | ||
19 | int dataIndex; | ||
20 | |||
21 | // Values | ||
22 | K key; | ||
23 | V value; | ||
24 | |||
25 | // Hash code for checking concurrent modifications | ||
26 | final VersionedMap<K,V> map; | ||
27 | final int creationHash; | ||
28 | |||
29 | public MapCursor(Node<K, V> root, VersionedMap<K,V> map) { | ||
30 | // Initializing tree stack | ||
31 | super(); | ||
32 | this.nodeStack = new ArrayDeque<>(); | ||
33 | this.nodeIndexStack = new ArrayDeque<>(); | ||
34 | if(root != null) { | ||
35 | this.nodeStack.add(root); | ||
36 | this.nodeIndexStack.push(INDEX_START); | ||
37 | } | ||
38 | |||
39 | this.dataIndex = INDEX_START; | ||
40 | |||
41 | // Initializing cache | ||
42 | this.key = null; | ||
43 | this.value = null; | ||
44 | |||
45 | // Initializing state | ||
46 | this.map=map; | ||
47 | this.creationHash = map.hashCode(); | ||
48 | } | ||
49 | |||
50 | public K getKey() { | ||
51 | return key; | ||
52 | } | ||
53 | |||
54 | public V getValue() { | ||
55 | return value; | ||
56 | } | ||
57 | |||
58 | public boolean isTerminated() { | ||
59 | return this.nodeStack.isEmpty(); | ||
60 | } | ||
61 | |||
62 | public boolean move() { | ||
63 | if(isDirty()) { | ||
64 | throw new ConcurrentModificationException(); | ||
65 | } | ||
66 | if(!isTerminated()) { | ||
67 | boolean result = this.nodeStack.peek().moveToNext(this); | ||
68 | if(this.nodeIndexStack.size() != this.nodeStack.size()) { | ||
69 | throw new IllegalArgumentException("Node stack is corrupted by illegal moves!"); | ||
70 | } | ||
71 | return result; | ||
72 | } | ||
73 | return false; | ||
74 | } | ||
75 | public boolean skipCurrentNode() { | ||
76 | nodeStack.pop(); | ||
77 | nodeIndexStack.pop(); | ||
78 | dataIndex = INDEX_FINISH; | ||
79 | return move(); | ||
80 | } | ||
81 | @Override | ||
82 | public boolean isDirty() { | ||
83 | return this.map.hashCode() != this.creationHash; | ||
84 | } | ||
85 | @Override | ||
86 | public List<VersionedMap<?, ?>> getDependingMaps() { | ||
87 | return List.of(this.map); | ||
88 | } | ||
89 | |||
90 | public static <K,V> boolean sameSubnode(MapCursor<K,V> cursor1, MapCursor<K,V> cursor2) { | ||
91 | Node<K, V> nodeOfCursor1 = cursor1.nodeStack.peek(); | ||
92 | Node<K, V> nodeOfCursor2 = cursor2.nodeStack.peek(); | ||
93 | if(nodeOfCursor1 != null && nodeOfCursor2 != null) { | ||
94 | return nodeOfCursor1.equals(nodeOfCursor2); | ||
95 | } else { | ||
96 | return false; | ||
97 | } | ||
98 | } | ||
99 | |||
100 | /** | ||
101 | * | ||
102 | * @param <K> | ||
103 | * @param <V> | ||
104 | * @param cursor1 | ||
105 | * @param cursor2 | ||
106 | * @return Positive number if cursor 1 is behind, negative number if cursor 2 is behind, and 0 if they are at the same position. | ||
107 | */ | ||
108 | public static <K,V> int compare(MapCursor<K,V> cursor1, MapCursor<K,V> cursor2) { | ||
109 | // two cursors are equally deep | ||
110 | Iterator<Integer> stack1 = cursor1.nodeIndexStack.descendingIterator(); | ||
111 | Iterator<Integer> stack2 = cursor2.nodeIndexStack.descendingIterator(); | ||
112 | if(stack1.hasNext()) { | ||
113 | if(!stack2.hasNext()) { | ||
114 | // stack 2 has no more element, thus stack 1 is deeper | ||
115 | return 1; | ||
116 | } | ||
117 | int val1 = stack1.next(); | ||
118 | int val2 = stack2.next(); | ||
119 | if(val1 < val2) { | ||
120 | return -1; | ||
121 | } else if(val2 < val1) { | ||
122 | return 1; | ||
123 | } | ||
124 | } | ||
125 | if(stack2.hasNext()) { | ||
126 | // stack 2 has more element, thus stack 2 is deeper | ||
127 | return 1; | ||
128 | } | ||
129 | return Integer.compare(cursor1.dataIndex, cursor2.dataIndex); | ||
130 | } | ||
131 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MapDiffCursor.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MapDiffCursor.java new file mode 100644 index 00000000..e97e4aa1 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MapDiffCursor.java | |||
@@ -0,0 +1,214 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.internal; | ||
2 | |||
3 | import java.util.List; | ||
4 | import java.util.stream.Collectors; | ||
5 | import java.util.stream.Stream; | ||
6 | |||
7 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
8 | import org.eclipse.viatra.solver.data.map.Cursor; | ||
9 | import org.eclipse.viatra.solver.data.map.DiffCursor; | ||
10 | import org.eclipse.viatra.solver.data.map.VersionedMap; | ||
11 | |||
12 | /** | ||
13 | * A cursor representing the difference between two states of a map. | ||
14 | * @author Oszkar Semerath | ||
15 | * | ||
16 | */ | ||
17 | public class MapDiffCursor<K,V> implements DiffCursor<K, V>, Cursor<K,V>{ | ||
18 | /** | ||
19 | * Default value representing missing elements. | ||
20 | */ | ||
21 | private V defaultValue; | ||
22 | private MapCursor<K,V> cursor1; | ||
23 | private MapCursor<K,V> cursor2; | ||
24 | private ContinousHashProvider<? super K> hashProvider; | ||
25 | |||
26 | // Values | ||
27 | private K key; | ||
28 | private V fromValue; | ||
29 | private V toValue; | ||
30 | |||
31 | // State | ||
32 | /** | ||
33 | * Positive number if cursor 1 is behind, negative number if cursor 2 is behind, and 0 if they are at the same position. | ||
34 | */ | ||
35 | private int cursorRelation; | ||
36 | /** | ||
37 | * Denotes a state when two cursors are in the same position, but they contain different keys. | ||
38 | * Possible values: | ||
39 | * <ul> | ||
40 | * <li>0: not stuck</li> | ||
41 | * <li>1: hashClash, next it should return the key of cursor 1.</li> | ||
42 | * <li>2: hashClash, next it should return the key of cursor 2.</li> | ||
43 | * </ul> | ||
44 | */ | ||
45 | private int hashClash=0; | ||
46 | |||
47 | public MapDiffCursor(ContinousHashProvider<? super K> hashProvider, V defaultValue, Cursor<K, V> cursor1, Cursor<K, V> cursor2) { | ||
48 | super(); | ||
49 | this.hashProvider = hashProvider; | ||
50 | this.defaultValue = defaultValue; | ||
51 | this.cursor1 = (MapCursor<K, V>) cursor1; | ||
52 | this.cursor2 = (MapCursor<K, V>) cursor2; | ||
53 | } | ||
54 | @Override | ||
55 | public K getKey() { | ||
56 | return key; | ||
57 | } | ||
58 | @Override | ||
59 | public V getFromValue() { | ||
60 | return fromValue; | ||
61 | } | ||
62 | @Override | ||
63 | public V getToValue() { | ||
64 | return toValue; | ||
65 | } | ||
66 | @Override | ||
67 | public V getValue() { | ||
68 | return getToValue(); | ||
69 | } | ||
70 | public boolean isTerminated() { | ||
71 | return cursor1.isTerminated() && cursor2.isTerminated(); | ||
72 | } | ||
73 | @Override | ||
74 | public boolean isDirty() { | ||
75 | return this.cursor1.isDirty() || this.cursor2.isDirty(); | ||
76 | } | ||
77 | @Override | ||
78 | public List<VersionedMap<?, ?>> getDependingMaps() { | ||
79 | return Stream.concat( | ||
80 | cursor1.getDependingMaps().stream(), | ||
81 | cursor2.getDependingMaps().stream() | ||
82 | ).collect(Collectors.toList()); | ||
83 | } | ||
84 | |||
85 | protected void updateState() { | ||
86 | if(!isTerminated()) { | ||
87 | this.cursorRelation = MapCursor.compare(cursor1, cursor2); | ||
88 | if(cursorRelation > 0 || cursor2.isTerminated()) { | ||
89 | this.key = cursor1.getKey(); | ||
90 | this.fromValue = cursor1.getValue(); | ||
91 | this.toValue = defaultValue; | ||
92 | } else if(cursorRelation < 0 || cursor1.isTerminated()){ | ||
93 | this.key = cursor2.getKey(); | ||
94 | this.fromValue = defaultValue; | ||
95 | this.toValue = cursor1.getValue(); | ||
96 | } else { | ||
97 | // cursor1 = cursor2 | ||
98 | if(cursor1.getKey().equals(cursor2.getKey())) { | ||
99 | this.key = cursor1.getKey(); | ||
100 | this.fromValue = cursor1.getValue(); | ||
101 | this.toValue = defaultValue; | ||
102 | } else { | ||
103 | resolveHashClash1(); | ||
104 | } | ||
105 | } | ||
106 | } | ||
107 | } | ||
108 | protected void resolveHashClash1() { | ||
109 | int compareResult = this.hashProvider.compare(cursor1.key, cursor2.key); | ||
110 | if(compareResult<0) { | ||
111 | this.hashClash = 2; | ||
112 | this.cursorRelation = 0; | ||
113 | this.key = cursor1.key; | ||
114 | this.fromValue = cursor1.value; | ||
115 | this.toValue = defaultValue; | ||
116 | } else if(compareResult>0) { | ||
117 | this.hashClash = 1; | ||
118 | this.cursorRelation = 0; | ||
119 | this.key = cursor2.key; | ||
120 | this.fromValue = defaultValue; | ||
121 | this.toValue = cursor2.value; | ||
122 | } else { | ||
123 | throw new IllegalArgumentException("Inconsistent compare result for diffcursor"); | ||
124 | } | ||
125 | } | ||
126 | protected boolean isInHashClash() { | ||
127 | return this.hashClash != 0; | ||
128 | } | ||
129 | protected void resolveHashClash2() { | ||
130 | if(hashClash == 1) { | ||
131 | this.hashClash = 0; | ||
132 | this.cursorRelation = 0; | ||
133 | this.key = cursor1.key; | ||
134 | this.fromValue = cursor1.value; | ||
135 | this.toValue = defaultValue; | ||
136 | } else if(hashClash == 2) { | ||
137 | this.hashClash = 0; | ||
138 | this.cursorRelation = 0; | ||
139 | this.key = cursor2.key; | ||
140 | this.fromValue = defaultValue; | ||
141 | this.toValue = cursor2.value; | ||
142 | } else throw new IllegalArgumentException("Inconsistent compare result for diffcursor"); | ||
143 | } | ||
144 | |||
145 | |||
146 | protected boolean sameValues() { | ||
147 | if(this.fromValue == null) { | ||
148 | return this.toValue == null; | ||
149 | } else { | ||
150 | return this.fromValue.equals(this.toValue); | ||
151 | } | ||
152 | } | ||
153 | protected boolean moveOne() { | ||
154 | if(isTerminated()) { | ||
155 | return false; | ||
156 | } | ||
157 | if(this.cursorRelation > 0 || cursor2.isTerminated()) { | ||
158 | return cursor1.move(); | ||
159 | } else if(this.cursorRelation < 0 || cursor1.isTerminated()) { | ||
160 | return cursor2.move(); | ||
161 | } else { | ||
162 | boolean moved1 = cursor1.move(); | ||
163 | boolean moved2 = cursor2.move(); | ||
164 | return moved1 && moved2; | ||
165 | } | ||
166 | } | ||
167 | private boolean skipNode() { | ||
168 | if(isTerminated()) { | ||
169 | throw new IllegalStateException("DiffCursor tries to skip when terminated!"); | ||
170 | } | ||
171 | boolean update1 = cursor1.skipCurrentNode(); | ||
172 | boolean update2 = cursor2.skipCurrentNode(); | ||
173 | updateState(); | ||
174 | return update1 && update2; | ||
175 | } | ||
176 | |||
177 | protected boolean moveToConsistentState() { | ||
178 | if(!isTerminated()) { | ||
179 | boolean changed; | ||
180 | boolean lastResult = true; | ||
181 | do { | ||
182 | changed = false; | ||
183 | if(MapCursor.sameSubnode(cursor1, cursor2)) { | ||
184 | lastResult = skipNode(); | ||
185 | changed = true; | ||
186 | } | ||
187 | if(sameValues()) { | ||
188 | lastResult = moveOne(); | ||
189 | changed = true; | ||
190 | } | ||
191 | updateState(); | ||
192 | } while(changed && !isTerminated()); | ||
193 | return lastResult; | ||
194 | } else { | ||
195 | return false; | ||
196 | } | ||
197 | } | ||
198 | |||
199 | public boolean move() { | ||
200 | if(!isTerminated()) { | ||
201 | if(isInHashClash()) { | ||
202 | this.resolveHashClash2(); | ||
203 | return true; | ||
204 | } else { | ||
205 | if(moveOne()) { | ||
206 | return moveToConsistentState(); | ||
207 | } else { | ||
208 | return false; | ||
209 | } | ||
210 | } | ||
211 | |||
212 | } else return false; | ||
213 | } | ||
214 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MutableNode.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MutableNode.java new file mode 100644 index 00000000..9e63b941 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/MutableNode.java | |||
@@ -0,0 +1,449 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.internal; | ||
2 | |||
3 | import java.util.Arrays; | ||
4 | import java.util.Map; | ||
5 | |||
6 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
7 | |||
8 | public class MutableNode<K, V> extends Node<K, V> { | ||
9 | int cachedHash; | ||
10 | protected Object[] content; | ||
11 | |||
12 | protected MutableNode() { | ||
13 | this.content = new Object[2 * FACTOR]; | ||
14 | updateHash(); | ||
15 | } | ||
16 | |||
17 | public static <K, V> MutableNode<K, V> initialize(K key, V value, ContinousHashProvider<? super K> hashProvider, | ||
18 | V defaultValue) { | ||
19 | if (value == defaultValue) { | ||
20 | return null; | ||
21 | } else { | ||
22 | int hash = hashProvider.getHash(key, 0); | ||
23 | int fragment = hashFragment(hash, 0); | ||
24 | MutableNode<K, V> res = new MutableNode<>(); | ||
25 | res.content[2 * fragment] = key; | ||
26 | res.content[2 * fragment + 1] = value; | ||
27 | res.updateHash(); | ||
28 | return res; | ||
29 | } | ||
30 | } | ||
31 | |||
32 | /** | ||
33 | * Constructs a {@link MutableNode} as a copy of an {@link ImmutableNode} | ||
34 | * | ||
35 | * @param node | ||
36 | */ | ||
37 | protected MutableNode(ImmutableNode<K, V> node) { | ||
38 | this.content = new Object[2 * FACTOR]; | ||
39 | int dataUsed = 0; | ||
40 | int nodeUsed = 0; | ||
41 | for (int i = 0; i < FACTOR; i++) { | ||
42 | int bitposition = 1 << i; | ||
43 | if ((node.dataMap & bitposition) != 0) { | ||
44 | content[2 * i] = node.content[dataUsed * 2]; | ||
45 | content[2 * i + 1] = node.content[dataUsed * 2 + 1]; | ||
46 | dataUsed++; | ||
47 | } else if ((node.nodeMap & bitposition) != 0) { | ||
48 | content[2 * i + 1] = node.content[node.content.length - 1 - nodeUsed]; | ||
49 | nodeUsed++; | ||
50 | } | ||
51 | } | ||
52 | this.cachedHash = node.hashCode(); | ||
53 | } | ||
54 | |||
55 | @SuppressWarnings("unchecked") | ||
56 | @Override | ||
57 | public V getValue(K key, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth) { | ||
58 | int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); | ||
59 | K keyCandidate = (K) this.content[2 * selectedHashFragment]; | ||
60 | if (keyCandidate != null) { | ||
61 | if (keyCandidate.equals(key)) { | ||
62 | return (V) this.content[2 * selectedHashFragment + 1]; | ||
63 | } else { | ||
64 | return defaultValue; | ||
65 | } | ||
66 | } else { | ||
67 | Node<K, V> nodeCandidate = (Node<K, V>) content[2 * selectedHashFragment + 1]; | ||
68 | if (nodeCandidate != null) { | ||
69 | int newDepth = depth + 1; | ||
70 | int newHash = newHash(hashProvider, key, hash, newDepth); | ||
71 | return nodeCandidate.getValue(key, hashProvider, defaultValue, newHash, newDepth); | ||
72 | } else { | ||
73 | return defaultValue; | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | |||
78 | @SuppressWarnings("unchecked") | ||
79 | @Override | ||
80 | public Node<K, V> putValue(K key, V value, OldValueBox<V> oldValue, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, | ||
81 | int depth) { | ||
82 | int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); | ||
83 | K keyCandidate = (K) content[2 * selectedHashFragment]; | ||
84 | if (keyCandidate != null) { | ||
85 | // If has key | ||
86 | if (keyCandidate.equals(key)) { | ||
87 | // The key is equals to an existing key -> update entry | ||
88 | if (value == defaultValue) { | ||
89 | return removeEntry(selectedHashFragment, oldValue); | ||
90 | } else { | ||
91 | return updateValue(value, oldValue, selectedHashFragment); | ||
92 | } | ||
93 | } else { | ||
94 | // The key is not equivalent to an existing key on the same hash bin | ||
95 | // -> split entry if it is necessary | ||
96 | if (value == defaultValue) { | ||
97 | // Value is default -> do not need to add new node | ||
98 | oldValue.setOldValue(defaultValue); | ||
99 | return this; | ||
100 | } else { | ||
101 | // Value is not default -> Split entry data to a new node | ||
102 | oldValue.setOldValue(defaultValue); | ||
103 | return moveDownAndSplit(hashProvider, key, value, keyCandidate, hash, depth, selectedHashFragment); | ||
104 | } | ||
105 | } | ||
106 | } else { | ||
107 | // If it does not have key, check for value | ||
108 | Node<K, V> nodeCandidate = (Node<K, V>) content[2 * selectedHashFragment + 1]; | ||
109 | if (nodeCandidate != null) { | ||
110 | // If it has value, it is a subnode -> upate that | ||
111 | Node<K, V> newNode = nodeCandidate.putValue(key, value, oldValue, hashProvider, defaultValue, | ||
112 | newHash(hashProvider, key, hash, depth + 1), depth + 1); | ||
113 | return updateWithSubNode(selectedHashFragment, newNode, value.equals(defaultValue)); | ||
114 | } else { | ||
115 | // If it does not have value, put it in the empty place | ||
116 | if (value == defaultValue) { | ||
117 | // dont need to add new key-value pair | ||
118 | oldValue.setOldValue(defaultValue); | ||
119 | return this; | ||
120 | } else { | ||
121 | return addEntry(key, value, oldValue, selectedHashFragment); | ||
122 | } | ||
123 | |||
124 | } | ||
125 | } | ||
126 | } | ||
127 | |||
128 | @SuppressWarnings("unchecked") | ||
129 | private Node<K, V> addEntry(K key, V value, OldValueBox<V> oldValue, int selectedHashFragment) { | ||
130 | content[2 * selectedHashFragment] = key; | ||
131 | oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]); | ||
132 | content[2 * selectedHashFragment + 1] = value; | ||
133 | updateHash(); | ||
134 | return this; | ||
135 | } | ||
136 | |||
137 | /** | ||
138 | * Updates an entry in a selected hash-fragment to a non-default value. | ||
139 | * | ||
140 | * @param value | ||
141 | * @param selectedHashFragment | ||
142 | * @return | ||
143 | */ | ||
144 | @SuppressWarnings("unchecked") | ||
145 | Node<K, V> updateValue(V value, OldValueBox<V> oldValue, int selectedHashFragment) { | ||
146 | oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]); | ||
147 | content[2 * selectedHashFragment + 1] = value; | ||
148 | updateHash(); | ||
149 | return this; | ||
150 | } | ||
151 | |||
152 | /** | ||
153 | * | ||
154 | * @param selectedHashFragment | ||
155 | * @param newNode | ||
156 | * @return | ||
157 | */ | ||
158 | Node<K, V> updateWithSubNode(int selectedHashFragment, Node<K, V> newNode, boolean deletionHappened) { | ||
159 | if (deletionHappened) { | ||
160 | if (newNode == null) { | ||
161 | // Check whether this node become empty | ||
162 | content[2 * selectedHashFragment + 1] = null; // i.e. the new node | ||
163 | if (hasContent()) { | ||
164 | updateHash(); | ||
165 | return this; | ||
166 | } else { | ||
167 | return null; | ||
168 | } | ||
169 | } else { | ||
170 | // check whether newNode is orphan | ||
171 | MutableNode<K, V> immutableNewNode = newNode.isMutable(); | ||
172 | if (immutableNewNode != null) { | ||
173 | int orphaned = immutableNewNode.isOrphaned(); | ||
174 | if (orphaned >= 0) { | ||
175 | // orphan subnode data is replaced with data | ||
176 | content[2 * selectedHashFragment] = immutableNewNode.content[orphaned * 2]; | ||
177 | content[2 * selectedHashFragment + 1] = immutableNewNode.content[orphaned * 2 + 1]; | ||
178 | updateHash(); | ||
179 | return this; | ||
180 | } | ||
181 | } | ||
182 | } | ||
183 | } | ||
184 | // normal behaviour | ||
185 | content[2 * selectedHashFragment + 1] = newNode; | ||
186 | updateHash(); | ||
187 | return this; | ||
188 | |||
189 | } | ||
190 | |||
191 | private boolean hasContent() { | ||
192 | for (Object element : this.content) { | ||
193 | if (element != null) | ||
194 | return true; | ||
195 | } | ||
196 | return false; | ||
197 | } | ||
198 | |||
199 | @Override | ||
200 | protected MutableNode<K, V> isMutable() { | ||
201 | return this; | ||
202 | } | ||
203 | |||
204 | protected int isOrphaned() { | ||
205 | int dataFound = -2; | ||
206 | for (int i = 0; i < FACTOR; i++) { | ||
207 | if (content[i * 2] != null) { | ||
208 | if (dataFound >= 0) { | ||
209 | return -1; | ||
210 | } else { | ||
211 | dataFound = i; | ||
212 | } | ||
213 | } else if (content[i * 2 + 1] != null) { | ||
214 | return -3; | ||
215 | } | ||
216 | } | ||
217 | return dataFound; | ||
218 | } | ||
219 | |||
220 | @SuppressWarnings("unchecked") | ||
221 | private Node<K, V> moveDownAndSplit(ContinousHashProvider<? super K> hashProvider, K newKey, V newValue, | ||
222 | K previousKey, int hashOfNewKey, int depth, int selectedHashFragmentOfCurrentDepth) { | ||
223 | V previousValue = (V) content[2 * selectedHashFragmentOfCurrentDepth + 1]; | ||
224 | |||
225 | MutableNode<K, V> newSubNode = newNodeWithTwoEntries(hashProvider, previousKey, previousValue, | ||
226 | hashProvider.getHash(previousKey, hashDepth(depth)), newKey, newValue, hashOfNewKey, depth + 1); | ||
227 | |||
228 | content[2 * selectedHashFragmentOfCurrentDepth] = null; | ||
229 | content[2 * selectedHashFragmentOfCurrentDepth + 1] = newSubNode; | ||
230 | updateHash(); | ||
231 | return this; | ||
232 | } | ||
233 | |||
234 | private MutableNode<K, V> newNodeWithTwoEntries(ContinousHashProvider<? super K> hashProvider, K key1, V value1, | ||
235 | int oldHash1, K key2, V value2, int oldHash2, int newdepth) { | ||
236 | int newHash1 = newHash(hashProvider, key1, oldHash1, newdepth); | ||
237 | int newHash2 = newHash(hashProvider, key2, oldHash2, newdepth); | ||
238 | int newFragment1 = hashFragment(newHash1, shiftDepth(newdepth)); | ||
239 | int newFragment2 = hashFragment(newHash2, shiftDepth(newdepth)); | ||
240 | |||
241 | MutableNode<K, V> subNode = new MutableNode<>(); | ||
242 | if (newFragment1 != newFragment2) { | ||
243 | subNode.content[newFragment1 * 2] = key1; | ||
244 | subNode.content[newFragment1 * 2 + 1] = value1; | ||
245 | |||
246 | subNode.content[newFragment2 * 2] = key2; | ||
247 | subNode.content[newFragment2 * 2 + 1] = value2; | ||
248 | } else { | ||
249 | MutableNode<K, V> subSubNode = newNodeWithTwoEntries(hashProvider, key1, value1, newHash1, key2, value2, | ||
250 | newHash2, newdepth + 1); | ||
251 | subNode.content[newFragment1 * 2 + 1] = subSubNode; | ||
252 | } | ||
253 | subNode.updateHash(); | ||
254 | return subNode; | ||
255 | } | ||
256 | |||
257 | @SuppressWarnings("unchecked") | ||
258 | Node<K, V> removeEntry(int selectedHashFragment, OldValueBox<V> oldValue) { | ||
259 | content[2 * selectedHashFragment] = null; | ||
260 | oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]); | ||
261 | content[2 * selectedHashFragment + 1] = null; | ||
262 | if (hasContent()) { | ||
263 | updateHash(); | ||
264 | return this; | ||
265 | } else { | ||
266 | return null; | ||
267 | } | ||
268 | } | ||
269 | |||
270 | @SuppressWarnings("unchecked") | ||
271 | @Override | ||
272 | public long getSize() { | ||
273 | int size = 0; | ||
274 | for (int i = 0; i < FACTOR; i++) { | ||
275 | if (content[i * 2] != null) { | ||
276 | size++; | ||
277 | } else { | ||
278 | Node<K, V> nodeCandidate = (Node<K, V>) content[i * 2 + 1]; | ||
279 | if (nodeCandidate != null) { | ||
280 | size += nodeCandidate.getSize(); | ||
281 | } | ||
282 | } | ||
283 | } | ||
284 | return size; | ||
285 | } | ||
286 | |||
287 | @Override | ||
288 | protected MutableNode<K, V> toMutable() { | ||
289 | return this; | ||
290 | } | ||
291 | |||
292 | @Override | ||
293 | public ImmutableNode<K, V> toImmutable(Map<Node<K, V>, ImmutableNode<K, V>> cache) { | ||
294 | return ImmutableNode.constructImmutable(this, cache); | ||
295 | } | ||
296 | |||
297 | @SuppressWarnings("unchecked") | ||
298 | @Override | ||
299 | boolean moveToNext(MapCursor<K, V> cursor) { | ||
300 | // 1. try to move to data | ||
301 | if (cursor.dataIndex != MapCursor.INDEX_FINISH) { | ||
302 | for (int index = cursor.dataIndex + 1; index < FACTOR; index++) { | ||
303 | if (this.content[index * 2] != null) { | ||
304 | // 1.1 found next data | ||
305 | cursor.dataIndex = index; | ||
306 | cursor.key = (K) this.content[index * 2]; | ||
307 | cursor.value = (V) this.content[index * 2 + 1]; | ||
308 | return true; | ||
309 | } | ||
310 | } | ||
311 | cursor.dataIndex = MapCursor.INDEX_FINISH; | ||
312 | } | ||
313 | |||
314 | // 2. look inside the subnodes | ||
315 | for (int index = cursor.nodeIndexStack.peek() + 1; index < FACTOR; index++) { | ||
316 | if (this.content[index * 2] == null && this.content[index * 2 + 1] != null) { | ||
317 | // 2.1 found next subnode, move down to the subnode | ||
318 | Node<K, V> subnode = (Node<K, V>) this.content[index * 2 + 1]; | ||
319 | |||
320 | cursor.dataIndex = MapCursor.INDEX_START; | ||
321 | cursor.nodeIndexStack.pop(); | ||
322 | cursor.nodeIndexStack.push(index); | ||
323 | cursor.nodeIndexStack.push(MapCursor.INDEX_START); | ||
324 | cursor.nodeStack.push(subnode); | ||
325 | |||
326 | return subnode.moveToNext(cursor); | ||
327 | } | ||
328 | } | ||
329 | // 3. no subnode found, move up | ||
330 | cursor.nodeStack.pop(); | ||
331 | cursor.nodeIndexStack.pop(); | ||
332 | if (!cursor.nodeStack.isEmpty()) { | ||
333 | Node<K, V> supernode = cursor.nodeStack.peek(); | ||
334 | return supernode.moveToNext(cursor); | ||
335 | } else { | ||
336 | cursor.key = null; | ||
337 | cursor.value = null; | ||
338 | return false; | ||
339 | } | ||
340 | } | ||
341 | |||
342 | @Override | ||
343 | public void prettyPrint(StringBuilder builder, int depth, int code) { | ||
344 | for (int i = 0; i < depth; i++) { | ||
345 | builder.append("\t"); | ||
346 | } | ||
347 | if (code >= 0) { | ||
348 | builder.append(code); | ||
349 | builder.append(":"); | ||
350 | } | ||
351 | builder.append("Mutable("); | ||
352 | // print content | ||
353 | boolean hadContent = false; | ||
354 | for (int i = 0; i < FACTOR; i++) { | ||
355 | if (content[2 * i] != null) { | ||
356 | if (hadContent) { | ||
357 | builder.append(","); | ||
358 | } | ||
359 | builder.append(i); | ||
360 | builder.append(":["); | ||
361 | builder.append(content[2 * i].toString()); | ||
362 | builder.append("]->["); | ||
363 | builder.append(content[2 * i + 1].toString()); | ||
364 | builder.append("]"); | ||
365 | hadContent = true; | ||
366 | } | ||
367 | } | ||
368 | builder.append(")"); | ||
369 | // print subnodes | ||
370 | for (int i = 0; i < FACTOR; i++) { | ||
371 | if (content[2 * i] == null && content[2 * i + 1] != null) { | ||
372 | @SuppressWarnings("unchecked") | ||
373 | Node<K, V> subNode = (Node<K, V>) content[2 * i + 1]; | ||
374 | builder.append("\n"); | ||
375 | subNode.prettyPrint(builder, depth + 1, i); | ||
376 | } | ||
377 | } | ||
378 | } | ||
379 | |||
380 | @SuppressWarnings({ "unchecked" }) | ||
381 | @Override | ||
382 | public void checkIntegrity(ContinousHashProvider<? super K> hashProvider, V defaultValue, int depth) { | ||
383 | // check for orphan nodes | ||
384 | if (depth > 0) { | ||
385 | int orphaned = isOrphaned(); | ||
386 | if (orphaned >= 0) { | ||
387 | throw new IllegalStateException("Orphaned node! " + orphaned + ": " + content[2 * orphaned]); | ||
388 | } | ||
389 | } | ||
390 | // check the place of data | ||
391 | for (int i = 0; i < FACTOR; i++) { | ||
392 | if (this.content[2 * i] != null) { | ||
393 | K key = (K) this.content[2 * i]; | ||
394 | V value = (V) this.content[2 * i + 1]; | ||
395 | |||
396 | if (value == defaultValue) { | ||
397 | throw new IllegalStateException("Node contains default value!"); | ||
398 | } | ||
399 | int hashCode = hashProvider.getHash(key, hashDepth(depth)); | ||
400 | int shiftDepth = shiftDepth(depth); | ||
401 | int selectedHashFragment = hashFragment(hashCode, shiftDepth); | ||
402 | if (i != selectedHashFragment) { | ||
403 | throw new IllegalStateException("Key " + key + " with hash code " + hashCode | ||
404 | + " is in bad place! Fragment=" + selectedHashFragment + ", Place=" + i); | ||
405 | } | ||
406 | } | ||
407 | } | ||
408 | // check subnodes | ||
409 | for (int i = 0; i < FACTOR; i++) { | ||
410 | if (this.content[2 * i + 1] != null && this.content[2 * i] == null) { | ||
411 | Node<K, V> subNode = (Node<K, V>) this.content[2 * i + 1]; | ||
412 | subNode.checkIntegrity(hashProvider, defaultValue, depth + 1); | ||
413 | } | ||
414 | } | ||
415 | // check the hash | ||
416 | int oldHash = this.cachedHash; | ||
417 | updateHash(); | ||
418 | int newHash = this.cachedHash; | ||
419 | if (oldHash != newHash) { | ||
420 | throw new IllegalStateException("Hash code was not up to date! (old=" + oldHash + ",new=" + newHash + ")"); | ||
421 | } | ||
422 | } | ||
423 | |||
424 | protected void updateHash() { | ||
425 | this.cachedHash = Arrays.hashCode(content); | ||
426 | } | ||
427 | |||
428 | @Override | ||
429 | public int hashCode() { | ||
430 | return this.cachedHash; | ||
431 | } | ||
432 | |||
433 | @Override | ||
434 | public boolean equals(Object obj) { | ||
435 | if (this == obj) | ||
436 | return true; | ||
437 | if (obj == null) | ||
438 | return false; | ||
439 | if (obj instanceof MutableNode<?, ?>) { | ||
440 | MutableNode<?, ?> other = (MutableNode<?, ?>) obj; | ||
441 | return Arrays.deepEquals(this.content, other.content); | ||
442 | } else if (obj instanceof ImmutableNode<?, ?>) { | ||
443 | ImmutableNode<?, ?> other = (ImmutableNode<?, ?>) obj; | ||
444 | return ImmutableNode.compareImmutableMutable(other, this); | ||
445 | } else { | ||
446 | return false; | ||
447 | } | ||
448 | } | ||
449 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/Node.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/Node.java new file mode 100644 index 00000000..d40f980a --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/Node.java | |||
@@ -0,0 +1,85 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.internal; | ||
2 | |||
3 | import java.util.Map; | ||
4 | |||
5 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
6 | |||
7 | public abstract class Node<K,V>{ | ||
8 | public static final int BRANCHING_FACTOR_BITS = 5; | ||
9 | public static final int FACTOR = 1<<BRANCHING_FACTOR_BITS; | ||
10 | protected static final int NUMBER_OF_FACTORS = Integer.SIZE / BRANCHING_FACTOR_BITS; | ||
11 | protected static final int FACTOR_MASK = FACTOR-1; | ||
12 | public static final int EFFECTIVE_BITS = BRANCHING_FACTOR_BITS * NUMBER_OF_FACTORS; | ||
13 | |||
14 | /** | ||
15 | * Calculates the index for the continuous hash. | ||
16 | * @param depth The depth of the node in the tree. | ||
17 | * @return The index of the continuous hash. | ||
18 | */ | ||
19 | protected static int hashDepth(int depth) { | ||
20 | return depth/NUMBER_OF_FACTORS; | ||
21 | } | ||
22 | |||
23 | /** | ||
24 | * Calculates the which segment of a single hash should be used. | ||
25 | * @param depth The depth of the node in the tree. | ||
26 | * @return The segment of a hash code. | ||
27 | */ | ||
28 | protected static int shiftDepth(int depth) { | ||
29 | return depth%NUMBER_OF_FACTORS; | ||
30 | } | ||
31 | /** | ||
32 | * Selects a segments from a complete hash for a given depth. | ||
33 | * @param hash A complete hash. | ||
34 | * @param shiftDepth The index of the segment. | ||
35 | * @return The segment as an integer. | ||
36 | */ | ||
37 | protected static int hashFragment(int hash, int shiftDepth) { | ||
38 | if(shiftDepth<0 || Node.NUMBER_OF_FACTORS<shiftDepth) throw new IllegalArgumentException("Invalid shift depth! valid intervall=[0;5], input="+shiftDepth); | ||
39 | return (hash >>> shiftDepth*BRANCHING_FACTOR_BITS) & FACTOR_MASK; | ||
40 | } | ||
41 | |||
42 | /** | ||
43 | * Returns the hash code for a given depth. It may calculate new hash code, or reuse a hash code calculated for depth-1. | ||
44 | * @param key The key. | ||
45 | * @param hash Hash code for depth-1 | ||
46 | * @param depth The depth. | ||
47 | * @return The new hash code. | ||
48 | */ | ||
49 | protected int newHash(final ContinousHashProvider<? super K> hashProvider, K key, int hash, int depth) { | ||
50 | final int hashDepth = hashDepth(depth); | ||
51 | if(hashDepth>=ContinousHashProvider.MAX_PRACTICAL_DEPTH) { | ||
52 | throw new IllegalArgumentException("Key "+key+" have the clashing hashcode over the practical depth limitation ("+ContinousHashProvider.MAX_PRACTICAL_DEPTH+")!"); | ||
53 | } | ||
54 | return depth%NUMBER_OF_FACTORS == 0? | ||
55 | hashProvider.getHash(key, hashDepth) : | ||
56 | hash; | ||
57 | } | ||
58 | |||
59 | |||
60 | public abstract V getValue(K key, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth); | ||
61 | public abstract Node<K,V> putValue(K key, V value, OldValueBox<V> old, ContinousHashProvider<? super K> hashProvider, V defaultValue, int hash, int depth); | ||
62 | public abstract long getSize(); | ||
63 | |||
64 | abstract MutableNode<K, V> toMutable(); | ||
65 | public abstract ImmutableNode<K, V> toImmutable( | ||
66 | Map<Node<K, V>,ImmutableNode<K, V>> cache); | ||
67 | protected abstract MutableNode<K, V> isMutable(); | ||
68 | /** | ||
69 | * Moves a {@link MapCursor} to its next position. | ||
70 | * @param cursor the cursor | ||
71 | * @return Whether there was a next value to move on. | ||
72 | */ | ||
73 | abstract boolean moveToNext(MapCursor<K,V> cursor); | ||
74 | |||
75 | ///////// FOR printing | ||
76 | public abstract void prettyPrint(StringBuilder builder, int depth, int code); | ||
77 | @Override | ||
78 | public String toString() { | ||
79 | StringBuilder stringBuilder = new StringBuilder(); | ||
80 | prettyPrint(stringBuilder, 0, -1); | ||
81 | return stringBuilder.toString(); | ||
82 | } | ||
83 | public void checkIntegrity(ContinousHashProvider<? super K> hashProvider, V defaultValue, int depth) {} | ||
84 | |||
85 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/OldValueBox.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/OldValueBox.java new file mode 100644 index 00000000..23502857 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/OldValueBox.java | |||
@@ -0,0 +1,19 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.internal; | ||
2 | |||
3 | public class OldValueBox<V>{ | ||
4 | V oldValue; | ||
5 | boolean isSet = false; | ||
6 | |||
7 | public V getOldValue() { | ||
8 | if(!isSet) throw new IllegalStateException(); | ||
9 | isSet = false; | ||
10 | return oldValue; | ||
11 | } | ||
12 | |||
13 | public void setOldValue(V ouldValue) { | ||
14 | if(isSet) throw new IllegalStateException(); | ||
15 | this.oldValue = ouldValue; | ||
16 | isSet = true; | ||
17 | } | ||
18 | |||
19 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/VersionedMapImpl.java b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/VersionedMapImpl.java new file mode 100644 index 00000000..de41e602 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/map/internal/VersionedMapImpl.java | |||
@@ -0,0 +1,171 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.internal; | ||
2 | |||
3 | import java.util.Iterator; | ||
4 | import java.util.LinkedList; | ||
5 | import java.util.List; | ||
6 | |||
7 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
8 | import org.eclipse.viatra.solver.data.map.Cursor; | ||
9 | import org.eclipse.viatra.solver.data.map.DiffCursor; | ||
10 | import org.eclipse.viatra.solver.data.map.VersionedMap; | ||
11 | import org.eclipse.viatra.solver.data.map.VersionedMapStoreImpl; | ||
12 | |||
13 | /** | ||
14 | * Not threadSafe in itself | ||
15 | * @author Oszkar Semerath | ||
16 | * | ||
17 | * @param <K> | ||
18 | * @param <V> | ||
19 | */ | ||
20 | public class VersionedMapImpl<K,V> implements VersionedMap<K,V>{ | ||
21 | protected final VersionedMapStoreImpl<K,V> store; | ||
22 | |||
23 | protected final ContinousHashProvider<K> hashProvider; | ||
24 | protected final V defaultValue; | ||
25 | protected Node<K,V> root; | ||
26 | |||
27 | private OldValueBox<V> oldValueBox = new OldValueBox<>(); | ||
28 | |||
29 | public VersionedMapImpl( | ||
30 | VersionedMapStoreImpl<K,V> store, | ||
31 | ContinousHashProvider<K> hashProvider, | ||
32 | V defaultValue) | ||
33 | { | ||
34 | this.store = store; | ||
35 | this.hashProvider = hashProvider; | ||
36 | this.defaultValue = defaultValue; | ||
37 | this.root = null; | ||
38 | } | ||
39 | public VersionedMapImpl( | ||
40 | VersionedMapStoreImpl<K,V> store, | ||
41 | ContinousHashProvider<K> hashProvider, | ||
42 | V defaultValue, Node<K,V> data) | ||
43 | { | ||
44 | this.store = store; | ||
45 | this.hashProvider = hashProvider; | ||
46 | this.defaultValue = defaultValue; | ||
47 | this.root = data; | ||
48 | } | ||
49 | |||
50 | public V getDefaultValue() { | ||
51 | return defaultValue; | ||
52 | } | ||
53 | public ContinousHashProvider<K> getHashProvider() { | ||
54 | return hashProvider; | ||
55 | } | ||
56 | @Override | ||
57 | public V put(K key, V value) { | ||
58 | if(root!=null) { | ||
59 | root = root.putValue(key, value, oldValueBox, hashProvider, defaultValue, hashProvider.getHash(key, 0), 0); | ||
60 | return oldValueBox.getOldValue(); | ||
61 | } else { | ||
62 | root = MutableNode.initialize(key, value, hashProvider, defaultValue); | ||
63 | return defaultValue; | ||
64 | } | ||
65 | } | ||
66 | |||
67 | @Override | ||
68 | public void putAll(Cursor<K, V> cursor) { | ||
69 | if(cursor.getDependingMaps().contains(this)) { | ||
70 | List<K> keys = new LinkedList<>(); | ||
71 | List<V> values = new LinkedList<>(); | ||
72 | while(cursor.move()) { | ||
73 | keys.add(cursor.getKey()); | ||
74 | values.add(cursor.getValue()); | ||
75 | } | ||
76 | Iterator<K> keyIterator = keys.iterator(); | ||
77 | Iterator<V> valueIterator = values.iterator(); | ||
78 | while(keyIterator.hasNext()) { | ||
79 | this.put(keyIterator.next(), valueIterator.next()); | ||
80 | } | ||
81 | } else { | ||
82 | while(cursor.move()) { | ||
83 | this.put(cursor.getKey(), cursor.getValue()); | ||
84 | } | ||
85 | } | ||
86 | } | ||
87 | |||
88 | @Override | ||
89 | public V get(K key) { | ||
90 | if(root!=null) { | ||
91 | return root.getValue(key, hashProvider, defaultValue, hashProvider.getHash(key, 0), 0); | ||
92 | } else { | ||
93 | return defaultValue; | ||
94 | } | ||
95 | } | ||
96 | @Override | ||
97 | public long getSize() { | ||
98 | if(root == null) { | ||
99 | return 0; | ||
100 | } else { | ||
101 | return root.getSize(); | ||
102 | } | ||
103 | } | ||
104 | |||
105 | @Override | ||
106 | public Cursor<K, V> getAll() { | ||
107 | return new MapCursor<>(this.root,this); | ||
108 | } | ||
109 | @Override | ||
110 | public DiffCursor<K, V> getDiffCursor(long toVersion) { | ||
111 | Cursor<K, V> fromCursor = this.getAll(); | ||
112 | VersionedMap<K, V> toMap = this.store.createMap(toVersion); | ||
113 | Cursor<K, V> toCursor = toMap.getAll(); | ||
114 | return new MapDiffCursor<>(this.hashProvider,this.defaultValue, fromCursor, toCursor); | ||
115 | |||
116 | } | ||
117 | |||
118 | |||
119 | @Override | ||
120 | public long commit() { | ||
121 | return this.store.commit(root,this); | ||
122 | } | ||
123 | public void setRoot(Node<K, V> root) { | ||
124 | this.root = root; | ||
125 | } | ||
126 | |||
127 | @Override | ||
128 | public void restore(long state) { | ||
129 | root = this.store.revert(state); | ||
130 | } | ||
131 | |||
132 | @Override | ||
133 | public int hashCode() { | ||
134 | final int prime = 31; | ||
135 | int result = 1; | ||
136 | result = prime * result + ((root == null) ? 0 : root.hashCode()); | ||
137 | return result; | ||
138 | } | ||
139 | |||
140 | @Override | ||
141 | public boolean equals(Object obj) { | ||
142 | if (this == obj) | ||
143 | return true; | ||
144 | if (obj == null) | ||
145 | return false; | ||
146 | if (getClass() != obj.getClass()) | ||
147 | return false; | ||
148 | VersionedMapImpl<?,?> other = (VersionedMapImpl<?,?>) obj; | ||
149 | if (root == null) { | ||
150 | if (other.root != null) | ||
151 | return false; | ||
152 | } else if (!root.equals(other.root)) | ||
153 | return false; | ||
154 | return true; | ||
155 | } | ||
156 | public void prettyPrint() { | ||
157 | StringBuilder s = new StringBuilder(); | ||
158 | if(this.root != null) { | ||
159 | this.root.prettyPrint(s, 0, -1); | ||
160 | System.out.println(s.toString()); | ||
161 | } else { | ||
162 | System.out.println("empty tree"); | ||
163 | } | ||
164 | } | ||
165 | public void checkIntegrity() { | ||
166 | if(this.root != null) { | ||
167 | this.root.checkIntegrity(hashProvider, defaultValue, 0); | ||
168 | } | ||
169 | } | ||
170 | |||
171 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/model/Model.java b/store/src/main/java/org/eclipse/viatra/solver/data/model/Model.java new file mode 100644 index 00000000..2b21e3e7 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/model/Model.java | |||
@@ -0,0 +1,20 @@ | |||
1 | package org.eclipse.viatra.solver.data.model; | ||
2 | |||
3 | import java.util.Set; | ||
4 | |||
5 | import org.eclipse.viatra.solver.data.map.Cursor; | ||
6 | import org.eclipse.viatra.solver.data.map.Versioned; | ||
7 | import org.eclipse.viatra.solver.data.model.representation.DataRepresentation; | ||
8 | |||
9 | public interface Model extends Versioned{ | ||
10 | @SuppressWarnings("squid:S1452") | ||
11 | Set<DataRepresentation<?, ?>> getDataRepresentations(); | ||
12 | |||
13 | <K,V> V get(DataRepresentation<K,V> representation, K key); | ||
14 | <K,V> Cursor<K,V> getAll(DataRepresentation<K,V> representation); | ||
15 | <K,V> V put(DataRepresentation<K,V> representation, K key, V value); | ||
16 | <K,V> void putAll(DataRepresentation<K,V> representation, Cursor<K,V> cursor); | ||
17 | <K,V> long getSize(DataRepresentation<K,V> representation); | ||
18 | |||
19 | ModelDiffCursor getDiffCursor(long to); | ||
20 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/model/ModelCursor.java b/store/src/main/java/org/eclipse/viatra/solver/data/model/ModelCursor.java new file mode 100644 index 00000000..3157c9f0 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/model/ModelCursor.java | |||
@@ -0,0 +1,25 @@ | |||
1 | package org.eclipse.viatra.solver.data.model; | ||
2 | |||
3 | import java.util.Map; | ||
4 | |||
5 | import org.eclipse.viatra.solver.data.map.Cursor; | ||
6 | import org.eclipse.viatra.solver.data.model.representation.DataRepresentation; | ||
7 | |||
8 | public class ModelCursor { | ||
9 | final Map<DataRepresentation<?, ?>,Cursor<?,?>> cursors; | ||
10 | |||
11 | public ModelCursor(Map<DataRepresentation<?, ?>, Cursor<?, ?>> cursors) { | ||
12 | super(); | ||
13 | this.cursors = cursors; | ||
14 | } | ||
15 | |||
16 | @SuppressWarnings("unchecked") | ||
17 | public <K,V> Cursor<K,V> getCursor(DataRepresentation<K, V> representation) { | ||
18 | Cursor<?, ?> cursor = cursors.get(representation); | ||
19 | if(cursor != null) { | ||
20 | return (Cursor<K, V>) cursor; | ||
21 | } else { | ||
22 | throw new IllegalArgumentException("ModelCursor does not contain cursor for representation "+representation); | ||
23 | } | ||
24 | } | ||
25 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/model/ModelDiffCursor.java b/store/src/main/java/org/eclipse/viatra/solver/data/model/ModelDiffCursor.java new file mode 100644 index 00000000..d3551e47 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/model/ModelDiffCursor.java | |||
@@ -0,0 +1,26 @@ | |||
1 | package org.eclipse.viatra.solver.data.model; | ||
2 | |||
3 | import java.util.Map; | ||
4 | |||
5 | import org.eclipse.viatra.solver.data.map.Cursor; | ||
6 | import org.eclipse.viatra.solver.data.map.DiffCursor; | ||
7 | import org.eclipse.viatra.solver.data.model.representation.DataRepresentation; | ||
8 | |||
9 | public class ModelDiffCursor { | ||
10 | final Map<DataRepresentation<?, ?>,DiffCursor<?,?>> diffcursors; | ||
11 | |||
12 | public ModelDiffCursor(Map<DataRepresentation<?, ?>, DiffCursor<?, ?>> diffcursors) { | ||
13 | super(); | ||
14 | this.diffcursors = diffcursors; | ||
15 | } | ||
16 | |||
17 | @SuppressWarnings("unchecked") | ||
18 | public <K,V> DiffCursor<K,V> getCursor(DataRepresentation<K, V> representation) { | ||
19 | Cursor<?, ?> cursor = diffcursors.get(representation); | ||
20 | if(cursor != null) { | ||
21 | return (DiffCursor<K, V>) cursor; | ||
22 | } else { | ||
23 | throw new IllegalArgumentException("ModelCursor does not contain cursor for representation "+representation); | ||
24 | } | ||
25 | } | ||
26 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/model/ModelStore.java b/store/src/main/java/org/eclipse/viatra/solver/data/model/ModelStore.java new file mode 100644 index 00000000..35ac72b5 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/model/ModelStore.java | |||
@@ -0,0 +1,16 @@ | |||
1 | package org.eclipse.viatra.solver.data.model; | ||
2 | |||
3 | import java.util.Set; | ||
4 | |||
5 | import org.eclipse.viatra.solver.data.model.representation.DataRepresentation; | ||
6 | |||
7 | public interface ModelStore { | ||
8 | @SuppressWarnings("squid:S1452") | ||
9 | Set<DataRepresentation<?, ?>> getDataRepresentations(); | ||
10 | |||
11 | Model createModel(); | ||
12 | Model createModel(long state); | ||
13 | |||
14 | Set<Long> getStates(); | ||
15 | ModelDiffCursor getDiffCursor(long from, long to); | ||
16 | } \ No newline at end of file | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/model/ModelStoreImpl.java b/store/src/main/java/org/eclipse/viatra/solver/data/model/ModelStoreImpl.java new file mode 100644 index 00000000..f5e3b141 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/model/ModelStoreImpl.java | |||
@@ -0,0 +1,127 @@ | |||
1 | package org.eclipse.viatra.solver.data.model; | ||
2 | |||
3 | import java.util.HashMap; | ||
4 | import java.util.HashSet; | ||
5 | import java.util.LinkedList; | ||
6 | import java.util.List; | ||
7 | import java.util.Map; | ||
8 | import java.util.Map.Entry; | ||
9 | import java.util.Set; | ||
10 | |||
11 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
12 | import org.eclipse.viatra.solver.data.map.DiffCursor; | ||
13 | import org.eclipse.viatra.solver.data.map.VersionedMap; | ||
14 | import org.eclipse.viatra.solver.data.map.VersionedMapStore; | ||
15 | import org.eclipse.viatra.solver.data.map.VersionedMapStoreImpl; | ||
16 | import org.eclipse.viatra.solver.data.model.internal.ModelImpl; | ||
17 | import org.eclipse.viatra.solver.data.model.internal.SimilarRelationEquivalenceClass; | ||
18 | import org.eclipse.viatra.solver.data.model.representation.AuxilaryData; | ||
19 | import org.eclipse.viatra.solver.data.model.representation.DataRepresentation; | ||
20 | import org.eclipse.viatra.solver.data.model.representation.Relation; | ||
21 | |||
22 | public class ModelStoreImpl implements ModelStore { | ||
23 | |||
24 | private final Map<DataRepresentation<?, ?>, VersionedMapStore<?, ?>> stores; | ||
25 | |||
26 | public ModelStoreImpl(Set<DataRepresentation<?, ?>> dataRepresentations) { | ||
27 | stores = initStores(dataRepresentations); | ||
28 | } | ||
29 | |||
30 | private Map<DataRepresentation<?, ?>, VersionedMapStore<?, ?>> initStores( | ||
31 | Set<DataRepresentation<?, ?>> dataRepresentations) { | ||
32 | Map<DataRepresentation<?, ?>, VersionedMapStore<?, ?>> result = new HashMap<>(); | ||
33 | |||
34 | Map<SimilarRelationEquivalenceClass, List<Relation<?>>> symbolRepresentationsPerHashPerArity = new HashMap<>(); | ||
35 | |||
36 | for (DataRepresentation<?, ?> dataRepresentation : dataRepresentations) { | ||
37 | if (dataRepresentation instanceof Relation<?>) { | ||
38 | Relation<?> symbolRepresentation = (Relation<?>) dataRepresentation; | ||
39 | addOrCreate(symbolRepresentationsPerHashPerArity, | ||
40 | new SimilarRelationEquivalenceClass(symbolRepresentation), symbolRepresentation); | ||
41 | } else if (dataRepresentation instanceof AuxilaryData<?, ?>) { | ||
42 | VersionedMapStoreImpl<?, ?> store = new VersionedMapStoreImpl<>(dataRepresentation.getHashProvider(), | ||
43 | dataRepresentation.getDefaultValue()); | ||
44 | result.put(dataRepresentation, store); | ||
45 | } else { | ||
46 | throw new UnsupportedOperationException( | ||
47 | "Model store does not have strategy to use " + dataRepresentation.getClass() + "!"); | ||
48 | } | ||
49 | } | ||
50 | for (List<Relation<?>> symbolGroup : symbolRepresentationsPerHashPerArity.values()) { | ||
51 | initRepresentationGroup(result, symbolGroup); | ||
52 | } | ||
53 | |||
54 | return result; | ||
55 | } | ||
56 | |||
57 | private void initRepresentationGroup(Map<DataRepresentation<?, ?>, VersionedMapStore<?, ?>> result, | ||
58 | List<Relation<?>> symbolGroup) { | ||
59 | final ContinousHashProvider<Tuple> hashProvider = symbolGroup.get(0).getHashProvider(); | ||
60 | final Object defaultValue = symbolGroup.get(0).getDefaultValue(); | ||
61 | |||
62 | List<VersionedMapStore<Tuple, Object>> maps = VersionedMapStoreImpl | ||
63 | .createSharedVersionedMapStores(symbolGroup.size(), hashProvider, defaultValue); | ||
64 | |||
65 | for (int i = 0; i < symbolGroup.size(); i++) { | ||
66 | result.put(symbolGroup.get(i), maps.get(i)); | ||
67 | } | ||
68 | } | ||
69 | |||
70 | private static <K, V> void addOrCreate(Map<K, List<V>> map, K key, V value) { | ||
71 | List<V> list; | ||
72 | if (map.containsKey(key)) { | ||
73 | list = map.get(key); | ||
74 | } else { | ||
75 | list = new LinkedList<>(); | ||
76 | map.put(key, list); | ||
77 | } | ||
78 | list.add(value); | ||
79 | } | ||
80 | |||
81 | @Override | ||
82 | public Set<DataRepresentation<?, ?>> getDataRepresentations() { | ||
83 | return this.stores.keySet(); | ||
84 | } | ||
85 | |||
86 | @Override | ||
87 | public ModelImpl createModel() { | ||
88 | Map<DataRepresentation<?, ?>, VersionedMap<?, ?>> maps = new HashMap<>(); | ||
89 | for (Entry<DataRepresentation<?, ?>, VersionedMapStore<?, ?>> entry : this.stores.entrySet()) { | ||
90 | maps.put(entry.getKey(), entry.getValue().createMap()); | ||
91 | } | ||
92 | return new ModelImpl(this, maps); | ||
93 | } | ||
94 | |||
95 | @Override | ||
96 | public synchronized ModelImpl createModel(long state) { | ||
97 | Map<DataRepresentation<?, ?>, VersionedMap<?, ?>> maps = new HashMap<>(); | ||
98 | for (Entry<DataRepresentation<?, ?>, VersionedMapStore<?, ?>> entry : this.stores.entrySet()) { | ||
99 | maps.put(entry.getKey(), entry.getValue().createMap(state)); | ||
100 | } | ||
101 | return new ModelImpl(this, maps); | ||
102 | } | ||
103 | |||
104 | @Override | ||
105 | @SuppressWarnings("squid:S1751") | ||
106 | public synchronized Set<Long> getStates() { | ||
107 | // if not empty, return first | ||
108 | for(VersionedMapStore<?, ?> store : stores.values()) { | ||
109 | return new HashSet<>(store.getStates()); | ||
110 | } | ||
111 | // if empty | ||
112 | Set<Long> result = new HashSet<>(); | ||
113 | result.add(0l); | ||
114 | return result; | ||
115 | } | ||
116 | |||
117 | @Override | ||
118 | public synchronized ModelDiffCursor getDiffCursor(long from, long to) { | ||
119 | Map<DataRepresentation<?, ?>,DiffCursor<?,?>> diffcursors = new HashMap<>(); | ||
120 | for(Entry<DataRepresentation<?, ?>, VersionedMapStore<?, ?>> entry : stores.entrySet()) { | ||
121 | DataRepresentation<?, ?> representation = entry.getKey(); | ||
122 | DiffCursor<?, ?> diffCursor = entry.getValue().getDiffCursor(from, to); | ||
123 | diffcursors.put(representation, diffCursor); | ||
124 | } | ||
125 | return new ModelDiffCursor(diffcursors); | ||
126 | } | ||
127 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/model/Tuple.java b/store/src/main/java/org/eclipse/viatra/solver/data/model/Tuple.java new file mode 100644 index 00000000..ca6548a4 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/model/Tuple.java | |||
@@ -0,0 +1,148 @@ | |||
1 | package org.eclipse.viatra.solver.data.model; | ||
2 | |||
3 | import java.util.ArrayList; | ||
4 | import java.util.Arrays; | ||
5 | import java.util.List; | ||
6 | |||
7 | public abstract class Tuple { | ||
8 | private static final int CUSTOMTUPLESIZE = 2; | ||
9 | protected static final List<Tuple1> tuple1Cash = new ArrayList<>(1024); | ||
10 | |||
11 | public abstract int getSize(); | ||
12 | public abstract int get(int element); | ||
13 | public abstract int[] toArray(); | ||
14 | |||
15 | @Override | ||
16 | public String toString() { | ||
17 | StringBuilder b = new StringBuilder(); | ||
18 | b.append("["); | ||
19 | for(int i = 0; i<getSize(); i++) { | ||
20 | if(i!=0) { | ||
21 | b.append(","); | ||
22 | } | ||
23 | b.append(get(i)); | ||
24 | } | ||
25 | b.append("]"); | ||
26 | return b.toString(); | ||
27 | } | ||
28 | |||
29 | public static Tuple1 of1(int value) { | ||
30 | if(value < tuple1Cash.size()) { | ||
31 | return tuple1Cash.get(value); | ||
32 | } else { | ||
33 | Tuple1 newlyCreated = null; | ||
34 | while(value >= tuple1Cash.size()) { | ||
35 | newlyCreated = new Tuple1(tuple1Cash.size()); | ||
36 | tuple1Cash.add(newlyCreated); | ||
37 | } | ||
38 | return newlyCreated; | ||
39 | } | ||
40 | } | ||
41 | |||
42 | public static Tuple of(int... values) { | ||
43 | if(values.length == 0) { | ||
44 | return new Tuple0(); | ||
45 | } else if(values.length == 1) { | ||
46 | return of1(values[0]); | ||
47 | } else if(values.length == 2) { | ||
48 | return new Tuple2(values[0],values[1]); | ||
49 | } else return new TupleN(values); | ||
50 | } | ||
51 | |||
52 | protected IllegalArgumentException doesNotContain(int element) { | ||
53 | return new IllegalArgumentException("Tuple does not contain element "+element); | ||
54 | } | ||
55 | |||
56 | public static class Tuple0 extends Tuple{ | ||
57 | protected Tuple0() { } | ||
58 | @Override public int getSize() { return 0; } | ||
59 | @Override public int get(int element) { | ||
60 | throw doesNotContain(element); | ||
61 | } | ||
62 | @Override public int[] toArray() {return new int[]{};} | ||
63 | @Override public int hashCode() { return TupleHashProvider.singleton().getHash(this, 0); } | ||
64 | @Override | ||
65 | public boolean equals(Object obj) { | ||
66 | if (this == obj) | ||
67 | return true; | ||
68 | if (obj == null) | ||
69 | return false; | ||
70 | if (getClass() != obj.getClass()) | ||
71 | return false; | ||
72 | return true; | ||
73 | } | ||
74 | } | ||
75 | public static class Tuple1 extends Tuple{ | ||
76 | final int value0; | ||
77 | protected Tuple1(int value0) { this.value0 = value0; } | ||
78 | @Override public int getSize() { return 1; } | ||
79 | @Override public int get(int element) { | ||
80 | if(element == 0) return value0; | ||
81 | throw doesNotContain(element); | ||
82 | } | ||
83 | @Override public int[] toArray() {return new int[]{ value0 };} | ||
84 | @Override public int hashCode() { return TupleHashProvider.singleton().getHash(this, 0); } | ||
85 | @Override | ||
86 | public boolean equals(Object obj) { | ||
87 | if (this == obj) | ||
88 | return true; | ||
89 | if (obj == null) | ||
90 | return false; | ||
91 | if (getClass() != obj.getClass()) | ||
92 | return false; | ||
93 | Tuple1 other = (Tuple1) obj; | ||
94 | return value0 == other.value0; | ||
95 | } | ||
96 | } | ||
97 | public static class Tuple2 extends Tuple{ | ||
98 | final int value0; | ||
99 | final int value1; | ||
100 | protected Tuple2(int value0, int value1) { this.value0 = value0; this.value1 = value1; } | ||
101 | @Override public int getSize() { return 2; } | ||
102 | @Override public int get(int element) { | ||
103 | if(element == 0) return value0; | ||
104 | else if(element == 1) return value1; | ||
105 | throw doesNotContain(element); | ||
106 | } | ||
107 | @Override public int[] toArray() {return new int[]{ value0,value1 };} | ||
108 | @Override public int hashCode() { return TupleHashProvider.singleton().getHash(this, 0); } | ||
109 | @Override | ||
110 | public boolean equals(Object obj) { | ||
111 | if (this == obj) | ||
112 | return true; | ||
113 | if (obj == null) | ||
114 | return false; | ||
115 | if (getClass() != obj.getClass()) | ||
116 | return false; | ||
117 | Tuple2 other = (Tuple2) obj; | ||
118 | return value0 == other.value0 && value1 == other.value1; | ||
119 | } | ||
120 | } | ||
121 | public static class TupleN extends Tuple{ | ||
122 | final int[] values; | ||
123 | protected TupleN(int[] values) { | ||
124 | if(values.length<CUSTOMTUPLESIZE) | ||
125 | throw new IllegalArgumentException(); | ||
126 | this.values = Arrays.copyOf(values, values.length); | ||
127 | } | ||
128 | @Override public int getSize() { return values.length; } | ||
129 | @Override public int get(int element) { | ||
130 | if(0<=element && element < values.length) { | ||
131 | return values[element]; | ||
132 | } else throw doesNotContain(element); | ||
133 | } | ||
134 | @Override public int[] toArray() { return values; } | ||
135 | @Override public int hashCode() { return TupleHashProvider.singleton().getHash(this, 0); } | ||
136 | @Override | ||
137 | public boolean equals(Object obj) { | ||
138 | if (this == obj) | ||
139 | return true; | ||
140 | if (obj == null) | ||
141 | return false; | ||
142 | if (getClass() != obj.getClass()) | ||
143 | return false; | ||
144 | TupleN other = (TupleN) obj; | ||
145 | return Arrays.equals(values, other.values); | ||
146 | } | ||
147 | } | ||
148 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/model/TupleHashProvider.java b/store/src/main/java/org/eclipse/viatra/solver/data/model/TupleHashProvider.java new file mode 100644 index 00000000..6c37aa37 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/model/TupleHashProvider.java | |||
@@ -0,0 +1,65 @@ | |||
1 | package org.eclipse.viatra.solver.data.model; | ||
2 | |||
3 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
4 | |||
5 | public class TupleHashProvider implements ContinousHashProvider<Tuple> { | ||
6 | protected static TupleHashProvider instance; | ||
7 | |||
8 | public static TupleHashProvider singleton() { | ||
9 | if (instance == null) { | ||
10 | instance = new TupleHashProvider(); | ||
11 | } | ||
12 | return instance; | ||
13 | } | ||
14 | |||
15 | protected static final int[] primes = new int[] { 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, | ||
16 | 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, | ||
17 | 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, | ||
18 | 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, | ||
19 | 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, | ||
20 | 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, | ||
21 | 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, | ||
22 | 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, | ||
23 | 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, | ||
24 | 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, | ||
25 | 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, | ||
26 | 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543, 1549, | ||
27 | 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, | ||
28 | 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, | ||
29 | 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, | ||
30 | 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, 2089, | ||
31 | 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243, | ||
32 | 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, | ||
33 | 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, | ||
34 | 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, | ||
35 | 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, 2791, | ||
36 | 2797, 2801, 2803, 2819, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, 3203, 3209, 3217, | ||
37 | 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, | ||
38 | 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, | ||
39 | 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, | ||
40 | 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, | ||
41 | 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911 }; | ||
42 | |||
43 | protected static final long LARGESTPRIME30BITS = 1073741789; | ||
44 | |||
45 | public TupleHashProvider() { | ||
46 | if (primes.length < MAX_PRACTICAL_DEPTH) { | ||
47 | throw new UnsupportedOperationException( | ||
48 | "Not enough prime numbers to support the practical depth of continuous hash!"); | ||
49 | } | ||
50 | } | ||
51 | |||
52 | @Override | ||
53 | public int getHash(Tuple key, int index) { | ||
54 | if (index >= primes.length) { | ||
55 | throw new IllegalArgumentException("Not enough prime numbers to support index"); | ||
56 | } | ||
57 | long accumulator = 0; | ||
58 | final int prime = primes[index]; | ||
59 | for (int i = 0; i < key.getSize(); i++) { | ||
60 | accumulator = (prime * accumulator + key.get(i)) % LARGESTPRIME30BITS; | ||
61 | } | ||
62 | |||
63 | return (int) accumulator; | ||
64 | } | ||
65 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/model/TupleHashProviderBitMagic.java b/store/src/main/java/org/eclipse/viatra/solver/data/model/TupleHashProviderBitMagic.java new file mode 100644 index 00000000..2a514d66 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/model/TupleHashProviderBitMagic.java | |||
@@ -0,0 +1,28 @@ | |||
1 | package org.eclipse.viatra.solver.data.model; | ||
2 | |||
3 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
4 | |||
5 | public class TupleHashProviderBitMagic implements ContinousHashProvider<Tuple> { | ||
6 | |||
7 | @Override | ||
8 | public int getHash(Tuple key, int index) { | ||
9 | if(key.getSize() == 1) { | ||
10 | return key.get(0); | ||
11 | } | ||
12 | |||
13 | int result = 0; | ||
14 | final int startBitIndex = index*30; | ||
15 | final int finalBitIndex = startBitIndex+30; | ||
16 | final int arity = key.getSize(); | ||
17 | |||
18 | for(int i = startBitIndex; i<=finalBitIndex; i++) { | ||
19 | final int selectedKey = key.get(i%arity); | ||
20 | final int selectedPosition = 1<<(i/arity); | ||
21 | if((selectedKey&selectedPosition) != 0) { | ||
22 | result |= 1<<(i%30); | ||
23 | } | ||
24 | } | ||
25 | |||
26 | return result; | ||
27 | } | ||
28 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/model/internal/ModelImpl.java b/store/src/main/java/org/eclipse/viatra/solver/data/model/internal/ModelImpl.java new file mode 100644 index 00000000..6d7f4e97 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/model/internal/ModelImpl.java | |||
@@ -0,0 +1,124 @@ | |||
1 | package org.eclipse.viatra.solver.data.model.internal; | ||
2 | |||
3 | import java.util.HashMap; | ||
4 | import java.util.Map; | ||
5 | import java.util.Set; | ||
6 | |||
7 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
8 | import org.eclipse.viatra.solver.data.map.Cursor; | ||
9 | import org.eclipse.viatra.solver.data.map.DiffCursor; | ||
10 | import org.eclipse.viatra.solver.data.map.VersionedMap; | ||
11 | import org.eclipse.viatra.solver.data.map.internal.MapDiffCursor; | ||
12 | import org.eclipse.viatra.solver.data.model.Model; | ||
13 | import org.eclipse.viatra.solver.data.model.ModelDiffCursor; | ||
14 | import org.eclipse.viatra.solver.data.model.ModelStore; | ||
15 | import org.eclipse.viatra.solver.data.model.representation.DataRepresentation; | ||
16 | |||
17 | public class ModelImpl implements Model { | ||
18 | private final ModelStore store; | ||
19 | private final Map<DataRepresentation<?, ?>, VersionedMap<?, ?>> maps; | ||
20 | |||
21 | public ModelImpl(ModelStore store, Map<DataRepresentation<?, ?>, VersionedMap<?, ?>> maps) { | ||
22 | this.store = store; | ||
23 | this.maps = maps; | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public Set<DataRepresentation<?, ?>> getDataRepresentations() { | ||
28 | return maps.keySet(); | ||
29 | } | ||
30 | |||
31 | @SuppressWarnings("unchecked") | ||
32 | private <K, V> VersionedMap<K, V> getMap(DataRepresentation<K, V> representation) { | ||
33 | if (maps.containsKey(representation)) { | ||
34 | return (VersionedMap<K, V>) maps.get(representation); | ||
35 | } else { | ||
36 | throw new IllegalArgumentException("Model does have representation " + representation); | ||
37 | } | ||
38 | } | ||
39 | |||
40 | private <K, V> VersionedMap<K, V> getMapValidateKey(DataRepresentation<K, V> representation, K key) { | ||
41 | if (representation.isValidKey(key)) { | ||
42 | return getMap(representation); | ||
43 | } else { | ||
44 | throw new IllegalArgumentException( | ||
45 | "Key is not valid for representation! (representation=" + representation + ", key=" + key + ");"); | ||
46 | } | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public <K, V> V get(DataRepresentation<K, V> representation, K key) { | ||
51 | return getMapValidateKey(representation, key).get(key); | ||
52 | } | ||
53 | |||
54 | @Override | ||
55 | public <K, V> Cursor<K, V> getAll(DataRepresentation<K, V> representation) { | ||
56 | return getMap(representation).getAll(); | ||
57 | } | ||
58 | |||
59 | @Override | ||
60 | public <K, V> V put(DataRepresentation<K, V> representation, K key, V value) { | ||
61 | return getMapValidateKey(representation, key).put(key, value); | ||
62 | } | ||
63 | |||
64 | @Override | ||
65 | public <K, V> void putAll(DataRepresentation<K, V> representation, Cursor<K, V> cursor) { | ||
66 | getMap(representation).putAll(cursor); | ||
67 | } | ||
68 | |||
69 | @Override | ||
70 | public <K, V> long getSize(DataRepresentation<K, V> representation) { | ||
71 | return getMap(representation).getSize(); | ||
72 | } | ||
73 | |||
74 | @Override | ||
75 | public ModelDiffCursor getDiffCursor(long to) { | ||
76 | Model toModel = store.createModel(to); | ||
77 | Map<DataRepresentation<?, ?>, DiffCursor<?, ?>> diffCursors = new HashMap<>(); | ||
78 | for (DataRepresentation<?, ?> representation : this.maps.keySet()) { | ||
79 | MapDiffCursor<?, ?> diffCursor = constructDiffCursor(toModel, representation); | ||
80 | diffCursors.put(representation, diffCursor); | ||
81 | } | ||
82 | return new ModelDiffCursor(diffCursors); | ||
83 | } | ||
84 | |||
85 | private <K, V> MapDiffCursor<K, V> constructDiffCursor(Model toModel, DataRepresentation<K, V> representation) { | ||
86 | @SuppressWarnings("unchecked") | ||
87 | Cursor<K, V> fromCursor = (Cursor<K, V>) this.maps.get(representation).getAll(); | ||
88 | Cursor<K, V> toCursor = toModel.getAll(representation); | ||
89 | |||
90 | ContinousHashProvider<K> hashProvider = representation.getHashProvider(); | ||
91 | V defaultValue = representation.getDefaultValue(); | ||
92 | return new MapDiffCursor<>(hashProvider, defaultValue, fromCursor, toCursor); | ||
93 | } | ||
94 | |||
95 | @Override | ||
96 | public long commit() { | ||
97 | long version = 0; | ||
98 | boolean versionSet = false; | ||
99 | for (VersionedMap<?, ?> map : maps.values()) { | ||
100 | long newVersion = map.commit(); | ||
101 | if (versionSet) { | ||
102 | if (version != newVersion) { | ||
103 | throw new IllegalStateException( | ||
104 | "Maps in model have different versions! (" + version + " and" + newVersion + ")"); | ||
105 | } | ||
106 | } else { | ||
107 | version = newVersion; | ||
108 | versionSet = true; | ||
109 | } | ||
110 | } | ||
111 | return version; | ||
112 | } | ||
113 | |||
114 | @Override | ||
115 | public void restore(long state) { | ||
116 | if(store.getStates().contains(state)) { | ||
117 | for (VersionedMap<?, ?> map : maps.values()) { | ||
118 | map.restore(state); | ||
119 | } | ||
120 | } else { | ||
121 | throw new IllegalArgumentException("Map does not contain state "+state+"!"); | ||
122 | } | ||
123 | } | ||
124 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/model/internal/SimilarRelationEquivalenceClass.java b/store/src/main/java/org/eclipse/viatra/solver/data/model/internal/SimilarRelationEquivalenceClass.java new file mode 100644 index 00000000..7054e4db --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/model/internal/SimilarRelationEquivalenceClass.java | |||
@@ -0,0 +1,33 @@ | |||
1 | package org.eclipse.viatra.solver.data.model.internal; | ||
2 | |||
3 | import java.util.Objects; | ||
4 | |||
5 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
6 | import org.eclipse.viatra.solver.data.model.Tuple; | ||
7 | import org.eclipse.viatra.solver.data.model.representation.Relation; | ||
8 | |||
9 | public class SimilarRelationEquivalenceClass { | ||
10 | final ContinousHashProvider<Tuple> hashProvider; | ||
11 | final Object defaultValue; | ||
12 | final int arity; | ||
13 | public SimilarRelationEquivalenceClass(Relation<?> representation) { | ||
14 | this.hashProvider = representation.getHashProvider(); | ||
15 | this.defaultValue = representation.getDefaultValue(); | ||
16 | this.arity = representation.getArity(); | ||
17 | } | ||
18 | @Override | ||
19 | public int hashCode() { | ||
20 | return Objects.hash(arity, defaultValue, hashProvider); | ||
21 | } | ||
22 | @Override | ||
23 | public boolean equals(Object obj) { | ||
24 | if (this == obj) | ||
25 | return true; | ||
26 | if (!(obj instanceof SimilarRelationEquivalenceClass)) | ||
27 | return false; | ||
28 | SimilarRelationEquivalenceClass other = (SimilarRelationEquivalenceClass) obj; | ||
29 | return arity == other.arity && Objects.equals(defaultValue, other.defaultValue) | ||
30 | && Objects.equals(hashProvider, other.hashProvider); | ||
31 | } | ||
32 | |||
33 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/model/representation/AuxilaryData.java b/store/src/main/java/org/eclipse/viatra/solver/data/model/representation/AuxilaryData.java new file mode 100644 index 00000000..7fc79348 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/model/representation/AuxilaryData.java | |||
@@ -0,0 +1,22 @@ | |||
1 | package org.eclipse.viatra.solver.data.model.representation; | ||
2 | |||
3 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
4 | |||
5 | public class AuxilaryData<K,V> extends DataRepresentation<K, V> { | ||
6 | private final String name; | ||
7 | |||
8 | public AuxilaryData(String name, ContinousHashProvider<K> hashProvider, V defaultValue) { | ||
9 | super(hashProvider, defaultValue); | ||
10 | this.name = name; | ||
11 | } | ||
12 | |||
13 | @Override | ||
14 | public String getName() { | ||
15 | return name; | ||
16 | } | ||
17 | |||
18 | @Override | ||
19 | public boolean isValidKey(K key) { | ||
20 | return true; | ||
21 | } | ||
22 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/model/representation/DataRepresentation.java b/store/src/main/java/org/eclipse/viatra/solver/data/model/representation/DataRepresentation.java new file mode 100644 index 00000000..fd48eb94 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/model/representation/DataRepresentation.java | |||
@@ -0,0 +1,24 @@ | |||
1 | package org.eclipse.viatra.solver.data.model.representation; | ||
2 | |||
3 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
4 | |||
5 | public abstract class DataRepresentation<K, V> { | ||
6 | protected final ContinousHashProvider<K> hashProvider; | ||
7 | protected final V defaultValue; | ||
8 | |||
9 | protected DataRepresentation(ContinousHashProvider<K> hashProvider, V defaultValue) { | ||
10 | this.hashProvider = hashProvider; | ||
11 | this.defaultValue = defaultValue; | ||
12 | } | ||
13 | |||
14 | public abstract String getName(); | ||
15 | |||
16 | public ContinousHashProvider<K> getHashProvider() { | ||
17 | return hashProvider; | ||
18 | } | ||
19 | public abstract boolean isValidKey(K key); | ||
20 | |||
21 | public V getDefaultValue() { | ||
22 | return defaultValue; | ||
23 | } | ||
24 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/model/representation/Relation.java b/store/src/main/java/org/eclipse/viatra/solver/data/model/representation/Relation.java new file mode 100644 index 00000000..eafb5c56 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/model/representation/Relation.java | |||
@@ -0,0 +1,31 @@ | |||
1 | package org.eclipse.viatra.solver.data.model.representation; | ||
2 | |||
3 | import org.eclipse.viatra.solver.data.model.Tuple; | ||
4 | import org.eclipse.viatra.solver.data.model.TupleHashProvider; | ||
5 | |||
6 | public class Relation<D> extends DataRepresentation<Tuple, D> { | ||
7 | private final String name; | ||
8 | private final int arity; | ||
9 | |||
10 | public Relation(String name, int arity, D defaultValue) { | ||
11 | super(TupleHashProvider.singleton(), defaultValue); | ||
12 | this.name = name; | ||
13 | this.arity = arity; | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public String getName() { | ||
18 | return name; | ||
19 | } | ||
20 | |||
21 | public int getArity() { | ||
22 | return arity; | ||
23 | } | ||
24 | |||
25 | @Override | ||
26 | public boolean isValidKey(Tuple key) { | ||
27 | if(key == null) { | ||
28 | return false; | ||
29 | } else return key.getSize() == getArity(); | ||
30 | } | ||
31 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/model/representation/TruthValue.java b/store/src/main/java/org/eclipse/viatra/solver/data/model/representation/TruthValue.java new file mode 100644 index 00000000..aeccde9e --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/model/representation/TruthValue.java | |||
@@ -0,0 +1,44 @@ | |||
1 | package org.eclipse.viatra.solver.data.model.representation; | ||
2 | |||
3 | public class TruthValue { | ||
4 | public static final TruthValue True = new TruthValue("true"); | ||
5 | public static final TruthValue False = new TruthValue("false"); | ||
6 | public static final TruthValue Unknown = new TruthValue("unknown"); | ||
7 | public static final TruthValue Error = new TruthValue("error"); | ||
8 | |||
9 | private final String name; | ||
10 | protected TruthValue(String name) { | ||
11 | this.name = name; | ||
12 | } | ||
13 | |||
14 | public String getName() { | ||
15 | return name; | ||
16 | } | ||
17 | |||
18 | public static TruthValue toTruthValue(boolean value) { | ||
19 | if(value) return True; | ||
20 | else return False; | ||
21 | } | ||
22 | public boolean isConsistent() { | ||
23 | return this != Error; | ||
24 | } | ||
25 | public boolean isComplete() { | ||
26 | return this != Unknown; | ||
27 | } | ||
28 | public boolean must() { | ||
29 | return this == True || this == Error; | ||
30 | } | ||
31 | public boolean may() { | ||
32 | return this == True || this == Unknown; | ||
33 | } | ||
34 | |||
35 | public TruthValue not() { | ||
36 | if(this == True) { | ||
37 | return False; | ||
38 | } else if(this == False) { | ||
39 | return True; | ||
40 | } else { | ||
41 | return this; | ||
42 | } | ||
43 | } | ||
44 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/RelationalScope.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/RelationalScope.java new file mode 100644 index 00000000..97b33935 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/RelationalScope.java | |||
@@ -0,0 +1,34 @@ | |||
1 | package org.eclipse.viatra.solver.data.query; | ||
2 | |||
3 | import java.util.Set; | ||
4 | |||
5 | import org.apache.log4j.Logger; | ||
6 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; | ||
7 | import org.eclipse.viatra.query.runtime.api.scope.IEngineContext; | ||
8 | import org.eclipse.viatra.query.runtime.api.scope.IIndexingErrorListener; | ||
9 | import org.eclipse.viatra.query.runtime.api.scope.QueryScope; | ||
10 | import org.eclipse.viatra.solver.data.model.Model; | ||
11 | import org.eclipse.viatra.solver.data.model.Tuple; | ||
12 | import org.eclipse.viatra.solver.data.query.internal.RelationUpdateListener; | ||
13 | import org.eclipse.viatra.solver.data.query.internal.RelationalEngineContext; | ||
14 | import org.eclipse.viatra.solver.data.query.view.RelationView; | ||
15 | |||
16 | public class RelationalScope extends QueryScope{ | ||
17 | private final Model model; | ||
18 | private final RelationUpdateListener updateListener; | ||
19 | |||
20 | public RelationalScope(Model model, Set<RelationView<?>> relationViews) { | ||
21 | this.model = model; | ||
22 | updateListener = new RelationUpdateListener(relationViews); | ||
23 | } | ||
24 | |||
25 | public <D> void processUpdate(RelationView<D> relationView, Tuple key, D oldValue, D newValue) { | ||
26 | updateListener.processChange(relationView, key, oldValue, newValue); | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | protected IEngineContext createEngineContext(ViatraQueryEngine engine, IIndexingErrorListener errorListener, | ||
31 | Logger logger) { | ||
32 | return new RelationalEngineContext(model, updateListener); | ||
33 | } | ||
34 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/building/DNFAnd.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/building/DNFAnd.java new file mode 100644 index 00000000..ff5a7848 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/building/DNFAnd.java | |||
@@ -0,0 +1,37 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.building; | ||
2 | |||
3 | import java.util.HashMap; | ||
4 | import java.util.HashSet; | ||
5 | import java.util.List; | ||
6 | import java.util.Map; | ||
7 | import java.util.Set; | ||
8 | |||
9 | public class DNFAnd { | ||
10 | private Set<Variable> existentiallyQuantified; | ||
11 | private List<DNFAtom> constraints; | ||
12 | public DNFAnd(Set<Variable> quantifiedVariables, List<DNFAtom> constraints) { | ||
13 | super(); | ||
14 | this.existentiallyQuantified = quantifiedVariables; | ||
15 | this.constraints = constraints; | ||
16 | } | ||
17 | public Set<Variable> getExistentiallyQuantified() { | ||
18 | return existentiallyQuantified; | ||
19 | } | ||
20 | public List<DNFAtom> getConstraints() { | ||
21 | return constraints; | ||
22 | } | ||
23 | void unifyVariables(Map<String,Variable> uniqueVariableMap) { | ||
24 | Map<String,Variable> uniqueVariableMapForClause = new HashMap<>(uniqueVariableMap); | ||
25 | for(DNFAtom atom : constraints) { | ||
26 | atom.unifyVariables(uniqueVariableMapForClause); | ||
27 | } | ||
28 | } | ||
29 | void collectQuantifiedVariables(Set<Variable> parameters) { | ||
30 | Set<Variable> result = new HashSet<>(); | ||
31 | for(DNFAtom constraint : constraints) { | ||
32 | constraint.collectAllVariables(result); | ||
33 | } | ||
34 | result.removeAll(parameters); | ||
35 | existentiallyQuantified = result; | ||
36 | } | ||
37 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/building/DNFAtom.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/building/DNFAtom.java new file mode 100644 index 00000000..05a3e3f8 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/building/DNFAtom.java | |||
@@ -0,0 +1,33 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.building; | ||
2 | |||
3 | import java.util.Collection; | ||
4 | import java.util.Iterator; | ||
5 | import java.util.Map; | ||
6 | import java.util.Set; | ||
7 | |||
8 | public interface DNFAtom { | ||
9 | void unifyVariables(Map<String,Variable> variables); | ||
10 | static Variable unifyVariables(Map<String,Variable> unifiedVariables, Variable variable) { | ||
11 | if(variable != null) { | ||
12 | if(variable.isNamed() && unifiedVariables.containsKey(variable.getName())) { | ||
13 | return unifiedVariables.get(variable.getName()); | ||
14 | } | ||
15 | return variable; | ||
16 | } else { | ||
17 | return null; | ||
18 | } | ||
19 | } | ||
20 | void collectAllVariables(Set<Variable> variables); | ||
21 | static void addToCollection(Set<Variable> variables, Variable variable) { | ||
22 | if(variable != null) { | ||
23 | variables.add(variable); | ||
24 | } | ||
25 | } | ||
26 | static void addToCollection(Set<Variable> variables, Collection<Variable> variableCollection) { | ||
27 | Iterator<Variable> iterator = variableCollection.iterator(); | ||
28 | while(iterator.hasNext()) { | ||
29 | Variable variable = iterator.next(); | ||
30 | addToCollection(variables, variable); | ||
31 | } | ||
32 | } | ||
33 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/building/DNFPredicate.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/building/DNFPredicate.java new file mode 100644 index 00000000..8ee540ae --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/building/DNFPredicate.java | |||
@@ -0,0 +1,72 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.building; | ||
2 | |||
3 | import java.util.HashMap; | ||
4 | import java.util.HashSet; | ||
5 | import java.util.List; | ||
6 | import java.util.Map; | ||
7 | import java.util.UUID; | ||
8 | |||
9 | public class DNFPredicate { | ||
10 | private final String name; | ||
11 | private final String uniqueName; | ||
12 | private final List<Variable> parameters; | ||
13 | private final List<DNFAnd> clauses; | ||
14 | |||
15 | public DNFPredicate(String name, List<Variable> parameters, List<DNFAnd> clauses) { | ||
16 | this.name = name; | ||
17 | this.uniqueName = generateUniqueName(name,"predicate"); | ||
18 | this.parameters = parameters; | ||
19 | this.clauses = clauses; | ||
20 | |||
21 | postProcess(); | ||
22 | } | ||
23 | |||
24 | public static String generateUniqueName(String originalName, String defaultPrefix) { | ||
25 | UUID uuid = UUID.randomUUID(); | ||
26 | String uniqueString = uuid.toString().replace('-', '_'); | ||
27 | if(originalName == null) { | ||
28 | return defaultPrefix+uniqueString; | ||
29 | } else { | ||
30 | return originalName+uniqueString; | ||
31 | } | ||
32 | } | ||
33 | |||
34 | public String getName() { | ||
35 | return name; | ||
36 | } | ||
37 | public String getUniqueName() { | ||
38 | return uniqueName; | ||
39 | } | ||
40 | public List<Variable> getVariables() { | ||
41 | return parameters; | ||
42 | } | ||
43 | public List<DNFAnd> getClauses() { | ||
44 | return clauses; | ||
45 | } | ||
46 | |||
47 | public void unifyVariables() { | ||
48 | Map<String,Variable> uniqueVariableMap = new HashMap<>(); | ||
49 | for(Variable parameter : this.parameters) { | ||
50 | if(parameter.isNamed()) { | ||
51 | String parameterName = parameter.getName(); | ||
52 | if(uniqueVariableMap.containsKey(parameterName)) { | ||
53 | throw new IllegalArgumentException("Multiple parameters has the name "+parameterName); | ||
54 | } else { | ||
55 | uniqueVariableMap.put(parameterName, parameter); | ||
56 | } | ||
57 | } | ||
58 | } | ||
59 | for(DNFAnd clause : this.clauses) { | ||
60 | clause.unifyVariables(uniqueVariableMap); | ||
61 | } | ||
62 | } | ||
63 | public void collectQuantifiedVariables() { | ||
64 | for(DNFAnd clause : this.clauses) { | ||
65 | clause.collectQuantifiedVariables(new HashSet<>(parameters)); | ||
66 | } | ||
67 | } | ||
68 | public void postProcess() { | ||
69 | unifyVariables(); | ||
70 | collectQuantifiedVariables(); | ||
71 | } | ||
72 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/building/EquivalenceAtom.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/building/EquivalenceAtom.java new file mode 100644 index 00000000..b47fe2a8 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/building/EquivalenceAtom.java | |||
@@ -0,0 +1,44 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.building; | ||
2 | |||
3 | import java.util.Map; | ||
4 | import java.util.Set; | ||
5 | |||
6 | public class EquivalenceAtom implements DNFAtom{ | ||
7 | private boolean positive; | ||
8 | private Variable left; | ||
9 | private Variable right; | ||
10 | public EquivalenceAtom(boolean positive, Variable left, Variable right) { | ||
11 | this.positive = positive; | ||
12 | this.left = left; | ||
13 | this.right = right; | ||
14 | } | ||
15 | public boolean isPositive() { | ||
16 | return positive; | ||
17 | } | ||
18 | public void setPositive(boolean positive) { | ||
19 | this.positive = positive; | ||
20 | } | ||
21 | public Variable getLeft() { | ||
22 | return left; | ||
23 | } | ||
24 | public void setLeft(Variable left) { | ||
25 | this.left = left; | ||
26 | } | ||
27 | public Variable getRight() { | ||
28 | return right; | ||
29 | } | ||
30 | public void setRight(Variable right) { | ||
31 | this.right = right; | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | public void unifyVariables(Map<String, Variable> variables) { | ||
36 | this.left = DNFAtom.unifyVariables(variables,left); | ||
37 | this.right = DNFAtom.unifyVariables(variables,right); | ||
38 | } | ||
39 | @Override | ||
40 | public void collectAllVariables(Set<Variable> variables) { | ||
41 | DNFAtom.addToCollection(variables, left); | ||
42 | DNFAtom.addToCollection(variables, right); | ||
43 | } | ||
44 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/building/PredicateAtom.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/building/PredicateAtom.java new file mode 100644 index 00000000..3e5ef88e --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/building/PredicateAtom.java | |||
@@ -0,0 +1,57 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.building; | ||
2 | |||
3 | import java.util.List; | ||
4 | import java.util.Map; | ||
5 | import java.util.Set; | ||
6 | |||
7 | public class PredicateAtom implements DNFAtom{ | ||
8 | private DNFPredicate referred; | ||
9 | private List<Variable> substitution; | ||
10 | private boolean positive; | ||
11 | private boolean transitive; | ||
12 | |||
13 | public PredicateAtom(boolean positive, boolean transitive, DNFPredicate referred, List<Variable> substitution) { | ||
14 | this.positive = positive; | ||
15 | this.referred = referred; | ||
16 | this.substitution = substitution; | ||
17 | this.transitive = transitive; | ||
18 | } | ||
19 | public DNFPredicate getReferred() { | ||
20 | return referred; | ||
21 | } | ||
22 | public void setReferred(DNFPredicate referred) { | ||
23 | this.referred = referred; | ||
24 | } | ||
25 | public List<Variable> getSubstitution() { | ||
26 | return substitution; | ||
27 | } | ||
28 | public void setSubstitution(List<Variable> substitution) { | ||
29 | this.substitution = substitution; | ||
30 | } | ||
31 | public boolean isPositive() { | ||
32 | return positive; | ||
33 | } | ||
34 | public void setPositive(boolean positive) { | ||
35 | this.positive = positive; | ||
36 | } | ||
37 | public boolean isTransitive() { | ||
38 | return transitive; | ||
39 | } | ||
40 | public void setTransitive(boolean transitive) { | ||
41 | this.transitive = transitive; | ||
42 | } | ||
43 | @Override | ||
44 | public void unifyVariables(Map<String, Variable> variables) { | ||
45 | for(int i = 0; i<this.substitution.size(); i++) { | ||
46 | final Object term = this.substitution.get(i); | ||
47 | if(term instanceof Variable) { | ||
48 | Variable variableReference = (Variable) term; | ||
49 | this.substitution.set(i, DNFAtom.unifyVariables(variables, variableReference)); | ||
50 | } | ||
51 | } | ||
52 | } | ||
53 | @Override | ||
54 | public void collectAllVariables(Set<Variable> variables) { | ||
55 | DNFAtom.addToCollection(variables, substitution); | ||
56 | } | ||
57 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/building/PredicateBuilder_string.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/building/PredicateBuilder_string.java new file mode 100644 index 00000000..41f85d39 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/building/PredicateBuilder_string.java | |||
@@ -0,0 +1,107 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.building; | ||
2 | |||
3 | import java.util.ArrayList; | ||
4 | import java.util.Collections; | ||
5 | import java.util.HashSet; | ||
6 | import java.util.List; | ||
7 | |||
8 | import org.eclipse.viatra.solver.data.query.view.RelationView; | ||
9 | |||
10 | public class PredicateBuilder_string { | ||
11 | private PredicateBuilder_string() {} | ||
12 | |||
13 | public static PredicateBuild1 predicate(String name) { | ||
14 | return new PredicateBuild1(name); | ||
15 | } | ||
16 | public static class PredicateBuild1 { | ||
17 | private String name; | ||
18 | public PredicateBuild1(String name) { | ||
19 | this.name = name; | ||
20 | } | ||
21 | public PredicateBuild2 parameters(String... parameters) { | ||
22 | return new PredicateBuild2(name, parameters); | ||
23 | } | ||
24 | } | ||
25 | public static class PredicateBuild2 { | ||
26 | private String name; | ||
27 | private String[] parameters; | ||
28 | public PredicateBuild2(String name, String[] parameters) { | ||
29 | this.name = name; | ||
30 | this.parameters = parameters; | ||
31 | } | ||
32 | |||
33 | public PredicateBuild3 clause(DNFAtom...constraints) { | ||
34 | return new PredicateBuild3(name,parameters,List.<DNFAtom[]>of(constraints)); | ||
35 | } | ||
36 | } | ||
37 | public static class PredicateBuild3 { | ||
38 | String name; | ||
39 | String[] parameters; | ||
40 | List<DNFAtom[]> clauses; | ||
41 | public PredicateBuild3(String name, String[] parameters, List<DNFAtom[]> clauses) { | ||
42 | super(); | ||
43 | this.name = name; | ||
44 | this.parameters = parameters; | ||
45 | this.clauses = clauses; | ||
46 | } | ||
47 | |||
48 | public PredicateBuild3 clause(DNFAtom...constraints) { | ||
49 | List<DNFAtom[]> newClauses = new ArrayList<>(); | ||
50 | newClauses.addAll(clauses); | ||
51 | newClauses.add(constraints); | ||
52 | return new PredicateBuild3(name, parameters, newClauses); | ||
53 | } | ||
54 | public DNFPredicate build() { | ||
55 | List<Variable> newParameters = new ArrayList<>(this.parameters.length); | ||
56 | for(int i = 0; i<this.parameters.length; i++) { | ||
57 | newParameters.add(new Variable(parameters[i])); | ||
58 | } | ||
59 | |||
60 | List<DNFAnd> newClauses = new ArrayList<>(this.clauses.size()); | ||
61 | for(DNFAtom[] clause : this.clauses) { | ||
62 | List<DNFAtom> constraints = new ArrayList<>(clause.length); | ||
63 | Collections.addAll(constraints, clause); | ||
64 | newClauses.add(new DNFAnd(new HashSet<>(), constraints)); | ||
65 | } | ||
66 | |||
67 | return new DNFPredicate(name,newParameters,newClauses); | ||
68 | } | ||
69 | } | ||
70 | |||
71 | private static Variable stringToVariable(String name) { | ||
72 | if(name != null) { | ||
73 | return new Variable(name); | ||
74 | } else { | ||
75 | return null; | ||
76 | } | ||
77 | } | ||
78 | private static List<Variable> stringToVariable(String[] names) { | ||
79 | List<Variable> variables = new ArrayList<>(); | ||
80 | for(int i = 0; i<names.length; i++) { | ||
81 | variables.add(stringToVariable(names[i])); | ||
82 | } | ||
83 | return variables; | ||
84 | } | ||
85 | |||
86 | public static EquivalenceAtom cEquals(String v1, String v2) { | ||
87 | return new EquivalenceAtom(true,stringToVariable(v1),stringToVariable(v2)); | ||
88 | } | ||
89 | public static EquivalenceAtom cNotEquals(String v1, String v2) { | ||
90 | return new EquivalenceAtom(false,stringToVariable(v1),stringToVariable(v2)); | ||
91 | } | ||
92 | |||
93 | public static RelationAtom cInRelation(RelationView<?> view, String... variables) { | ||
94 | |||
95 | return new RelationAtom(view, stringToVariable(variables)); | ||
96 | } | ||
97 | |||
98 | public static PredicateAtom cInPredicate(DNFPredicate referred, String... variables) { | ||
99 | return new PredicateAtom(true, false, referred, stringToVariable(variables)); | ||
100 | } | ||
101 | public static PredicateAtom cInTransitivePredicate(DNFPredicate referred, String... variables) { | ||
102 | return new PredicateAtom(true, true, referred, stringToVariable(variables)); | ||
103 | } | ||
104 | public static PredicateAtom cNotInPredicate(DNFPredicate referred, String... variables) { | ||
105 | return new PredicateAtom(false, false, referred, stringToVariable(variables)); | ||
106 | } | ||
107 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/building/RelationAtom.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/building/RelationAtom.java new file mode 100644 index 00000000..f7152bba --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/building/RelationAtom.java | |||
@@ -0,0 +1,45 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.building; | ||
2 | |||
3 | import java.util.List; | ||
4 | import java.util.Map; | ||
5 | import java.util.Set; | ||
6 | |||
7 | import org.eclipse.viatra.solver.data.query.view.FilteredRelationView; | ||
8 | import org.eclipse.viatra.solver.data.query.view.RelationView; | ||
9 | |||
10 | public class RelationAtom implements DNFAtom{ | ||
11 | RelationView<?> view; | ||
12 | List<Variable> substitution; | ||
13 | |||
14 | public RelationAtom(RelationView<?> view, List<Variable> substitution) { | ||
15 | this.view = view; | ||
16 | this.substitution = substitution; | ||
17 | } | ||
18 | public RelationView<?> getView() { | ||
19 | return view; | ||
20 | } | ||
21 | public void setView(FilteredRelationView<?> view) { | ||
22 | this.view = view; | ||
23 | } | ||
24 | public List<Variable> getSubstitution() { | ||
25 | return substitution; | ||
26 | } | ||
27 | public void setSubstitution(List<Variable> substitution) { | ||
28 | this.substitution = substitution; | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public void unifyVariables(Map<String, Variable> variables) { | ||
33 | for(int i = 0; i<this.substitution.size(); i++) { | ||
34 | final Object term = this.substitution.get(i); | ||
35 | if(term instanceof Variable) { | ||
36 | Variable variableReference = (Variable) term; | ||
37 | this.substitution.set(i, DNFAtom.unifyVariables(variables, variableReference)); | ||
38 | } | ||
39 | } | ||
40 | } | ||
41 | @Override | ||
42 | public void collectAllVariables(Set<Variable> variables) { | ||
43 | DNFAtom.addToCollection(variables, substitution); | ||
44 | } | ||
45 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/building/Variable.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/building/Variable.java new file mode 100644 index 00000000..29f9fc8b --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/building/Variable.java | |||
@@ -0,0 +1,22 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.building; | ||
2 | |||
3 | public class Variable { | ||
4 | private final String name; | ||
5 | private final String uniqueName; | ||
6 | |||
7 | public Variable(String name) { | ||
8 | super(); | ||
9 | this.name = name; | ||
10 | this.uniqueName = DNFPredicate.generateUniqueName(name, "variable"); | ||
11 | |||
12 | } | ||
13 | public String getName() { | ||
14 | return name; | ||
15 | } | ||
16 | public String getUniqueName() { | ||
17 | return uniqueName; | ||
18 | } | ||
19 | public boolean isNamed() { | ||
20 | return name != null; | ||
21 | } | ||
22 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/DummyBaseIndexer.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/DummyBaseIndexer.java new file mode 100644 index 00000000..042ec3dc --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/DummyBaseIndexer.java | |||
@@ -0,0 +1,59 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.internal; | ||
2 | |||
3 | import java.lang.reflect.InvocationTargetException; | ||
4 | import java.util.concurrent.Callable; | ||
5 | |||
6 | import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex; | ||
7 | import org.eclipse.viatra.query.runtime.api.scope.IIndexingErrorListener; | ||
8 | import org.eclipse.viatra.query.runtime.api.scope.IInstanceObserver; | ||
9 | import org.eclipse.viatra.query.runtime.api.scope.ViatraBaseIndexChangeListener; | ||
10 | |||
11 | /** | ||
12 | * copied from org.eclipse.viatra.query.runtime.tabular.TabularEngineContext; | ||
13 | */ | ||
14 | public class DummyBaseIndexer implements IBaseIndex{ | ||
15 | |||
16 | @Override | ||
17 | public <V> V coalesceTraversals(Callable<V> callable) throws InvocationTargetException { | ||
18 | try { | ||
19 | return callable.call(); | ||
20 | } catch (Exception e) { | ||
21 | throw new InvocationTargetException(e); | ||
22 | } | ||
23 | } | ||
24 | |||
25 | @Override | ||
26 | public void addBaseIndexChangeListener(ViatraBaseIndexChangeListener listener) { | ||
27 | // no notification support | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public void removeBaseIndexChangeListener(ViatraBaseIndexChangeListener listener) { | ||
32 | // no notification support | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public void resampleDerivedFeatures() { | ||
37 | throw new UnsupportedOperationException(); | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public boolean addIndexingErrorListener(IIndexingErrorListener listener) { | ||
42 | return true; | ||
43 | } | ||
44 | |||
45 | @Override | ||
46 | public boolean removeIndexingErrorListener(IIndexingErrorListener listener) { | ||
47 | return true; | ||
48 | } | ||
49 | |||
50 | @Override | ||
51 | public boolean addInstanceObserver(IInstanceObserver observer, Object observedObject) { | ||
52 | return true; | ||
53 | } | ||
54 | |||
55 | @Override | ||
56 | public boolean removeInstanceObserver(IInstanceObserver observer, Object observedObject) { | ||
57 | return true; | ||
58 | } | ||
59 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/PredicateTranslator.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/PredicateTranslator.java new file mode 100644 index 00000000..54cb4bab --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/PredicateTranslator.java | |||
@@ -0,0 +1,209 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.internal; | ||
2 | |||
3 | import java.util.ArrayList; | ||
4 | import java.util.HashMap; | ||
5 | import java.util.LinkedHashSet; | ||
6 | import java.util.LinkedList; | ||
7 | import java.util.List; | ||
8 | import java.util.Map; | ||
9 | import java.util.Set; | ||
10 | |||
11 | import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher; | ||
12 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; | ||
13 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; | ||
14 | import org.eclipse.viatra.query.runtime.api.scope.QueryScope; | ||
15 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | ||
16 | import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; | ||
17 | import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; | ||
18 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Equality; | ||
19 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.ExportedParameter; | ||
20 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Inequality; | ||
21 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.NegativePatternCall; | ||
22 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.BinaryReflexiveTransitiveClosure; | ||
23 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure; | ||
24 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall; | ||
25 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; | ||
26 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.BasePQuery; | ||
27 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; | ||
28 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; | ||
29 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; | ||
30 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
31 | import org.eclipse.viatra.solver.data.query.RelationalScope; | ||
32 | import org.eclipse.viatra.solver.data.query.view.RelationView; | ||
33 | |||
34 | public class PredicateTranslator extends BasePQuery { | ||
35 | |||
36 | private final Map<String, PParameter> parameters = new HashMap<String, PParameter>(); | ||
37 | private String fullyQualifiedName; | ||
38 | private LinkedList<PBody> bodies = new LinkedList<PBody>(); | ||
39 | private List<ExportedParameter> symbolicParameters; | ||
40 | |||
41 | public PredicateTranslator(String fullyQualifiedName) { | ||
42 | super(PVisibility.PUBLIC); | ||
43 | this.fullyQualifiedName = fullyQualifiedName; | ||
44 | PBody body = new PBody(this); | ||
45 | bodies.add(body); | ||
46 | } | ||
47 | |||
48 | @Override | ||
49 | public String getFullyQualifiedName() { | ||
50 | return fullyQualifiedName; | ||
51 | } | ||
52 | |||
53 | public PredicateTranslator addParameter(String name, RelationView<?> type) { | ||
54 | PParameter parameter = new PParameter(name); | ||
55 | parameters.put(name, parameter); | ||
56 | |||
57 | PBody body = bodies.peekLast(); | ||
58 | List<ExportedParameter> symbolicParameters = new ArrayList<>(); | ||
59 | parameters.forEach((pName, pParameter) -> { | ||
60 | PVariable var = body.getOrCreateVariableByName(pName); | ||
61 | symbolicParameters.add(new ExportedParameter(body, var, pParameter)); | ||
62 | }); | ||
63 | body.setSymbolicParameters(symbolicParameters); | ||
64 | |||
65 | return this; | ||
66 | } | ||
67 | |||
68 | @Override | ||
69 | public List<PParameter> getParameters() { | ||
70 | return new ArrayList<PParameter>(parameters.values()); | ||
71 | } | ||
72 | public <D> PredicateTranslator addConstraint(RelationView<D> view, String... name) { | ||
73 | if(name.length != view.getArity()) { | ||
74 | throw new IllegalArgumentException("Arity ("+view.getArity()+") does not match parameter numbers ("+name.length+")"); | ||
75 | } | ||
76 | PBody body = bodies.peekLast(); | ||
77 | Object[] variables = new Object[name.length]; | ||
78 | for(int i = 0; i<name.length; i++) { | ||
79 | variables[i] = body.getOrCreateVariableByName(name[i]); | ||
80 | } | ||
81 | new TypeConstraint(body, Tuples.flatTupleOf(variables), view); | ||
82 | return this; | ||
83 | } | ||
84 | |||
85 | // // Type constraint | ||
86 | // public RelationQuery addConstraint(String type, String name) { | ||
87 | // PBody body = bodies.peekLast(); | ||
88 | // PVariable var = body.getOrCreateVariableByName(name); | ||
89 | // new TypeConstraint(body, Tuples.flatTupleOf(var), new StringExactInstancesKey(type)); | ||
90 | // return this; | ||
91 | // } | ||
92 | // | ||
93 | // // Relation constraint | ||
94 | // public RelationQuery addConstraint(String type, String sourceName, String targetName) { | ||
95 | // PBody body = bodies.peekLast(); | ||
96 | // PVariable var_source = body.getOrCreateVariableByName(sourceName); | ||
97 | // PVariable var_target = body.getOrCreateVariableByName(targetName); | ||
98 | // new TypeConstraint(body, Tuples.flatTupleOf(var_source, var_target), | ||
99 | // new StringStructuralFeatureInstancesKey(type)); | ||
100 | // return this; | ||
101 | // } | ||
102 | |||
103 | // Create new Body | ||
104 | public PredicateTranslator or() { | ||
105 | PBody body = new PBody(this); | ||
106 | List<ExportedParameter> symbolicParameters = new ArrayList<>(); | ||
107 | parameters.forEach((name, parameter) -> { | ||
108 | PVariable var = body.getOrCreateVariableByName(name); | ||
109 | symbolicParameters.add(new ExportedParameter(body, var, parameter)); | ||
110 | }); | ||
111 | body.setSymbolicParameters(symbolicParameters); | ||
112 | bodies.add(body); | ||
113 | return this; | ||
114 | } | ||
115 | |||
116 | // Equality constraint | ||
117 | public PredicateTranslator addEquality(String sourceName, String targetName) { | ||
118 | PBody body = bodies.peekLast(); | ||
119 | PVariable var_source = body.getOrCreateVariableByName(sourceName); | ||
120 | PVariable var_target = body.getOrCreateVariableByName(targetName); | ||
121 | new Equality(body, var_source, var_target); | ||
122 | return this; | ||
123 | } | ||
124 | |||
125 | // Inequality constraint | ||
126 | public PredicateTranslator addInequality(String sourceName, String targetName) { | ||
127 | PBody body = bodies.peekLast(); | ||
128 | PVariable var_source = body.getOrCreateVariableByName(sourceName); | ||
129 | PVariable var_target = body.getOrCreateVariableByName(targetName); | ||
130 | new Inequality(body, var_source, var_target); | ||
131 | return this; | ||
132 | } | ||
133 | |||
134 | // Positive pattern call | ||
135 | public PredicateTranslator addPatternCall(PQuery query, String... names) { | ||
136 | PBody body = bodies.peekLast(); | ||
137 | PVariable[] vars = new PVariable[names.length]; | ||
138 | for (int i = 0; i < names.length; i++) { | ||
139 | vars[i] = body.getOrCreateVariableByName(names[i]); | ||
140 | } | ||
141 | new PositivePatternCall(body, Tuples.flatTupleOf(vars), query); | ||
142 | return this; | ||
143 | } | ||
144 | |||
145 | // Negative pattern call | ||
146 | public PredicateTranslator addNegativePatternCall(PQuery query, String... names) { | ||
147 | PBody body = bodies.peekLast(); | ||
148 | PVariable[] vars = new PVariable[names.length]; | ||
149 | for (int i = 0; i < names.length; i++) { | ||
150 | vars[i] = body.getOrCreateVariableByName(names[i]); | ||
151 | } | ||
152 | new NegativePatternCall(body, Tuples.flatTupleOf(vars), query); | ||
153 | return this; | ||
154 | } | ||
155 | |||
156 | // Binary transitive closure pattern call | ||
157 | public PredicateTranslator addBinaryTransitiveClosure(PQuery query, String sourceName, String targetName) { | ||
158 | PBody body = bodies.peekLast(); | ||
159 | PVariable var_source = body.getOrCreateVariableByName(sourceName); | ||
160 | PVariable var_target = body.getOrCreateVariableByName(targetName); | ||
161 | new BinaryTransitiveClosure(body, Tuples.flatTupleOf(var_source, var_target), query); | ||
162 | return this; | ||
163 | } | ||
164 | |||
165 | // Binary reflexive transitive closure pattern call | ||
166 | public PredicateTranslator addBinaryReflexiveTransitiveClosure(PQuery query, String sourceName, String targetName) { | ||
167 | PBody body = bodies.peekLast(); | ||
168 | PVariable var_source = body.getOrCreateVariableByName(sourceName); | ||
169 | PVariable var_target = body.getOrCreateVariableByName(targetName); | ||
170 | new BinaryReflexiveTransitiveClosure(body, Tuples.flatTupleOf(var_source, var_target), query, | ||
171 | query.getParameters().get(0).getDeclaredUnaryType()); | ||
172 | return this; | ||
173 | } | ||
174 | |||
175 | @Override | ||
176 | public Set<PBody> doGetContainedBodies() { | ||
177 | setEvaluationHints(new QueryEvaluationHint(null, QueryEvaluationHint.BackendRequirement.UNSPECIFIED)); | ||
178 | return new LinkedHashSet<PBody>(bodies); | ||
179 | } | ||
180 | |||
181 | public void addSymbolicParameters(ExportedParameter symbolicParameter) { | ||
182 | checkMutability(); | ||
183 | if (symbolicParameters == null) { | ||
184 | symbolicParameters = new ArrayList<>(); | ||
185 | } | ||
186 | symbolicParameters.add(symbolicParameter); | ||
187 | } | ||
188 | |||
189 | public GenericQuerySpecification<GenericPatternMatcher> build() { | ||
190 | return new GenericQuerySpecification<GenericPatternMatcher>(this) { | ||
191 | |||
192 | @Override | ||
193 | public Class<? extends QueryScope> getPreferredScopeClass() { | ||
194 | return RelationalScope.class; | ||
195 | } | ||
196 | |||
197 | @Override | ||
198 | protected GenericPatternMatcher instantiate(ViatraQueryEngine engine) { | ||
199 | return defaultInstantiate(engine); | ||
200 | } | ||
201 | |||
202 | @Override | ||
203 | public GenericPatternMatcher instantiate() { | ||
204 | return new GenericPatternMatcher(this); | ||
205 | } | ||
206 | |||
207 | }; | ||
208 | } | ||
209 | } \ No newline at end of file | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/RelationUpdateListener.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/RelationUpdateListener.java new file mode 100644 index 00000000..c6d12614 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/RelationUpdateListener.java | |||
@@ -0,0 +1,51 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.internal; | ||
2 | |||
3 | import java.util.HashMap; | ||
4 | import java.util.HashSet; | ||
5 | import java.util.Map; | ||
6 | import java.util.Set; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; | ||
9 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
10 | import org.eclipse.viatra.solver.data.model.Tuple; | ||
11 | import org.eclipse.viatra.solver.data.query.view.RelationView; | ||
12 | |||
13 | public class RelationUpdateListener { | ||
14 | private final Map<RelationView<?>,Set<RelationUpdateListenerEntry<?>>> view2Listeners; | ||
15 | |||
16 | public RelationUpdateListener(Set<RelationView<?>> relationViews) { | ||
17 | view2Listeners = new HashMap<>(); | ||
18 | for(RelationView<?> relationView : relationViews) { | ||
19 | view2Listeners.put(relationView, new HashSet<>()); | ||
20 | } | ||
21 | } | ||
22 | public boolean containsRelationalView(RelationView<?> relationalKey) { | ||
23 | RelationView<?> relationView = relationalKey.getWrappedKey(); | ||
24 | return view2Listeners.containsKey(relationView); | ||
25 | } | ||
26 | public void addListener(RelationView<?> relationalKey, ITuple seed, IQueryRuntimeContextListener listener) { | ||
27 | RelationView<?> relationView = relationalKey.getWrappedKey(); | ||
28 | if(view2Listeners.containsKey(relationView)) { | ||
29 | RelationUpdateListenerEntry<?> entry = new RelationUpdateListenerEntry<>(relationalKey, seed, listener); | ||
30 | view2Listeners.get(relationView).add(entry); | ||
31 | } else throw new IllegalArgumentException(); | ||
32 | } | ||
33 | public void removeListener(RelationView<?> relationalKey, ITuple seed, IQueryRuntimeContextListener listener) { | ||
34 | RelationView<?> relationView = relationalKey.getWrappedKey(); | ||
35 | if(view2Listeners.containsKey(relationView)) { | ||
36 | RelationUpdateListenerEntry<?> entry = new RelationUpdateListenerEntry<>(relationalKey, seed, listener); | ||
37 | view2Listeners.get(relationView).remove(entry); | ||
38 | } else throw new IllegalArgumentException(); | ||
39 | } | ||
40 | |||
41 | public <D> void processChange(RelationView<D> relationView, Tuple tuple, D oldValue, D newValue) { | ||
42 | Set<RelationUpdateListenerEntry<?>> listeners = view2Listeners.get(relationView); | ||
43 | if(listeners != null) { | ||
44 | for(RelationUpdateListenerEntry<?> listener : listeners) { | ||
45 | @SuppressWarnings("unchecked") | ||
46 | RelationUpdateListenerEntry<D> typeCorrectListener = (RelationUpdateListenerEntry<D>) listener; | ||
47 | typeCorrectListener.processChange(tuple, oldValue, newValue); | ||
48 | } | ||
49 | } else throw new IllegalArgumentException("View was not indexed in constructor "+relationView); | ||
50 | } | ||
51 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/RelationUpdateListenerEntry.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/RelationUpdateListenerEntry.java new file mode 100644 index 00000000..55aed7c8 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/RelationUpdateListenerEntry.java | |||
@@ -0,0 +1,63 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.internal; | ||
2 | |||
3 | import java.util.Arrays; | ||
4 | import java.util.Objects; | ||
5 | |||
6 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; | ||
7 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
8 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
9 | import org.eclipse.viatra.solver.data.model.Tuple; | ||
10 | import org.eclipse.viatra.solver.data.query.view.RelationView; | ||
11 | |||
12 | public class RelationUpdateListenerEntry<D> { | ||
13 | final RelationView<D> key; | ||
14 | final ITuple filter; | ||
15 | final IQueryRuntimeContextListener listener; | ||
16 | |||
17 | public RelationUpdateListenerEntry(RelationView<D> key, ITuple filter, IQueryRuntimeContextListener listener) { | ||
18 | super(); | ||
19 | this.key = key; | ||
20 | this.filter = filter; | ||
21 | this.listener = listener; | ||
22 | } | ||
23 | |||
24 | public void processChange(Tuple tuple, D oldValue, D newValue) { | ||
25 | Object[] oldTuple = isMatching(key.getWrappedKey().transform(tuple, oldValue), filter); | ||
26 | Object[] newTuple = isMatching(key.getWrappedKey().transform(tuple, newValue), filter); | ||
27 | |||
28 | if(!Arrays.equals(oldTuple, newTuple)) { | ||
29 | if(oldTuple != null) { | ||
30 | listener.update(key, Tuples.flatTupleOf(oldTuple), false); | ||
31 | } | ||
32 | if(newTuple != null) { | ||
33 | listener.update(key, Tuples.flatTupleOf(newTuple), true); | ||
34 | } | ||
35 | } | ||
36 | } | ||
37 | |||
38 | private Object[] isMatching(Object[] tuple, ITuple filter) { | ||
39 | for(int i = 0; i<filter.getSize(); i++) { | ||
40 | final Object filterObject = filter.get(i); | ||
41 | if(filterObject != null && !filterObject.equals(tuple[i])) { | ||
42 | return null; | ||
43 | } | ||
44 | } | ||
45 | return tuple; | ||
46 | } | ||
47 | |||
48 | @Override | ||
49 | public int hashCode() { | ||
50 | return Objects.hash(filter, key, listener); | ||
51 | } | ||
52 | |||
53 | @Override | ||
54 | public boolean equals(Object obj) { | ||
55 | if (this == obj) | ||
56 | return true; | ||
57 | if (!(obj instanceof RelationUpdateListenerEntry)) | ||
58 | return false; | ||
59 | RelationUpdateListenerEntry<?> other = (RelationUpdateListenerEntry<?>) obj; | ||
60 | return Objects.equals(filter, other.filter) && Objects.equals(key, other.key) | ||
61 | && Objects.equals(listener, other.listener); | ||
62 | } | ||
63 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/RelationalEngineContext.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/RelationalEngineContext.java new file mode 100644 index 00000000..01948828 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/RelationalEngineContext.java | |||
@@ -0,0 +1,32 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.internal; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex; | ||
4 | import org.eclipse.viatra.query.runtime.api.scope.IEngineContext; | ||
5 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext; | ||
6 | import org.eclipse.viatra.solver.data.model.Model; | ||
7 | |||
8 | public class RelationalEngineContext implements IEngineContext{ | ||
9 | private final IBaseIndex baseIndex = new DummyBaseIndexer(); | ||
10 | private final RelationalRuntimeContext runtimeContext; | ||
11 | |||
12 | |||
13 | public RelationalEngineContext(Model model, RelationUpdateListener updateListener) { | ||
14 | runtimeContext = new RelationalRuntimeContext(model, updateListener); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | public IBaseIndex getBaseIndex() { | ||
19 | return this.baseIndex; | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public void dispose() { | ||
24 | //lifecycle not controlled by engine | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public IQueryRuntimeContext getQueryRuntimeContext() { | ||
29 | return runtimeContext; | ||
30 | } | ||
31 | |||
32 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/RelationalQueryMetaContext.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/RelationalQueryMetaContext.java new file mode 100644 index 00000000..de500fc9 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/RelationalQueryMetaContext.java | |||
@@ -0,0 +1,57 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.internal; | ||
2 | |||
3 | import java.util.Collection; | ||
4 | import java.util.Collections; | ||
5 | import java.util.HashMap; | ||
6 | import java.util.HashSet; | ||
7 | import java.util.Map; | ||
8 | import java.util.Set; | ||
9 | |||
10 | import org.eclipse.viatra.query.runtime.matchers.context.AbstractQueryMetaContext; | ||
11 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | ||
12 | import org.eclipse.viatra.query.runtime.matchers.context.InputKeyImplication; | ||
13 | import org.eclipse.viatra.solver.data.query.view.RelationView; | ||
14 | |||
15 | /** | ||
16 | * The meta context information for String scopes. | ||
17 | */ | ||
18 | public final class RelationalQueryMetaContext extends AbstractQueryMetaContext { | ||
19 | |||
20 | @Override | ||
21 | public boolean isEnumerable(IInputKey key) { | ||
22 | ensureValidKey(key); | ||
23 | return key.isEnumerable(); | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public boolean isStateless(IInputKey key) { | ||
28 | ensureValidKey(key); | ||
29 | return key instanceof RelationView<?>; | ||
30 | } | ||
31 | |||
32 | @Override | ||
33 | public Collection<InputKeyImplication> getImplications(IInputKey implyingKey) { | ||
34 | ensureValidKey(implyingKey); | ||
35 | return new HashSet<InputKeyImplication>(); | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public Map<Set<Integer>, Set<Integer>> getFunctionalDependencies(IInputKey key) { | ||
40 | ensureValidKey(key); | ||
41 | if (key instanceof RelationView) { | ||
42 | return new HashMap<Set<Integer>, Set<Integer>>(); | ||
43 | } else { | ||
44 | return Collections.emptyMap(); | ||
45 | } | ||
46 | } | ||
47 | |||
48 | public void ensureValidKey(IInputKey key) { | ||
49 | if (! (key instanceof RelationView<?>)) | ||
50 | illegalInputKey(key); | ||
51 | } | ||
52 | |||
53 | public void illegalInputKey(IInputKey key) { | ||
54 | throw new IllegalArgumentException("The input key " + key + " is not a valid input key."); | ||
55 | } | ||
56 | |||
57 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/RelationalRuntimeContext.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/RelationalRuntimeContext.java new file mode 100644 index 00000000..7d1682b2 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/internal/RelationalRuntimeContext.java | |||
@@ -0,0 +1,187 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.internal; | ||
2 | |||
3 | import static org.eclipse.viatra.solver.data.util.CollectionsUtil.filter; | ||
4 | import static org.eclipse.viatra.solver.data.util.CollectionsUtil.map; | ||
5 | |||
6 | import java.lang.reflect.InvocationTargetException; | ||
7 | import java.util.Iterator; | ||
8 | import java.util.Optional; | ||
9 | import java.util.concurrent.Callable; | ||
10 | |||
11 | import org.eclipse.viatra.query.runtime.base.core.NavigationHelperImpl; | ||
12 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | ||
13 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryMetaContext; | ||
14 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext; | ||
15 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; | ||
16 | import org.eclipse.viatra.query.runtime.matchers.context.IndexingService; | ||
17 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
18 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; | ||
19 | import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; | ||
20 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
21 | import org.eclipse.viatra.query.runtime.matchers.util.Accuracy; | ||
22 | import org.eclipse.viatra.solver.data.model.Model; | ||
23 | import org.eclipse.viatra.solver.data.query.view.RelationView; | ||
24 | |||
25 | public class RelationalRuntimeContext implements IQueryRuntimeContext { | ||
26 | private final RelationalQueryMetaContext metaContext = new RelationalQueryMetaContext(); | ||
27 | private final RelationUpdateListener relationUpdateListener; | ||
28 | private final Model model; | ||
29 | |||
30 | public RelationalRuntimeContext(Model model, RelationUpdateListener relationUpdateListener) { | ||
31 | this.model = model; | ||
32 | this.relationUpdateListener = relationUpdateListener; | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public IQueryMetaContext getMetaContext() { | ||
37 | return metaContext; | ||
38 | } | ||
39 | |||
40 | // | ||
41 | /** | ||
42 | * TODO: check {@link NavigationHelperImpl#coalesceTraversals(Callable)} | ||
43 | */ | ||
44 | @Override | ||
45 | public <V> V coalesceTraversals(Callable<V> callable) throws InvocationTargetException { | ||
46 | try { | ||
47 | return callable.call(); | ||
48 | } catch (Exception e) { | ||
49 | throw new InvocationTargetException(e); | ||
50 | } | ||
51 | } | ||
52 | |||
53 | @Override | ||
54 | public boolean isCoalescing() { | ||
55 | return true; | ||
56 | } | ||
57 | |||
58 | @Override | ||
59 | public boolean isIndexed(IInputKey key, IndexingService service) { | ||
60 | if(key instanceof RelationView<?>) { | ||
61 | RelationView<?> relationalKey = (RelationView<?>) key; | ||
62 | return this.relationUpdateListener.containsRelationalView(relationalKey); | ||
63 | } else { | ||
64 | return false; | ||
65 | } | ||
66 | } | ||
67 | |||
68 | @Override | ||
69 | public void ensureIndexed(IInputKey key, IndexingService service) { | ||
70 | if(!isIndexed(key, service)) { | ||
71 | throw new IllegalStateException("Engine tries to index a new key " +key); | ||
72 | } | ||
73 | } | ||
74 | |||
75 | RelationView<?> checkKey(IInputKey key) { | ||
76 | if(key instanceof RelationView) { | ||
77 | RelationView<?> relationViewKey = (RelationView<?>) key; | ||
78 | if(relationUpdateListener.containsRelationalView(relationViewKey)) { | ||
79 | return relationViewKey; | ||
80 | } else { | ||
81 | throw new IllegalStateException("Query is asking for non-indexed key"); | ||
82 | } | ||
83 | } else { | ||
84 | throw new IllegalStateException("Query is asking for non-relational key"); | ||
85 | } | ||
86 | } | ||
87 | |||
88 | @Override | ||
89 | public int countTuples(IInputKey key, TupleMask seedMask, ITuple seed) { | ||
90 | RelationView<?> relationalViewKey = checkKey(key); | ||
91 | Iterable<Object[]> allObjects = relationalViewKey.getAll(model); | ||
92 | Iterable<Object[]> filteredBySeed = filter(allObjects,objectArray -> isMatching(objectArray,seedMask,seed)); | ||
93 | Iterator<Object[]> iterator = filteredBySeed.iterator(); | ||
94 | int result = 0; | ||
95 | while(iterator.hasNext()) { | ||
96 | iterator.next(); | ||
97 | result++; | ||
98 | } | ||
99 | return result; | ||
100 | } | ||
101 | |||
102 | @Override | ||
103 | public Optional<Long> estimateCardinality(IInputKey key, TupleMask groupMask, Accuracy requiredAccuracy) { | ||
104 | return Optional.empty(); | ||
105 | } | ||
106 | |||
107 | @Override | ||
108 | public Iterable<Tuple> enumerateTuples(IInputKey key, TupleMask seedMask, ITuple seed) { | ||
109 | RelationView<?> relationalViewKey = checkKey(key); | ||
110 | Iterable<Object[]> allObjects = relationalViewKey.getAll(model); | ||
111 | Iterable<Object[]> filteredBySeed = filter(allObjects,objectArray -> isMatching(objectArray,seedMask,seed)); | ||
112 | return map(filteredBySeed,Tuples::flatTupleOf); | ||
113 | } | ||
114 | |||
115 | private boolean isMatching(Object[] tuple, TupleMask seedMask, ITuple seed) { | ||
116 | for(int i=0; i<seedMask.indices.length; i++) { | ||
117 | final Object seedElement = seed.get(i); | ||
118 | final Object tupleElement = tuple[seedMask.indices[i]]; | ||
119 | if(!tupleElement.equals(seedElement)) { | ||
120 | return false; | ||
121 | } | ||
122 | } | ||
123 | return true; | ||
124 | } | ||
125 | // private Object[] toObjectMask(RelationViewKey<?> relationalViewKey, TupleMask seedMask, ITuple seed) { | ||
126 | // final int arity = relationalViewKey.getArity(); | ||
127 | // Object[] result = new Object[arity]; | ||
128 | // for(int i = 0; i<seedMask.indices.length; i++) { | ||
129 | // result[seedMask.indices[i]] = seed.get(i); | ||
130 | // } | ||
131 | // return result; | ||
132 | // } | ||
133 | |||
134 | @Override | ||
135 | public Iterable<? extends Object> enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed) { | ||
136 | return enumerateTuples(key, seedMask, seed); | ||
137 | } | ||
138 | |||
139 | @Override | ||
140 | public boolean containsTuple(IInputKey key, ITuple seed) { | ||
141 | RelationView<?> relationalViewKey = checkKey(key); | ||
142 | return relationalViewKey.get(model,seed.getElements()); | ||
143 | } | ||
144 | |||
145 | @Override | ||
146 | public void addUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener) { | ||
147 | RelationView<?> relationalKey = checkKey(key); | ||
148 | this.relationUpdateListener.addListener(relationalKey, seed, listener); | ||
149 | |||
150 | } | ||
151 | |||
152 | @Override | ||
153 | public void removeUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener) { | ||
154 | RelationView<?> relationalKey = checkKey(key); | ||
155 | this.relationUpdateListener.removeListener(relationalKey, seed, listener); | ||
156 | } | ||
157 | |||
158 | @Override | ||
159 | public Object wrapElement(Object externalElement) { | ||
160 | return externalElement; | ||
161 | } | ||
162 | |||
163 | @Override | ||
164 | public Object unwrapElement(Object internalElement) { | ||
165 | return internalElement; | ||
166 | } | ||
167 | |||
168 | @Override | ||
169 | public Tuple wrapTuple(Tuple externalElements) { | ||
170 | return externalElements; | ||
171 | } | ||
172 | |||
173 | @Override | ||
174 | public Tuple unwrapTuple(Tuple internalElements) { | ||
175 | return internalElements; | ||
176 | } | ||
177 | |||
178 | @Override | ||
179 | public void ensureWildcardIndexing(IndexingService service) { | ||
180 | throw new UnsupportedOperationException(); | ||
181 | } | ||
182 | |||
183 | @Override | ||
184 | public void executeAfterTraversal(Runnable runnable) throws InvocationTargetException { | ||
185 | runnable.run(); | ||
186 | } | ||
187 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/view/FilteredRelationView.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/view/FilteredRelationView.java new file mode 100644 index 00000000..edc534b7 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/view/FilteredRelationView.java | |||
@@ -0,0 +1,48 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.view; | ||
2 | |||
3 | import java.util.function.BiPredicate; | ||
4 | |||
5 | import org.eclipse.viatra.solver.data.model.Model; | ||
6 | import org.eclipse.viatra.solver.data.model.Tuple; | ||
7 | import org.eclipse.viatra.solver.data.model.Tuple.Tuple1; | ||
8 | import org.eclipse.viatra.solver.data.model.representation.Relation; | ||
9 | |||
10 | public class FilteredRelationView<D> extends RelationView<D>{ | ||
11 | private final BiPredicate<Tuple,D> predicate; | ||
12 | |||
13 | public FilteredRelationView(Relation<D> representation, BiPredicate<Tuple,D> predicate) { | ||
14 | super(representation); | ||
15 | this.predicate = predicate; | ||
16 | } | ||
17 | @Override | ||
18 | protected Object[] forwardMap(Tuple key, D value) { | ||
19 | return toTuple1Array(key); | ||
20 | } | ||
21 | @Override | ||
22 | public boolean get(Model model, Object[] tuple) { | ||
23 | int[] content = new int[tuple.length]; | ||
24 | for(int i = 0; i<tuple.length; i++) { | ||
25 | content[i] =((Tuple1)tuple[i]).get(0); | ||
26 | } | ||
27 | Tuple key = Tuple.of(content); | ||
28 | D value = model.get(representation, key); | ||
29 | return filter(key, value); | ||
30 | } | ||
31 | |||
32 | public static Object[] toTuple1Array(Tuple t) { | ||
33 | Object[] result = new Object[t.getSize()]; | ||
34 | for(int i = 0; i<t.getSize(); i++) { | ||
35 | result[i] = t.get(i); | ||
36 | } | ||
37 | return result; | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public int getArity() { | ||
42 | return this.representation.getArity(); | ||
43 | } | ||
44 | @Override | ||
45 | protected boolean filter(Tuple key, D value) { | ||
46 | return this.predicate.test(key, value); | ||
47 | } | ||
48 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/view/FunctionalRelationView.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/view/FunctionalRelationView.java new file mode 100644 index 00000000..4aa7cfd0 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/view/FunctionalRelationView.java | |||
@@ -0,0 +1,50 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.view; | ||
2 | |||
3 | import org.eclipse.viatra.solver.data.model.Model; | ||
4 | import org.eclipse.viatra.solver.data.model.Tuple; | ||
5 | import org.eclipse.viatra.solver.data.model.Tuple.Tuple1; | ||
6 | import org.eclipse.viatra.solver.data.model.representation.Relation; | ||
7 | |||
8 | public class FunctionalRelationView<D> extends RelationView<D> { | ||
9 | |||
10 | public FunctionalRelationView(Relation<D> representation) { | ||
11 | super(representation); | ||
12 | } | ||
13 | |||
14 | @Override | ||
15 | protected boolean filter(Tuple key, D value) { | ||
16 | return true; | ||
17 | } | ||
18 | |||
19 | @Override | ||
20 | protected Object[] forwardMap(Tuple key, D value) { | ||
21 | return toTuple1ArrayPlusValue(key, value); | ||
22 | } | ||
23 | |||
24 | @Override | ||
25 | public boolean get(Model model, Object[] tuple) { | ||
26 | int[] content = new int[tuple.length-1]; | ||
27 | for(int i = 0; i<tuple.length-1; i++) { | ||
28 | content[i] =((Tuple1)tuple[i]).get(0); | ||
29 | } | ||
30 | Tuple key = Tuple.of(content); | ||
31 | @SuppressWarnings("unchecked") | ||
32 | D valueInTuple = (D) tuple[tuple.length-1]; | ||
33 | D valueInMap = model.get(representation, key); | ||
34 | return valueInTuple.equals(valueInMap); | ||
35 | } | ||
36 | |||
37 | public static <D> Object[] toTuple1ArrayPlusValue(Tuple t, D value) { | ||
38 | Object[] result = new Object[t.getSize()+1]; | ||
39 | for(int i = 0; i<t.getSize(); i++) { | ||
40 | result[i] = t.get(i); | ||
41 | } | ||
42 | result[t.getSize()] = value; | ||
43 | return result; | ||
44 | } | ||
45 | |||
46 | @Override | ||
47 | public int getArity() { | ||
48 | return this.representation.getArity()+1; | ||
49 | } | ||
50 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/view/KeyOnlyRelationView.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/view/KeyOnlyRelationView.java new file mode 100644 index 00000000..11a24fc8 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/view/KeyOnlyRelationView.java | |||
@@ -0,0 +1,16 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.view; | ||
2 | |||
3 | import org.eclipse.viatra.solver.data.model.Tuple; | ||
4 | import org.eclipse.viatra.solver.data.model.representation.Relation; | ||
5 | |||
6 | public class KeyOnlyRelationView extends FilteredRelationView<Boolean>{ | ||
7 | |||
8 | public KeyOnlyRelationView(Relation<Boolean> representation) { | ||
9 | super(representation, (k,v)->true); | ||
10 | } | ||
11 | @Override | ||
12 | protected boolean filter(Tuple key, Boolean value) { | ||
13 | return true; | ||
14 | } | ||
15 | |||
16 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/query/view/RelationView.java b/store/src/main/java/org/eclipse/viatra/solver/data/query/view/RelationView.java new file mode 100644 index 00000000..c5bc5228 --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/query/view/RelationView.java | |||
@@ -0,0 +1,85 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.view; | ||
2 | |||
3 | import java.util.Objects; | ||
4 | |||
5 | import org.eclipse.viatra.query.runtime.matchers.context.common.BaseInputKeyWrapper; | ||
6 | import org.eclipse.viatra.solver.data.map.CursorAsIterator; | ||
7 | import org.eclipse.viatra.solver.data.model.Model; | ||
8 | import org.eclipse.viatra.solver.data.model.Tuple; | ||
9 | import org.eclipse.viatra.solver.data.model.representation.Relation; | ||
10 | |||
11 | /** | ||
12 | * Represents a view of a {@link Relation} that can be queried. | ||
13 | * | ||
14 | * @author Oszkar Semerath | ||
15 | * | ||
16 | * @param <D> | ||
17 | */ | ||
18 | public abstract class RelationView<D> extends BaseInputKeyWrapper<RelationView<D>> { | ||
19 | protected final Relation<D> representation; | ||
20 | |||
21 | protected RelationView(Relation<D> representation) { | ||
22 | super(null); | ||
23 | this.wrappedKey = this; | ||
24 | this.representation = representation; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String getPrettyPrintableName() { | ||
29 | return representation.getName(); | ||
30 | } | ||
31 | |||
32 | @Override | ||
33 | public String getStringID() { | ||
34 | return representation.getName() + this.getClass().getName(); | ||
35 | } | ||
36 | |||
37 | public Relation<D> getRepresentation() { | ||
38 | return representation; | ||
39 | } | ||
40 | |||
41 | @Override | ||
42 | public boolean isEnumerable() { | ||
43 | return true; | ||
44 | } | ||
45 | |||
46 | protected abstract boolean filter(Tuple key, D value); | ||
47 | |||
48 | protected abstract Object[] forwardMap(Tuple key, D value); | ||
49 | |||
50 | public abstract boolean get(Model model, Object[] tuple); | ||
51 | |||
52 | public Object[] transform(Tuple tuple, D value) { | ||
53 | if (filter(tuple, value)) { | ||
54 | return forwardMap(tuple, value); | ||
55 | } else | ||
56 | return null; | ||
57 | } | ||
58 | |||
59 | public Iterable<Object[]> getAll(Model model) { | ||
60 | return (() -> new CursorAsIterator<>(model.getAll(representation), (k, v) -> forwardMap(k, v), | ||
61 | (k, v) -> filter(k, v))); | ||
62 | } | ||
63 | |||
64 | @Override | ||
65 | public int hashCode() { | ||
66 | final int prime = 31; | ||
67 | int result = 1; | ||
68 | result = prime * result + Objects.hash(representation); | ||
69 | return result; | ||
70 | } | ||
71 | |||
72 | @Override | ||
73 | public boolean equals(Object obj) { | ||
74 | if (this == obj) | ||
75 | return true; | ||
76 | if (!super.equals(obj)) | ||
77 | return false; | ||
78 | if (!(obj instanceof RelationView)) | ||
79 | return false; | ||
80 | @SuppressWarnings("unchecked") | ||
81 | RelationView<D> other = ((RelationView<D>) obj); | ||
82 | return Objects.equals(representation, other.representation); | ||
83 | } | ||
84 | |||
85 | } | ||
diff --git a/store/src/main/java/org/eclipse/viatra/solver/data/util/CollectionsUtil.java b/store/src/main/java/org/eclipse/viatra/solver/data/util/CollectionsUtil.java new file mode 100644 index 00000000..21b0a9df --- /dev/null +++ b/store/src/main/java/org/eclipse/viatra/solver/data/util/CollectionsUtil.java | |||
@@ -0,0 +1,72 @@ | |||
1 | package org.eclipse.viatra.solver.data.util; | ||
2 | |||
3 | import java.util.Iterator; | ||
4 | import java.util.NoSuchElementException; | ||
5 | import java.util.function.Function; | ||
6 | import java.util.function.Predicate; | ||
7 | |||
8 | public final class CollectionsUtil { | ||
9 | private CollectionsUtil() { | ||
10 | throw new UnsupportedOperationException(); | ||
11 | } | ||
12 | |||
13 | public static <S,T> Iterator<T> map(Iterator<S> source, Function<S, T> transformation) { | ||
14 | return new Iterator<T>() { | ||
15 | |||
16 | @Override | ||
17 | public boolean hasNext() { | ||
18 | return source.hasNext(); | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | public T next() { | ||
23 | return transformation.apply(source.next()); | ||
24 | } | ||
25 | }; | ||
26 | } | ||
27 | |||
28 | public static <S,T> Iterable<T> map(Iterable<S> source, Function<S, T> transformation) { | ||
29 | return (()->map(source.iterator(),transformation)); | ||
30 | } | ||
31 | |||
32 | public static <T> Iterator<T> filter(Iterator<T> source, Predicate<T> condition) { | ||
33 | return new Iterator<T>() { | ||
34 | T internalNext = move(); | ||
35 | boolean internalHasNext; | ||
36 | |||
37 | private T move() { | ||
38 | internalHasNext = source.hasNext(); | ||
39 | if(internalHasNext) { | ||
40 | internalNext = source.next(); | ||
41 | } | ||
42 | while(internalHasNext && !condition.test(internalNext)) { | ||
43 | internalHasNext = source.hasNext(); | ||
44 | if(internalHasNext) { | ||
45 | internalNext = source.next(); | ||
46 | } | ||
47 | } | ||
48 | return internalNext; | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | public boolean hasNext() { | ||
53 | return internalHasNext; | ||
54 | } | ||
55 | |||
56 | @Override | ||
57 | public T next() { | ||
58 | if(!internalHasNext) { | ||
59 | throw new NoSuchElementException(); | ||
60 | } else { | ||
61 | T result = internalNext; | ||
62 | move(); | ||
63 | return result; | ||
64 | } | ||
65 | } | ||
66 | }; | ||
67 | } | ||
68 | |||
69 | public static <T> Iterable<T> filter(Iterable<T> source, Predicate<T> condition) { | ||
70 | return (()->filter(source.iterator(),condition)); | ||
71 | } | ||
72 | } | ||
diff --git a/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/CommitFuzzTest.java b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/CommitFuzzTest.java new file mode 100644 index 00000000..e160df2b --- /dev/null +++ b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/CommitFuzzTest.java | |||
@@ -0,0 +1,95 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.tests.fuzz; | ||
2 | |||
3 | import static org.junit.jupiter.api.Assertions.fail; | ||
4 | |||
5 | import java.util.Random; | ||
6 | import java.util.stream.Stream; | ||
7 | |||
8 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
9 | import org.eclipse.viatra.solver.data.map.VersionedMapStore; | ||
10 | import org.eclipse.viatra.solver.data.map.VersionedMapStoreImpl; | ||
11 | import org.eclipse.viatra.solver.data.map.internal.VersionedMapImpl; | ||
12 | import org.eclipse.viatra.solver.data.map.tests.fuzz.utils.FuzzTestUtils; | ||
13 | import org.eclipse.viatra.solver.data.map.tests.utils.MapTestEnvironment; | ||
14 | import org.junit.jupiter.api.Tag; | ||
15 | import org.junit.jupiter.api.Timeout; | ||
16 | import org.junit.jupiter.params.ParameterizedTest; | ||
17 | import org.junit.jupiter.params.provider.Arguments; | ||
18 | import org.junit.jupiter.params.provider.MethodSource; | ||
19 | |||
20 | class CommitFuzzTest { | ||
21 | private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, | ||
22 | boolean evilHash) { | ||
23 | String[] values = MapTestEnvironment.prepareValues(maxValue); | ||
24 | ContinousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash); | ||
25 | |||
26 | VersionedMapStore<Integer, String> store = new VersionedMapStoreImpl<Integer, String>(chp, values[0]); | ||
27 | VersionedMapImpl<Integer, String> sut = (VersionedMapImpl<Integer, String>) store.createMap(); | ||
28 | MapTestEnvironment<Integer, String> e = new MapTestEnvironment<Integer, String>(sut); | ||
29 | |||
30 | Random r = new Random(seed); | ||
31 | |||
32 | iterativeRandomPutsAndCommits(scenario, steps, maxKey, values, e, r, commitFrequency); | ||
33 | } | ||
34 | |||
35 | private void iterativeRandomPutsAndCommits(String scenario, int steps, int maxKey, String[] values, | ||
36 | MapTestEnvironment<Integer, String> e, Random r, int commitFrequency) { | ||
37 | int stopAt = -1; | ||
38 | for (int i = 0; i < steps; i++) { | ||
39 | int index = i + 1; | ||
40 | int nextKey = r.nextInt(maxKey); | ||
41 | String nextValue = values[r.nextInt(values.length)]; | ||
42 | if (index == stopAt) { | ||
43 | System.out.println("issue!"); | ||
44 | System.out.println("State before:"); | ||
45 | e.printComparison(); | ||
46 | e.sut.prettyPrint(); | ||
47 | System.out.println("Next: put(" + nextKey + "," + nextValue + ")"); | ||
48 | } | ||
49 | try { | ||
50 | e.put(nextKey, nextValue); | ||
51 | if (index == stopAt) { | ||
52 | e.sut.prettyPrint(); | ||
53 | } | ||
54 | e.checkEquivalence(scenario + ":" + index); | ||
55 | } catch (Exception exception) { | ||
56 | exception.printStackTrace(); | ||
57 | fail(scenario + ":" + index + ": exception happened: " + exception); | ||
58 | } | ||
59 | MapTestEnvironment.printStatus(scenario, index, steps, null); | ||
60 | if (index % commitFrequency == 0) { | ||
61 | e.sut.commit(); | ||
62 | } | ||
63 | } | ||
64 | } | ||
65 | |||
66 | @ParameterizedTest(name = "Commit {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") | ||
67 | @MethodSource | ||
68 | @Timeout(value = 10) | ||
69 | @Tag("fuzz") | ||
70 | void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, | ||
71 | boolean evilHash) { | ||
72 | runFuzzTest("CommitS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, | ||
73 | commitFrequency, evilHash); | ||
74 | } | ||
75 | |||
76 | static Stream<Arguments> parametrizedFastFuzz() { | ||
77 | return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, | ||
78 | new Object[] { 2, 3 }, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, | ||
79 | new Object[] { false, true }); | ||
80 | } | ||
81 | |||
82 | @ParameterizedTest(name = "Commit {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") | ||
83 | @MethodSource | ||
84 | @Tag("fuzz") | ||
85 | @Tag("slow") | ||
86 | void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, | ||
87 | boolean evilHash) { | ||
88 | runFuzzTest("CommitS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, | ||
89 | commitFrequency, evilHash); | ||
90 | } | ||
91 | |||
92 | static Stream<Arguments> parametrizedSlowFuzz() { | ||
93 | return FuzzTestUtils.changeStepCount(parametrizedFastFuzz(), 1); | ||
94 | } | ||
95 | } | ||
diff --git a/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/ContentEqualsFuzzTest.java b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/ContentEqualsFuzzTest.java new file mode 100644 index 00000000..5004c152 --- /dev/null +++ b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/ContentEqualsFuzzTest.java | |||
@@ -0,0 +1,142 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.tests.fuzz; | ||
2 | |||
3 | import static org.junit.jupiter.api.Assertions.assertEquals; | ||
4 | import static org.junit.jupiter.api.Assertions.fail; | ||
5 | |||
6 | import java.util.AbstractMap.SimpleEntry; | ||
7 | import java.util.Collections; | ||
8 | import java.util.LinkedList; | ||
9 | import java.util.List; | ||
10 | import java.util.Random; | ||
11 | import java.util.stream.Stream; | ||
12 | |||
13 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
14 | import org.eclipse.viatra.solver.data.map.Cursor; | ||
15 | import org.eclipse.viatra.solver.data.map.VersionedMap; | ||
16 | import org.eclipse.viatra.solver.data.map.VersionedMapStore; | ||
17 | import org.eclipse.viatra.solver.data.map.VersionedMapStoreImpl; | ||
18 | import org.eclipse.viatra.solver.data.map.internal.VersionedMapImpl; | ||
19 | import org.eclipse.viatra.solver.data.map.tests.fuzz.utils.FuzzTestUtils; | ||
20 | import org.eclipse.viatra.solver.data.map.tests.utils.MapTestEnvironment; | ||
21 | import org.junit.jupiter.api.Tag; | ||
22 | import org.junit.jupiter.api.Timeout; | ||
23 | import org.junit.jupiter.params.ParameterizedTest; | ||
24 | import org.junit.jupiter.params.provider.Arguments; | ||
25 | import org.junit.jupiter.params.provider.MethodSource; | ||
26 | |||
27 | class ContentEqualsFuzzTest { | ||
28 | private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, | ||
29 | boolean evilHash) { | ||
30 | String[] values = MapTestEnvironment.prepareValues(maxValue); | ||
31 | ContinousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash); | ||
32 | |||
33 | Random r = new Random(seed); | ||
34 | |||
35 | iterativeRandomPutsAndCommitsThenCompare(scenario, chp, steps, maxKey, values, r, commitFrequency); | ||
36 | } | ||
37 | |||
38 | private void iterativeRandomPutsAndCommitsThenCompare(String scenario, ContinousHashProvider<Integer> chp, int steps, int maxKey, String[] values, Random r, int commitFrequency) { | ||
39 | |||
40 | VersionedMapStore<Integer, String> store1 = new VersionedMapStoreImpl<Integer, String>(chp, values[0]); | ||
41 | VersionedMap<Integer, String> sut1 = store1.createMap(); | ||
42 | |||
43 | // Fill one map | ||
44 | for (int i = 0; i < steps; i++) { | ||
45 | int index1 = i + 1; | ||
46 | int nextKey = r.nextInt(maxKey); | ||
47 | String nextValue = values[r.nextInt(values.length)]; | ||
48 | try { | ||
49 | sut1.put(nextKey, nextValue); | ||
50 | } catch (Exception exception) { | ||
51 | exception.printStackTrace(); | ||
52 | fail(scenario + ":" + index1 + ": exception happened: " + exception); | ||
53 | } | ||
54 | MapTestEnvironment.printStatus(scenario, index1, steps, "Fill"); | ||
55 | if (index1 % commitFrequency == 0) { | ||
56 | sut1.commit(); | ||
57 | } | ||
58 | } | ||
59 | |||
60 | // Get the content of the first map | ||
61 | List<SimpleEntry<Integer, String>> content = new LinkedList<>(); | ||
62 | Cursor<Integer, String> cursor = sut1.getAll(); | ||
63 | while (cursor.move()) { | ||
64 | content.add(new SimpleEntry<>(cursor.getKey(), cursor.getValue())); | ||
65 | } | ||
66 | |||
67 | // Randomize the order of the content | ||
68 | Collections.shuffle(content, r); | ||
69 | |||
70 | VersionedMapStore<Integer, String> store2 = new VersionedMapStoreImpl<Integer, String>(chp, values[0]); | ||
71 | VersionedMap<Integer, String> sut2 = store2.createMap(); | ||
72 | int index2 = 1; | ||
73 | for (SimpleEntry<Integer, String> entry : content) { | ||
74 | sut2.put(entry.getKey(), entry.getValue()); | ||
75 | if(index2++%commitFrequency == 0) | ||
76 | sut2.commit(); | ||
77 | } | ||
78 | |||
79 | // Check the integrity of the maps | ||
80 | ((VersionedMapImpl<Integer,String>) sut1).checkIntegrity(); | ||
81 | ((VersionedMapImpl<Integer,String>) sut2).checkIntegrity(); | ||
82 | |||
83 | // // Compare the two maps | ||
84 | // By size | ||
85 | assertEquals(sut1.getSize(), content.size()); | ||
86 | assertEquals(sut2.getSize(), content.size()); | ||
87 | |||
88 | |||
89 | |||
90 | // By cursors | ||
91 | Cursor<Integer, String> cursor1 = sut1.getAll(); | ||
92 | Cursor<Integer, String> cursor2 = sut2.getAll(); | ||
93 | int index3 = 1; | ||
94 | boolean canMove = true; | ||
95 | do{ | ||
96 | boolean canMove1 = cursor1.move(); | ||
97 | boolean canMove2 = cursor2.move(); | ||
98 | assertEquals(canMove1, canMove2, scenario + ":" + index3 +" Cursors stopped at different times!"); | ||
99 | assertEquals(cursor1.getKey(), cursor2.getKey(), scenario + ":" + index3 +" Cursors have different keys!"); | ||
100 | assertEquals(cursor1.getValue(), cursor2.getValue(), scenario + ":" + index3 +" Cursors have different values!"); | ||
101 | |||
102 | canMove = canMove1; | ||
103 | MapTestEnvironment.printStatus(scenario, index3++, content.size(), "Compare"); | ||
104 | } while (canMove); | ||
105 | |||
106 | // By hashcode | ||
107 | assertEquals(sut1.hashCode(), sut2.hashCode(), "Hash codes are not equal!"); | ||
108 | |||
109 | // By equals | ||
110 | assertEquals(sut1, sut2, "Maps are not equals"); | ||
111 | } | ||
112 | |||
113 | @ParameterizedTest(name = "Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") | ||
114 | @MethodSource | ||
115 | @Timeout(value = 10) | ||
116 | @Tag("fuzz") | ||
117 | void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, | ||
118 | boolean evilHash) { | ||
119 | runFuzzTest("CompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, | ||
120 | commitFrequency, evilHash); | ||
121 | } | ||
122 | |||
123 | static Stream<Arguments> parametrizedFastFuzz() { | ||
124 | return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, | ||
125 | new Object[] { 2, 3 }, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, | ||
126 | new Object[] { false, true }); | ||
127 | } | ||
128 | |||
129 | @ParameterizedTest(name = "Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") | ||
130 | @MethodSource | ||
131 | @Tag("fuzz") | ||
132 | @Tag("slow") | ||
133 | void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, | ||
134 | boolean evilHash) { | ||
135 | runFuzzTest("CompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, | ||
136 | commitFrequency, evilHash); | ||
137 | } | ||
138 | |||
139 | static Stream<Arguments> parametrizedSlowFuzz() { | ||
140 | return FuzzTestUtils.changeStepCount(parametrizedFastFuzz(), 1); | ||
141 | } | ||
142 | } | ||
diff --git a/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/DiffCursorFuzzTest.java b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/DiffCursorFuzzTest.java new file mode 100644 index 00000000..4b79a98a --- /dev/null +++ b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/DiffCursorFuzzTest.java | |||
@@ -0,0 +1,116 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.tests.fuzz; | ||
2 | |||
3 | import static org.junit.jupiter.api.Assertions.fail; | ||
4 | |||
5 | import java.util.Random; | ||
6 | import java.util.stream.Stream; | ||
7 | |||
8 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
9 | import org.eclipse.viatra.solver.data.map.DiffCursor; | ||
10 | import org.eclipse.viatra.solver.data.map.VersionedMapStore; | ||
11 | import org.eclipse.viatra.solver.data.map.VersionedMapStoreImpl; | ||
12 | import org.eclipse.viatra.solver.data.map.internal.VersionedMapImpl; | ||
13 | import org.eclipse.viatra.solver.data.map.tests.fuzz.utils.FuzzTestUtils; | ||
14 | import org.eclipse.viatra.solver.data.map.tests.utils.MapTestEnvironment; | ||
15 | import org.junit.jupiter.api.Tag; | ||
16 | import org.junit.jupiter.api.Timeout; | ||
17 | import org.junit.jupiter.params.ParameterizedTest; | ||
18 | import org.junit.jupiter.params.provider.Arguments; | ||
19 | import org.junit.jupiter.params.provider.MethodSource; | ||
20 | |||
21 | class DiffCursorFuzzTest { | ||
22 | private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, | ||
23 | boolean evilHash) { | ||
24 | String[] values = MapTestEnvironment.prepareValues(maxValue); | ||
25 | ContinousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash); | ||
26 | |||
27 | VersionedMapStore<Integer, String> store = new VersionedMapStoreImpl<Integer, String>(chp, values[0]); | ||
28 | iterativeRandomPutsAndCommitsThenDiffcursor(scenario, store, steps, maxKey, values, seed, commitFrequency); | ||
29 | } | ||
30 | |||
31 | private void iterativeRandomPutsAndCommitsThenDiffcursor(String scenario, VersionedMapStore<Integer, String> store, | ||
32 | int steps, int maxKey, String[] values, int seed, int commitFrequency) { | ||
33 | // 1. build a map with versions | ||
34 | Random r = new Random(seed); | ||
35 | VersionedMapImpl<Integer, String> versioned = (VersionedMapImpl<Integer, String>) store.createMap(); | ||
36 | int largestCommit = -1; | ||
37 | |||
38 | for (int i = 0; i < steps; i++) { | ||
39 | int index = i + 1; | ||
40 | int nextKey = r.nextInt(maxKey); | ||
41 | String nextValue = values[r.nextInt(values.length)]; | ||
42 | try { | ||
43 | versioned.put(nextKey, nextValue); | ||
44 | } catch (Exception exception) { | ||
45 | exception.printStackTrace(); | ||
46 | fail(scenario + ":" + index + ": exception happened: " + exception); | ||
47 | } | ||
48 | if (index % commitFrequency == 0) { | ||
49 | long version = versioned.commit(); | ||
50 | largestCommit = (int) version; | ||
51 | } | ||
52 | if (index % 10000 == 0) | ||
53 | System.out.println(scenario + ":" + index + "/" + steps + " building finished"); | ||
54 | } | ||
55 | // 2. create a non-versioned map, | ||
56 | VersionedMapImpl<Integer, String> moving = (VersionedMapImpl<Integer, String>) store.createMap(); | ||
57 | Random r2 = new Random(seed + 1); | ||
58 | |||
59 | final int diffTravelFrequency = commitFrequency * 2; | ||
60 | for (int i = 0; i < steps; i++) { | ||
61 | int index = i + 1; | ||
62 | if (index % diffTravelFrequency == 0) { | ||
63 | // difftravel | ||
64 | long travelToVersion = r2.nextInt(largestCommit + 1); | ||
65 | DiffCursor<Integer, String> diffCursor = moving.getDiffCursor(travelToVersion); | ||
66 | moving.putAll(diffCursor); | ||
67 | |||
68 | } else { | ||
69 | // random puts | ||
70 | int nextKey = r2.nextInt(maxKey); | ||
71 | String nextValue = values[r2.nextInt(values.length)]; | ||
72 | try { | ||
73 | moving.put(nextKey, nextValue); | ||
74 | } catch (Exception exception) { | ||
75 | exception.printStackTrace(); | ||
76 | fail(scenario + ":" + index + ": exception happened: " + exception); | ||
77 | } | ||
78 | if (index % commitFrequency == 0) { | ||
79 | versioned.commit(); | ||
80 | } | ||
81 | if (index % 10000 == 0) | ||
82 | System.out.println(scenario + ":" + index + "/" + steps + " building finished"); | ||
83 | } | ||
84 | } | ||
85 | |||
86 | } | ||
87 | |||
88 | @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") | ||
89 | @MethodSource | ||
90 | @Timeout(value = 10) | ||
91 | @Tag("fuzz") | ||
92 | void parametrizedFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, | ||
93 | boolean evilHash) { | ||
94 | runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, | ||
95 | noKeys, noValues, commitFrequency, evilHash); | ||
96 | } | ||
97 | |||
98 | static Stream<Arguments> parametrizedFuzz() { | ||
99 | return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, | ||
100 | new Object[] { 2, 3 }, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, | ||
101 | new Object[] { false, true }); | ||
102 | } | ||
103 | @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") | ||
104 | @MethodSource | ||
105 | @Tag("fuzz") | ||
106 | @Tag("slow") | ||
107 | void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, | ||
108 | boolean evilHash) { | ||
109 | runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, | ||
110 | commitFrequency, evilHash); | ||
111 | } | ||
112 | |||
113 | static Stream<Arguments> parametrizedSlowFuzz() { | ||
114 | return FuzzTestUtils.changeStepCount(parametrizedFuzz(), 1); | ||
115 | } | ||
116 | } | ||
diff --git a/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/MultiThreadFuzzTest.java b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/MultiThreadFuzzTest.java new file mode 100644 index 00000000..c3a3e8ea --- /dev/null +++ b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/MultiThreadFuzzTest.java | |||
@@ -0,0 +1,96 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.tests.fuzz; | ||
2 | |||
3 | import static org.junit.jupiter.api.Assertions.assertEquals; | ||
4 | import static org.junit.jupiter.api.Assertions.fail; | ||
5 | |||
6 | import java.util.Collections; | ||
7 | import java.util.LinkedList; | ||
8 | import java.util.List; | ||
9 | import java.util.stream.Stream; | ||
10 | |||
11 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
12 | import org.eclipse.viatra.solver.data.map.VersionedMapStore; | ||
13 | import org.eclipse.viatra.solver.data.map.VersionedMapStoreImpl; | ||
14 | import org.eclipse.viatra.solver.data.map.tests.fuzz.utils.FuzzTestUtils; | ||
15 | import org.eclipse.viatra.solver.data.map.tests.utils.MapTestEnvironment; | ||
16 | import org.junit.jupiter.api.Tag; | ||
17 | import org.junit.jupiter.api.Timeout; | ||
18 | import org.junit.jupiter.params.ParameterizedTest; | ||
19 | import org.junit.jupiter.params.provider.Arguments; | ||
20 | import org.junit.jupiter.params.provider.MethodSource; | ||
21 | |||
22 | class MultiThreadFuzzTest { | ||
23 | public static final int noThreads = 32; | ||
24 | |||
25 | private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, | ||
26 | boolean evilHash) { | ||
27 | String[] values = MapTestEnvironment.prepareValues(maxValue); | ||
28 | ContinousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash); | ||
29 | |||
30 | VersionedMapStore<Integer, String> store = new VersionedMapStoreImpl<Integer, String>(chp, values[0]); | ||
31 | |||
32 | // initialize runnables | ||
33 | MultiThreadTestRunnable[] runnables = new MultiThreadTestRunnable[noThreads]; | ||
34 | for(int i = 0; i<noThreads; i++) { | ||
35 | runnables[i] = new MultiThreadTestRunnable(scenario+"-T"+(i+1), store, steps, maxKey, values, seed, commitFrequency); | ||
36 | } | ||
37 | |||
38 | // initialize threads | ||
39 | Thread[] threads = new Thread[noThreads]; | ||
40 | for(int i = 0; i<noThreads; i++) { | ||
41 | threads[i] = new Thread(runnables[i]); | ||
42 | } | ||
43 | |||
44 | // start threads; | ||
45 | for(int i = 0; i<noThreads; i++) { | ||
46 | threads[i].start(); | ||
47 | } | ||
48 | |||
49 | // wait all the threads; | ||
50 | for(int i = 0; i<noThreads; i++) { | ||
51 | try { | ||
52 | threads[i].join(); | ||
53 | } catch (InterruptedException e) { | ||
54 | fail("Thread "+i+" interrupted."); | ||
55 | } | ||
56 | } | ||
57 | |||
58 | // collect errors | ||
59 | List<Throwable> errors = new LinkedList<>(); | ||
60 | for(int i = 0; i<noThreads; i++) { | ||
61 | errors.addAll(runnables[i].getErrors()); | ||
62 | } | ||
63 | |||
64 | assertEquals(Collections.EMPTY_LIST, errors); | ||
65 | } | ||
66 | |||
67 | @ParameterizedTest(name = "Multithread {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") | ||
68 | @MethodSource | ||
69 | @Timeout(value = 10) | ||
70 | @Tag("fuzz") | ||
71 | void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, | ||
72 | boolean evilHash) { | ||
73 | runFuzzTest("MultithreadS" + steps + "K" + noKeys + "V" + noValues + "CF" + commitFrequency + "s" + seed, seed, steps, noKeys, noValues, | ||
74 | commitFrequency, evilHash); | ||
75 | } | ||
76 | |||
77 | static Stream<Arguments> parametrizedFastFuzz() { | ||
78 | return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, | ||
79 | new Object[] { 2, 3 }, new Object[] { 10, 100 }, new Object[] { 1, 2, 3 }, | ||
80 | new Object[] { false, true }); | ||
81 | } | ||
82 | |||
83 | @ParameterizedTest(name = "Multithread {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") | ||
84 | @MethodSource | ||
85 | @Tag("fuzz") | ||
86 | @Tag("slow") | ||
87 | void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, | ||
88 | boolean evilHash) { | ||
89 | runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, | ||
90 | commitFrequency, evilHash); | ||
91 | } | ||
92 | |||
93 | static Stream<Arguments> parametrizedSlowFuzz() { | ||
94 | return FuzzTestUtils.changeStepCount(RestoreFuzzTest.parametrizedFastFuzz(), 1); | ||
95 | } | ||
96 | } | ||
diff --git a/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/MultiThreadTestRunnable.java b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/MultiThreadTestRunnable.java new file mode 100644 index 00000000..a18298a2 --- /dev/null +++ b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/MultiThreadTestRunnable.java | |||
@@ -0,0 +1,101 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.tests.fuzz; | ||
2 | |||
3 | import java.util.ArrayList; | ||
4 | import java.util.Collections; | ||
5 | import java.util.HashMap; | ||
6 | import java.util.LinkedList; | ||
7 | import java.util.List; | ||
8 | import java.util.Map; | ||
9 | import java.util.Random; | ||
10 | |||
11 | import org.eclipse.viatra.solver.data.map.VersionedMapStore; | ||
12 | import org.eclipse.viatra.solver.data.map.internal.VersionedMapImpl; | ||
13 | import org.eclipse.viatra.solver.data.map.tests.utils.MapTestEnvironment; | ||
14 | |||
15 | public class MultiThreadTestRunnable implements Runnable { | ||
16 | String scenario; | ||
17 | VersionedMapStore<Integer, String> store; | ||
18 | int steps; | ||
19 | int maxKey; | ||
20 | String[] values; | ||
21 | int seed; | ||
22 | int commitFrequency; | ||
23 | List<Throwable> errors = new LinkedList<>(); | ||
24 | |||
25 | public MultiThreadTestRunnable(String scenario, VersionedMapStore<Integer, String> store, int steps, | ||
26 | int maxKey, String[] values, int seed, int commitFrequency) { | ||
27 | super(); | ||
28 | this.scenario = scenario; | ||
29 | this.store = store; | ||
30 | this.steps = steps; | ||
31 | this.maxKey = maxKey; | ||
32 | this.values = values; | ||
33 | this.seed = seed; | ||
34 | this.commitFrequency = commitFrequency; | ||
35 | } | ||
36 | |||
37 | private void logAndThrowError(String message) { | ||
38 | AssertionError error = new AssertionError(message); | ||
39 | errors.add(error); | ||
40 | } | ||
41 | |||
42 | public List<Throwable> getErrors() { | ||
43 | return errors; | ||
44 | } | ||
45 | |||
46 | @Override | ||
47 | public void run() { | ||
48 | // 1. build a map with versions | ||
49 | Random r = new Random(seed); | ||
50 | VersionedMapImpl<Integer, String> versioned = (VersionedMapImpl<Integer, String>) store.createMap(); | ||
51 | Map<Integer, Long> index2Version = new HashMap<>(); | ||
52 | |||
53 | for (int i = 0; i < steps; i++) { | ||
54 | int index = i + 1; | ||
55 | int nextKey = r.nextInt(maxKey); | ||
56 | String nextValue = values[r.nextInt(values.length)]; | ||
57 | try { | ||
58 | versioned.put(nextKey, nextValue); | ||
59 | } catch (Exception exception) { | ||
60 | exception.printStackTrace(); | ||
61 | logAndThrowError(scenario + ":" + index + ": exception happened: " + exception); | ||
62 | } | ||
63 | if (index % commitFrequency == 0) { | ||
64 | long version = versioned.commit(); | ||
65 | index2Version.put(i, version); | ||
66 | } | ||
67 | MapTestEnvironment.printStatus(scenario, index, steps, "building"); | ||
68 | } | ||
69 | // 2. create a non-versioned | ||
70 | VersionedMapImpl<Integer, String> reference = (VersionedMapImpl<Integer, String>) store.createMap(); | ||
71 | r = new Random(seed); | ||
72 | Random r2 = new Random(seed+1); | ||
73 | |||
74 | for (int i = 0; i < steps; i++) { | ||
75 | int index = i + 1; | ||
76 | int nextKey = r.nextInt(maxKey); | ||
77 | String nextValue = values[r.nextInt(values.length)]; | ||
78 | try { | ||
79 | reference.put(nextKey, nextValue); | ||
80 | } catch (Exception exception) { | ||
81 | exception.printStackTrace(); | ||
82 | logAndThrowError(scenario + ":" + index + ": exception happened: " + exception); | ||
83 | } | ||
84 | // go back to an existing state and compare to the reference | ||
85 | if (index % (commitFrequency) == 0) { | ||
86 | versioned.restore(index2Version.get(i)); | ||
87 | MapTestEnvironment.compareTwoMaps(scenario + ":" + index, reference, versioned,errors); | ||
88 | |||
89 | // go back to a random state (probably created by another thread) | ||
90 | List<Long> states = new ArrayList<>(store.getStates()); | ||
91 | Collections.shuffle(states, r2); | ||
92 | for(Long state : states.subList(0, Math.min(states.size(), 100))) { | ||
93 | versioned.restore(state); | ||
94 | } | ||
95 | versioned.restore(index2Version.get(i)); | ||
96 | } | ||
97 | |||
98 | MapTestEnvironment.printStatus(scenario, index, steps, "comparison"); | ||
99 | } | ||
100 | } | ||
101 | } | ||
diff --git a/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/MutableFuzzTest.java b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/MutableFuzzTest.java new file mode 100644 index 00000000..2d589dc9 --- /dev/null +++ b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/MutableFuzzTest.java | |||
@@ -0,0 +1,91 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.tests.fuzz; | ||
2 | |||
3 | import static org.junit.jupiter.api.Assertions.fail; | ||
4 | |||
5 | import java.util.Random; | ||
6 | import java.util.stream.Stream; | ||
7 | |||
8 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
9 | import org.eclipse.viatra.solver.data.map.VersionedMapStore; | ||
10 | import org.eclipse.viatra.solver.data.map.VersionedMapStoreImpl; | ||
11 | import org.eclipse.viatra.solver.data.map.internal.VersionedMapImpl; | ||
12 | import org.eclipse.viatra.solver.data.map.tests.fuzz.utils.FuzzTestUtils; | ||
13 | import org.eclipse.viatra.solver.data.map.tests.utils.MapTestEnvironment; | ||
14 | import org.junit.jupiter.api.Tag; | ||
15 | import org.junit.jupiter.api.Timeout; | ||
16 | import org.junit.jupiter.params.ParameterizedTest; | ||
17 | import org.junit.jupiter.params.provider.Arguments; | ||
18 | import org.junit.jupiter.params.provider.MethodSource; | ||
19 | |||
20 | class MutableFuzzTest { | ||
21 | private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean evilHash) { | ||
22 | String[] values = MapTestEnvironment.prepareValues(maxValue); | ||
23 | ContinousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash); | ||
24 | |||
25 | VersionedMapStore<Integer, String> store = new VersionedMapStoreImpl<Integer, String>(chp, values[0]); | ||
26 | VersionedMapImpl<Integer, String> sut = (VersionedMapImpl<Integer, String>) store.createMap(); | ||
27 | MapTestEnvironment<Integer, String> e = new MapTestEnvironment<Integer, String>(sut); | ||
28 | |||
29 | Random r = new Random(seed); | ||
30 | |||
31 | iterativeRandomPuts(scenario, steps, maxKey, values, e, r); | ||
32 | } | ||
33 | |||
34 | private void iterativeRandomPuts(String scenario, int steps, int maxKey, String[] values, | ||
35 | MapTestEnvironment<Integer, String> e, Random r) { | ||
36 | int stopAt = -1; | ||
37 | for (int i = 0; i < steps; i++) { | ||
38 | int index = i + 1; | ||
39 | int nextKey = r.nextInt(maxKey); | ||
40 | String nextValue = values[r.nextInt(values.length)]; | ||
41 | if (index == stopAt) { | ||
42 | System.out.println("issue!"); | ||
43 | System.out.println("State before:"); | ||
44 | e.printComparison(); | ||
45 | e.sut.prettyPrint(); | ||
46 | System.out.println("Next: put(" + nextKey + "," + nextValue + ")"); | ||
47 | } | ||
48 | try { | ||
49 | e.put(nextKey, nextValue); | ||
50 | if (index == stopAt) { | ||
51 | e.sut.prettyPrint(); | ||
52 | } | ||
53 | e.checkEquivalence(scenario + ":" + index); | ||
54 | } catch (Exception exception) { | ||
55 | exception.printStackTrace(); | ||
56 | fail(scenario + ":" + index + ": exception happened: " + exception); | ||
57 | } | ||
58 | MapTestEnvironment.printStatus(scenario, index, steps, null); | ||
59 | } | ||
60 | } | ||
61 | |||
62 | @ParameterizedTest(name = "Mutable {index}/{0} Steps={1} Keys={2} Values={3} seed={4} evil-hash={5}") | ||
63 | @MethodSource | ||
64 | @Timeout(value = 10) | ||
65 | @Tag("fuzz") | ||
66 | void parametrizedFuzz(int test, int steps, int noKeys, int noValues, int seed, boolean evilHash) { | ||
67 | runFuzzTest( | ||
68 | "MutableS" + steps + "K" + noKeys + "V" + noValues + "s" + seed + "H" + (evilHash ? "Evil" : "Normal"), | ||
69 | seed, steps, noKeys, noValues, evilHash); | ||
70 | } | ||
71 | |||
72 | static Stream<Arguments> parametrizedFuzz() { | ||
73 | return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, | ||
74 | new Object[] { 3, 32, 32 * 32, 32 * 32 * 32 * 32 }, new Object[] { 2, 3 }, new Object[] { 1, 2, 3 }, | ||
75 | new Object[] { false, true }); | ||
76 | } | ||
77 | |||
78 | @ParameterizedTest(name = "Mutable {index}/{0} Steps={1} Keys={2} Values={3} seed={4} evil-hash={5}") | ||
79 | @MethodSource | ||
80 | @Tag("fuzz") | ||
81 | @Tag("slow") | ||
82 | void parametrizedSlowFuzz(int test, int steps, int noKeys, int noValues, int seed, boolean evilHash) { | ||
83 | runFuzzTest( | ||
84 | "MutableS" + steps + "K" + noKeys + "V" + noValues + "s" + seed + "H" + (evilHash ? "Evil" : "Normal"), | ||
85 | seed, steps, noKeys, noValues, evilHash); | ||
86 | } | ||
87 | |||
88 | static Stream<Arguments> parametrizedSlowFuzz() { | ||
89 | return FuzzTestUtils.changeStepCount(parametrizedFuzz(), 1); | ||
90 | } | ||
91 | } | ||
diff --git a/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/MutableImmutableCompareFuzzTest.java b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/MutableImmutableCompareFuzzTest.java new file mode 100644 index 00000000..cc2abfe4 --- /dev/null +++ b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/MutableImmutableCompareFuzzTest.java | |||
@@ -0,0 +1,88 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.tests.fuzz; | ||
2 | |||
3 | import static org.junit.jupiter.api.Assertions.fail; | ||
4 | |||
5 | import java.util.Random; | ||
6 | import java.util.stream.Stream; | ||
7 | |||
8 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
9 | import org.eclipse.viatra.solver.data.map.VersionedMapStore; | ||
10 | import org.eclipse.viatra.solver.data.map.VersionedMapStoreImpl; | ||
11 | import org.eclipse.viatra.solver.data.map.internal.VersionedMapImpl; | ||
12 | import org.eclipse.viatra.solver.data.map.tests.fuzz.utils.FuzzTestUtils; | ||
13 | import org.eclipse.viatra.solver.data.map.tests.utils.MapTestEnvironment; | ||
14 | import org.junit.jupiter.api.Tag; | ||
15 | import org.junit.jupiter.api.Timeout; | ||
16 | import org.junit.jupiter.params.ParameterizedTest; | ||
17 | import org.junit.jupiter.params.provider.Arguments; | ||
18 | import org.junit.jupiter.params.provider.MethodSource; | ||
19 | |||
20 | class MutableImmutableCompareFuzzTest { | ||
21 | private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, | ||
22 | boolean evilHash) { | ||
23 | String[] values = MapTestEnvironment.prepareValues(maxValue); | ||
24 | ContinousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash); | ||
25 | |||
26 | VersionedMapStore<Integer, String> store = new VersionedMapStoreImpl<Integer, String>(chp, values[0]); | ||
27 | VersionedMapImpl<Integer, String> immutable = (VersionedMapImpl<Integer, String>) store.createMap(); | ||
28 | VersionedMapImpl<Integer, String> mutable = (VersionedMapImpl<Integer, String>) store.createMap(); | ||
29 | |||
30 | Random r = new Random(seed); | ||
31 | |||
32 | iterativeRandomPutsAndCommitsAndCompare(scenario, immutable, mutable, steps, maxKey, values, r, | ||
33 | commitFrequency); | ||
34 | } | ||
35 | |||
36 | private void iterativeRandomPutsAndCommitsAndCompare(String scenario, VersionedMapImpl<Integer, String> immutable, | ||
37 | VersionedMapImpl<Integer, String> mutable, int steps, int maxKey, String[] values, Random r, | ||
38 | int commitFrequency) { | ||
39 | for (int i = 0; i < steps; i++) { | ||
40 | int index = i + 1; | ||
41 | int nextKey = r.nextInt(maxKey); | ||
42 | String nextValue = values[r.nextInt(values.length)]; | ||
43 | try { | ||
44 | immutable.put(nextKey, nextValue); | ||
45 | mutable.put(nextKey, nextValue); | ||
46 | } catch (Exception exception) { | ||
47 | exception.printStackTrace(); | ||
48 | fail(scenario + ":" + index + ": exception happened: " + exception); | ||
49 | } | ||
50 | if (index % commitFrequency == 0) { | ||
51 | immutable.commit(); | ||
52 | } | ||
53 | MapTestEnvironment.compareTwoMaps(scenario + ":" + index, immutable, mutable); | ||
54 | |||
55 | MapTestEnvironment.printStatus(scenario, index, steps, null); | ||
56 | } | ||
57 | } | ||
58 | |||
59 | @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") | ||
60 | @MethodSource | ||
61 | @Timeout(value = 10) | ||
62 | @Tag("fuzz") | ||
63 | void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, | ||
64 | boolean evilHash) { | ||
65 | runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, | ||
66 | noKeys, noValues, commitFrequency, evilHash); | ||
67 | } | ||
68 | |||
69 | static Stream<Arguments> parametrizedFastFuzz() { | ||
70 | return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, | ||
71 | new Object[] { 2, 3 }, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, | ||
72 | new Object[] { false, true }); | ||
73 | } | ||
74 | |||
75 | @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") | ||
76 | @MethodSource | ||
77 | @Tag("fuzz") | ||
78 | @Tag("slow") | ||
79 | void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, | ||
80 | boolean evilHash) { | ||
81 | runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, | ||
82 | noKeys, noValues, commitFrequency, evilHash); | ||
83 | } | ||
84 | |||
85 | static Stream<Arguments> parametrizedSlowFuzz() { | ||
86 | return FuzzTestUtils.changeStepCount(MutableImmutableCompareFuzzTest.parametrizedFastFuzz(), 1); | ||
87 | } | ||
88 | } | ||
diff --git a/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/RestoreFuzzTest.java b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/RestoreFuzzTest.java new file mode 100644 index 00000000..7d9f5372 --- /dev/null +++ b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/RestoreFuzzTest.java | |||
@@ -0,0 +1,108 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.tests.fuzz; | ||
2 | |||
3 | import static org.junit.jupiter.api.Assertions.fail; | ||
4 | |||
5 | import java.util.HashMap; | ||
6 | import java.util.Map; | ||
7 | import java.util.Random; | ||
8 | import java.util.stream.Stream; | ||
9 | |||
10 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
11 | import org.eclipse.viatra.solver.data.map.VersionedMapStore; | ||
12 | import org.eclipse.viatra.solver.data.map.VersionedMapStoreImpl; | ||
13 | import org.eclipse.viatra.solver.data.map.internal.VersionedMapImpl; | ||
14 | import org.eclipse.viatra.solver.data.map.tests.fuzz.utils.FuzzTestUtils; | ||
15 | import org.eclipse.viatra.solver.data.map.tests.utils.MapTestEnvironment; | ||
16 | import org.junit.jupiter.api.Tag; | ||
17 | import org.junit.jupiter.api.Timeout; | ||
18 | import org.junit.jupiter.params.ParameterizedTest; | ||
19 | import org.junit.jupiter.params.provider.Arguments; | ||
20 | import org.junit.jupiter.params.provider.MethodSource; | ||
21 | |||
22 | class RestoreFuzzTest { | ||
23 | private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, | ||
24 | boolean evilHash) { | ||
25 | String[] values = MapTestEnvironment.prepareValues(maxValue); | ||
26 | ContinousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash); | ||
27 | |||
28 | VersionedMapStore<Integer, String> store = new VersionedMapStoreImpl<Integer, String>(chp, values[0]); | ||
29 | |||
30 | iterativeRandomPutsAndCommitsThenRestore(scenario, store, steps, maxKey, values, seed, commitFrequency); | ||
31 | } | ||
32 | |||
33 | private void iterativeRandomPutsAndCommitsThenRestore(String scenario, VersionedMapStore<Integer, String> store, | ||
34 | int steps, int maxKey, String[] values, int seed, int commitFrequency) { | ||
35 | // 1. build a map with versions | ||
36 | Random r = new Random(seed); | ||
37 | VersionedMapImpl<Integer, String> versioned = (VersionedMapImpl<Integer, String>) store.createMap(); | ||
38 | Map<Integer, Long> index2Version = new HashMap<>(); | ||
39 | |||
40 | for (int i = 0; i < steps; i++) { | ||
41 | int index = i + 1; | ||
42 | int nextKey = r.nextInt(maxKey); | ||
43 | String nextValue = values[r.nextInt(values.length)]; | ||
44 | try { | ||
45 | versioned.put(nextKey, nextValue); | ||
46 | } catch (Exception exception) { | ||
47 | exception.printStackTrace(); | ||
48 | fail(scenario + ":" + index + ": exception happened: " + exception); | ||
49 | } | ||
50 | if (index % commitFrequency == 0) { | ||
51 | long version = versioned.commit(); | ||
52 | index2Version.put(i, version); | ||
53 | } | ||
54 | MapTestEnvironment.printStatus(scenario, index, steps, "building"); | ||
55 | } | ||
56 | // 2. create a non-versioned and | ||
57 | VersionedMapImpl<Integer, String> reference = (VersionedMapImpl<Integer, String>) store.createMap(); | ||
58 | r = new Random(seed); | ||
59 | |||
60 | for (int i = 0; i < steps; i++) { | ||
61 | int index = i + 1; | ||
62 | int nextKey = r.nextInt(maxKey); | ||
63 | String nextValue = values[r.nextInt(values.length)]; | ||
64 | try { | ||
65 | reference.put(nextKey, nextValue); | ||
66 | } catch (Exception exception) { | ||
67 | exception.printStackTrace(); | ||
68 | fail(scenario + ":" + index + ": exception happened: " + exception); | ||
69 | } | ||
70 | if (index % commitFrequency == 0) { | ||
71 | versioned.restore(index2Version.get(i)); | ||
72 | MapTestEnvironment.compareTwoMaps(scenario + ":" + index, reference, versioned); | ||
73 | } | ||
74 | MapTestEnvironment.printStatus(scenario, index, steps, "comparison"); | ||
75 | } | ||
76 | |||
77 | } | ||
78 | |||
79 | @ParameterizedTest(name = "Restore {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") | ||
80 | @MethodSource | ||
81 | @Timeout(value = 10) | ||
82 | @Tag("smoke") | ||
83 | void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, | ||
84 | boolean evilHash) { | ||
85 | runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, | ||
86 | commitFrequency, evilHash); | ||
87 | } | ||
88 | |||
89 | static Stream<Arguments> parametrizedFastFuzz() { | ||
90 | return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, | ||
91 | new Object[] { 2, 3 }, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, | ||
92 | new Object[] { false, true }); | ||
93 | } | ||
94 | |||
95 | @ParameterizedTest(name = "Restore {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") | ||
96 | @MethodSource | ||
97 | @Tag("smoke") | ||
98 | @Tag("slow") | ||
99 | void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, | ||
100 | boolean evilHash) { | ||
101 | runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, | ||
102 | commitFrequency, evilHash); | ||
103 | } | ||
104 | |||
105 | static Stream<Arguments> parametrizedSlowFuzz() { | ||
106 | return FuzzTestUtils.changeStepCount(RestoreFuzzTest.parametrizedFastFuzz(), 1); | ||
107 | } | ||
108 | } | ||
diff --git a/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/SharedStoreFuzzTest.java b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/SharedStoreFuzzTest.java new file mode 100644 index 00000000..800308b4 --- /dev/null +++ b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/SharedStoreFuzzTest.java | |||
@@ -0,0 +1,112 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.tests.fuzz; | ||
2 | |||
3 | import java.util.HashMap; | ||
4 | import java.util.LinkedList; | ||
5 | import java.util.List; | ||
6 | import java.util.Map; | ||
7 | import java.util.Random; | ||
8 | import java.util.stream.Stream; | ||
9 | |||
10 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
11 | import org.eclipse.viatra.solver.data.map.VersionedMapStore; | ||
12 | import org.eclipse.viatra.solver.data.map.VersionedMapStoreImpl; | ||
13 | import org.eclipse.viatra.solver.data.map.internal.VersionedMapImpl; | ||
14 | import org.eclipse.viatra.solver.data.map.tests.fuzz.utils.FuzzTestUtils; | ||
15 | import org.eclipse.viatra.solver.data.map.tests.utils.MapTestEnvironment; | ||
16 | import org.junit.jupiter.api.Tag; | ||
17 | import org.junit.jupiter.api.Timeout; | ||
18 | import org.junit.jupiter.params.ParameterizedTest; | ||
19 | import org.junit.jupiter.params.provider.Arguments; | ||
20 | import org.junit.jupiter.params.provider.MethodSource; | ||
21 | |||
22 | class SharedStoreFuzzTest { | ||
23 | private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, | ||
24 | boolean evilHash) { | ||
25 | String[] values = MapTestEnvironment.prepareValues(maxValue); | ||
26 | ContinousHashProvider<Integer> chp = MapTestEnvironment.prepareHashProvider(evilHash); | ||
27 | |||
28 | List<VersionedMapStore<Integer, String>> stores = VersionedMapStoreImpl.createSharedVersionedMapStores(5, chp, values[0]); | ||
29 | |||
30 | iterativeRandomPutsAndCommitsThenRestore(scenario, stores, steps, maxKey, values, seed, commitFrequency); | ||
31 | } | ||
32 | |||
33 | private void iterativeRandomPutsAndCommitsThenRestore(String scenario, List<VersionedMapStore<Integer, String>> stores, | ||
34 | int steps, int maxKey, String[] values, int seed, int commitFrequency) { | ||
35 | // 1. maps with versions | ||
36 | Random r = new Random(seed); | ||
37 | List<VersionedMapImpl<Integer, String>> versioneds = new LinkedList<>(); | ||
38 | for(VersionedMapStore<Integer, String> store : stores) { | ||
39 | versioneds.add((VersionedMapImpl<Integer, String>) store.createMap()); | ||
40 | } | ||
41 | |||
42 | List<Map<Integer, Long>> index2Version = new LinkedList<>(); | ||
43 | for(int i = 0; i<stores.size(); i++) { | ||
44 | index2Version.add(new HashMap<>()); | ||
45 | } | ||
46 | |||
47 | for (int i = 0; i < steps; i++) { | ||
48 | int stepIndex = i + 1; | ||
49 | for (int storeIndex = 0; storeIndex<versioneds.size(); storeIndex++) { | ||
50 | int nextKey = r.nextInt(maxKey); | ||
51 | String nextValue = values[r.nextInt(values.length)]; | ||
52 | versioneds.get(storeIndex).put(nextKey, nextValue); | ||
53 | if (stepIndex % commitFrequency == 0) { | ||
54 | long version = versioneds.get(storeIndex).commit(); | ||
55 | index2Version.get(storeIndex).put(i, version); | ||
56 | } | ||
57 | MapTestEnvironment.printStatus(scenario, stepIndex, steps, "building"); | ||
58 | } | ||
59 | } | ||
60 | // 2. create a non-versioned and | ||
61 | List<VersionedMapImpl<Integer, String>> reference = new LinkedList<>(); | ||
62 | for(VersionedMapStore<Integer, String> store : stores) { | ||
63 | reference.add((VersionedMapImpl<Integer, String>) store.createMap()); | ||
64 | } | ||
65 | r = new Random(seed); | ||
66 | |||
67 | for (int i = 0; i < steps; i++) { | ||
68 | int index = i + 1; | ||
69 | for (int storeIndex = 0; storeIndex<versioneds.size(); storeIndex++) { | ||
70 | int nextKey = r.nextInt(maxKey); | ||
71 | String nextValue = values[r.nextInt(values.length)]; | ||
72 | reference.get(storeIndex).put(nextKey, nextValue); | ||
73 | if (index % commitFrequency == 0) { | ||
74 | versioneds.get(storeIndex).restore(index2Version.get(storeIndex).get(i)); | ||
75 | MapTestEnvironment.compareTwoMaps(scenario + ":" + index, reference.get(storeIndex), versioneds.get(storeIndex)); | ||
76 | } | ||
77 | } | ||
78 | MapTestEnvironment.printStatus(scenario, index, steps, "comparison"); | ||
79 | } | ||
80 | |||
81 | } | ||
82 | |||
83 | @ParameterizedTest(name = "Shared Store {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") | ||
84 | @MethodSource | ||
85 | @Timeout(value = 10) | ||
86 | @Tag("smoke") | ||
87 | void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, | ||
88 | boolean evilHash) { | ||
89 | runFuzzTest("SharedS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, | ||
90 | commitFrequency, evilHash); | ||
91 | } | ||
92 | |||
93 | static Stream<Arguments> parametrizedFastFuzz() { | ||
94 | return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, | ||
95 | new Object[] { 2, 3 }, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, | ||
96 | new Object[] { false, true }); | ||
97 | } | ||
98 | |||
99 | @ParameterizedTest(name = "Shared Store {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") | ||
100 | @MethodSource | ||
101 | @Tag("smoke") | ||
102 | @Tag("slow") | ||
103 | void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, | ||
104 | boolean evilHash) { | ||
105 | runFuzzTest("SharedS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, | ||
106 | commitFrequency, evilHash); | ||
107 | } | ||
108 | |||
109 | static Stream<Arguments> parametrizedSlowFuzz() { | ||
110 | return FuzzTestUtils.changeStepCount(RestoreFuzzTest.parametrizedFastFuzz(), 1); | ||
111 | } | ||
112 | } | ||
diff --git a/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/utils/FuzzTestUtils.java b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/utils/FuzzTestUtils.java new file mode 100644 index 00000000..ec21bb7b --- /dev/null +++ b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/utils/FuzzTestUtils.java | |||
@@ -0,0 +1,64 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.tests.fuzz.utils; | ||
2 | |||
3 | import java.util.Arrays; | ||
4 | import java.util.LinkedList; | ||
5 | import java.util.List; | ||
6 | import java.util.stream.Stream; | ||
7 | |||
8 | import org.junit.jupiter.params.provider.Arguments; | ||
9 | |||
10 | public final class FuzzTestUtils { | ||
11 | public static final int FAST_STEP_COUNT = 500; | ||
12 | public static final int SLOW_STEP_COUNT = 32 * 32 * 32 * 32; | ||
13 | |||
14 | private FuzzTestUtils() { | ||
15 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
16 | } | ||
17 | |||
18 | public static Stream<Arguments> changeStepCount(Stream<Arguments> arguments, int parameterIndex) { | ||
19 | return arguments.map(x -> Arguments.of(updatedStepCount(x.get(), parameterIndex))); | ||
20 | } | ||
21 | |||
22 | public static Object[] updatedStepCount(Object[] arguments, int parameterIndex) { | ||
23 | Object[] copy = Arrays.copyOf(arguments, arguments.length); | ||
24 | copy[parameterIndex] = SLOW_STEP_COUNT; | ||
25 | return copy; | ||
26 | } | ||
27 | |||
28 | static List<List<Object>> permutationInternal(int from, Object[]... valueOption) { | ||
29 | if (valueOption.length == from) { | ||
30 | return List.of(List.of()); | ||
31 | } else { | ||
32 | Object[] permuteThis = valueOption[from]; | ||
33 | List<List<Object>> otherCombination = permutationInternal(from + 1, valueOption); | ||
34 | List<List<Object>> result = new LinkedList<>(); | ||
35 | for (Object permuteThisElement : permuteThis) { | ||
36 | for (List<Object> otherCombinationList : otherCombination) { | ||
37 | List<Object> newResult = new LinkedList<>(); | ||
38 | newResult.add(permuteThisElement); | ||
39 | newResult.addAll(otherCombinationList); | ||
40 | result.add(newResult); | ||
41 | } | ||
42 | } | ||
43 | return result; | ||
44 | } | ||
45 | } | ||
46 | |||
47 | public static Stream<Arguments> permutation(Object[]... valueOption) { | ||
48 | List<List<Object>> permutations = permutationInternal(0, valueOption); | ||
49 | return permutations.stream().map(x -> Arguments.of(x.toArray())); | ||
50 | } | ||
51 | |||
52 | public static Stream<Arguments> permutationWithSize(Object[]... valueOption) { | ||
53 | int size = 1; | ||
54 | for (int i = 0; i < valueOption.length; i++) { | ||
55 | size *= valueOption[i].length; | ||
56 | } | ||
57 | Object[][] newValueOption = new Object[valueOption.length + 1][]; | ||
58 | newValueOption[0] = new Object[] { size }; | ||
59 | for (int i = 1; i < newValueOption.length; i++) { | ||
60 | newValueOption[i] = valueOption[i - 1]; | ||
61 | } | ||
62 | return permutation(newValueOption); | ||
63 | } | ||
64 | } | ||
diff --git a/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/utils/FuzzTestUtilsTest.java b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/utils/FuzzTestUtilsTest.java new file mode 100644 index 00000000..3f4214bc --- /dev/null +++ b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/fuzz/utils/FuzzTestUtilsTest.java | |||
@@ -0,0 +1,33 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.tests.fuzz.utils; | ||
2 | |||
3 | import static org.junit.jupiter.api.Assertions.assertEquals; | ||
4 | |||
5 | import java.util.List; | ||
6 | |||
7 | import org.junit.jupiter.api.Test; | ||
8 | |||
9 | class FuzzTestUtilsTest { | ||
10 | @Test | ||
11 | void permutationInternalTest() { | ||
12 | List<List<Object>> res = FuzzTestUtils.permutationInternal(0, new Object[] { 1, 2, 3 }, | ||
13 | new Object[] { 'a', 'b', 'c' }, new Object[] { "alpha", "beta", "gamma", "delta" }); | ||
14 | assertEquals(3 * 3 * 4, res.size()); | ||
15 | } | ||
16 | |||
17 | @Test | ||
18 | void permutationTest1() { | ||
19 | var res = FuzzTestUtils.permutation(new Object[] { 1, 2, 3 }, new Object[] { 'a', 'b', 'c' }, | ||
20 | new Object[] { "alpha", "beta", "gamma", "delta" }); | ||
21 | assertEquals(3 * 3 * 4, res.count()); | ||
22 | } | ||
23 | |||
24 | @Test | ||
25 | void permutationTest2() { | ||
26 | var res = FuzzTestUtils.permutation(new Object[] { 1, 2, 3 }, new Object[] { 'a', 'b', 'c' }, | ||
27 | new Object[] { "alpha", "beta", "gamma", "delta" }); | ||
28 | var arguments = res.findFirst().get().get(); | ||
29 | assertEquals(1, arguments[0]); | ||
30 | assertEquals('a', arguments[1]); | ||
31 | assertEquals("alpha", arguments[2]); | ||
32 | } | ||
33 | } | ||
diff --git a/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/utils/MapTestEnvironment.java b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/utils/MapTestEnvironment.java new file mode 100644 index 00000000..4c043350 --- /dev/null +++ b/store/src/test/java/org/eclipse/viatra/solver/data/map/tests/utils/MapTestEnvironment.java | |||
@@ -0,0 +1,213 @@ | |||
1 | package org.eclipse.viatra.solver.data.map.tests.utils; | ||
2 | |||
3 | import static org.junit.jupiter.api.Assertions.assertEquals; | ||
4 | import static org.junit.jupiter.api.Assertions.assertTrue; | ||
5 | import static org.junit.jupiter.api.Assertions.fail; | ||
6 | |||
7 | import java.util.HashMap; | ||
8 | import java.util.Iterator; | ||
9 | import java.util.List; | ||
10 | import java.util.Map; | ||
11 | import java.util.Map.Entry; | ||
12 | import java.util.TreeMap; | ||
13 | |||
14 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
15 | import org.eclipse.viatra.solver.data.map.Cursor; | ||
16 | import org.eclipse.viatra.solver.data.map.VersionedMap; | ||
17 | import org.eclipse.viatra.solver.data.map.internal.VersionedMapImpl; | ||
18 | |||
19 | public class MapTestEnvironment<K, V> { | ||
20 | public static String[] prepareValues(int maxValue) { | ||
21 | String[] values = new String[maxValue]; | ||
22 | values[0] = "DEFAULT"; | ||
23 | for (int i = 1; i < values.length; i++) { | ||
24 | values[i] = "VAL" + i; | ||
25 | } | ||
26 | return values; | ||
27 | } | ||
28 | |||
29 | public static ContinousHashProvider<Integer> prepareHashProvider(final boolean evil) { | ||
30 | // Use maxPrime = 2147483629 | ||
31 | |||
32 | ContinousHashProvider<Integer> chp = new ContinousHashProvider<Integer>() { | ||
33 | |||
34 | @Override | ||
35 | public int getHash(Integer key, int index) { | ||
36 | if (evil && index < 15 && index < key / 3) { | ||
37 | return 7; | ||
38 | } | ||
39 | int result = 1; | ||
40 | final int prime = 31; | ||
41 | |||
42 | result = prime * result + key; | ||
43 | result = prime * result + index; | ||
44 | |||
45 | return result; | ||
46 | } | ||
47 | }; | ||
48 | return chp; | ||
49 | } | ||
50 | |||
51 | public static void printStatus(String scenario, int actual, int max, String stepName) { | ||
52 | if (actual % 10000 == 0) { | ||
53 | String printStepName = stepName == null ? "" : stepName; | ||
54 | System.out.format(scenario + ":%d/%d (%d%%) " + printStepName + "%n", actual, max, actual * 100 / max); | ||
55 | } | ||
56 | |||
57 | } | ||
58 | |||
59 | public static <K, V> void compareTwoMaps(String title, VersionedMapImpl<K, V> map1, | ||
60 | VersionedMapImpl<K, V> map2) { | ||
61 | compareTwoMaps(title, map1, map2, null); | ||
62 | } | ||
63 | public static <K, V> void compareTwoMaps(String title, VersionedMapImpl<K, V> map1, | ||
64 | VersionedMapImpl<K, V> map2, List<Throwable> errors) { | ||
65 | // 1. Comparing cursors. | ||
66 | Cursor<K, V> cursor1 = map1.getAll(); | ||
67 | Cursor<K, V> cursor2 = map2.getAll(); | ||
68 | while (!cursor1.isTerminated()) { | ||
69 | if (cursor2.isTerminated()) { | ||
70 | fail("cursor 2 terminated before cursor1"); | ||
71 | } | ||
72 | assertEqualsList(cursor1.getKey(), cursor2.getKey(),"Keys not equal", errors); | ||
73 | assertEqualsList(cursor2.getValue(), cursor2.getValue(), "Values not equal", errors); | ||
74 | cursor1.move(); | ||
75 | cursor2.move(); | ||
76 | } | ||
77 | if (!cursor2.isTerminated()) | ||
78 | fail("cursor 1 terminated before cursor 2"); | ||
79 | |||
80 | // 2.1. comparing hash codes | ||
81 | assertEqualsList(map1.hashCode(), map2.hashCode(), title + ": hash code check",errors); | ||
82 | assertEqualsList(map1, map2, title + ": 1.equals(2)",errors); | ||
83 | assertEqualsList(map2, map1, title + ": 2.equals(1)",errors); | ||
84 | } | ||
85 | private static void assertEqualsList(Object o1, Object o2, String message, List<Throwable> errors) { | ||
86 | if(errors == null) { | ||
87 | assertEquals(o1, o2, message); | ||
88 | } else { | ||
89 | if(o1 != null) { | ||
90 | if(!(o1.equals(o2))) { | ||
91 | AssertionError error = new AssertionError((message != null ? message+" " : "") + "expected: " + o1 + " but was : " + o2); | ||
92 | errors.add(error); | ||
93 | } | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | |||
98 | public VersionedMapImpl<K, V> sut; | ||
99 | Map<K, V> oracle = new HashMap<K, V>(); | ||
100 | |||
101 | public MapTestEnvironment(VersionedMapImpl<K, V> sut) { | ||
102 | this.sut = sut; | ||
103 | } | ||
104 | |||
105 | public void put(K key, V value) { | ||
106 | V oldSutValue = sut.put(key, value); | ||
107 | V oldOracleValue; | ||
108 | if (value != sut.getDefaultValue()) { | ||
109 | oldOracleValue = oracle.put(key, value); | ||
110 | } else { | ||
111 | oldOracleValue = oracle.remove(key); | ||
112 | } | ||
113 | if(oldSutValue == sut.getDefaultValue() && oldOracleValue != null) { | ||
114 | fail("After put, SUT old value was default, but oracle old walue was " + oldOracleValue); | ||
115 | } | ||
116 | if(oldSutValue != sut.getDefaultValue()) { | ||
117 | assertEquals(oldOracleValue, oldSutValue); | ||
118 | } | ||
119 | } | ||
120 | |||
121 | public void checkEquivalence(String title) { | ||
122 | // 0. Checking integrity | ||
123 | try { | ||
124 | sut.checkIntegrity(); | ||
125 | } catch (IllegalStateException e) { | ||
126 | fail(title + ": " + e.getMessage()); | ||
127 | } | ||
128 | |||
129 | // 1. Checking: if Reference contains <key,value> pair, then SUT contains | ||
130 | // <key,value> pair. | ||
131 | // Tests get functions | ||
132 | for (Entry<K, V> entry : oracle.entrySet()) { | ||
133 | V sutValue = sut.get(entry.getKey()); | ||
134 | V oracleValue = entry.getValue(); | ||
135 | if (sutValue != oracleValue) { | ||
136 | printComparison(); | ||
137 | fail(title + ": Non-equivalent get(" + entry.getKey() + ") results: SUT=" + sutValue + ", Oracle=" | ||
138 | + oracleValue + "!"); | ||
139 | } | ||
140 | } | ||
141 | |||
142 | // 2. Checking: if SUT contains <key,value> pair, then Reference contains | ||
143 | // <key,value> pair. | ||
144 | // Tests iterators | ||
145 | int elementsInSutEntrySet = 0; | ||
146 | Cursor<K, V> cursor = sut.getAll(); | ||
147 | while (cursor.move()) { | ||
148 | elementsInSutEntrySet++; | ||
149 | K key = cursor.getKey(); | ||
150 | V sutValue = cursor.getValue(); | ||
151 | // System.out.println(key + " -> " + sutValue); | ||
152 | V oracleValue = oracle.get(key); | ||
153 | if (sutValue != oracleValue) { | ||
154 | printComparison(); | ||
155 | fail(title + ": Non-equivalent entry in iterator: SUT=<" + key + "," + sutValue + ">, Oracle=<" + key | ||
156 | + "," + oracleValue + ">!"); | ||
157 | } | ||
158 | |||
159 | } | ||
160 | |||
161 | // 3. Checking sizes | ||
162 | // Counting of non-default value pairs. | ||
163 | int oracleSize = oracle.entrySet().size(); | ||
164 | long sutSize = sut.getSize(); | ||
165 | if (oracleSize != sutSize || oracleSize != elementsInSutEntrySet) { | ||
166 | printComparison(); | ||
167 | fail(title + ": Non-eqivalent size() result: SUT.getSize()=" + sutSize + ", SUT.entryset.size=" | ||
168 | + elementsInSutEntrySet + ", Oracle=" + oracleSize + "!"); | ||
169 | } | ||
170 | } | ||
171 | |||
172 | public static <K,V> void checkOrder(String scenario, VersionedMap<K,V> versionedMap) { | ||
173 | K previous = null; | ||
174 | Cursor<K, V> cursor = versionedMap.getAll(); | ||
175 | while(cursor.move()) { | ||
176 | System.out.println(cursor.getKey() + " " + ((VersionedMapImpl<K, V>) versionedMap).getHashProvider().getHash(cursor.getKey(), 0)); | ||
177 | if(previous != null) { | ||
178 | int comparisonResult = ((VersionedMapImpl<K, V>) versionedMap).getHashProvider().compare(previous, cursor.getKey()); | ||
179 | assertTrue(comparisonResult<0,scenario+" Cursor order is not incremental!"); | ||
180 | } | ||
181 | previous = cursor.getKey(); | ||
182 | } | ||
183 | System.out.println(); | ||
184 | } | ||
185 | |||
186 | public void printComparison() { | ||
187 | System.out.println("SUT:"); | ||
188 | printEntrySet(sut.getAll()); | ||
189 | System.out.println("Oracle:"); | ||
190 | printEntrySet(oracle.entrySet().iterator()); | ||
191 | } | ||
192 | |||
193 | private void printEntrySet(Iterator<Entry<K, V>> iterator) { | ||
194 | TreeMap<K, V> treemap = new TreeMap<>(); | ||
195 | while (iterator.hasNext()) { | ||
196 | Entry<K, V> entry = iterator.next(); | ||
197 | treemap.put(entry.getKey(), entry.getValue()); | ||
198 | } | ||
199 | for (Entry<K, V> e : treemap.entrySet()) { | ||
200 | System.out.println("\t" + e.getKey() + " -> " + e.getValue()); | ||
201 | } | ||
202 | } | ||
203 | |||
204 | private void printEntrySet(Cursor<K, V> cursor) { | ||
205 | TreeMap<K, V> treemap = new TreeMap<>(); | ||
206 | while (cursor.move()) { | ||
207 | treemap.put(cursor.getKey(), cursor.getValue()); | ||
208 | } | ||
209 | for (Entry<K, V> e : treemap.entrySet()) { | ||
210 | System.out.println("\t" + e.getKey() + " -> " + e.getValue()); | ||
211 | } | ||
212 | } | ||
213 | } | ||
diff --git a/store/src/test/java/org/eclipse/viatra/solver/data/model/hashTests/HashEfficiencyTest.java b/store/src/test/java/org/eclipse/viatra/solver/data/model/hashTests/HashEfficiencyTest.java new file mode 100644 index 00000000..c4d98a43 --- /dev/null +++ b/store/src/test/java/org/eclipse/viatra/solver/data/model/hashTests/HashEfficiencyTest.java | |||
@@ -0,0 +1,160 @@ | |||
1 | package org.eclipse.viatra.solver.data.model.hashTests; | ||
2 | |||
3 | import static org.junit.jupiter.api.Assertions.assertEquals; | ||
4 | |||
5 | import java.util.ArrayList; | ||
6 | import java.util.LinkedList; | ||
7 | import java.util.List; | ||
8 | import java.util.Random; | ||
9 | |||
10 | import org.eclipse.viatra.solver.data.map.ContinousHashProvider; | ||
11 | import org.eclipse.viatra.solver.data.model.Tuple; | ||
12 | import org.eclipse.viatra.solver.data.model.TupleHashProvider; | ||
13 | import org.eclipse.viatra.solver.data.model.TupleHashProviderBitMagic; | ||
14 | import org.junit.jupiter.api.Test; | ||
15 | |||
16 | class HashEfficiencyTest { | ||
17 | |||
18 | private static List<Tuple> permutations(int range, int arity) { | ||
19 | if(arity == 1) { | ||
20 | List<Tuple> result = new ArrayList<>(range); | ||
21 | for(int i=0; i<range; i++) { | ||
22 | result.add(Tuple.of(i)); | ||
23 | } | ||
24 | return result; | ||
25 | } else if(arity > 1) { | ||
26 | List<Tuple> smallers = permutations(range, arity-1); | ||
27 | List<Tuple> result = new ArrayList<>(range*smallers.size()); | ||
28 | for(Tuple smaller : smallers) { | ||
29 | for(int i=0; i<range; i++) { | ||
30 | int[] larger = new int[arity]; | ||
31 | for(int x = 0; x<smaller.getSize(); x++) { | ||
32 | larger[x] = smaller.get(x); | ||
33 | } | ||
34 | larger[arity-1] = i; | ||
35 | result.add(Tuple.of(larger)); | ||
36 | } | ||
37 | } | ||
38 | return result; | ||
39 | } else throw new IllegalArgumentException(); | ||
40 | } | ||
41 | |||
42 | private static int amountToRange(int arity, int n) { | ||
43 | int range = 1; | ||
44 | while(Math.pow(range,arity)<n+0.1) { | ||
45 | range++; | ||
46 | } | ||
47 | return 1024; | ||
48 | } | ||
49 | |||
50 | public static List<Tuple> nPermutations(int arity, int n) { | ||
51 | int range = amountToRange(arity, n); | ||
52 | List<Tuple> permutations = permutations(range, arity); | ||
53 | return permutations.subList(0, n); | ||
54 | } | ||
55 | |||
56 | public static List<Tuple> nRandoms(int arity, int n, int seed) { | ||
57 | int range = amountToRange(arity, n); | ||
58 | List<Tuple> permutations = new ArrayList<>(n); | ||
59 | Random r = new Random(seed); | ||
60 | for(int i = 0; i<n; i++) { | ||
61 | int[] tuple = new int[arity]; | ||
62 | for(int j=0; j<arity; j++) { | ||
63 | tuple[j] = r.nextInt(range); | ||
64 | } | ||
65 | permutations.add(Tuple.of(tuple)); | ||
66 | } | ||
67 | return permutations; | ||
68 | } | ||
69 | |||
70 | @Test | ||
71 | void permutationTest() { | ||
72 | List<Tuple> p = permutations(10, 2); | ||
73 | assertEquals(p.size(),10*10); | ||
74 | } | ||
75 | // private void printTuples(List<Tuple> p) { | ||
76 | // for(Tuple element : p) { | ||
77 | // System.out.println(element); | ||
78 | // } | ||
79 | // } | ||
80 | @Test | ||
81 | void nPermutationTest() { | ||
82 | final int amount = 500; | ||
83 | List<Tuple> p = nPermutations(2, amount); | ||
84 | assertEquals(amount,p.size()); | ||
85 | } | ||
86 | @Test | ||
87 | void nRandomTest() { | ||
88 | final int amount = 500; | ||
89 | List<Tuple> p = nRandoms(2, amount, 1);; | ||
90 | assertEquals(amount,p.size()); | ||
91 | } | ||
92 | private static double calculateHashClashes(List<Tuple> tuples, ContinousHashProvider<Tuple> chp) { | ||
93 | int sumClashes = 0; | ||
94 | |||
95 | for(int i = 0; i<tuples.size(); i++) { | ||
96 | int height = 0; | ||
97 | for(int j=0; j<tuples.size(); j++) { | ||
98 | int clashes = calculateHashClash(chp, tuples.get(i), tuples.get(j)); | ||
99 | height = Math.max(height, clashes); | ||
100 | } | ||
101 | sumClashes += height; | ||
102 | } | ||
103 | return (sumClashes+0.0) / tuples.size(); | ||
104 | } | ||
105 | private static int calculateHashClash(ContinousHashProvider<Tuple> chp, Tuple a, Tuple b) { | ||
106 | if(a.equals(b)) return 0; | ||
107 | final int bits = 5; | ||
108 | final int segments = Integer.SIZE/bits; | ||
109 | final int mask = (1<<bits)-1; | ||
110 | for(int i = 0;;i++) { | ||
111 | int index = i/segments; | ||
112 | int depth = i%segments; | ||
113 | int aHash = (chp.getHash(a, index)>>(depth*5))&mask; | ||
114 | int bHash = (chp.getHash(b, index)>>(depth*5))&mask; | ||
115 | if(aHash != bHash) { | ||
116 | return i+1; | ||
117 | } | ||
118 | if(i>400) { | ||
119 | throw new IllegalStateException(a+" vs "+b); | ||
120 | } | ||
121 | } | ||
122 | } | ||
123 | private static double caclulateOptimalHashClash(int size) { | ||
124 | return (Math.log(size)/Math.log(32)); | ||
125 | } | ||
126 | public static void main(String[] args) { | ||
127 | List<String> hashNames = new LinkedList<>(); | ||
128 | List<ContinousHashProvider<Tuple>> hashes = new LinkedList<>(); | ||
129 | hashNames.add("PrimeGroup"); | ||
130 | hashes.add(new TupleHashProvider()); | ||
131 | hashNames.add("BitMagic"); | ||
132 | hashes.add(new TupleHashProviderBitMagic()); | ||
133 | |||
134 | int[] arities = new int[] {2,3,4,5}; | ||
135 | int[] sizes = new int[] {32*32,32*32*8}; | ||
136 | |||
137 | System.out.println("Size,Arity,DataSource,Hash,Chashes,Optimal,Badness"); | ||
138 | for(int size : sizes) { | ||
139 | double optimalClashes = caclulateOptimalHashClash(size); | ||
140 | for(int arity : arities) { | ||
141 | List<String> dataSourceNames = new LinkedList<>(); | ||
142 | List<List<Tuple>> dataSources = new LinkedList<>(); | ||
143 | |||
144 | // dataSourceNames.add("Permutation"); | ||
145 | // dataSources.add(nPermutations(arity, size)); | ||
146 | dataSourceNames.add("Random"); | ||
147 | dataSources.add(nRandoms(arity, size, 0)); | ||
148 | |||
149 | for(int dataSourceIndex = 0; dataSourceIndex<dataSourceNames.size(); dataSourceIndex++) { | ||
150 | for(int hashIndex = 0; hashIndex<hashNames.size(); hashIndex++) { | ||
151 | double clashes = calculateHashClashes(dataSources.get(dataSourceIndex),hashes.get(hashIndex)); | ||
152 | System.out.println( | ||
153 | size+","+arity+","+dataSourceNames.get(dataSourceIndex)+","+hashNames.get(hashIndex)+","+ | ||
154 | clashes+","+optimalClashes+","+(clashes+0.0)/optimalClashes); | ||
155 | } | ||
156 | } | ||
157 | } | ||
158 | } | ||
159 | } | ||
160 | } | ||
diff --git a/store/src/test/java/org/eclipse/viatra/solver/data/model/tests/ModelTest.java b/store/src/test/java/org/eclipse/viatra/solver/data/model/tests/ModelTest.java new file mode 100644 index 00000000..c9bf3da9 --- /dev/null +++ b/store/src/test/java/org/eclipse/viatra/solver/data/model/tests/ModelTest.java | |||
@@ -0,0 +1,147 @@ | |||
1 | package org.eclipse.viatra.solver.data.model.tests; | ||
2 | |||
3 | import static org.junit.jupiter.api.Assertions.assertEquals; | ||
4 | import static org.junit.jupiter.api.Assertions.assertFalse; | ||
5 | import static org.junit.jupiter.api.Assertions.assertTrue; | ||
6 | |||
7 | import java.util.Set; | ||
8 | |||
9 | import org.eclipse.viatra.solver.data.model.Model; | ||
10 | import org.eclipse.viatra.solver.data.model.ModelStore; | ||
11 | import org.eclipse.viatra.solver.data.model.ModelStoreImpl; | ||
12 | import org.eclipse.viatra.solver.data.model.Tuple; | ||
13 | import org.eclipse.viatra.solver.data.model.representation.Relation; | ||
14 | import org.junit.jupiter.api.Assertions; | ||
15 | import org.junit.jupiter.api.Test; | ||
16 | |||
17 | class ModelTest { | ||
18 | |||
19 | @Test | ||
20 | void modelConstructionTest() { | ||
21 | Relation<Boolean> person = new Relation<>("Person", 1, false); | ||
22 | Relation<Boolean> friend = new Relation<>("friend", 2, false); | ||
23 | |||
24 | ModelStore store = new ModelStoreImpl(Set.of(person, friend)); | ||
25 | Model model = store.createModel(); | ||
26 | |||
27 | assertTrue(store.getDataRepresentations().contains(person)); | ||
28 | assertTrue(store.getDataRepresentations().contains(friend)); | ||
29 | assertTrue(model.getDataRepresentations().contains(person)); | ||
30 | assertTrue(model.getDataRepresentations().contains(friend)); | ||
31 | |||
32 | Relation<Integer> other = new Relation<Integer>("other", 2, null); | ||
33 | assertFalse(model.getDataRepresentations().contains(other)); | ||
34 | } | ||
35 | |||
36 | @Test | ||
37 | void modelBuildingTest() { | ||
38 | Relation<Boolean> person = new Relation<>("Person", 1, false); | ||
39 | Relation<Integer> age = new Relation<Integer>("age", 1, null); | ||
40 | Relation<Boolean> friend = new Relation<>("friend", 2, false); | ||
41 | |||
42 | ModelStore store = new ModelStoreImpl(Set.of(person, age, friend)); | ||
43 | Model model = store.createModel(); | ||
44 | |||
45 | model.put(person, Tuple.of(0), true); | ||
46 | model.put(person, Tuple.of(1), true); | ||
47 | model.put(age, Tuple.of(0), 3); | ||
48 | model.put(age, Tuple.of(1), 1); | ||
49 | model.put(friend, Tuple.of(0, 1), true); | ||
50 | model.put(friend, Tuple.of(1, 0), true); | ||
51 | |||
52 | assertTrue(model.get(person, Tuple.of(0))); | ||
53 | assertTrue(model.get(person, Tuple.of(1))); | ||
54 | assertFalse(model.get(person, Tuple.of(2))); | ||
55 | |||
56 | assertEquals(3, model.get(age, Tuple.of(0))); | ||
57 | assertEquals(1, model.get(age, Tuple.of(1))); | ||
58 | assertEquals(null, model.get(age, Tuple.of(2))); | ||
59 | |||
60 | assertTrue(model.get(friend, Tuple.of(0, 1))); | ||
61 | assertFalse(model.get(friend, Tuple.of(0, 5))); | ||
62 | } | ||
63 | |||
64 | @Test | ||
65 | void modelBuildingArityFailTest() { | ||
66 | Relation<Boolean> person = new Relation<>("Person", 1, false); | ||
67 | ModelStore store = new ModelStoreImpl(Set.of(person)); | ||
68 | Model model = store.createModel(); | ||
69 | |||
70 | final Tuple tuple3 = Tuple.of(1, 1, 1); | ||
71 | Assertions.assertThrows(IllegalArgumentException.class, () -> model.put(person, tuple3, true)); | ||
72 | Assertions.assertThrows(IllegalArgumentException.class, () -> model.get(person, tuple3)); | ||
73 | } | ||
74 | |||
75 | @Test | ||
76 | void modelBuildingNullFailTest() { | ||
77 | Relation<Integer> age = new Relation<Integer>("age", 1, null); | ||
78 | ModelStore store = new ModelStoreImpl(Set.of(age)); | ||
79 | Model model = store.createModel(); | ||
80 | |||
81 | model.put(age, Tuple.of(1), null); // valid | ||
82 | Assertions.assertThrows(IllegalArgumentException.class, () -> model.put(age, null, 1)); | ||
83 | Assertions.assertThrows(IllegalArgumentException.class, () -> model.get(age, null)); | ||
84 | |||
85 | } | ||
86 | |||
87 | @Test | ||
88 | void modelUpdateTest() { | ||
89 | Relation<Boolean> person = new Relation<>("Person", 1, false); | ||
90 | Relation<Integer> age = new Relation<Integer>("age", 1, null); | ||
91 | Relation<Boolean> friend = new Relation<>("friend", 2, false); | ||
92 | |||
93 | ModelStore store = new ModelStoreImpl(Set.of(person, age, friend)); | ||
94 | Model model = store.createModel(); | ||
95 | |||
96 | model.put(person, Tuple.of(0), true); | ||
97 | model.put(person, Tuple.of(1), true); | ||
98 | model.put(age, Tuple.of(0), 3); | ||
99 | model.put(age, Tuple.of(1), 1); | ||
100 | model.put(friend, Tuple.of(0, 1), true); | ||
101 | model.put(friend, Tuple.of(1, 0), true); | ||
102 | |||
103 | assertEquals(3, model.get(age, Tuple.of(0))); | ||
104 | assertTrue(model.get(friend, Tuple.of(0, 1))); | ||
105 | |||
106 | model.put(age, Tuple.of(0), 4); | ||
107 | model.put(friend, Tuple.of(0, 1), false); | ||
108 | |||
109 | assertEquals(4, model.get(age, Tuple.of(0))); | ||
110 | assertFalse(model.get(friend, Tuple.of(0, 1))); | ||
111 | } | ||
112 | |||
113 | @Test | ||
114 | void restoreTest() { | ||
115 | Relation<Boolean> person = new Relation<Boolean>("Person", 1, false); | ||
116 | Relation<Boolean> friend = new Relation<Boolean>("friend", 2, false); | ||
117 | |||
118 | ModelStore store = new ModelStoreImpl(Set.of(person, friend)); | ||
119 | Model model = store.createModel(); | ||
120 | |||
121 | model.put(person, Tuple.of(0), true); | ||
122 | model.put(person, Tuple.of(1), true); | ||
123 | model.put(friend, Tuple.of(0, 1), true); | ||
124 | model.put(friend, Tuple.of(1, 0), true); | ||
125 | long state1 = model.commit(); | ||
126 | |||
127 | assertFalse(model.get(person, Tuple.of(2))); | ||
128 | assertFalse(model.get(friend, Tuple.of(0, 2))); | ||
129 | |||
130 | model.put(person, Tuple.of(2), true); | ||
131 | model.put(friend, Tuple.of(0, 2), true); | ||
132 | long state2 = model.commit(); | ||
133 | |||
134 | assertTrue(model.get(person, Tuple.of(2))); | ||
135 | assertTrue(model.get(friend, Tuple.of(0, 2))); | ||
136 | |||
137 | model.restore(state1); | ||
138 | |||
139 | assertFalse(model.get(person, Tuple.of(2))); | ||
140 | assertFalse(model.get(friend, Tuple.of(0, 2))); | ||
141 | |||
142 | model.restore(state2); | ||
143 | |||
144 | assertTrue(model.get(person, Tuple.of(2))); | ||
145 | assertTrue(model.get(friend, Tuple.of(0, 2))); | ||
146 | } | ||
147 | } | ||
diff --git a/store/src/test/java/org/eclipse/viatra/solver/data/query/test/QueryTest.java b/store/src/test/java/org/eclipse/viatra/solver/data/query/test/QueryTest.java new file mode 100644 index 00000000..182f6c94 --- /dev/null +++ b/store/src/test/java/org/eclipse/viatra/solver/data/query/test/QueryTest.java | |||
@@ -0,0 +1,89 @@ | |||
1 | package org.eclipse.viatra.solver.data.query.test; | ||
2 | |||
3 | import static org.junit.jupiter.api.Assertions.assertEquals; | ||
4 | import static org.junit.jupiter.api.Assertions.assertFalse; | ||
5 | import static org.junit.jupiter.api.Assertions.assertTrue; | ||
6 | |||
7 | import java.util.Collection; | ||
8 | import java.util.Set; | ||
9 | |||
10 | import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine; | ||
11 | import org.eclipse.viatra.query.runtime.api.GenericPatternMatch; | ||
12 | import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher; | ||
13 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; | ||
14 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; | ||
15 | import org.eclipse.viatra.solver.data.model.Model; | ||
16 | import org.eclipse.viatra.solver.data.model.ModelStore; | ||
17 | import org.eclipse.viatra.solver.data.model.ModelStoreImpl; | ||
18 | import org.eclipse.viatra.solver.data.model.Tuple; | ||
19 | import org.eclipse.viatra.solver.data.model.representation.Relation; | ||
20 | import org.eclipse.viatra.solver.data.model.representation.TruthValue; | ||
21 | import org.eclipse.viatra.solver.data.query.RelationalScope; | ||
22 | import org.eclipse.viatra.solver.data.query.internal.PredicateTranslator; | ||
23 | import org.eclipse.viatra.solver.data.query.view.FilteredRelationView; | ||
24 | import org.eclipse.viatra.solver.data.query.view.FunctionalRelationView; | ||
25 | import org.eclipse.viatra.solver.data.query.view.RelationView; | ||
26 | import org.eclipse.viatra.solver.data.query.view.KeyOnlyRelationView; | ||
27 | import org.junit.jupiter.api.Test; | ||
28 | |||
29 | class QueryTest { | ||
30 | @Test | ||
31 | void minimalTest() { | ||
32 | Relation<Boolean> person = new Relation<>("Person", 1, false); | ||
33 | |||
34 | RelationView<Boolean> persionView = new KeyOnlyRelationView(person); | ||
35 | GenericQuerySpecification<GenericPatternMatcher> personQuery = (new PredicateTranslator("PersonQuery")) | ||
36 | .addParameter("p", persionView).addConstraint(persionView, "p").build(); | ||
37 | |||
38 | ModelStore store = new ModelStoreImpl(Set.of(person)); | ||
39 | Model model = store.createModel(); | ||
40 | |||
41 | model.put(person, Tuple.of(0), true); | ||
42 | model.put(person, Tuple.of(1), true); | ||
43 | |||
44 | RelationalScope scope = new RelationalScope(model, Set.of(persionView)); | ||
45 | |||
46 | ViatraQueryEngine engine = AdvancedViatraQueryEngine.on(scope); | ||
47 | GenericPatternMatcher personMatcher = engine.getMatcher(personQuery); | ||
48 | |||
49 | assertEquals(2, personMatcher.countMatches()); | ||
50 | } | ||
51 | |||
52 | void modelBuildingTest() { | ||
53 | Relation<Boolean> person = new Relation<>("Person", 1, false); | ||
54 | Relation<Integer> age = new Relation<Integer>("age", 1, null); | ||
55 | Relation<TruthValue> friend = new Relation<>("friend", 2, TruthValue.False); | ||
56 | |||
57 | ModelStore store = new ModelStoreImpl(Set.of(person, age, friend)); | ||
58 | Model model = store.createModel(); | ||
59 | |||
60 | model.put(person, Tuple.of(0), true); | ||
61 | model.put(person, Tuple.of(1), true); | ||
62 | model.put(age, Tuple.of(0), 3); | ||
63 | model.put(age, Tuple.of(1), 1); | ||
64 | model.put(friend, Tuple.of(0, 1), TruthValue.True); | ||
65 | model.put(friend, Tuple.of(1, 0), TruthValue.Unknown); | ||
66 | |||
67 | // Sanity check | ||
68 | assertTrue(model.get(person, Tuple.of(0))); | ||
69 | assertTrue(model.get(person, Tuple.of(1))); | ||
70 | assertFalse(model.get(person, Tuple.of(2))); | ||
71 | |||
72 | RelationView<Boolean> persionView = new KeyOnlyRelationView(person); | ||
73 | RelationView<Integer> ageView = new FunctionalRelationView<>(age); | ||
74 | RelationView<TruthValue> friendMustView = new FilteredRelationView<TruthValue>(friend, (k, v) -> v.must()); | ||
75 | RelationView<TruthValue> friendMayView = new FilteredRelationView<TruthValue>(friend, (k, v) -> v.may()); | ||
76 | |||
77 | RelationalScope scope = new RelationalScope(model, Set.of(persionView, ageView, friendMustView, friendMayView)); | ||
78 | |||
79 | GenericQuerySpecification<GenericPatternMatcher> personQuery = (new PredicateTranslator("PersonQuery")) | ||
80 | .addParameter("p", persionView).addConstraint(persionView, "p").build(); | ||
81 | |||
82 | ViatraQueryEngine engine = AdvancedViatraQueryEngine.on(scope); | ||
83 | GenericPatternMatcher personMatcher = engine.getMatcher(personQuery); | ||
84 | Collection<GenericPatternMatch> personMatches = personMatcher.getAllMatches(); | ||
85 | for (GenericPatternMatch personMatch : personMatches) { | ||
86 | System.out.println(personMatch); | ||
87 | } | ||
88 | } | ||
89 | } \ No newline at end of file | ||
diff --git a/store/src/test/java/org/eclipse/viatra/solver/data/util/CollectionsUtilTests.java b/store/src/test/java/org/eclipse/viatra/solver/data/util/CollectionsUtilTests.java new file mode 100644 index 00000000..16368912 --- /dev/null +++ b/store/src/test/java/org/eclipse/viatra/solver/data/util/CollectionsUtilTests.java | |||
@@ -0,0 +1,77 @@ | |||
1 | package org.eclipse.viatra.solver.data.util; | ||
2 | |||
3 | import java.util.ArrayList; | ||
4 | import java.util.Iterator; | ||
5 | import java.util.List; | ||
6 | import java.util.NoSuchElementException; | ||
7 | |||
8 | import org.junit.jupiter.api.Assertions; | ||
9 | import org.junit.jupiter.api.Test; | ||
10 | |||
11 | import static org.eclipse.viatra.solver.data.util.CollectionsUtil.*; | ||
12 | import static org.junit.jupiter.api.Assertions.assertEquals; | ||
13 | |||
14 | class CollectionsUtilTests { | ||
15 | List<Integer> list10 = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); | ||
16 | List<String> listTen = List.of("1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); | ||
17 | |||
18 | private static <T> void compare(Iterable<T> a, Iterable<T> b) { | ||
19 | List<T> listA = toList(a); | ||
20 | List<T> listB = toList(b); | ||
21 | assertEquals(listA, listB); | ||
22 | } | ||
23 | |||
24 | private static <T> List<T> toList(Iterable<T> a) { | ||
25 | List<T> result = new ArrayList<T>(); | ||
26 | Iterator<T> iterator = a.iterator(); | ||
27 | while (iterator.hasNext()) { | ||
28 | result.add(iterator.next()); | ||
29 | } | ||
30 | return result; | ||
31 | } | ||
32 | |||
33 | @Test | ||
34 | void testFilterEven() { | ||
35 | compare(List.of(2, 4, 6, 8, 10), filter(list10, (x -> x % 2 == 0))); | ||
36 | } | ||
37 | |||
38 | @Test | ||
39 | void testFilterOdd() { | ||
40 | compare(List.of(1, 3, 5, 7, 9), filter(list10, (x -> x % 2 == 1))); | ||
41 | } | ||
42 | |||
43 | @Test | ||
44 | void testFilterFalse() { | ||
45 | compare(List.of(), filter(list10, (x -> false))); | ||
46 | } | ||
47 | |||
48 | @Test | ||
49 | void testFilterTrue() { | ||
50 | compare(list10, filter(list10, (x -> true))); | ||
51 | } | ||
52 | |||
53 | @Test | ||
54 | void testFilterEmpty() { | ||
55 | compare(List.of(), filter(List.of(), (x -> true))); | ||
56 | } | ||
57 | |||
58 | @Test() | ||
59 | void testNoSuchElement() { | ||
60 | Iterable<Integer> iterable = filter(list10, (x -> x % 2 == 0)); | ||
61 | Iterator<Integer> iterator = iterable.iterator(); | ||
62 | while (iterator.hasNext()) { | ||
63 | iterator.next(); | ||
64 | } | ||
65 | Assertions.assertThrows(NoSuchElementException.class, () -> iterator.next()); | ||
66 | } | ||
67 | |||
68 | @Test() | ||
69 | void mapTest() { | ||
70 | compare(listTen, map(list10, x -> x.toString())); | ||
71 | } | ||
72 | |||
73 | @Test() | ||
74 | void mapEmtyTest() { | ||
75 | compare(List.of(), map(List.of(), x -> x.toString())); | ||
76 | } | ||
77 | } | ||