From 8403277d0a967f234c46f09cf54185ce3ed01c8b Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Sun, 5 Feb 2023 20:05:04 +0100 Subject: Extending map tests with null as default value. --- .../store/map/tests/fuzz/CommitFuzzTest.java | 28 ++++++++------ .../map/tests/fuzz/ContentEqualsFuzzTest.java | 27 +++++++------- .../store/map/tests/fuzz/DiffCursorFuzzTest.java | 29 +++++++++------ .../store/map/tests/fuzz/MultiThreadFuzzTest.java | 43 ++++++++++++---------- .../store/map/tests/fuzz/MutableFuzzTest.java | 27 ++++++++------ .../fuzz/MutableImmutableCompareFuzzTest.java | 26 +++++++------ .../store/map/tests/fuzz/RestoreFuzzTest.java | 25 +++++++------ .../store/map/tests/fuzz/SharedStoreFuzzTest.java | 32 ++++++++-------- .../store/map/tests/utils/MapTestEnvironment.java | 9 ++++- 9 files changed, 138 insertions(+), 108 deletions(-) (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java index 1f9d022f..c872b9da 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java @@ -19,9 +19,10 @@ import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; class CommitFuzzTest { - private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, - boolean evilHash) { - String[] values = MapTestEnvironment.prepareValues(maxValue); + private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, + boolean nullDefault, int commitFrequency, + boolean evilHash) { + String[] values = MapTestEnvironment.prepareValues(maxValue,nullDefault); ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); VersionedMapStore store = new VersionedMapStoreImpl(chp, values[0]); @@ -64,30 +65,33 @@ class CommitFuzzTest { } } - @ParameterizedTest(name = "Commit {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") + @ParameterizedTest(name = "Commit {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit frequency={5} " + + "seed={6} evil-hash={7}") @MethodSource @Timeout(value = 10) @Tag("fuzz") - void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, - boolean evilHash) { + void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + int seed, + boolean evilHash) { runFuzzTest("CommitS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - commitFrequency, evilHash); + nullDefault, commitFrequency, evilHash); } static Stream parametrizedFastFuzz() { return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, - new Object[] { 2, 3 }, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, + new Object[] { 2, 3 }, new Object[]{false,true}, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, new Object[] { false, true }); } - @ParameterizedTest(name = "Commit {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") + @ParameterizedTest(name = "Commit {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit frequency={5} " + + "seed={6} evil-hash={7}") @MethodSource @Tag("fuzz") @Tag("slow") - void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, - boolean evilHash) { + void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + int seed, boolean evilHash) { runFuzzTest("CommitS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - commitFrequency, evilHash); + nullDefault, commitFrequency, evilHash); } static Stream parametrizedSlowFuzz() { diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java index 93ecfec3..a5a68b94 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java @@ -20,9 +20,10 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; class ContentEqualsFuzzTest { - private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, + private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, + boolean nullDefault, int commitFrequency, boolean evilHash) { - String[] values = MapTestEnvironment.prepareValues(maxValue); + String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); Random r = new Random(seed); @@ -80,33 +81,33 @@ class ContentEqualsFuzzTest { MapTestEnvironment.compareTwoMaps(scenario, sut1, sut2); } - @ParameterizedTest(name = "Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} " + - "evil-hash={6}") + @ParameterizedTest(name = "Compare {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit frequency={5}" + + "seed={6} evil-hash={7}") @MethodSource @Timeout(value = 10) @Tag("fuzz") - void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, - boolean evilHash) { + void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + int seed, boolean evilHash) { runFuzzTest("CompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - commitFrequency, evilHash); + nullDefault, commitFrequency, evilHash); } static Stream parametrizedFastFuzz() { return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, new Object[]{3, 32, 32 * 32}, - new Object[]{2, 3}, new Object[]{1, 10, 100}, new Object[]{1, 2, 3}, + new Object[]{2, 3}, new Object[]{false,true}, new Object[]{1, 10, 100}, new Object[]{1, 2, 3}, new Object[]{false, true}); } - @ParameterizedTest(name = "Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} " + - "evil-hash={6}") + @ParameterizedTest(name = "Compare {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit frequency={5}" + + "seed={6} evil-hash={7}") @MethodSource @Tag("fuzz") @Tag("slow") - void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, - boolean evilHash) { + void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, boolean defaultNull, int commitFrequency, + int seed, boolean evilHash) { runFuzzTest("CompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - commitFrequency, evilHash); + defaultNull, commitFrequency, evilHash); } static Stream parametrizedSlowFuzz() { diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java index e6334224..670b5445 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java @@ -20,17 +20,18 @@ import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; class DiffCursorFuzzTest { - private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, + private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, + boolean nullDefault, int commitFrequency, boolean evilHash) { - String[] values = MapTestEnvironment.prepareValues(maxValue); + String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); VersionedMapStore store = new VersionedMapStoreImpl(chp, values[0]); - iterativeRandomPutsAndCommitsThenDiffcursor(scenario, store, steps, maxKey, values, seed, commitFrequency); + iterativeRandomPutsAndCommitsThenDiffCursor(scenario, store, steps, maxKey, values, seed, commitFrequency); } - private void iterativeRandomPutsAndCommitsThenDiffcursor(String scenario, VersionedMapStore store, - int steps, int maxKey, String[] values, int seed, int commitFrequency) { + private void iterativeRandomPutsAndCommitsThenDiffCursor(String scenario, VersionedMapStore store, + int steps, int maxKey, String[] values, int seed, int commitFrequency) { // 1. build a map with versions Random r = new Random(seed); VersionedMapImpl versioned = (VersionedMapImpl) store.createMap(); @@ -86,29 +87,33 @@ class DiffCursorFuzzTest { } - @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") + @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} " + + "commit frequency={5} seed={6} evil-hash={7}") @MethodSource @Timeout(value = 10) @Tag("fuzz") - void parametrizedFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, + void parametrizedFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + int seed, boolean evilHash) { runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, - noKeys, noValues, commitFrequency, evilHash); + noKeys, noValues, nullDefault, commitFrequency, evilHash); } static Stream parametrizedFuzz() { return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, - new Object[] { 2, 3 }, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, + new Object[] { 2, 3 }, new Object[]{false,true}, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, new Object[] { false, true }); } - @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") + @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} " + + "commit frequency={5} seed={6} evil-hash={7}") @MethodSource @Tag("fuzz") @Tag("slow") - void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, + void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + int seed, boolean evilHash) { runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - commitFrequency, evilHash); + nullDefault, commitFrequency, evilHash); } static Stream parametrizedSlowFuzz() { diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java index 1ab431a8..d8e1a30f 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java @@ -22,31 +22,32 @@ import tools.refinery.store.map.tests.utils.MapTestEnvironment; class MultiThreadFuzzTest { public static final int noThreads = 32; - - private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, - boolean evilHash) { - String[] values = MapTestEnvironment.prepareValues(maxValue); + + private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, + boolean nullDefault, int commitFrequency, + boolean evilHash) { + String[] values = MapTestEnvironment.prepareValues(maxValue,nullDefault); ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); VersionedMapStore store = new VersionedMapStoreImpl(chp, values[0]); - + // initialize runnables MultiThreadTestRunnable[] runnables = new MultiThreadTestRunnable[noThreads]; for(int i = 0; i errors = new LinkedList<>(); for(int i = 0; i parametrizedFastFuzz() { return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, - new Object[] { 2, 3 }, new Object[] { 10, 100 }, new Object[] { 1, 2, 3 }, + new Object[] { 2, 3 }, new Object[]{false, true}, new Object[] { 10, 100 }, new Object[] { 1, 2, 3 }, new Object[] { false, true }); } - @ParameterizedTest(name = "Multithread {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") + @ParameterizedTest(name = "Multithread {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit " + + "frequency={5} seed={6} evil-hash={7}") @MethodSource @Tag("fuzz") @Tag("slow") - void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, - boolean evilHash) { + void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, + boolean evilHash) { runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - commitFrequency, evilHash); + nullDefault, commitFrequency, evilHash); } static Stream parametrizedSlowFuzz() { diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java index d40c49c4..008258bd 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java @@ -19,8 +19,9 @@ import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; class MutableFuzzTest { - private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean evilHash) { - String[] values = MapTestEnvironment.prepareValues(maxValue); + private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, + boolean nullDefault, boolean evilHash) { + String[] values = MapTestEnvironment.prepareValues(maxValue,nullDefault); ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); VersionedMapStore store = new VersionedMapStoreImpl(chp, values[0]); @@ -60,30 +61,34 @@ class MutableFuzzTest { } } - @ParameterizedTest(name = "Mutable {index}/{0} Steps={1} Keys={2} Values={3} seed={4} evil-hash={5}") + @ParameterizedTest(name = "Mutable {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} seed={5} " + + "evil-hash={6}") @MethodSource @Timeout(value = 10) @Tag("fuzz") - void parametrizedFuzz(int test, int steps, int noKeys, int noValues, int seed, boolean evilHash) { + void parametrizedFuzz(int test, int steps, int noKeys, int noValues, boolean defaultNull, int seed, + boolean evilHash) { runFuzzTest( "MutableS" + steps + "K" + noKeys + "V" + noValues + "s" + seed + "H" + (evilHash ? "Evil" : "Normal"), - seed, steps, noKeys, noValues, evilHash); + seed, steps, noKeys, noValues, defaultNull, evilHash); } static Stream parametrizedFuzz() { return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, - new Object[] { 3, 32, 32 * 32, 32 * 32 * 32 * 32 }, new Object[] { 2, 3 }, new Object[] { 1, 2, 3 }, - new Object[] { false, true }); + new Object[] { 3, 32, 32 * 32, 32 * 32 * 32 * 32 }, new Object[] { 2, 3 }, new Object[] { false, true }, + new Object[] { 1, 2, 3 }, new Object[] { false, true }); } - - @ParameterizedTest(name = "Mutable {index}/{0} Steps={1} Keys={2} Values={3} seed={4} evil-hash={5}") + + @ParameterizedTest(name = "Mutable {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} seed={5} " + + "evil-hash={6}") @MethodSource @Tag("fuzz") @Tag("slow") - void parametrizedSlowFuzz(int test, int steps, int noKeys, int noValues, int seed, boolean evilHash) { + void parametrizedSlowFuzz(int test, int steps, int noKeys, int noValues, boolean nullDefault, int seed, + boolean evilHash) { runFuzzTest( "MutableS" + steps + "K" + noKeys + "V" + noValues + "s" + seed + "H" + (evilHash ? "Evil" : "Normal"), - seed, steps, noKeys, noValues, evilHash); + seed, steps, noKeys, noValues, nullDefault, evilHash); } static Stream parametrizedSlowFuzz() { diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java index 410705a2..6e15b8ca 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java @@ -19,9 +19,9 @@ import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; class MutableImmutableCompareFuzzTest { - private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, - boolean evilHash) { - String[] values = MapTestEnvironment.prepareValues(maxValue); + private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, + boolean nullDefault, int commitFrequency, boolean evilHash) { + String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); VersionedMapStore store = new VersionedMapStoreImpl(chp, values[0]); @@ -57,30 +57,32 @@ class MutableImmutableCompareFuzzTest { } } - @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") + @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} " + + "commit frequency={5} seed={6} evil-hash={7}") @MethodSource @Timeout(value = 10) @Tag("fuzz") - void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, - boolean evilHash) { + void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + int seed, boolean evilHash) { runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, - noKeys, noValues, commitFrequency, evilHash); + noKeys, noValues, nullDefault, commitFrequency, evilHash); } static Stream parametrizedFastFuzz() { return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, - new Object[] { 2, 3 }, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, + new Object[] { 2, 3 }, new Object[]{false,true}, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, new Object[] { false, true }); } - @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") + @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} " + + "commit frequency={5} seed={6} evil-hash={7}") @MethodSource @Tag("fuzz") @Tag("slow") - void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, - boolean evilHash) { + void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + int seed, boolean evilHash) { runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, - noKeys, noValues, commitFrequency, evilHash); + noKeys, noValues, nullDefault, commitFrequency, evilHash); } static Stream parametrizedSlowFuzz() { diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java index 2e29a03f..35a54712 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java @@ -21,9 +21,10 @@ import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; class RestoreFuzzTest { - private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, + private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, + boolean nullDefault, int commitFrequency, boolean evilHash) { - String[] values = MapTestEnvironment.prepareValues(maxValue); + String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); VersionedMapStore store = new VersionedMapStoreImpl(chp, values[0]); @@ -77,30 +78,32 @@ class RestoreFuzzTest { } - @ParameterizedTest(name = "Restore {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") + @ParameterizedTest(name = "Restore {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit frequency={5}" + + " seed={6} evil-hash={7}") @MethodSource @Timeout(value = 10) @Tag("smoke") - void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, - boolean evilHash) { + void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + int seed, boolean evilHash) { runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - commitFrequency, evilHash); + nullDefault, commitFrequency, evilHash); } static Stream parametrizedFastFuzz() { return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, - new Object[] { 2, 3 }, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, + new Object[] { 2, 3 }, new Object[]{false, true}, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, new Object[] { false, true }); } - @ParameterizedTest(name = "Restore {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") + @ParameterizedTest(name = "Restore {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit frequency={5}" + + " seed={6} evil-hash={7}") @MethodSource @Tag("smoke") @Tag("slow") - void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, - boolean evilHash) { + void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + int seed, boolean evilHash) { runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - commitFrequency, evilHash); + nullDefault, commitFrequency, evilHash); } static Stream parametrizedSlowFuzz() { diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java index 914a0f63..ac033edb 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java @@ -21,9 +21,9 @@ import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; class SharedStoreFuzzTest { - private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, int commitFrequency, - boolean evilHash) { - String[] values = MapTestEnvironment.prepareValues(maxValue); + private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, + boolean nullDefault, int commitFrequency, boolean evilHash) { + String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); List> stores = VersionedMapStoreImpl.createSharedVersionedMapStores(5, chp, values[0]); @@ -39,7 +39,7 @@ class SharedStoreFuzzTest { for(VersionedMapStore store : stores) { versioneds.add((VersionedMapImpl) store.createMap()); } - + List> index2Version = new LinkedList<>(); for(int i = 0; i()); @@ -56,7 +56,7 @@ class SharedStoreFuzzTest { index2Version.get(storeIndex).put(i, version); } MapTestEnvironment.printStatus(scenario, stepIndex, steps, "building"); - } + } } // 2. create a non-versioned and List> reference = new LinkedList<>(); @@ -76,35 +76,37 @@ class SharedStoreFuzzTest { MapTestEnvironment.compareTwoMaps(scenario + ":" + index, reference.get(storeIndex), versioneds.get(storeIndex)); } } - MapTestEnvironment.printStatus(scenario, index, steps, "comparison"); + MapTestEnvironment.printStatus(scenario, index, steps, "comparison"); } } - @ParameterizedTest(name = "Shared Store {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") + @ParameterizedTest(name = "Shared Store {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit " + + "frequency={4} seed={5} evil-hash={6}") @MethodSource @Timeout(value = 10) @Tag("smoke") - void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, - boolean evilHash) { + void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + int seed, boolean evilHash) { runFuzzTest("SharedS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - commitFrequency, evilHash); + nullDefault, commitFrequency, evilHash); } static Stream parametrizedFastFuzz() { return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, - new Object[] { 2, 3 }, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, + new Object[] { 2, 3 }, new Object[]{false, true}, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, new Object[] { false, true }); } - @ParameterizedTest(name = "Shared Store {index}/{0} Steps={1} Keys={2} Values={3} commit frequency={4} seed={5} evil-hash={6}") + @ParameterizedTest(name = "Shared Store {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit " + + "frequency={4} seed={5} evil-hash={6}") @MethodSource @Tag("smoke") @Tag("slow") - void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, int commitFrequency, int seed, - boolean evilHash) { + void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + int seed, boolean evilHash) { runFuzzTest("SharedS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - commitFrequency, evilHash); + nullDefault, commitFrequency, evilHash); } static Stream parametrizedSlowFuzz() { diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java index 2d03ebaf..10ea2796 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java @@ -9,9 +9,14 @@ import java.util.Map.Entry; import static org.junit.jupiter.api.Assertions.*; public class MapTestEnvironment { - public static String[] prepareValues(int maxValue) { + public static String[] prepareValues(int maxValue, boolean nullDefault) { String[] values = new String[maxValue]; - values[0] = "DEFAULT"; + if(nullDefault) { + values[0] = null; + } else { + values[0] = "DEFAULT"; + } + for (int i = 1; i < values.length; i++) { values[i] = "VAL" + i; } -- cgit v1.2.3-70-g09d2 From 01d06bb9d04894029d2be304f6d4dccb757ea9dc Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Sun, 5 Feb 2023 20:27:45 +0100 Subject: Formatting fuzz tests + improving code qualitz --- .../store/map/tests/fuzz/CommitFuzzTest.java | 25 +++++----------- .../map/tests/fuzz/ContentEqualsFuzzTest.java | 6 ++-- .../store/map/tests/fuzz/DiffCursorFuzzTest.java | 17 ++++++----- .../store/map/tests/fuzz/MultiThreadFuzzTest.java | 32 ++++++++++---------- .../map/tests/fuzz/MultiThreadTestRunnable.java | 34 +++++++++++----------- .../store/map/tests/fuzz/MutableFuzzTest.java | 26 +++++------------ .../fuzz/MutableImmutableCompareFuzzTest.java | 12 ++++---- .../store/map/tests/fuzz/RestoreFuzzTest.java | 12 ++++---- .../store/map/tests/fuzz/SharedStoreFuzzTest.java | 18 ++++++------ .../store/map/tests/fuzz/utils/FuzzTestUtils.java | 10 +++---- .../map/tests/fuzz/utils/FuzzTestUtilsTest.java | 19 +++++++----- .../store/map/tests/utils/MapTestEnvironment.java | 25 +++++++--------- 12 files changed, 107 insertions(+), 129 deletions(-) (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java index c872b9da..cd32337e 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java @@ -22,12 +22,12 @@ class CommitFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, boolean evilHash) { - String[] values = MapTestEnvironment.prepareValues(maxValue,nullDefault); + String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); - VersionedMapStore store = new VersionedMapStoreImpl(chp, values[0]); + VersionedMapStore store = new VersionedMapStoreImpl<>(chp, values[0]); VersionedMapImpl sut = (VersionedMapImpl) store.createMap(); - MapTestEnvironment e = new MapTestEnvironment(sut); + MapTestEnvironment e = new MapTestEnvironment<>(sut); Random r = new Random(seed); @@ -35,24 +35,13 @@ class CommitFuzzTest { } private void iterativeRandomPutsAndCommits(String scenario, int steps, int maxKey, String[] values, - MapTestEnvironment e, Random r, int commitFrequency) { - int stopAt = -1; + MapTestEnvironment e, Random r, int commitFrequency) { for (int i = 0; i < steps; i++) { int index = i + 1; int nextKey = r.nextInt(maxKey); String nextValue = values[r.nextInt(values.length)]; - if (index == stopAt) { - System.out.println("issue!"); - System.out.println("State before:"); - e.printComparison(); - e.sut.prettyPrint(); - System.out.println("Next: put(" + nextKey + "," + nextValue + ")"); - } try { e.put(nextKey, nextValue); - if (index == stopAt) { - e.sut.prettyPrint(); - } e.checkEquivalence(scenario + ":" + index); } catch (Exception exception) { exception.printStackTrace(); @@ -78,9 +67,9 @@ class CommitFuzzTest { } static Stream parametrizedFastFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, - new Object[] { 2, 3 }, new Object[]{false,true}, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, - new Object[] { false, true }); + return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, new Object[]{3, 32, 32 * 32}, + new Object[]{2, 3}, new Object[]{false, true}, new Object[]{1, 10, 100}, new Object[]{1, 2, 3}, + new Object[]{false, true}); } @ParameterizedTest(name = "Commit {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit frequency={5} " + diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java index a5a68b94..996bfa03 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java @@ -34,7 +34,7 @@ class ContentEqualsFuzzTest { private void iterativeRandomPutsAndCommitsThenCompare(String scenario, ContinousHashProvider chp, int steps, int maxKey, String[] values, Random r, int commitFrequency) { - VersionedMapStore store1 = new VersionedMapStoreImpl(chp, values[0]); + VersionedMapStore store1 = new VersionedMapStoreImpl<>(chp, values[0]); VersionedMap sut1 = store1.createMap(); // Fill one map @@ -64,7 +64,7 @@ class ContentEqualsFuzzTest { // Randomize the order of the content Collections.shuffle(content, r); - VersionedMapStore store2 = new VersionedMapStoreImpl(chp, values[0]); + VersionedMapStore store2 = new VersionedMapStoreImpl<>(chp, values[0]); VersionedMap sut2 = store2.createMap(); int index2 = 1; for (SimpleEntry entry : content) { @@ -95,7 +95,7 @@ class ContentEqualsFuzzTest { static Stream parametrizedFastFuzz() { return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, new Object[]{3, 32, 32 * 32}, - new Object[]{2, 3}, new Object[]{false,true}, new Object[]{1, 10, 100}, new Object[]{1, 2, 3}, + new Object[]{2, 3}, new Object[]{false, true}, new Object[]{1, 10, 100}, new Object[]{1, 2, 3}, new Object[]{false, true}); } diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java index 670b5445..4f7f48b5 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java @@ -22,11 +22,11 @@ import tools.refinery.store.map.tests.utils.MapTestEnvironment; class DiffCursorFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, - boolean evilHash) { + boolean evilHash) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); - VersionedMapStore store = new VersionedMapStoreImpl(chp, values[0]); + VersionedMapStore store = new VersionedMapStoreImpl<>(chp, values[0]); iterativeRandomPutsAndCommitsThenDiffCursor(scenario, store, steps, maxKey, values, seed, commitFrequency); } @@ -62,7 +62,7 @@ class DiffCursorFuzzTest { for (int i = 0; i < steps; i++) { int index = i + 1; if (index % diffTravelFrequency == 0) { - // difftravel + // diff-travel long travelToVersion = r2.nextInt(largestCommit + 1); DiffCursor diffCursor = moving.getDiffCursor(travelToVersion); moving.putAll(diffCursor); @@ -94,16 +94,17 @@ class DiffCursorFuzzTest { @Tag("fuzz") void parametrizedFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, - boolean evilHash) { + boolean evilHash) { runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, evilHash); } static Stream parametrizedFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, - new Object[] { 2, 3 }, new Object[]{false,true}, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, - new Object[] { false, true }); + return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, new Object[]{3, 32, 32 * 32}, + new Object[]{2, 3}, new Object[]{false, true}, new Object[]{1, 10, 100}, new Object[]{1, 2, 3}, + new Object[]{false, true}); } + @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} " + "commit frequency={5} seed={6} evil-hash={7}") @MethodSource @@ -111,7 +112,7 @@ class DiffCursorFuzzTest { @Tag("slow") void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, - boolean evilHash) { + boolean evilHash) { runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, evilHash); } diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java index d8e1a30f..5412d958 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java @@ -26,64 +26,64 @@ class MultiThreadFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, boolean evilHash) { - String[] values = MapTestEnvironment.prepareValues(maxValue,nullDefault); + String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); - VersionedMapStore store = new VersionedMapStoreImpl(chp, values[0]); + VersionedMapStore store = new VersionedMapStoreImpl<>(chp, values[0]); // initialize runnables MultiThreadTestRunnable[] runnables = new MultiThreadTestRunnable[noThreads]; - for(int i = 0; i errors = new LinkedList<>(); - for(int i = 0; i parametrizedFastFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, - new Object[] { 2, 3 }, new Object[]{false, true}, new Object[] { 10, 100 }, new Object[] { 1, 2, 3 }, - new Object[] { false, true }); + return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, new Object[]{3, 32, 32 * 32}, + new Object[]{2, 3}, new Object[]{false, true}, new Object[]{10, 100}, new Object[]{1, 2, 3}, + new Object[]{false, true}); } - @ParameterizedTest(name = "Multithread {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit " + + @ParameterizedTest(name = "MultiThread {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit " + "frequency={5} seed={6} evil-hash={7}") @MethodSource @Tag("fuzz") diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java index f77f9ee5..4415e4e5 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java @@ -13,17 +13,17 @@ import tools.refinery.store.map.internal.VersionedMapImpl; import tools.refinery.store.map.tests.utils.MapTestEnvironment; public class MultiThreadTestRunnable implements Runnable { - String scenario; - VersionedMapStore store; - int steps; - int maxKey; - String[] values; - int seed; - int commitFrequency; - List errors = new LinkedList<>(); - + final String scenario; + final VersionedMapStore store; + final int steps; + final int maxKey; + final String[] values; + final int seed; + final int commitFrequency; + final List errors = new LinkedList<>(); + public MultiThreadTestRunnable(String scenario, VersionedMapStore store, int steps, - int maxKey, String[] values, int seed, int commitFrequency) { + int maxKey, String[] values, int seed, int commitFrequency) { super(); this.scenario = scenario; this.store = store; @@ -38,11 +38,11 @@ public class MultiThreadTestRunnable implements Runnable { AssertionError error = new AssertionError(message); errors.add(error); } - + public List getErrors() { return errors; } - + @Override public void run() { // 1. build a map with versions @@ -66,10 +66,10 @@ public class MultiThreadTestRunnable implements Runnable { } MapTestEnvironment.printStatus(scenario, index, steps, "building"); } - // 2. create a non-versioned + // 2. create a non-versioned VersionedMapImpl reference = (VersionedMapImpl) store.createMap(); r = new Random(seed); - Random r2 = new Random(seed+1); + Random r2 = new Random(seed + 1); for (int i = 0; i < steps; i++) { int index = i + 1; @@ -84,12 +84,12 @@ public class MultiThreadTestRunnable implements Runnable { // go back to an existing state and compare to the reference if (index % (commitFrequency) == 0) { versioned.restore(index2Version.get(i)); - MapTestEnvironment.compareTwoMaps(scenario + ":" + index, reference, versioned,errors); - + MapTestEnvironment.compareTwoMaps(scenario + ":" + index, reference, versioned, errors); + // go back to a random state (probably created by another thread) List states = new ArrayList<>(store.getStates()); Collections.shuffle(states, r2); - for(Long state : states.subList(0, Math.min(states.size(), 100))) { + for (Long state : states.subList(0, Math.min(states.size(), 100))) { versioned.restore(state); } versioned.restore(index2Version.get(i)); diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java index 008258bd..2448268a 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java @@ -21,12 +21,12 @@ import tools.refinery.store.map.tests.utils.MapTestEnvironment; class MutableFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, boolean evilHash) { - String[] values = MapTestEnvironment.prepareValues(maxValue,nullDefault); + String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); - VersionedMapStore store = new VersionedMapStoreImpl(chp, values[0]); + VersionedMapStore store = new VersionedMapStoreImpl<>(chp, values[0]); VersionedMapImpl sut = (VersionedMapImpl) store.createMap(); - MapTestEnvironment e = new MapTestEnvironment(sut); + MapTestEnvironment e = new MapTestEnvironment<>(sut); Random r = new Random(seed); @@ -34,24 +34,14 @@ class MutableFuzzTest { } private void iterativeRandomPuts(String scenario, int steps, int maxKey, String[] values, - MapTestEnvironment e, Random r) { - int stopAt = -1; + MapTestEnvironment e, Random r) { for (int i = 0; i < steps; i++) { int index = i + 1; int nextKey = r.nextInt(maxKey); String nextValue = values[r.nextInt(values.length)]; - if (index == stopAt) { - System.out.println("issue!"); - System.out.println("State before:"); - e.printComparison(); - e.sut.prettyPrint(); - System.out.println("Next: put(" + nextKey + "," + nextValue + ")"); - } + try { e.put(nextKey, nextValue); - if (index == stopAt) { - e.sut.prettyPrint(); - } e.checkEquivalence(scenario + ":" + index); } catch (Exception exception) { exception.printStackTrace(); @@ -74,9 +64,9 @@ class MutableFuzzTest { } static Stream parametrizedFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, - new Object[] { 3, 32, 32 * 32, 32 * 32 * 32 * 32 }, new Object[] { 2, 3 }, new Object[] { false, true }, - new Object[] { 1, 2, 3 }, new Object[] { false, true }); + return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, + new Object[]{3, 32, 32 * 32, 32 * 32 * 32 * 32}, new Object[]{2, 3}, new Object[]{false, true}, + new Object[]{1, 2, 3}, new Object[]{false, true}); } @ParameterizedTest(name = "Mutable {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} seed={5} " + diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java index 6e15b8ca..07ca4f69 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java @@ -24,7 +24,7 @@ class MutableImmutableCompareFuzzTest { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); - VersionedMapStore store = new VersionedMapStoreImpl(chp, values[0]); + VersionedMapStore store = new VersionedMapStoreImpl<>(chp, values[0]); VersionedMapImpl immutable = (VersionedMapImpl) store.createMap(); VersionedMapImpl mutable = (VersionedMapImpl) store.createMap(); @@ -35,8 +35,8 @@ class MutableImmutableCompareFuzzTest { } private void iterativeRandomPutsAndCommitsAndCompare(String scenario, VersionedMapImpl immutable, - VersionedMapImpl mutable, int steps, int maxKey, String[] values, Random r, - int commitFrequency) { + VersionedMapImpl mutable, int steps, int maxKey, String[] values, Random r, + int commitFrequency) { for (int i = 0; i < steps; i++) { int index = i + 1; int nextKey = r.nextInt(maxKey); @@ -69,9 +69,9 @@ class MutableImmutableCompareFuzzTest { } static Stream parametrizedFastFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, - new Object[] { 2, 3 }, new Object[]{false,true}, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, - new Object[] { false, true }); + return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, new Object[]{3, 32, 32 * 32}, + new Object[]{2, 3}, new Object[]{false, true}, new Object[]{1, 10, 100}, new Object[]{1, 2, 3}, + new Object[]{false, true}); } @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} " + diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java index 35a54712..e0366381 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java @@ -23,17 +23,17 @@ import tools.refinery.store.map.tests.utils.MapTestEnvironment; class RestoreFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, - boolean evilHash) { + boolean evilHash) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); - VersionedMapStore store = new VersionedMapStoreImpl(chp, values[0]); + VersionedMapStore store = new VersionedMapStoreImpl<>(chp, values[0]); iterativeRandomPutsAndCommitsThenRestore(scenario, store, steps, maxKey, values, seed, commitFrequency); } private void iterativeRandomPutsAndCommitsThenRestore(String scenario, VersionedMapStore store, - int steps, int maxKey, String[] values, int seed, int commitFrequency) { + int steps, int maxKey, String[] values, int seed, int commitFrequency) { // 1. build a map with versions Random r = new Random(seed); VersionedMapImpl versioned = (VersionedMapImpl) store.createMap(); @@ -90,9 +90,9 @@ class RestoreFuzzTest { } static Stream parametrizedFastFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, - new Object[] { 2, 3 }, new Object[]{false, true}, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, - new Object[] { false, true }); + return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, new Object[]{3, 32, 32 * 32}, + new Object[]{2, 3}, new Object[]{false, true}, new Object[]{1, 10, 100}, new Object[]{1, 2, 3}, + new Object[]{false, true}); } @ParameterizedTest(name = "Restore {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit frequency={5}" + diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java index ac033edb..a576b1c8 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java @@ -32,22 +32,22 @@ class SharedStoreFuzzTest { } private void iterativeRandomPutsAndCommitsThenRestore(String scenario, List> stores, - int steps, int maxKey, String[] values, int seed, int commitFrequency) { + int steps, int maxKey, String[] values, int seed, int commitFrequency) { // 1. maps with versions Random r = new Random(seed); List> versioneds = new LinkedList<>(); - for(VersionedMapStore store : stores) { + for (VersionedMapStore store : stores) { versioneds.add((VersionedMapImpl) store.createMap()); } List> index2Version = new LinkedList<>(); - for(int i = 0; i()); } for (int i = 0; i < steps; i++) { int stepIndex = i + 1; - for (int storeIndex = 0; storeIndex> reference = new LinkedList<>(); - for(VersionedMapStore store : stores) { + for (VersionedMapStore store : stores) { reference.add((VersionedMapImpl) store.createMap()); } r = new Random(seed); for (int i = 0; i < steps; i++) { int index = i + 1; - for (int storeIndex = 0; storeIndex parametrizedFastFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[] { FuzzTestUtils.FAST_STEP_COUNT }, new Object[] { 3, 32, 32 * 32 }, - new Object[] { 2, 3 }, new Object[]{false, true}, new Object[] { 1, 10, 100 }, new Object[] { 1, 2, 3 }, - new Object[] { false, true }); + return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, new Object[]{3, 32, 32 * 32}, + new Object[]{2, 3}, new Object[]{false, true}, new Object[]{1, 10, 100}, new Object[]{1, 2, 3}, + new Object[]{false, true}); } @ParameterizedTest(name = "Shared Store {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit " + diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java index e75d7f5a..92208e48 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java @@ -51,14 +51,12 @@ public final class FuzzTestUtils { public static Stream permutationWithSize(Object[]... valueOption) { int size = 1; - for (int i = 0; i < valueOption.length; i++) { - size *= valueOption[i].length; + for (Object[] objects : valueOption) { + size *= objects.length; } Object[][] newValueOption = new Object[valueOption.length + 1][]; - newValueOption[0] = new Object[] { size }; - for (int i = 1; i < newValueOption.length; i++) { - newValueOption[i] = valueOption[i - 1]; - } + newValueOption[0] = new Object[]{size}; + System.arraycopy(valueOption, 0, newValueOption, 1, newValueOption.length - 1); return permutation(newValueOption); } } diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtilsTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtilsTest.java index 72f2a46c..8c641205 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtilsTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtilsTest.java @@ -1,31 +1,36 @@ package tools.refinery.store.map.tests.fuzz.utils; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.provider.Arguments; class FuzzTestUtilsTest { @Test void permutationInternalTest() { - List> res = FuzzTestUtils.permutationInternal(0, new Object[] { 1, 2, 3 }, - new Object[] { 'a', 'b', 'c' }, new Object[] { "alpha", "beta", "gamma", "delta" }); + List> res = FuzzTestUtils.permutationInternal(0, new Object[]{1, 2, 3}, + new Object[]{'a', 'b', 'c'}, new Object[]{"alpha", "beta", "gamma", "delta"}); assertEquals(3 * 3 * 4, res.size()); } @Test void permutationTest1() { - var res = FuzzTestUtils.permutation(new Object[] { 1, 2, 3 }, new Object[] { 'a', 'b', 'c' }, - new Object[] { "alpha", "beta", "gamma", "delta" }); + var res = FuzzTestUtils.permutation(new Object[]{1, 2, 3}, new Object[]{'a', 'b', 'c'}, + new Object[]{"alpha", "beta", "gamma", "delta"}); assertEquals(3 * 3 * 4, res.count()); } @Test void permutationTest2() { - var res = FuzzTestUtils.permutation(new Object[] { 1, 2, 3 }, new Object[] { 'a', 'b', 'c' }, - new Object[] { "alpha", "beta", "gamma", "delta" }); - var arguments = res.findFirst().get().get(); + var res = FuzzTestUtils.permutation(new Object[]{1, 2, 3}, new Object[]{'a', 'b', 'c'}, + new Object[]{"alpha", "beta", "gamma", "delta"}); + Optional first = res.findFirst(); + assertTrue(first.isPresent()); + var arguments = first.get().get(); assertEquals(1, arguments[0]); assertEquals('a', arguments[1]); assertEquals("alpha", arguments[2]); diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java index 10ea2796..30f38201 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java @@ -11,7 +11,7 @@ import static org.junit.jupiter.api.Assertions.*; public class MapTestEnvironment { public static String[] prepareValues(int maxValue, boolean nullDefault) { String[] values = new String[maxValue]; - if(nullDefault) { + if (nullDefault) { values[0] = null; } else { values[0] = "DEFAULT"; @@ -26,23 +26,18 @@ public class MapTestEnvironment { public static ContinousHashProvider prepareHashProvider(final boolean evil) { // Use maxPrime = 2147483629 - ContinousHashProvider chp = new ContinousHashProvider() { - - @Override - public int getHash(Integer key, int index) { - if (evil && index < 15 && index < key / 3) { - return 7; - } - int result = 1; - final int prime = 31; + return (key, index) -> { + if (evil && index < 15 && index < key / 3) { + return 7; + } + int result = 1; + final int prime = 31; - result = prime * result + key; - result = prime * result + index; + result = prime * result + key; + result = prime * result + index; - return result; - } + return result; }; - return chp; } public static void printStatus(String scenario, int actual, int max, String stepName) { -- cgit v1.2.3-70-g09d2 From 3b7bb0e09a292df61892613fda8ff1b17a999369 Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Sun, 5 Feb 2023 20:34:15 +0100 Subject: Fixing warning caused by an "unused parameter" which is used by an annotation --- .../test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java | 4 ++-- .../tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java | 4 ++-- .../java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java | 4 ++-- .../java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java | 4 ++-- .../java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java | 4 ++-- .../store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java | 4 ++-- .../java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java | 4 ++-- .../java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java index cd32337e..9e3f636b 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java @@ -59,7 +59,7 @@ class CommitFuzzTest { @MethodSource @Timeout(value = 10) @Tag("fuzz") - void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, boolean evilHash) { runFuzzTest("CommitS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, @@ -77,7 +77,7 @@ class CommitFuzzTest { @MethodSource @Tag("fuzz") @Tag("slow") - void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, boolean evilHash) { runFuzzTest("CommitS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, evilHash); diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java index 996bfa03..71cb3d11 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java @@ -86,7 +86,7 @@ class ContentEqualsFuzzTest { @MethodSource @Timeout(value = 10) @Tag("fuzz") - void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, boolean evilHash) { runFuzzTest("CompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, evilHash); @@ -104,7 +104,7 @@ class ContentEqualsFuzzTest { @MethodSource @Tag("fuzz") @Tag("slow") - void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, boolean defaultNull, int commitFrequency, + void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean defaultNull, int commitFrequency, int seed, boolean evilHash) { runFuzzTest("CompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, defaultNull, commitFrequency, evilHash); diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java index 4f7f48b5..8a5576aa 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java @@ -92,7 +92,7 @@ class DiffCursorFuzzTest { @MethodSource @Timeout(value = 10) @Tag("fuzz") - void parametrizedFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + void parametrizedFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, boolean evilHash) { runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, @@ -110,7 +110,7 @@ class DiffCursorFuzzTest { @MethodSource @Tag("fuzz") @Tag("slow") - void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, boolean evilHash) { runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java index 5412d958..5d5f3ce3 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java @@ -71,7 +71,7 @@ class MultiThreadFuzzTest { @MethodSource @Timeout(value = 10) @Tag("fuzz") - void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, boolean defaultNull, int commitFrequency, + void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean defaultNull, int commitFrequency, int seed, boolean evilHash) { runFuzzTest("MultiThreadS" + steps + "K" + noKeys + "V" + noValues + defaultNull + "CF" + commitFrequency + "s" + seed, seed, steps, noKeys, noValues, defaultNull, commitFrequency, evilHash); @@ -88,7 +88,7 @@ class MultiThreadFuzzTest { @MethodSource @Tag("fuzz") @Tag("slow") - void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, + void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, boolean evilHash) { runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, evilHash); diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java index 2448268a..2d65ba0c 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java @@ -56,7 +56,7 @@ class MutableFuzzTest { @MethodSource @Timeout(value = 10) @Tag("fuzz") - void parametrizedFuzz(int test, int steps, int noKeys, int noValues, boolean defaultNull, int seed, + void parametrizedFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean defaultNull, int seed, boolean evilHash) { runFuzzTest( "MutableS" + steps + "K" + noKeys + "V" + noValues + "s" + seed + "H" + (evilHash ? "Evil" : "Normal"), @@ -74,7 +74,7 @@ class MutableFuzzTest { @MethodSource @Tag("fuzz") @Tag("slow") - void parametrizedSlowFuzz(int test, int steps, int noKeys, int noValues, boolean nullDefault, int seed, + void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int seed, boolean evilHash) { runFuzzTest( "MutableS" + steps + "K" + noKeys + "V" + noValues + "s" + seed + "H" + (evilHash ? "Evil" : "Normal"), diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java index 07ca4f69..da8a43c2 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java @@ -62,7 +62,7 @@ class MutableImmutableCompareFuzzTest { @MethodSource @Timeout(value = 10) @Tag("fuzz") - void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, boolean evilHash) { runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, evilHash); @@ -79,7 +79,7 @@ class MutableImmutableCompareFuzzTest { @MethodSource @Tag("fuzz") @Tag("slow") - void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, boolean evilHash) { runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, evilHash); diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java index e0366381..bd03d1e9 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java @@ -83,7 +83,7 @@ class RestoreFuzzTest { @MethodSource @Timeout(value = 10) @Tag("smoke") - void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, boolean evilHash) { runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, evilHash); @@ -100,7 +100,7 @@ class RestoreFuzzTest { @MethodSource @Tag("smoke") @Tag("slow") - void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, boolean evilHash) { runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, evilHash); diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java index a576b1c8..0fc9cd38 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java @@ -86,7 +86,7 @@ class SharedStoreFuzzTest { @MethodSource @Timeout(value = 10) @Tag("smoke") - void parametrizedFastFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, boolean evilHash) { runFuzzTest("SharedS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, evilHash); @@ -103,7 +103,7 @@ class SharedStoreFuzzTest { @MethodSource @Tag("smoke") @Tag("slow") - void parametrizedSlowFuzz(int tests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, + void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, boolean evilHash) { runFuzzTest("SharedS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, evilHash); -- cgit v1.2.3-70-g09d2 From 55c8acd527bf0b5608e15587be9a9bfad3dc3c6b Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Fri, 10 Feb 2023 11:49:47 +0100 Subject: Moved test parametrization to FuzzTestCollections.java --- .../tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java | 8 +++++--- .../refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java | 7 +++---- .../refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java | 12 +++++------- .../refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java | 7 ++++--- .../tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java | 6 +++--- .../map/tests/fuzz/MutableImmutableCompareFuzzTest.java | 6 +++--- .../tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java | 6 +++--- .../refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java | 7 ++++--- .../store/map/tests/fuzz/utils/FuzzTestCollections.java | 11 +++++++++++ 9 files changed, 41 insertions(+), 29 deletions(-) create mode 100644 subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java index 9e3f636b..b61152f5 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java @@ -18,7 +18,10 @@ import tools.refinery.store.map.internal.VersionedMapImpl; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; +import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; + class CommitFuzzTest { + private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, boolean evilHash) { @@ -67,9 +70,8 @@ class CommitFuzzTest { } static Stream parametrizedFastFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, new Object[]{3, 32, 32 * 32}, - new Object[]{2, 3}, new Object[]{false, true}, new Object[]{1, 10, 100}, new Object[]{1, 2, 3}, - new Object[]{false, true}); + return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, + commitFrequencyOptions, randomSeedOptions, evilHashOptions); } @ParameterizedTest(name = "Commit {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit frequency={5} " + diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java index 71cb3d11..c17b0a95 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java @@ -18,6 +18,7 @@ import java.util.Random; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; +import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; class ContentEqualsFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, @@ -93,10 +94,8 @@ class ContentEqualsFuzzTest { } static Stream parametrizedFastFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, new Object[]{3, 32, - 32 * 32}, - new Object[]{2, 3}, new Object[]{false, true}, new Object[]{1, 10, 100}, new Object[]{1, 2, 3}, - new Object[]{false, true}); + return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, + commitFrequencyOptions, randomSeedOptions, evilHashOptions); } @ParameterizedTest(name = "Compare {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit frequency={5}" + diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java index 8a5576aa..90aa8e01 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java @@ -1,6 +1,7 @@ package tools.refinery.store.map.tests.fuzz; import static org.junit.jupiter.api.Assertions.fail; +import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; import java.util.Random; import java.util.stream.Stream; @@ -93,16 +94,14 @@ class DiffCursorFuzzTest { @Timeout(value = 10) @Tag("fuzz") void parametrizedFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, - boolean evilHash) { + int seed, boolean evilHash) { runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, evilHash); } static Stream parametrizedFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, new Object[]{3, 32, 32 * 32}, - new Object[]{2, 3}, new Object[]{false, true}, new Object[]{1, 10, 100}, new Object[]{1, 2, 3}, - new Object[]{false, true}); + return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, + commitFrequencyOptions, randomSeedOptions, evilHashOptions); } @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} " + @@ -111,8 +110,7 @@ class DiffCursorFuzzTest { @Tag("fuzz") @Tag("slow") void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, - boolean evilHash) { + int seed, boolean evilHash) { runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, evilHash); } diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java index 5d5f3ce3..9325a938 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java @@ -2,6 +2,7 @@ package tools.refinery.store.map.tests.fuzz; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; +import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; import java.util.Collections; import java.util.LinkedList; @@ -78,9 +79,9 @@ class MultiThreadFuzzTest { } static Stream parametrizedFastFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, new Object[]{3, 32, 32 * 32}, - new Object[]{2, 3}, new Object[]{false, true}, new Object[]{10, 100}, new Object[]{1, 2, 3}, - new Object[]{false, true}); + return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, + new Object[]{10, 100}, randomSeedOptions, + evilHashOptions); } @ParameterizedTest(name = "MultiThread {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit " + diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java index 2d65ba0c..ab37ef83 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java @@ -1,6 +1,7 @@ package tools.refinery.store.map.tests.fuzz; import static org.junit.jupiter.api.Assertions.fail; +import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; import java.util.Random; import java.util.stream.Stream; @@ -64,9 +65,8 @@ class MutableFuzzTest { } static Stream parametrizedFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, - new Object[]{3, 32, 32 * 32, 32 * 32 * 32 * 32}, new Object[]{2, 3}, new Object[]{false, true}, - new Object[]{1, 2, 3}, new Object[]{false, true}); + return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, + randomSeedOptions, evilHashOptions); } @ParameterizedTest(name = "Mutable {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} seed={5} " + diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java index da8a43c2..b58b06f9 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java @@ -1,6 +1,7 @@ package tools.refinery.store.map.tests.fuzz; import static org.junit.jupiter.api.Assertions.fail; +import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; import java.util.Random; import java.util.stream.Stream; @@ -69,9 +70,8 @@ class MutableImmutableCompareFuzzTest { } static Stream parametrizedFastFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, new Object[]{3, 32, 32 * 32}, - new Object[]{2, 3}, new Object[]{false, true}, new Object[]{1, 10, 100}, new Object[]{1, 2, 3}, - new Object[]{false, true}); + return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, + commitFrequencyOptions, randomSeedOptions, evilHashOptions); } @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} " + diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java index bd03d1e9..77b26c41 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java @@ -1,6 +1,7 @@ package tools.refinery.store.map.tests.fuzz; import static org.junit.jupiter.api.Assertions.fail; +import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; import java.util.HashMap; import java.util.Map; @@ -90,9 +91,8 @@ class RestoreFuzzTest { } static Stream parametrizedFastFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, new Object[]{3, 32, 32 * 32}, - new Object[]{2, 3}, new Object[]{false, true}, new Object[]{1, 10, 100}, new Object[]{1, 2, 3}, - new Object[]{false, true}); + return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, + commitFrequencyOptions, randomSeedOptions, evilHashOptions); } @ParameterizedTest(name = "Restore {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit frequency={5}" + diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java index 0fc9cd38..4462c55b 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java @@ -20,6 +20,8 @@ import tools.refinery.store.map.internal.VersionedMapImpl; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; +import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; + class SharedStoreFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, boolean evilHash) { @@ -93,9 +95,8 @@ class SharedStoreFuzzTest { } static Stream parametrizedFastFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[]{FuzzTestUtils.FAST_STEP_COUNT}, new Object[]{3, 32, 32 * 32}, - new Object[]{2, 3}, new Object[]{false, true}, new Object[]{1, 10, 100}, new Object[]{1, 2, 3}, - new Object[]{false, true}); + return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, + commitFrequencyOptions, randomSeedOptions, evilHashOptions); } @ParameterizedTest(name = "Shared Store {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit " + diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java new file mode 100644 index 00000000..add4ca5d --- /dev/null +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java @@ -0,0 +1,11 @@ +package tools.refinery.store.map.tests.fuzz.utils; + +public final class FuzzTestCollections { + public static final Object[] stepCounts = {FuzzTestUtils.FAST_STEP_COUNT}; + public static final Object[] keyCounts = {3, 32, 32 * 32}; + public static final Object[] valueCounts = {2, 3}; + public static final Object[] nullDefaultOptions = {false, true}; + public static final Object[] commitFrequencyOptions = {1, 10, 100}; + public static final Object[] randomSeedOptions = {1, 2, 3}; + public static final Object[] evilHashOptions = {false, true}; +} -- cgit v1.2.3-70-g09d2 From 6598f296d79382a7eaf6ec42819d0123b0acc0d1 Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Tue, 14 Feb 2023 00:43:00 +0100 Subject: Test environment cannot rely upon the order of elements in a map since VersionedMapDelta appeared. --- .../store/map/tests/utils/MapTestEnvironment.java | 38 +++++++++------------- 1 file changed, 15 insertions(+), 23 deletions(-) (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java index 30f38201..69ae811e 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java @@ -55,23 +55,9 @@ public class MapTestEnvironment { public static void compareTwoMaps(String title, VersionedMap map1, VersionedMap map2, List errors) { + map1.checkIntegrity(); + map2.checkIntegrity(); assertEqualsList(map1.getSize(), map2.getSize(), title + ": Sizes not equal", errors); - - Cursor cursor1 = map1.getAll(); - Cursor cursor2 = map2.getAll(); - while (!cursor1.isTerminated()) { - if (cursor2.isTerminated()) { - fail("cursor 2 terminated before cursor1"); - } - assertEqualsList(cursor1.getKey(), cursor2.getKey(), title + ": Keys not equal", errors); - assertEqualsList(cursor2.getValue(), cursor2.getValue(), title + ": Values not equal", errors); - cursor1.move(); - cursor2.move(); - } - if (!cursor2.isTerminated()) { - fail("cursor 1 terminated before cursor 2"); - } - for (var mode : ContentHashCode.values()) { assertEqualsList(map1.contentHashCode(mode), map2.contentHashCode(mode), title + ": " + mode + " hashCode check", errors); @@ -107,29 +93,35 @@ public class MapTestEnvironment { } } - public VersionedMapImpl sut; - Map oracle = new HashMap(); + final private VersionedMap sut; + final private V defaultValue; + Map oracle = new HashMap<>(); - public MapTestEnvironment(VersionedMapImpl sut) { + public MapTestEnvironment(VersionedMap sut) { this.sut = sut; + this.defaultValue = sut.getDefaultValue(); } public void put(K key, V value) { V oldSutValue = sut.put(key, value); V oldOracleValue; - if (value != sut.getDefaultValue()) { + if (value != defaultValue) { oldOracleValue = oracle.put(key, value); } else { oldOracleValue = oracle.remove(key); } - if (oldSutValue == sut.getDefaultValue() && oldOracleValue != null) { + if (oldSutValue == defaultValue && oldOracleValue != null) { fail("After put, SUT old nodeId was default, but oracle old value was " + oldOracleValue); } - if (oldSutValue != sut.getDefaultValue()) { + if (oldSutValue != defaultValue) { assertEquals(oldOracleValue, oldSutValue); } } + public long commit(){ + return sut.commit(); + } + public void checkEquivalence(String title) { // 0. Checking integrity try { @@ -176,7 +168,7 @@ public class MapTestEnvironment { long sutSize = sut.getSize(); if (oracleSize != sutSize || oracleSize != elementsInSutEntrySet) { printComparison(); - fail(title + ": Non-equivalent size() result: SUT.getSize()=" + sutSize + ", SUT.entryset.size=" + fail(title + ": Non-equivalent size() result: SUT.getSize()=" + sutSize + ", SUT.entrySet.size=" + elementsInSutEntrySet + ", Oracle=" + oracleSize + "!"); } } -- cgit v1.2.3-70-g09d2 From 189d1181052fc014830ac53d0090d7f3cfbc36aa Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Tue, 14 Feb 2023 00:46:26 +0100 Subject: Fuzz test environment is parametrized by VersionedMapStoreBuilder configurations --- .../store/map/tests/fuzz/CommitFuzzTest.java | 34 +++++++-------- .../map/tests/fuzz/ContentEqualsFuzzTest.java | 37 ++++++++-------- .../store/map/tests/fuzz/DiffCursorFuzzTest.java | 50 ++++++++++------------ .../store/map/tests/fuzz/MultiThreadFuzzTest.java | 42 +++++++++--------- .../map/tests/fuzz/MultiThreadTestRunnable.java | 22 +++++++--- .../store/map/tests/fuzz/MutableFuzzTest.java | 35 +++++++-------- .../fuzz/MutableImmutableCompareFuzzTest.java | 2 +- .../store/map/tests/fuzz/RestoreFuzzTest.java | 32 +++++++------- .../store/map/tests/fuzz/SharedStoreFuzzTest.java | 2 +- .../map/tests/fuzz/utils/FuzzTestCollections.java | 40 +++++++++++++++-- 10 files changed, 161 insertions(+), 135 deletions(-) (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java index b61152f5..14a9e2e0 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java @@ -11,10 +11,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import tools.refinery.store.map.ContinousHashProvider; import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.VersionedMapStoreImpl; -import tools.refinery.store.map.internal.VersionedMapImpl; +import tools.refinery.store.map.VersionedMapStoreBuilder; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; @@ -23,13 +21,11 @@ import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; class CommitFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, - boolean nullDefault, int commitFrequency, - boolean evilHash) { + boolean nullDefault, int commitFrequency, VersionedMapStoreBuilder builder) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); - ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); - VersionedMapStore store = new VersionedMapStoreImpl<>(chp, values[0]); - VersionedMapImpl sut = (VersionedMapImpl) store.createMap(); + VersionedMapStore store = builder.setDefaultValue(values[0]).buildOne(); + var sut = store.createMap(); MapTestEnvironment e = new MapTestEnvironment<>(sut); Random r = new Random(seed); @@ -52,37 +48,37 @@ class CommitFuzzTest { } MapTestEnvironment.printStatus(scenario, index, steps, null); if (index % commitFrequency == 0) { - e.sut.commit(); + e.commit(); } } } - @ParameterizedTest(name = "Commit {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit frequency={5} " + - "seed={6} evil-hash={7}") + public static final String title = "Commit {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit frequency={5} " + + "seed={6} config={7}"; + + @ParameterizedTest(name = title) @MethodSource @Timeout(value = 10) @Tag("fuzz") void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, - boolean evilHash) { + int seed, VersionedMapStoreBuilder builder) { runFuzzTest("CommitS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - nullDefault, commitFrequency, evilHash); + nullDefault, commitFrequency, builder); } static Stream parametrizedFastFuzz() { return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, - commitFrequencyOptions, randomSeedOptions, evilHashOptions); + commitFrequencyOptions, randomSeedOptions, storeConfigs); } - @ParameterizedTest(name = "Commit {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit frequency={5} " + - "seed={6} evil-hash={7}") + @ParameterizedTest(name = title) @MethodSource @Tag("fuzz") @Tag("slow") void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, boolean evilHash) { + int seed, VersionedMapStoreBuilder builder) { runFuzzTest("CommitS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - nullDefault, commitFrequency, evilHash); + nullDefault, commitFrequency, builder); } static Stream parametrizedSlowFuzz() { diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java index c17b0a95..b462ed40 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java @@ -6,7 +6,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import tools.refinery.store.map.*; -import tools.refinery.store.map.internal.VersionedMapImpl; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; @@ -22,20 +21,19 @@ import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; class ContentEqualsFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, - boolean nullDefault, int commitFrequency, - boolean evilHash) { + boolean nullDefault, int commitFrequency, VersionedMapStoreBuilder builder) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); - ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); + Random r = new Random(seed); - iterativeRandomPutsAndCommitsThenCompare(scenario, chp, steps, maxKey, values, r, commitFrequency); + iterativeRandomPutsAndCommitsThenCompare(scenario, builder, steps, maxKey, values, r, commitFrequency); } - private void iterativeRandomPutsAndCommitsThenCompare(String scenario, ContinousHashProvider chp, + private void iterativeRandomPutsAndCommitsThenCompare(String scenario, VersionedMapStoreBuilder builder, int steps, int maxKey, String[] values, Random r, int commitFrequency) { - VersionedMapStore store1 = new VersionedMapStoreImpl<>(chp, values[0]); + VersionedMapStore store1 = builder.setDefaultValue(values[0]).buildOne(); VersionedMap sut1 = store1.createMap(); // Fill one map @@ -65,7 +63,7 @@ class ContentEqualsFuzzTest { // Randomize the order of the content Collections.shuffle(content, r); - VersionedMapStore store2 = new VersionedMapStoreImpl<>(chp, values[0]); + VersionedMapStore store2 = builder.setDefaultValue(values[0]).buildOne(); VersionedMap sut2 = store2.createMap(); int index2 = 1; for (SimpleEntry entry : content) { @@ -75,38 +73,39 @@ class ContentEqualsFuzzTest { } // Check the integrity of the maps - ((VersionedMapImpl) sut1).checkIntegrity(); - ((VersionedMapImpl) sut2).checkIntegrity(); + sut1.checkIntegrity(); + sut2.checkIntegrity(); // Compare the two maps MapTestEnvironment.compareTwoMaps(scenario, sut1, sut2); } - @ParameterizedTest(name = "Compare {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit frequency={5}" + - "seed={6} evil-hash={7}") + public static final String title = "Compare {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit frequency={5}" + + "seed={6} config={7}"; + + @ParameterizedTest(name = title) @MethodSource @Timeout(value = 10) @Tag("fuzz") void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, boolean evilHash) { + int seed, VersionedMapStoreBuilder builder) { runFuzzTest("CompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - nullDefault, commitFrequency, evilHash); + nullDefault, commitFrequency, builder); } static Stream parametrizedFastFuzz() { return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, - commitFrequencyOptions, randomSeedOptions, evilHashOptions); + commitFrequencyOptions, randomSeedOptions, storeConfigs); } - @ParameterizedTest(name = "Compare {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit frequency={5}" + - "seed={6} evil-hash={7}") + @ParameterizedTest(name = title) @MethodSource @Tag("fuzz") @Tag("slow") void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean defaultNull, int commitFrequency, - int seed, boolean evilHash) { + int seed, VersionedMapStoreBuilder builder) { runFuzzTest("CompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - defaultNull, commitFrequency, evilHash); + defaultNull, commitFrequency, builder); } static Stream parametrizedSlowFuzz() { diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java index 90aa8e01..bf409a74 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java @@ -1,33 +1,26 @@ package tools.refinery.store.map.tests.fuzz; -import static org.junit.jupiter.api.Assertions.fail; -import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; - -import java.util.Random; -import java.util.stream.Stream; - import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; - -import tools.refinery.store.map.ContinousHashProvider; -import tools.refinery.store.map.DiffCursor; -import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.VersionedMapStoreImpl; -import tools.refinery.store.map.internal.VersionedMapImpl; +import tools.refinery.store.map.*; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; +import java.util.Random; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.fail; +import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; + class DiffCursorFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, - boolean nullDefault, int commitFrequency, - boolean evilHash) { + boolean nullDefault, int commitFrequency, VersionedMapStoreBuilder builder) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); - ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); - VersionedMapStore store = new VersionedMapStoreImpl<>(chp, values[0]); + VersionedMapStore store = builder.setDefaultValue(values[0]).buildOne(); iterativeRandomPutsAndCommitsThenDiffCursor(scenario, store, steps, maxKey, values, seed, commitFrequency); } @@ -35,7 +28,7 @@ class DiffCursorFuzzTest { int steps, int maxKey, String[] values, int seed, int commitFrequency) { // 1. build a map with versions Random r = new Random(seed); - VersionedMapImpl versioned = (VersionedMapImpl) store.createMap(); + VersionedMap versioned = store.createMap(); int largestCommit = -1; for (int i = 0; i < steps; i++) { @@ -56,7 +49,7 @@ class DiffCursorFuzzTest { System.out.println(scenario + ":" + index + "/" + steps + " building finished"); } // 2. create a non-versioned map, - VersionedMapImpl moving = (VersionedMapImpl) store.createMap(); + VersionedMap moving = store.createMap(); Random r2 = new Random(seed + 1); final int diffTravelFrequency = commitFrequency * 2; @@ -88,31 +81,32 @@ class DiffCursorFuzzTest { } - @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} " + - "commit frequency={5} seed={6} evil-hash={7}") + public static final String title = "DiffCursor {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} " + + "commit frequency={5} seed={6} config={7}"; + + @ParameterizedTest(name = title) @MethodSource @Timeout(value = 10) @Tag("fuzz") void parametrizedFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, boolean evilHash) { + int seed, VersionedMapStoreBuilder builder) { runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, - noKeys, noValues, nullDefault, commitFrequency, evilHash); + noKeys, noValues, nullDefault, commitFrequency, builder); } static Stream parametrizedFuzz() { - return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, - commitFrequencyOptions, randomSeedOptions, evilHashOptions); + return FuzzTestUtils.permutationWithSize(new Object[]{100}, keyCounts, valueCounts, nullDefaultOptions, + commitFrequencyOptions, randomSeedOptions, storeConfigs); } - @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} " + - "commit frequency={5} seed={6} evil-hash={7}") + @ParameterizedTest(name = title) @MethodSource @Tag("fuzz") @Tag("slow") void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, boolean evilHash) { + int seed, VersionedMapStoreBuilder builder) { runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - nullDefault, commitFrequency, evilHash); + nullDefault, commitFrequency, builder); } static Stream parametrizedSlowFuzz() { diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java index 9325a938..ec2224b4 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java @@ -15,27 +15,24 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import tools.refinery.store.map.ContinousHashProvider; import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.VersionedMapStoreImpl; +import tools.refinery.store.map.VersionedMapStoreBuilder; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; class MultiThreadFuzzTest { - public static final int noThreads = 32; + public static final int noThreads = 10; - private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, - boolean nullDefault, int commitFrequency, - boolean evilHash) { + private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, VersionedMapStoreBuilder builder) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); - ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); - VersionedMapStore store = new VersionedMapStoreImpl<>(chp, values[0]); + VersionedMapStore store = builder.setDefaultValue(values[0]).buildOne(); // initialize runnables MultiThreadTestRunnable[] runnables = new MultiThreadTestRunnable[noThreads]; for (int i = 0; i < noThreads; i++) { - runnables[i] = new MultiThreadTestRunnable(scenario + "-T" + (i + 1), store, steps, maxKey, values, seed, commitFrequency); + runnables[i] = new MultiThreadTestRunnable(scenario + "-T" + (i + 1), store, steps, maxKey, values, seed + , commitFrequency); } // initialize threads @@ -46,7 +43,8 @@ class MultiThreadFuzzTest { // start threads; for (int i = 0; i < noThreads; i++) { - threads[i].start(); + runnables[i].run(); + //threads[i].start(); } // wait all the threads; @@ -67,32 +65,32 @@ class MultiThreadFuzzTest { assertEquals(Collections.EMPTY_LIST, errors); } - @ParameterizedTest(name = "MultiThread {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit " + - "frequency={5} seed={6} evil-hash={7}") + static final String title = "MultiThread {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit " + + "frequency={5} seed={6} config={7}"; + + @ParameterizedTest(name = title) @MethodSource @Timeout(value = 10) @Tag("fuzz") - void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean defaultNull, int commitFrequency, - int seed, boolean evilHash) { + void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean defaultNull, + int commitFrequency, int seed, VersionedMapStoreBuilder builder) { runFuzzTest("MultiThreadS" + steps + "K" + noKeys + "V" + noValues + defaultNull + "CF" + commitFrequency + - "s" + seed, seed, steps, noKeys, noValues, defaultNull, commitFrequency, evilHash); + "s" + seed, seed, steps, noKeys, noValues, defaultNull, commitFrequency, builder); } static Stream parametrizedFastFuzz() { return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, - new Object[]{10, 100}, randomSeedOptions, - evilHashOptions); + new Object[]{10, 100}, randomSeedOptions, storeConfigs); } - @ParameterizedTest(name = "MultiThread {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit " + - "frequency={5} seed={6} evil-hash={7}") + @ParameterizedTest(name = title) @MethodSource @Tag("fuzz") @Tag("slow") - void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, - boolean evilHash) { + void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, + int commitFrequency, int seed, VersionedMapStoreBuilder builder) { runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - nullDefault, commitFrequency, evilHash); + nullDefault, commitFrequency, builder); } static Stream parametrizedSlowFuzz() { diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java index 4415e4e5..f449ca97 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java @@ -8,8 +8,8 @@ import java.util.List; import java.util.Map; import java.util.Random; +import tools.refinery.store.map.VersionedMap; import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.internal.VersionedMapImpl; import tools.refinery.store.map.tests.utils.MapTestEnvironment; public class MultiThreadTestRunnable implements Runnable { @@ -45,9 +45,17 @@ public class MultiThreadTestRunnable implements Runnable { @Override public void run() { + try{ + task(); + } catch(Exception e) { + e.printStackTrace(); + } + } + + private void task() { // 1. build a map with versions Random r = new Random(seed); - VersionedMapImpl versioned = (VersionedMapImpl) store.createMap(); + VersionedMap versioned = store.createMap(); Map index2Version = new HashMap<>(); for (int i = 0; i < steps; i++) { @@ -67,7 +75,7 @@ public class MultiThreadTestRunnable implements Runnable { MapTestEnvironment.printStatus(scenario, index, steps, "building"); } // 2. create a non-versioned - VersionedMapImpl reference = (VersionedMapImpl) store.createMap(); + VersionedMap reference = store.createMap(); r = new Random(seed); Random r2 = new Random(seed + 1); @@ -84,13 +92,17 @@ public class MultiThreadTestRunnable implements Runnable { // go back to an existing state and compare to the reference if (index % (commitFrequency) == 0) { versioned.restore(index2Version.get(i)); - MapTestEnvironment.compareTwoMaps(scenario + ":" + index, reference, versioned, errors); + MapTestEnvironment.compareTwoMaps(scenario + ":" + index, reference, versioned, null); // go back to a random state (probably created by another thread) List states = new ArrayList<>(store.getStates()); + states.sort(Long::compare); Collections.shuffle(states, r2); for (Long state : states.subList(0, Math.min(states.size(), 100))) { - versioned.restore(state); + long x = state; + versioned.restore(x); + var clean = store.createMap(x); + MapTestEnvironment.compareTwoMaps(scenario + ":" + index, clean, versioned, null); } versioned.restore(index2Version.get(i)); } diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java index ab37ef83..bdf72ce4 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java @@ -12,21 +12,17 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import tools.refinery.store.map.ContinousHashProvider; -import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.VersionedMapStoreImpl; -import tools.refinery.store.map.internal.VersionedMapImpl; +import tools.refinery.store.map.*; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; class MutableFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, - boolean nullDefault, boolean evilHash) { + boolean nullDefault, VersionedMapStoreBuilder builder) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); - ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); - VersionedMapStore store = new VersionedMapStoreImpl<>(chp, values[0]); - VersionedMapImpl sut = (VersionedMapImpl) store.createMap(); + VersionedMapStore store = builder.setDefaultValue(values[0]).buildOne(); + VersionedMap sut = store.createMap(); MapTestEnvironment e = new MapTestEnvironment<>(sut); Random r = new Random(seed); @@ -52,33 +48,34 @@ class MutableFuzzTest { } } - @ParameterizedTest(name = "Mutable {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} seed={5} " + - "evil-hash={6}") + final String title = "Mutable {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} seed={5} " + + "config={6}"; + + @ParameterizedTest(name = title) @MethodSource @Timeout(value = 10) @Tag("fuzz") void parametrizedFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean defaultNull, int seed, - boolean evilHash) { + VersionedMapStoreBuilder builder) { runFuzzTest( - "MutableS" + steps + "K" + noKeys + "V" + noValues + "s" + seed + "H" + (evilHash ? "Evil" : "Normal"), - seed, steps, noKeys, noValues, defaultNull, evilHash); + "MutableS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, + seed, steps, noKeys, noValues, defaultNull, builder); } static Stream parametrizedFuzz() { return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, - randomSeedOptions, evilHashOptions); + randomSeedOptions, storeConfigs); } - @ParameterizedTest(name = "Mutable {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} seed={5} " + - "evil-hash={6}") + @ParameterizedTest(name = title) @MethodSource @Tag("fuzz") @Tag("slow") void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int seed, - boolean evilHash) { + VersionedMapStoreBuilder builder) { runFuzzTest( - "MutableS" + steps + "K" + noKeys + "V" + noValues + "s" + seed + "H" + (evilHash ? "Evil" : "Normal"), - seed, steps, noKeys, noValues, nullDefault, evilHash); + "MutableS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, + seed, steps, noKeys, noValues, nullDefault, builder); } static Stream parametrizedSlowFuzz() { diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java index b58b06f9..cee15fe1 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java @@ -71,7 +71,7 @@ class MutableImmutableCompareFuzzTest { static Stream parametrizedFastFuzz() { return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, - commitFrequencyOptions, randomSeedOptions, evilHashOptions); + commitFrequencyOptions, randomSeedOptions, new Object[]{false, true}); } @ParameterizedTest(name = "Mutable-Immutable Compare {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} " + diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java index 77b26c41..568aaac9 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java @@ -14,9 +14,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import tools.refinery.store.map.ContinousHashProvider; -import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.VersionedMapStoreImpl; +import tools.refinery.store.map.*; import tools.refinery.store.map.internal.VersionedMapImpl; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; @@ -24,11 +22,10 @@ import tools.refinery.store.map.tests.utils.MapTestEnvironment; class RestoreFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, - boolean evilHash) { + VersionedMapStoreBuilder builder) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); - ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); - VersionedMapStore store = new VersionedMapStoreImpl<>(chp, values[0]); + VersionedMapStore store = builder.setDefaultValue(values[0]).buildOne(); iterativeRandomPutsAndCommitsThenRestore(scenario, store, steps, maxKey, values, seed, commitFrequency); } @@ -37,7 +34,7 @@ class RestoreFuzzTest { int steps, int maxKey, String[] values, int seed, int commitFrequency) { // 1. build a map with versions Random r = new Random(seed); - VersionedMapImpl versioned = (VersionedMapImpl) store.createMap(); + VersionedMap versioned = store.createMap(); Map index2Version = new HashMap<>(); for (int i = 0; i < steps; i++) { @@ -57,7 +54,7 @@ class RestoreFuzzTest { MapTestEnvironment.printStatus(scenario, index, steps, "building"); } // 2. create a non-versioned and - VersionedMapImpl reference = (VersionedMapImpl) store.createMap(); + VersionedMap reference = store.createMap(); r = new Random(seed); for (int i = 0; i < steps; i++) { @@ -79,31 +76,32 @@ class RestoreFuzzTest { } - @ParameterizedTest(name = "Restore {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit frequency={5}" + - " seed={6} evil-hash={7}") + public static final String title = "Commit {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit frequency={5} " + + "seed={6} config={7}"; + + @ParameterizedTest(name = title) @MethodSource @Timeout(value = 10) @Tag("smoke") void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, boolean evilHash) { + int seed, VersionedMapStoreBuilder builder) { runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - nullDefault, commitFrequency, evilHash); + nullDefault, commitFrequency, builder); } static Stream parametrizedFastFuzz() { return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, - commitFrequencyOptions, randomSeedOptions, evilHashOptions); + commitFrequencyOptions, randomSeedOptions, storeConfigs); } - @ParameterizedTest(name = "Restore {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit frequency={5}" + - " seed={6} evil-hash={7}") + @ParameterizedTest(name = title) @MethodSource @Tag("smoke") @Tag("slow") void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, boolean evilHash) { + int seed, VersionedMapStoreBuilder builder) { runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - nullDefault, commitFrequency, evilHash); + nullDefault, commitFrequency, builder); } static Stream parametrizedSlowFuzz() { diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java index 4462c55b..0544687a 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java @@ -96,7 +96,7 @@ class SharedStoreFuzzTest { static Stream parametrizedFastFuzz() { return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, - commitFrequencyOptions, randomSeedOptions, evilHashOptions); + commitFrequencyOptions, randomSeedOptions, new Object[]{false, true}); } @ParameterizedTest(name = "Shared Store {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} commit " + diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java index add4ca5d..fb6b28d8 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java @@ -1,11 +1,43 @@ package tools.refinery.store.map.tests.fuzz.utils; +import tools.refinery.store.map.VersionedMapStoreBuilder; +import tools.refinery.store.map.tests.utils.MapTestEnvironment; + public final class FuzzTestCollections { public static final Object[] stepCounts = {FuzzTestUtils.FAST_STEP_COUNT}; - public static final Object[] keyCounts = {3, 32, 32 * 32}; + public static final Object[] keyCounts = {1, 32, 32 * 32}; public static final Object[] valueCounts = {2, 3}; public static final Object[] nullDefaultOptions = {false, true}; - public static final Object[] commitFrequencyOptions = {1, 10, 100}; - public static final Object[] randomSeedOptions = {1, 2, 3}; - public static final Object[] evilHashOptions = {false, true}; + public static final Object[] commitFrequencyOptions = {10, 10, 100}; + public static final Object[] randomSeedOptions = {1/*, 2, 3*/}; + public static final Object[] storeConfigs = { + // State based + VersionedMapStoreBuilder.builder() + .setStrategy(VersionedMapStoreBuilder.StoreStrategy.STATE) + .setStateBasedImmutableWhenCommitting(true) + .setHashProvider(MapTestEnvironment.prepareHashProvider(false)) + .setStateBasedNodeSharingStrategy(VersionedMapStoreBuilder.StateStorageStrategy.SHARED_NODE_CACHE), + VersionedMapStoreBuilder.builder() + .setStrategy(VersionedMapStoreBuilder.StoreStrategy.STATE) + .setStateBasedImmutableWhenCommitting(true) + .setHashProvider(MapTestEnvironment.prepareHashProvider(true)) + .setStateBasedNodeSharingStrategy(VersionedMapStoreBuilder.StateStorageStrategy.SHARED_NODE_CACHE), + VersionedMapStoreBuilder.builder() + .setStrategy(VersionedMapStoreBuilder.StoreStrategy.STATE) + .setStateBasedImmutableWhenCommitting(false) + .setHashProvider(MapTestEnvironment.prepareHashProvider(false)) + .setStateBasedNodeSharingStrategy(VersionedMapStoreBuilder.StateStorageStrategy.SHARED_NODE_CACHE), + VersionedMapStoreBuilder.builder() + .setStrategy(VersionedMapStoreBuilder.StoreStrategy.STATE) + .setStateBasedImmutableWhenCommitting(false) + .setHashProvider(MapTestEnvironment.prepareHashProvider(false)) + .setStateBasedNodeSharingStrategy(VersionedMapStoreBuilder.StateStorageStrategy.NO_NODE_CACHE), + // Delta based + VersionedMapStoreBuilder.builder() + .setStrategy(VersionedMapStoreBuilder.StoreStrategy.DELTA) + .setDeltaStorageStrategy(VersionedMapStoreBuilder.DeltaStorageStrategy.SET), + VersionedMapStoreBuilder.builder() + .setStrategy(VersionedMapStoreBuilder.StoreStrategy.DELTA) + .setDeltaStorageStrategy(VersionedMapStoreBuilder.DeltaStorageStrategy.LIST) + }; } -- cgit v1.2.3-70-g09d2 From f0f04091e31f80f253f9129ca3a7191d0af6af1e Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Thu, 16 Feb 2023 00:41:09 +0100 Subject: Additional tests for delta restoration --- .../refinery/store/map/tests/MapUnitTests.java | 72 ++++++++++++++++++++++ .../store/map/tests/fuzz/SingleThreadFuzzTest.java | 61 ++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java index 77c62305..2216db76 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java @@ -2,6 +2,7 @@ package tools.refinery.store.map.tests; import org.junit.jupiter.api.Test; import tools.refinery.store.map.VersionedMapStore; +import tools.refinery.store.map.VersionedMapStoreBuilder; import tools.refinery.store.map.VersionedMapStoreImpl; import tools.refinery.store.model.TupleHashProvider; import tools.refinery.store.tuple.Tuple; @@ -18,4 +19,75 @@ class MapUnitTests { var out2 = map.put(Tuple.of(1), true); assertEquals(false, out2); } + + @Test + void deltaRestoreTest() { + VersionedMapStore store = + VersionedMapStoreBuilder.builder().setDefaultValue("x").buildOne(); + var map = store.createMap(); + map.put(1,"val"); + var version1 = map.commit(); + map.put(1,"x"); + map.restore(version1); + System.out.println(map.getSize()); + assertEquals(1,map.getSize()); + } + + @Test + void deltaRestoreTest2() { + VersionedMapStore store = + VersionedMapStoreBuilder.builder().setDefaultValue("x").buildOne(); + var map = store.createMap(); + map.put(1,"x"); + var version1 = map.commit(); + map.put(1,"1"); + map.restore(version1); + System.out.println(map.getSize()); + assertEquals(0,map.getSize()); + } + @Test + void deltaRestoreTest3() { + VersionedMapStore store = + VersionedMapStoreBuilder.builder().setDefaultValue("x").buildOne(); + var map = store.createMap(); + map.commit(); + map.put(1,"1"); + map.put(2,"x"); + assertEquals(1,map.getSize()); + var version1 = map.commit(); + map.put(1,"x"); + assertEquals(0,map.getSize()); + map.put(2,"2"); + assertEquals(1,map.getSize()); + map.put(2,"x"); + assertEquals(0,map.getSize()); + var version2 = map.commit(); + map.restore(version1); + assertEquals(1,map.getSize()); + map.restore(version2); + assertEquals(0,map.getSize()); + } + + @Test + void deltaRestoreTest4() { + VersionedMapStore store = + VersionedMapStoreBuilder.builder().setDefaultValue("x").buildOne(); + var map = store.createMap(); + map.commit(); + map.put(1,"1"); + map.put(2,"x"); + assertEquals(1,map.getSize()); + var version1 = map.commit(); + map.put(1,"x"); + assertEquals(0,map.getSize()); + map.put(2,"2"); + assertEquals(1,map.getSize()); + map.put(2,"x"); + assertEquals(0,map.getSize()); + var version2 = map.commit(); + map.restore(version1); + assertEquals(1,map.getSize()); + map.restore(version2); + assertEquals(0,map.getSize()); + } } diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java new file mode 100644 index 00000000..e7d49227 --- /dev/null +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java @@ -0,0 +1,61 @@ +package tools.refinery.store.map.tests.fuzz; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import tools.refinery.store.map.VersionedMapStore; +import tools.refinery.store.map.VersionedMapStoreBuilder; +import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; +import tools.refinery.store.map.tests.utils.MapTestEnvironment; + +import java.util.stream.Stream; + +import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; + +class SingleThreadFuzzTest { + private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, VersionedMapStoreBuilder builder) { + String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); + + VersionedMapStore store = builder.setDefaultValue(values[0]).buildOne(); + + // initialize runnables + MultiThreadTestRunnable runnable = new MultiThreadTestRunnable(scenario, store, steps, maxKey, values, seed, commitFrequency); + + // start threads; + runnable.run(); + } + + static final String title = "SingleThread {index}/{0} Steps={1} Keys={2} Values={3} defaultNull={4} commit " + + "frequency={5} seed={6} config={7}"; + + @ParameterizedTest(name = title) + @MethodSource + @Timeout(value = 10) + @Tag("fuzz") + void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean defaultNull, + int commitFrequency, int seed, VersionedMapStoreBuilder builder) { + runFuzzTest("SingleThreadS" + steps + "K" + noKeys + "V" + noValues + defaultNull + "CF" + commitFrequency + + "s" + seed, seed, steps, noKeys, noValues, defaultNull, commitFrequency, builder); + } + + static Stream parametrizedFastFuzz() { + return FuzzTestUtils.permutationWithSize(stepCounts, keyCounts, valueCounts, nullDefaultOptions, + new Object[]{10, 100}, randomSeedOptions, storeConfigs); + } + + @ParameterizedTest(name = title) + @MethodSource + @Tag("fuzz") + @Tag("slow") + void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, + int commitFrequency, int seed, VersionedMapStoreBuilder builder) { + runFuzzTest("SingleThreadS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, + nullDefault, commitFrequency, builder); + } + + static Stream parametrizedSlowFuzz() { + return FuzzTestUtils.changeStepCount(RestoreFuzzTest.parametrizedFastFuzz(), 1); + } +} -- cgit v1.2.3-70-g09d2 From 89e142514ae45d793c7dbe38f728b33451261338 Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Tue, 18 Jul 2023 15:38:59 +0200 Subject: Fixing long-standing bug with state based diff cursor. By implementing an InOrderMapCursor cursor, and a MapDiffCursor that synchronize two cursors. --- .../refinery/store/map/VersionedMapStoreImpl.java | 17 +- .../refinery/store/map/internal/ImmutableNode.java | 74 ++++- .../store/map/internal/InOrderMapCursor.java | 141 +++++++++ .../refinery/store/map/internal/MapCursor.java | 59 ---- .../refinery/store/map/internal/MapDiffCursor.java | 316 ++++++++++++--------- .../refinery/store/map/internal/MutableNode.java | 111 +++++--- .../tools/refinery/store/map/internal/Node.java | 2 + .../store/map/internal/VersionedMapImpl.java | 20 +- .../model/internal/VersionedInterpretation.java | 15 +- .../store/map/tests/InOrderCursorTest.java | 49 ++++ .../store/map/tests/fuzz/DiffCursorFuzzTest.java | 128 +++++---- .../map/tests/fuzz/utils/FuzzTestCollections.java | 12 +- .../store/map/tests/utils/MapTestEnvironment.java | 10 +- 13 files changed, 611 insertions(+), 343 deletions(-) create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/InOrderMapCursor.java create mode 100644 subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java index 113874e7..beeed110 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java @@ -1,9 +1,6 @@ package tools.refinery.store.map; -import tools.refinery.store.map.internal.ImmutableNode; -import tools.refinery.store.map.internal.MapDiffCursor; -import tools.refinery.store.map.internal.Node; -import tools.refinery.store.map.internal.VersionedMapImpl; +import tools.refinery.store.map.internal.*; import java.util.*; @@ -93,7 +90,7 @@ public class VersionedMapStoreImpl implements VersionedMapStore { } else { ArrayList existingKeys = new ArrayList<>(states.keySet()); Collections.sort(existingKeys); - throw new IllegalArgumentException("Store does not contain state " + state + "! Avaliable states: " + throw new IllegalArgumentException("Store does not contain state " + state + "! Available states: " + Arrays.toString(existingKeys.toArray())); } } @@ -118,10 +115,10 @@ public class VersionedMapStoreImpl implements VersionedMapStore { @Override public DiffCursor getDiffCursor(long fromState, long toState) { - VersionedMap map1 = createMap(fromState); - VersionedMap map2 = createMap(toState); - Cursor cursor1 = map1.getAll(); - Cursor cursor2 = map2.getAll(); - return new MapDiffCursor<>(this.hashProvider, this.defaultValue, cursor1, cursor2); + VersionedMapImpl map1 = (VersionedMapImpl) createMap(fromState); + VersionedMapImpl map2 = (VersionedMapImpl) createMap(toState); + InOrderMapCursor cursor1 = new InOrderMapCursor<>(map1); + InOrderMapCursor cursor2 = new InOrderMapCursor<>(map2); + return new MapDiffCursor<>(this.defaultValue, cursor1, cursor2); } } diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java index 914bab08..92446711 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java @@ -15,7 +15,7 @@ public class ImmutableNode extends Node { */ final int nodeMap; /** - * Stores Keys, Values, and subnodes. Structure: (K,V)*,NODE; NODES are stored + * Stores Keys, Values, and sub-nodes. Structure: (K,V)*,NODE; NODES are stored * backwards. */ final Object[] content; @@ -65,24 +65,24 @@ public class ImmutableNode extends Node { int resultDataMap = 0; int resultNodeMap = 0; final Object[] resultContent = new Object[size]; - int bitposition = 1; + int bitPosition = 1; for (int i = 0; i < FACTOR; i++) { Object key = node.content[i * 2]; if (key != null) { - resultDataMap |= bitposition; + resultDataMap |= bitPosition; resultContent[datas * 2] = key; resultContent[datas * 2 + 1] = node.content[i * 2 + 1]; datas++; } else { @SuppressWarnings("unchecked") var subnode = (Node) node.content[i * 2 + 1]; if (subnode != null) { - ImmutableNode immutableSubnode = subnode.toImmutable(cache); - resultNodeMap |= bitposition; - resultContent[size - 1 - nodes] = immutableSubnode; + ImmutableNode immutableSubNode = subnode.toImmutable(cache); + resultNodeMap |= bitPosition; + resultContent[size - 1 - nodes] = immutableSubNode; nodes++; } } - bitposition <<= 1; + bitPosition <<= 1; } final int resultHash = node.hashCode(); var newImmutable = new ImmutableNode(resultDataMap, resultNodeMap, resultContent, resultHash); @@ -130,9 +130,9 @@ public class ImmutableNode extends Node { @Override public Node putValue(K key, V value, OldValueBox oldValue, ContinousHashProvider hashProvider, V defaultValue, int hash, int depth) { int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); - int bitposition = 1 << selectedHashFragment; - if ((dataMap & bitposition) != 0) { - int keyIndex = 2 * index(dataMap, bitposition); + int bitPosition = 1 << selectedHashFragment; + if ((dataMap & bitPosition) != 0) { + int keyIndex = 2 * index(dataMap, bitPosition); @SuppressWarnings("unchecked") K keyCandidate = (K) content[keyIndex]; if (keyCandidate.equals(key)) { if (value == defaultValue) { @@ -159,8 +159,8 @@ public class ImmutableNode extends Node { return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth); } } - } else if ((nodeMap & bitposition) != 0) { - int keyIndex = content.length - 1 - index(nodeMap, bitposition); + } else if ((nodeMap & bitPosition) != 0) { + int keyIndex = content.length - 1 - index(nodeMap, bitPosition); @SuppressWarnings("unchecked") var subNode = (ImmutableNode) content[keyIndex]; int newDepth = incrementDepth(depth); int newHash = newHash(hashProvider, key, hash, newDepth); @@ -253,6 +253,49 @@ public class ImmutableNode extends Node { } } + @Override + @SuppressWarnings("unchecked") + boolean moveToNextInorder(InOrderMapCursor cursor) { + if(cursor.nodeIndexStack.peek()==null) { + throw new IllegalStateException("Cursor moved to the next state when the state is empty."); + } + + int position = cursor.nodeIndexStack.peek(); + for (int index = position + 1; index < FACTOR; index++) { + final int mask = 1< subnode = (Node) this.content[this.content.length - 1 - index(nodeMap, mask)]; + cursor.nodeIndexStack.pop(); + cursor.nodeIndexStack.push(index); + cursor.nodeIndexStack.push(InOrderMapCursor.INDEX_START); + cursor.nodeStack.push(subnode); + + return subnode.moveToNextInorder(cursor); + } + } + + // nothing found + cursor.nodeStack.pop(); + cursor.nodeIndexStack.pop(); + if (!cursor.nodeStack.isEmpty()) { + Node supernode = cursor.nodeStack.peek(); + return supernode.moveToNextInorder(cursor); + } else { + cursor.key = null; + cursor.value = null; + return false; + } + } + @Override public void prettyPrint(StringBuilder builder, int depth, int code) { builder.append("\t".repeat(Math.max(0, depth))); @@ -348,8 +391,8 @@ public class ImmutableNode extends Node { var mutableSubnode = (Node) mutable.content[i * 2 + 1]; if (mutableSubnode != null) { if (datas * 2 + nodes + 1 <= immutableLength) { - Object immutableSubnode = immutable.content[immutableLength - 1 - nodes]; - if (!mutableSubnode.equals(immutableSubnode)) { + Object immutableSubNode = immutable.content[immutableLength - 1 - nodes]; + if (!mutableSubnode.equals(immutableSubNode)) { return false; } nodes++; @@ -359,6 +402,7 @@ public class ImmutableNode extends Node { } } } - return true; + + return datas * 2 + nodes == immutable.content.length; } } diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/InOrderMapCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/InOrderMapCursor.java new file mode 100644 index 00000000..c559d9ad --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/InOrderMapCursor.java @@ -0,0 +1,141 @@ +package tools.refinery.store.map.internal; + +import tools.refinery.store.map.AnyVersionedMap; +import tools.refinery.store.map.ContentHashCode; +import tools.refinery.store.map.Cursor; +import tools.refinery.store.map.VersionedMap; + +import java.util.*; + +public class InOrderMapCursor implements Cursor { + // Constants + static final int INDEX_START = -1; + + // Tree stack + ArrayDeque> nodeStack; + ArrayDeque nodeIndexStack; + + + // Values + K key; + V value; + + // Hash code for checking concurrent modifications + final VersionedMap map; + final int creationHash; + + public InOrderMapCursor(VersionedMapImpl map) { + // Initializing tree stack + super(); + this.nodeStack = new ArrayDeque<>(); + this.nodeIndexStack = new ArrayDeque<>(); + if (map.root != null) { + this.nodeStack.add(map.root); + this.nodeIndexStack.push(INDEX_START); + } + + // Initializing cache + this.key = null; + this.value = null; + + // Initializing state + this.map = map; + this.creationHash = map.contentHashCode(ContentHashCode.APPROXIMATE_FAST); + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public boolean isTerminated() { + return this.nodeStack.isEmpty(); + } + + public boolean move() { + if (isDirty()) { + throw new ConcurrentModificationException(); + } + if (!isTerminated()) { + var node = this.nodeStack.peek(); + if (node == null) { + throw new IllegalStateException("Cursor is not terminated but the current node is missing"); + } + boolean result = node.moveToNextInorder(this); + if (this.nodeIndexStack.size() != this.nodeStack.size()) { + throw new IllegalArgumentException("Node stack is corrupted by illegal moves!"); + } + return result; + } + return false; + } + + public boolean skipCurrentNode() { + nodeStack.pop(); + nodeIndexStack.pop(); + return move(); + } + + @Override + public boolean isDirty() { + return this.map.contentHashCode(ContentHashCode.APPROXIMATE_FAST) != this.creationHash; + } + + @Override + public Set getDependingMaps() { + return Set.of(this.map); + } + + public static boolean sameSubNode(InOrderMapCursor cursor1, InOrderMapCursor cursor2) { + Node nodeOfCursor1 = cursor1.nodeStack.peek(); + Node nodeOfCursor2 = cursor2.nodeStack.peek(); + return Objects.equals(nodeOfCursor1, nodeOfCursor2); + } + + /** + * Compares the state of two cursors started on two {@link VersionedMap} of the same + * {@link tools.refinery.store.map.VersionedMapStore}. + * @param Key type + * @param Value type + * @param cursor1 first cursor + * @param cursor2 second cursor + * @return Positive number if cursor 1 is behind, negative number if cursor 2 is behind, and 0 if they are at the + * same position. + */ + public static int comparePosition(InOrderMapCursor cursor1, InOrderMapCursor cursor2) { + // If the state does not determine the order, then compare @nodeIndexStack. + Iterator nodeIndexStack1 = cursor1.nodeIndexStack.descendingIterator(); + Iterator nodeIndexStack2 = cursor2.nodeIndexStack.descendingIterator(); + + while(nodeIndexStack1.hasNext() && nodeIndexStack2.hasNext()){ + final int index1 = nodeIndexStack1.next(); + final int index2 = nodeIndexStack2.next(); + if(index1 < index2) { + return 1; + } else if(index1 > index2) { + return -1; + } + } + + return 0; + } + + /** + * Compares the depth of two cursors started on @{@link VersionedMap} of the same + * {@link tools.refinery.store.map.VersionedMapStore}. + * @param Key type + * @param Value type + * @param cursor1 first cursor + * @param cursor2 second cursor + * @return Positive number if cursor 1 is deeper, negative number if cursor 2 is deeper, and 0 if they are at the + * same depth. + */ + public static int compareDepth(InOrderMapCursor cursor1, InOrderMapCursor cursor2) { + int d1 = cursor1.nodeIndexStack.size(); + int d2 = cursor2.nodeIndexStack.size(); + return Integer.compare(d1, d2); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java index 50fcfcd3..7e4f82e8 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java @@ -7,7 +7,6 @@ import tools.refinery.store.map.VersionedMap; import java.util.ArrayDeque; import java.util.ConcurrentModificationException; -import java.util.Iterator; import java.util.Set; public class MapCursor implements Cursor { @@ -79,13 +78,6 @@ public class MapCursor implements Cursor { return false; } - public boolean skipCurrentNode() { - nodeStack.pop(); - nodeIndexStack.pop(); - dataIndex = INDEX_FINISH; - return move(); - } - @Override public boolean isDirty() { return this.map.contentHashCode(ContentHashCode.APPROXIMATE_FAST) != this.creationHash; @@ -95,55 +87,4 @@ public class MapCursor implements Cursor { public Set getDependingMaps() { return Set.of(this.map); } - - public static boolean sameSubNode(MapCursor cursor1, MapCursor cursor2) { - Node nodeOfCursor1 = cursor1.nodeStack.peek(); - Node nodeOfCursor2 = cursor2.nodeStack.peek(); - if (nodeOfCursor1 != null && nodeOfCursor2 != null) { - return nodeOfCursor1.equals(nodeOfCursor2); - } else { - return false; - } - } - - /** - * Compares the state of two cursors started on two @{@link VersionedMap of the }same - * {@link tools.refinery.store.map.VersionedMapStore}. - * @param Key type - * @param Value type - * @param cursor1 first cursor - * @param cursor2 second cursor - * @return Positive number if cursor 1 is behind, negative number if cursor 2 is behind, and 0 if they are at the - * same position. - */ - public static int compare(MapCursor cursor1, MapCursor cursor2) { - // Checking the state of the cursors - if(!cursor1.isTerminated() && cursor2.isTerminated()) return -1; - else if(cursor1.isTerminated() && !cursor2.isTerminated()) return 1; - else if(cursor1.isTerminated() && cursor2.isTerminated()) return 0; - - // If the state does not determine the order, then compare @nodeIndexStack. - Iterator stack1 = cursor1.nodeIndexStack.descendingIterator(); - Iterator stack2 = cursor2.nodeIndexStack.descendingIterator(); - if (stack1.hasNext()) { - if (!stack2.hasNext()) { - // stack 2 has no more element, thus stack 1 is deeper - return 1; - } - int val1 = stack1.next(); - int val2 = stack2.next(); - if (val1 < val2) { - return -1; - } else if (val2 < val1) { - return 1; - } - } - if (stack2.hasNext()) { - // stack 2 has more element, thus stack 2 is deeper - return 1; - } - - // two cursors are equally deep decide by data index - return Integer.compare(cursor1.dataIndex, cursor2.dataIndex); - } } diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java index 6c076ce5..59e8d738 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java @@ -1,7 +1,6 @@ package tools.refinery.store.map.internal; import tools.refinery.store.map.AnyVersionedMap; -import tools.refinery.store.map.ContinousHashProvider; import tools.refinery.store.map.Cursor; import tools.refinery.store.map.DiffCursor; @@ -14,37 +13,78 @@ import java.util.stream.Stream; * A cursor representing the difference between two states of a map. * * @author Oszkar Semerath - * */ public class MapDiffCursor implements DiffCursor, Cursor { + private enum State { + /** + * initialized state. + */ + INIT, + /** + * Unstable state. + */ + MOVING_MOVING_SAME_KEY_SAME_VALUE, + /** + * Both cursors are moving, and they are on the same sub-node. + */ + MOVING_MOVING_SAME_NODE, + /** + * Both cursors are moving, cursor 1 is behind. + */ + MOVING_MOVING_BEHIND1, + /** + * Both cursors are moving, cursor 2 is behind. + */ + MOVING_MOVING_BEHIND2, + /** + * Both cursors are moving, cursor 1 is on the same key as cursor 2, values are different + */ + MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE, + /** + * Cursor 1 is moving, Cursor 2 is terminated. + */ + MOVING_TERMINATED, + /** + * Cursor 1 is terminated , Cursor 2 is moving. + */ + TERMINATED_MOVING, + /** + * Both cursors are terminated. + */ + TERMINATED_TERMINATED, + /** + * Both Cursors are moving, and they are on an incomparable position. + * It is resolved by showing Cursor 1. + */ + MOVING_MOVING_HASH1, + /** + * Both Cursors are moving, and they are on an incomparable position. + * It is resolved by showing Cursor 2. + */ + MOVING_MOVING_HASH2 + } + /** * Default nodeId representing missing elements. */ private final V defaultValue; - private final MapCursor cursor1; - private final MapCursor cursor2; - private final ContinousHashProvider hashProvider; + private final InOrderMapCursor cursor1; + private final InOrderMapCursor cursor2; + + // State + State state = State.INIT; // Values private K key; private V fromValue; private V toValue; - // State - /** - * Positive number if cursor 1 is behind, negative number if cursor 2 is behind, - * and 0 if they are at the same position. - */ - private int cursorRelation; - private HashClash hashClash = HashClash.NONE; - public MapDiffCursor(ContinousHashProvider hashProvider, V defaultValue, Cursor cursor1, - Cursor cursor2) { + public MapDiffCursor(V defaultValue, InOrderMapCursor cursor1, InOrderMapCursor cursor2) { super(); - this.hashProvider = hashProvider; this.defaultValue = defaultValue; - this.cursor1 = (MapCursor) cursor1; - this.cursor2 = (MapCursor) cursor2; + this.cursor1 = cursor1; + this.cursor2 = cursor2; } @Override @@ -68,7 +108,7 @@ public class MapDiffCursor implements DiffCursor, Cursor { } public boolean isTerminated() { - return cursor1.isTerminated() && cursor2.isTerminated(); + return this.state == State.TERMINATED_TERMINATED; } @Override @@ -78,148 +118,142 @@ public class MapDiffCursor implements DiffCursor, Cursor { @Override public Set getDependingMaps() { - return Stream.concat(cursor1.getDependingMaps().stream(), cursor2.getDependingMaps().stream()) - .map(AnyVersionedMap.class::cast) - .collect(Collectors.toUnmodifiableSet()); - } - - protected void updateState() { - if (!isTerminated()) { - this.cursorRelation = MapCursor.compare(cursor1, cursor2); - if (cursorRelation > 0 || cursor2.isTerminated()) { - this.key = cursor1.getKey(); - this.fromValue = cursor1.getValue(); - this.toValue = defaultValue; - } else if (cursorRelation < 0 || cursor1.isTerminated()) { - this.key = cursor2.getKey(); - this.fromValue = defaultValue; - this.toValue = cursor1.getValue(); - } else { - // cursor1 = cursor2 - if (cursor1.getKey().equals(cursor2.getKey())) { - this.key = cursor1.getKey(); - this.fromValue = cursor1.getValue(); - this.toValue = defaultValue; - } else { - resolveHashClashWithFirstEntry(); - } - } - } + return Stream.concat(cursor1.getDependingMaps().stream(), cursor2.getDependingMaps().stream()).map(AnyVersionedMap.class::cast).collect(Collectors.toUnmodifiableSet()); } - protected void resolveHashClashWithFirstEntry() { - int compareResult = this.hashProvider.compare(cursor1.key, cursor2.key); - if (compareResult < 0) { - this.hashClash = HashClash.STUCK_CURSOR_2; - this.cursorRelation = 0; - this.key = cursor1.key; - this.fromValue = cursor1.value; - this.toValue = defaultValue; - } else if (compareResult > 0) { - this.hashClash = HashClash.STUCK_CURSOR_1; - this.cursorRelation = 0; - this.key = cursor2.key; - this.fromValue = defaultValue; - this.toValue = cursor2.value; - } else { - throw new IllegalArgumentException("Inconsistent compare result for diffCursor"); - } + private boolean isInStableState() { + return this.state != State.MOVING_MOVING_SAME_KEY_SAME_VALUE + && this.state != State.MOVING_MOVING_SAME_NODE && this.state != State.INIT; } - protected boolean isInHashClash() { - return this.hashClash != HashClash.NONE; + private boolean updateAndReturnWithResult() { + return switch (this.state) { + case INIT -> throw new IllegalStateException("DiffCursor terminated without starting!"); + case MOVING_MOVING_SAME_KEY_SAME_VALUE, MOVING_MOVING_SAME_NODE -> + throw new IllegalStateException("DiffCursor terminated in unstable state!"); + case MOVING_MOVING_BEHIND1, MOVING_TERMINATED, MOVING_MOVING_HASH1 -> { + this.key = this.cursor1.getKey(); + this.fromValue = this.cursor1.getValue(); + this.toValue = this.defaultValue; + yield true; + } + case MOVING_MOVING_BEHIND2, TERMINATED_MOVING, MOVING_MOVING_HASH2 -> { + this.key = this.cursor2.getKey(); + this.fromValue = this.defaultValue; + this.toValue = cursor2.getValue(); + yield true; + } + case MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE -> { + this.key = this.cursor1.getKey(); + this.fromValue = this.cursor1.getValue(); + this.toValue = this.cursor2.getValue(); + yield true; + } + case TERMINATED_TERMINATED -> { + this.key = null; + this.fromValue = null; + this.toValue = null; + yield false; + } + }; } - protected void resolveHashClashWithSecondEntry() { - switch (this.hashClash) { - case STUCK_CURSOR_1 -> { - this.hashClash = HashClash.NONE; - this.cursorRelation = 0; - this.key = cursor1.key; - this.fromValue = cursor1.value; - this.toValue = defaultValue; - } - case STUCK_CURSOR_2 -> { - this.hashClash = HashClash.NONE; - this.cursorRelation = 0; - this.key = cursor2.key; - this.fromValue = defaultValue; - this.toValue = cursor2.value; - } - default -> throw new IllegalArgumentException("Inconsistent compare result for diffCursor"); - } + public boolean move() { + do { + this.state = moveOne(this.state); + } while (!isInStableState()); + return updateAndReturnWithResult(); } - /** - * Checks if two states has the same values, i.e., there is no difference. - * @return whether two states has the same values - */ - protected boolean sameValues() { - if(cursor1.isTerminated() || cursor2.isTerminated()) return false; - else return Objects.equals(this.fromValue, this.toValue); + private State moveOne(State currentState) { + return switch (currentState) { + case INIT, MOVING_MOVING_HASH2, MOVING_MOVING_SAME_KEY_SAME_VALUE, MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE -> { + boolean cursor1Moved = cursor1.move(); + boolean cursor2Moved = cursor2.move(); + yield recalculateStateAfterCursorMovement(cursor1Moved, cursor2Moved); + } + case MOVING_MOVING_SAME_NODE -> { + boolean cursor1Moved = cursor1.skipCurrentNode(); + boolean cursor2Moved = cursor2.skipCurrentNode(); + yield recalculateStateAfterCursorMovement(cursor1Moved, cursor2Moved); + } + case MOVING_MOVING_BEHIND1 -> { + boolean cursorMoved = cursor1.move(); + if (cursorMoved) { + yield recalculateStateBasedOnCursorRelation(); + } else { + yield State.TERMINATED_MOVING; + } + } + case MOVING_MOVING_BEHIND2 -> { + boolean cursorMoved = cursor2.move(); + if (cursorMoved) { + yield recalculateStateBasedOnCursorRelation(); + } else { + yield State.MOVING_TERMINATED; + } + } + case TERMINATED_MOVING -> { + boolean cursorMoved = cursor2.move(); + if (cursorMoved) { + yield State.TERMINATED_MOVING; + } else { + yield State.TERMINATED_TERMINATED; + } + } + case MOVING_TERMINATED -> { + boolean cursorMoved = cursor1.move(); + if (cursorMoved) { + yield State.MOVING_TERMINATED; + } else { + yield State.TERMINATED_TERMINATED; + } + } + case MOVING_MOVING_HASH1 -> State.MOVING_MOVING_HASH2; + case TERMINATED_TERMINATED -> throw new IllegalStateException("Trying to move while terminated!"); + }; } - protected boolean moveOne() { - if (isTerminated()) { - return false; - } - if (this.cursorRelation > 0 || cursor2.isTerminated()) { - return cursor1.move(); - } else if (this.cursorRelation < 0 || cursor1.isTerminated()) { - return cursor2.move(); + private State recalculateStateAfterCursorMovement(boolean cursor1Moved, boolean cursor2Moved) { + if (cursor1Moved && cursor2Moved) { + return recalculateStateBasedOnCursorRelation(); + } else if (cursor1Moved) { + return State.MOVING_TERMINATED; + } else if (cursor2Moved) { + return State.TERMINATED_MOVING; } else { - boolean moved1 = cursor1.move(); - boolean moved2 = cursor2.move(); - return moved1 && moved2; + return State.TERMINATED_TERMINATED; } } - private boolean skipNode() { - if (isTerminated()) { - throw new IllegalStateException("DiffCursor tries to skip when terminated!"); - } - boolean update1 = cursor1.skipCurrentNode(); - boolean update2 = cursor2.skipCurrentNode(); - updateState(); - return update1 && update2; - } + private State recalculateStateBasedOnCursorRelation() { + final int relation = InOrderMapCursor.comparePosition(cursor1, cursor2); - protected boolean moveToConsistentState() { - if (!isTerminated()) { - boolean changed; - boolean lastResult = true; - do { - changed = false; - if (MapCursor.sameSubNode(cursor1, cursor2)) { - lastResult = skipNode(); - changed = true; - } - if (sameValues()) { - lastResult = moveOne(); - changed = true; - } - updateState(); - } while (changed && !isTerminated()); - return lastResult; - } else { - return false; + if (relation > 0) { + return State.MOVING_MOVING_BEHIND1; + } else if (relation < 0) { + return State.MOVING_MOVING_BEHIND2; } - } - public boolean move() { - if (!isTerminated()) { - if (isInHashClash()) { - this.resolveHashClashWithSecondEntry(); - return true; + if (InOrderMapCursor.sameSubNode(cursor1, cursor2)) { + return State.MOVING_MOVING_SAME_NODE; + } else if (Objects.equals(cursor1.getKey(), cursor2.getKey())) { + if (Objects.equals(cursor1.getValue(), cursor2.getValue())) { + return State.MOVING_MOVING_SAME_KEY_SAME_VALUE; } else { - if (moveOne()) { - return moveToConsistentState(); - } else { - return false; - } + return State.MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE; } + } + + final int depth = InOrderMapCursor.compareDepth(cursor1, cursor2); + + if (depth > 0) { + return State.MOVING_MOVING_BEHIND1; + } else if (depth < 0) { + return State.MOVING_MOVING_BEHIND2; + } else { + return State.MOVING_MOVING_HASH1; + } - } else - return false; } } diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java index cdc66a10..81bf6188 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java @@ -39,12 +39,12 @@ public class MutableNode extends Node { int dataUsed = 0; int nodeUsed = 0; for (int i = 0; i < FACTOR; i++) { - int bitposition = 1 << i; - if ((node.dataMap & bitposition) != 0) { + int bitPosition = 1 << i; + if ((node.dataMap & bitPosition) != 0) { content[2 * i] = node.content[dataUsed * 2]; content[2 * i + 1] = node.content[dataUsed * 2 + 1]; dataUsed++; - } else if ((node.nodeMap & bitposition) != 0) { + } else if ((node.nodeMap & bitPosition) != 0) { content[2 * i + 1] = node.content[node.content.length - 1 - nodeUsed]; nodeUsed++; } @@ -80,7 +80,7 @@ public class MutableNode extends Node { int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); @SuppressWarnings("unchecked") K keyCandidate = (K) content[2 * selectedHashFragment]; if (keyCandidate != null) { - // If has key + // If it has key if (keyCandidate.equals(key)) { // The key is equals to an existing key -> update entry if (value == defaultValue) { @@ -101,24 +101,22 @@ public class MutableNode extends Node { return moveDownAndSplit(hashProvider, key, value, keyCandidate, hash, depth, selectedHashFragment); } } + } + // If it does not have key, check for value + @SuppressWarnings("unchecked") var nodeCandidate = (Node) content[2 * selectedHashFragment + 1]; + if (nodeCandidate != null) { + // If it has value, it is a sub-node -> update that + int newDepth = incrementDepth(depth); + var newNode = nodeCandidate.putValue(key, value, oldValueBox, hashProvider, defaultValue, newHash(hashProvider, key, hash, newDepth), newDepth); + return updateWithSubNode(selectedHashFragment, newNode, (value == null && defaultValue == null) || (value != null && value.equals(defaultValue))); } else { - // If it does not have key, check for value - @SuppressWarnings("unchecked") var nodeCandidate = (Node) content[2 * selectedHashFragment + 1]; - if (nodeCandidate != null) { - // If it has value, it is a subnode -> upate that - int newDepth = incrementDepth(depth); - var newNode = nodeCandidate.putValue(key, value, oldValueBox, hashProvider, defaultValue, newHash(hashProvider, key, hash, newDepth), newDepth); - return updateWithSubNode(selectedHashFragment, newNode, (value == null && defaultValue == null) || (value != null && value.equals(defaultValue))); + // If it does not have value, put it in the empty place + if (value == defaultValue) { + // don't need to add new key-value pair + oldValueBox.setOldValue(defaultValue); + return this; } else { - // If it does not have value, put it in the empty place - if (value == defaultValue) { - // dont need to add new key-value pair - oldValueBox.setOldValue(defaultValue); - return this; - } else { - return addEntry(key, value, oldValueBox, selectedHashFragment, defaultValue); - } - + return addEntry(key, value, oldValueBox, selectedHashFragment, defaultValue); } } } @@ -170,7 +168,7 @@ public class MutableNode extends Node { if (immutableNewNode != null) { int orphaned = immutableNewNode.isOrphaned(); if (orphaned >= 0) { - // orphan subnode data is replaced with data + // orphan sub-node data is replaced with data content[2 * selectedHashFragment] = immutableNewNode.content[orphaned * 2]; content[2 * selectedHashFragment + 1] = immutableNewNode.content[orphaned * 2 + 1]; invalidateHash(); @@ -227,11 +225,11 @@ public class MutableNode extends Node { // Pass everything as parameters for performance. @SuppressWarnings("squid:S107") - private MutableNode newNodeWithTwoEntries(ContinousHashProvider hashProvider, K key1, V value1, int oldHash1, K key2, V value2, int oldHash2, int newdepth) { - int newHash1 = newHash(hashProvider, key1, oldHash1, newdepth); - int newHash2 = newHash(hashProvider, key2, oldHash2, newdepth); - int newFragment1 = hashFragment(newHash1, shiftDepth(newdepth)); - int newFragment2 = hashFragment(newHash2, shiftDepth(newdepth)); + private MutableNode newNodeWithTwoEntries(ContinousHashProvider hashProvider, K key1, V value1, int oldHash1, K key2, V value2, int oldHash2, int newDepth) { + int newHash1 = newHash(hashProvider, key1, oldHash1, newDepth); + int newHash2 = newHash(hashProvider, key2, oldHash2, newDepth); + int newFragment1 = hashFragment(newHash1, shiftDepth(newDepth)); + int newFragment2 = hashFragment(newHash2, shiftDepth(newDepth)); MutableNode subNode = new MutableNode<>(); if (newFragment1 != newFragment2) { @@ -241,7 +239,7 @@ public class MutableNode extends Node { subNode.content[newFragment2 * 2] = key2; subNode.content[newFragment2 * 2 + 1] = value2; } else { - MutableNode subSubNode = newNodeWithTwoEntries(hashProvider, key1, value1, newHash1, key2, value2, newHash2, incrementDepth(newdepth)); + MutableNode subSubNode = newNodeWithTwoEntries(hashProvider, key1, value1, newHash1, key2, value2, newHash2, incrementDepth(newDepth)); subNode.content[newFragment1 * 2 + 1] = subSubNode; } subNode.invalidateHash(); @@ -305,13 +303,13 @@ public class MutableNode extends Node { cursor.dataIndex = MapCursor.INDEX_FINISH; } - // 2. look inside the subnodes + // 2. look inside the sub-nodes if(cursor.nodeIndexStack.peek()==null) { throw new IllegalStateException("Cursor moved to the next state when the state is empty."); } for (int index = cursor.nodeIndexStack.peek() + 1; index < FACTOR; index++) { if (this.content[index * 2] == null && this.content[index * 2 + 1] != null) { - // 2.1 found next subnode, move down to the subnode + // 2.1 found next sub-node, move down to the sub-node Node subnode = (Node) this.content[index * 2 + 1]; cursor.dataIndex = MapCursor.INDEX_START; @@ -323,7 +321,7 @@ public class MutableNode extends Node { return subnode.moveToNext(cursor); } } - // 3. no subnode found, move up + // 3. no sub-node found, move up cursor.nodeStack.pop(); cursor.nodeIndexStack.pop(); if (!cursor.nodeStack.isEmpty()) { @@ -336,6 +334,49 @@ public class MutableNode extends Node { } } + @Override + @SuppressWarnings("unchecked") + boolean moveToNextInorder(InOrderMapCursor cursor) { + if(cursor.nodeIndexStack.peek()==null || cursor.nodeStack.peek()==null) { + throw new IllegalStateException("Cursor moved to the next state when the state is empty."); + } + + int position = cursor.nodeIndexStack.peek(); + + for (int index = position + 1; index < FACTOR; index++) { + // data found + if (this.content[index * 2] != null) { + cursor.nodeIndexStack.pop(); + cursor.nodeIndexStack.push(index); + + cursor.key = (K) this.content[index * 2]; + cursor.value = (V) this.content[index * 2 + 1]; + return true; + } else if (this.content[index * 2 +1] != null) { + // sub-node found + Node subnode = (Node) this.content[index * 2 +1]; + cursor.nodeIndexStack.pop(); + cursor.nodeIndexStack.push(index); + cursor.nodeIndexStack.push(InOrderMapCursor.INDEX_START); + cursor.nodeStack.push(subnode); + + return subnode.moveToNextInorder(cursor); + } + } + + // nothing found + cursor.nodeStack.pop(); + cursor.nodeIndexStack.pop(); + if (!cursor.nodeStack.isEmpty()) { + Node supernode = cursor.nodeStack.peek(); + return supernode.moveToNextInorder(cursor); + } else { + cursor.key = null; + cursor.value = null; + return false; + } + } + @Override public void prettyPrint(StringBuilder builder, int depth, int code) { builder.append("\t".repeat(Math.max(0, depth))); @@ -361,7 +402,7 @@ public class MutableNode extends Node { } } builder.append(")"); - // print subnodes + // print sub-nodes for (int i = 0; i < FACTOR; i++) { if (content[2 * i] == null && content[2 * i + 1] != null) { @SuppressWarnings("unchecked") Node subNode = (Node) content[2 * i + 1]; @@ -397,7 +438,7 @@ public class MutableNode extends Node { } } } - // check subnodes + // check sub-nodes for (int i = 0; i < FACTOR; i++) { if (this.content[2 * i + 1] != null && this.content[2 * i] == null) { @SuppressWarnings("unchecked") var subNode = (Node) this.content[2 * i + 1]; @@ -421,13 +462,11 @@ public class MutableNode extends Node { @Override public int hashCode() { - if (this.cachedHashValid) { - return this.cachedHash; - } else { + if (!this.cachedHashValid) { this.cachedHash = Arrays.hashCode(content); this.cachedHashValid = true; - return this.cachedHash; } + return this.cachedHash; } @Override diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java index c49be280..3dd332da 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java @@ -108,10 +108,12 @@ public abstract class Node { * @return Whether there was a next value to move on. */ abstract boolean moveToNext(MapCursor cursor); + abstract boolean moveToNextInorder(InOrderMapCursor cursor); ///////// FOR printing public abstract void prettyPrint(StringBuilder builder, int depth, int code); + @Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java index fb359431..2ceca463 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java @@ -75,7 +75,9 @@ public class VersionedMapImpl implements VersionedMap { Iterator keyIterator = keys.iterator(); Iterator valueIterator = values.iterator(); while (keyIterator.hasNext()) { - this.put(keyIterator.next(), valueIterator.next()); + var key = keyIterator.next(); + var value = valueIterator.next(); + this.put(key,value); } } else { while (cursor.move()) { @@ -109,12 +111,10 @@ public class VersionedMapImpl implements VersionedMap { @Override public DiffCursor getDiffCursor(long toVersion) { - - Cursor fromCursor = this.getAll(); - VersionedMap toMap = this.store.createMap(toVersion); - Cursor toCursor = toMap.getAll(); - return new MapDiffCursor<>(this.hashProvider, this.defaultValue, fromCursor, toCursor); - + InOrderMapCursor fromCursor = new InOrderMapCursor<>(this); + VersionedMapImpl toMap = (VersionedMapImpl) this.store.createMap(toVersion); + InOrderMapCursor toCursor = new InOrderMapCursor<>(toMap); + return new MapDiffCursor<>(this.defaultValue, fromCursor, toCursor); } @@ -152,7 +152,11 @@ public class VersionedMapImpl implements VersionedMap { @Override public int contentHashCode(ContentHashCode mode) { // Calculating the root hashCode is always fast, because {@link Node} caches its hashCode. - return Objects.hashCode(root); + if(root == null) { + return 0; + } else { + return root.hashCode(); + } } @Override diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java index 6d82f5d7..c850d334 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java @@ -4,11 +4,9 @@ import tools.refinery.store.map.Cursor; import tools.refinery.store.map.DiffCursor; import tools.refinery.store.map.VersionedMap; import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.internal.MapDiffCursor; import tools.refinery.store.model.Interpretation; import tools.refinery.store.model.InterpretationListener; import tools.refinery.store.model.Model; -import tools.refinery.store.model.TupleHashProvider; import tools.refinery.store.representation.AnySymbol; import tools.refinery.store.representation.Symbol; import tools.refinery.store.tuple.Tuple; @@ -19,16 +17,13 @@ import java.util.List; public class VersionedInterpretation implements Interpretation { private final ModelImpl model; private final Symbol symbol; - private final VersionedMapStore store; private final VersionedMap map; private final List> listeners = new ArrayList<>(); private final List> restoreListeners = new ArrayList<>(); - private VersionedInterpretation(ModelImpl model, Symbol symbol, VersionedMapStore store, - VersionedMap map) { + private VersionedInterpretation(ModelImpl model, Symbol symbol, VersionedMap map) { this.model = model; this.symbol = symbol; - this.store = store; this.map = map; } @@ -111,9 +106,7 @@ public class VersionedInterpretation implements Interpretation { @Override public DiffCursor getDiffCursor(long to) { - var fromCursor = getAll(); - var toCursor = store.createMap(to).getAll(); - return new MapDiffCursor<>(TupleHashProvider.INSTANCE, symbol.defaultValue(), fromCursor, toCursor); + return map.getDiffCursor(to); } public long commit() { @@ -148,7 +141,7 @@ public class VersionedInterpretation implements Interpretation { @SuppressWarnings("unchecked") var typedSymbol = (Symbol) symbol; var map = store.createMap(); - return new VersionedInterpretation<>(model, typedSymbol, store, map); + return new VersionedInterpretation<>(model, typedSymbol, map); } static VersionedInterpretation of(ModelImpl model, AnySymbol symbol, VersionedMapStore store, @@ -156,6 +149,6 @@ public class VersionedInterpretation implements Interpretation { @SuppressWarnings("unchecked") var typedSymbol = (Symbol) symbol; var map = store.createMap(state); - return new VersionedInterpretation<>(model, typedSymbol, store, map); + return new VersionedInterpretation<>(model, typedSymbol, map); } } diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java new file mode 100644 index 00000000..05cf5a74 --- /dev/null +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java @@ -0,0 +1,49 @@ +package tools.refinery.store.map.tests; + +import org.junit.jupiter.api.Test; +import tools.refinery.store.map.VersionedMapStoreBuilder; +import tools.refinery.store.map.internal.InOrderMapCursor; +import tools.refinery.store.map.internal.VersionedMapImpl; +import tools.refinery.store.map.tests.utils.MapTestEnvironment; + +import static org.junit.jupiter.api.Assertions.*; + +class InOrderCursorTest { + @Test + void testCursor() { + var store = VersionedMapStoreBuilder.builder() + .setStrategy(VersionedMapStoreBuilder.StoreStrategy.STATE) + .setStateBasedImmutableWhenCommitting(true) + .setHashProvider(MapTestEnvironment.prepareHashProvider(false)) + .setStateBasedNodeSharingStrategy(VersionedMapStoreBuilder.StateStorageStrategy.SHARED_NODE_CACHE) + .setDefaultValue("x") + .buildOne(); + + VersionedMapImpl map = (VersionedMapImpl) store.createMap(); + checkMove(map,0); + + map.put(1,"A"); + map.commit(); + checkMove(map,1); + + + map.put(2,"B"); + map.commit(); + checkMove(map,2); + + map.put(3,"C"); + map.commit(); + checkMove(map,3); + + } + + private void checkMove(VersionedMapImpl map, int num) { + InOrderMapCursor cursor = new InOrderMapCursor<>(map); + for(int i=0; i builder) { + private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, + int commitFrequency, boolean commitBeforeDiffCursor, + VersionedMapStoreBuilder builder) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); VersionedMapStore store = builder.setDefaultValue(values[0]).buildOne(); - iterativeRandomPutsAndCommitsThenDiffCursor(scenario, store, steps, maxKey, values, seed, commitFrequency); + iterativeRandomPutsAndCommitsThenDiffCursor(scenario, store, steps, maxKey, values, seed, commitFrequency, + commitBeforeDiffCursor); } private void iterativeRandomPutsAndCommitsThenDiffCursor(String scenario, VersionedMapStore store, - int steps, int maxKey, String[] values, int seed, int commitFrequency) { - // 1. build a map with versions - Random r = new Random(seed); - VersionedMap versioned = store.createMap(); + int steps, int maxKey, String[] values, int seed, + int commitFrequency, boolean commitBeforeDiffCursor) { + int largestCommit = -1; - for (int i = 0; i < steps; i++) { - int index = i + 1; - int nextKey = r.nextInt(maxKey); - String nextValue = values[r.nextInt(values.length)]; - try { - versioned.put(nextKey, nextValue); - } catch (Exception exception) { - exception.printStackTrace(); - fail(scenario + ":" + index + ": exception happened: " + exception); - } - if (index % commitFrequency == 0) { - long version = versioned.commit(); - largestCommit = (int) version; - } - if (index % 10000 == 0) - System.out.println(scenario + ":" + index + "/" + steps + " building finished"); - } - // 2. create a non-versioned map, - VersionedMap moving = store.createMap(); - Random r2 = new Random(seed + 1); - - final int diffTravelFrequency = commitFrequency * 2; - for (int i = 0; i < steps; i++) { - int index = i + 1; - if (index % diffTravelFrequency == 0) { - // diff-travel - long travelToVersion = r2.nextInt(largestCommit + 1); - DiffCursor diffCursor = moving.getDiffCursor(travelToVersion); - moving.putAll(diffCursor); - - } else { - // random puts - int nextKey = r2.nextInt(maxKey); - String nextValue = values[r2.nextInt(values.length)]; + { + // 1. build a map with versions + Random r = new Random(seed); + VersionedMap versioned = store.createMap(); + for (int i = 0; i < steps; i++) { + int index = i + 1; + int nextKey = r.nextInt(maxKey); + String nextValue = values[r.nextInt(values.length)]; try { - moving.put(nextKey, nextValue); + versioned.put(nextKey, nextValue); } catch (Exception exception) { exception.printStackTrace(); fail(scenario + ":" + index + ": exception happened: " + exception); } if (index % commitFrequency == 0) { - versioned.commit(); + long version = versioned.commit(); + largestCommit = (int) version; } if (index % 10000 == 0) System.out.println(scenario + ":" + index + "/" + steps + " building finished"); } } + { + // 2. create a non-versioned map, + VersionedMap moving = store.createMap(); + Random r2 = new Random(seed + 1); + + final int diffTravelFrequency = commitFrequency * 2; + for (int i = 0; i < steps; i++) { + int index = i + 1; + if (index % diffTravelFrequency == 0) { + // diff-travel + long travelToVersion = r2.nextInt(largestCommit + 1); + + VersionedMap oracle = store.createMap(travelToVersion); + + if(commitBeforeDiffCursor) { + moving.commit(); + } + DiffCursor diffCursor = moving.getDiffCursor(travelToVersion); + moving.putAll(diffCursor); + moving.commit(); + + MapTestEnvironment.compareTwoMaps(scenario + ":c" + index, oracle, moving); + + moving.restore(travelToVersion); + + } else { + // random puts + int nextKey = r2.nextInt(maxKey); + String nextValue = values[r2.nextInt(values.length)]; + try { + moving.put(nextKey, nextValue); + } catch (Exception exception) { + exception.printStackTrace(); + fail(scenario + ":" + index + ": exception happened: " + exception); + } + if (index % 10000 == 0) + System.out.println(scenario + ":" + index + "/" + steps + " building finished"); + } + } + } } public static final String title = "DiffCursor {index}/{0} Steps={1} Keys={2} Values={3} nullDefault={4} " + - "commit frequency={5} seed={6} config={7}"; + "commit frequency={5} seed={6} commit before diff={7} config={8}"; @ParameterizedTest(name = title) @MethodSource @Timeout(value = 10) @Tag("fuzz") - void parametrizedFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, VersionedMapStoreBuilder builder) { - runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, - noKeys, noValues, nullDefault, commitFrequency, builder); + void parametrizedFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, + int commitFrequency, int seed, boolean commitBeforeDiffCursor, + VersionedMapStoreBuilder builder) { + runFuzzTest("DiffCursorS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, + noKeys, noValues, nullDefault, commitFrequency, commitBeforeDiffCursor, builder); } static Stream parametrizedFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[]{100}, keyCounts, valueCounts, nullDefaultOptions, - commitFrequencyOptions, randomSeedOptions, storeConfigs); + return FuzzTestUtils.permutationWithSize(new Object[]{500}, keyCounts, valueCounts, nullDefaultOptions, + commitFrequencyOptions, randomSeedOptions, new Object[]{false,true}, storeConfigs); } @ParameterizedTest(name = title) @@ -104,9 +124,9 @@ class DiffCursorFuzzTest { @Tag("fuzz") @Tag("slow") void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, VersionedMapStoreBuilder builder) { - runFuzzTest("MutableImmutableCompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, - nullDefault, commitFrequency, builder); + int seed, boolean commitBeforeDiffCursor, VersionedMapStoreBuilder builder) { + runFuzzTest("DiffCursorS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, + nullDefault, commitFrequency, commitBeforeDiffCursor, builder); } static Stream parametrizedSlowFuzz() { diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java index fb6b28d8..b344d9b9 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java @@ -5,23 +5,25 @@ import tools.refinery.store.map.tests.utils.MapTestEnvironment; public final class FuzzTestCollections { public static final Object[] stepCounts = {FuzzTestUtils.FAST_STEP_COUNT}; - public static final Object[] keyCounts = {1, 32, 32 * 32}; + public static final Object[] keyCounts = {1 , 32, 32 * 32}; public static final Object[] valueCounts = {2, 3}; public static final Object[] nullDefaultOptions = {false, true}; - public static final Object[] commitFrequencyOptions = {10, 10, 100}; - public static final Object[] randomSeedOptions = {1/*, 2, 3*/}; + public static final Object[] commitFrequencyOptions = {1, 10, 100}; + public static final Object[] randomSeedOptions = {1}; public static final Object[] storeConfigs = { // State based VersionedMapStoreBuilder.builder() .setStrategy(VersionedMapStoreBuilder.StoreStrategy.STATE) .setStateBasedImmutableWhenCommitting(true) .setHashProvider(MapTestEnvironment.prepareHashProvider(false)) - .setStateBasedNodeSharingStrategy(VersionedMapStoreBuilder.StateStorageStrategy.SHARED_NODE_CACHE), + .setStateBasedNodeSharingStrategy(VersionedMapStoreBuilder.StateStorageStrategy + .SHARED_NODE_CACHE), VersionedMapStoreBuilder.builder() .setStrategy(VersionedMapStoreBuilder.StoreStrategy.STATE) .setStateBasedImmutableWhenCommitting(true) .setHashProvider(MapTestEnvironment.prepareHashProvider(true)) - .setStateBasedNodeSharingStrategy(VersionedMapStoreBuilder.StateStorageStrategy.SHARED_NODE_CACHE), + .setStateBasedNodeSharingStrategy(VersionedMapStoreBuilder.StateStorageStrategy + .SHARED_NODE_CACHE), VersionedMapStoreBuilder.builder() .setStrategy(VersionedMapStoreBuilder.StoreStrategy.STATE) .setStateBasedImmutableWhenCommitting(false) diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java index 69ae811e..0e695aaa 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java @@ -57,13 +57,15 @@ public class MapTestEnvironment { VersionedMap map2, List errors) { map1.checkIntegrity(); map2.checkIntegrity(); + + assertContentEqualsList(map1, map2, title + ": map1.contentEquals(map2)", errors); + assertContentEqualsList(map2, map1, title + ": map2.contentEquals(map1)", errors); assertEqualsList(map1.getSize(), map2.getSize(), title + ": Sizes not equal", errors); + for (var mode : ContentHashCode.values()) { assertEqualsList(map1.contentHashCode(mode), map2.contentHashCode(mode), title + ": " + mode + " hashCode check", errors); } - assertContentEqualsList(map1, map2, title + ": map1.contentEquals(map2)", errors); - assertContentEqualsList(map2, map1, title + ": map2.contentEquals(map1)", errors); } private static void assertEqualsList(Object o1, Object o2, String message, List errors) { @@ -177,7 +179,8 @@ public class MapTestEnvironment { K previous = null; Cursor cursor = versionedMap.getAll(); while (cursor.move()) { - System.out.println(cursor.getKey() + " " + ((VersionedMapImpl) versionedMap).getHashProvider().getHash(cursor.getKey(), 0)); + //System.out.println(cursor.getKey() + " " + ((VersionedMapImpl) versionedMap).getHashProvider() + // .getHash(cursor.getKey(), 0)); if (previous != null) { int comparisonResult = ((VersionedMapImpl) versionedMap).getHashProvider().compare(previous, cursor.getKey()); @@ -185,7 +188,6 @@ public class MapTestEnvironment { } previous = cursor.getKey(); } - System.out.println(); } public void printComparison() { -- cgit v1.2.3-70-g09d2 From a6dcff6293e960b420e26c57374a281467821556 Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Fri, 21 Jul 2023 20:27:30 +0200 Subject: VersionedMapStoreFactoryBuilder.java is introduced, all tests are updated. VersionedMapStoreBuilder.java is removed. --- .../refinery/store/map/VersionedMapStore.java | 20 ++-- .../store/map/VersionedMapStoreBuilder.java | 130 -------------------- .../store/map/VersionedMapStoreFactory.java | 19 +++ .../store/map/VersionedMapStoreFactoryBuilder.java | 24 ++++ .../DeltaBasedVersionedMapStoreFactory.java | 34 ++++++ .../StateBasedVersionedMapStoreFactory.java | 33 ++++++ .../VersionedMapStoreFactoryBuilderImpl.java | 132 +++++++++++++++++++++ .../store/map/tests/InOrderCursorTest.java | 18 +-- .../refinery/store/map/tests/MapUnitTests.java | 9 +- .../store/map/tests/fuzz/CommitFuzzTest.java | 21 ++-- .../map/tests/fuzz/ContentEqualsFuzzTest.java | 20 ++-- .../store/map/tests/fuzz/DiffCursorFuzzTest.java | 10 +- .../store/map/tests/fuzz/MultiThreadFuzzTest.java | 29 +++-- .../store/map/tests/fuzz/MutableFuzzTest.java | 8 +- .../store/map/tests/fuzz/RestoreFuzzTest.java | 30 ++--- .../store/map/tests/fuzz/SingleThreadFuzzTest.java | 10 +- .../map/tests/fuzz/utils/FuzzTestCollections.java | 52 ++++---- 17 files changed, 357 insertions(+), 242 deletions(-) delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreBuilder.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactory.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaBasedVersionedMapStoreFactory.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/StateBasedVersionedMapStoreFactory.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java index a8d7fb1a..7768287a 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java @@ -1,14 +1,20 @@ package tools.refinery.store.map; +import tools.refinery.store.map.internal.VersionedMapStoreFactoryBuilderImpl; + import java.util.Set; public interface VersionedMapStore { - - public VersionedMap createMap(); - public VersionedMap createMap(long state); - - public Set getStates(); + VersionedMap createMap(); + + VersionedMap createMap(long state); + + Set getStates(); + + DiffCursor getDiffCursor(long fromState, long toState); - public DiffCursor getDiffCursor(long fromState, long toState); -} \ No newline at end of file + static VersionedMapStoreFactoryBuilder builder() { + return new VersionedMapStoreFactoryBuilderImpl<>(); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreBuilder.java deleted file mode 100644 index 1a9aa0b3..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreBuilder.java +++ /dev/null @@ -1,130 +0,0 @@ -package tools.refinery.store.map; - -import java.util.ArrayList; -import java.util.List; - -public class VersionedMapStoreBuilder { - public enum StoreStrategy { - STATE, DELTA - } - - public enum DeltaStorageStrategy { - LIST, SET - } - - public enum StateStorageStrategy { - NO_NODE_CACHE, SHARED_NODE_CACHE, SHARED_NODE_CACHE_IN_GROUP - } - - public static VersionedMapStoreBuilder builder() { - return new VersionedMapStoreBuilder<>(); - } - protected VersionedMapStoreBuilder() { - } - protected VersionedMapStoreBuilder(VersionedMapStoreBuilder other) { - this.defaultValue = other.defaultValue; - this.defaultSet = other.defaultSet; - this.strategy = other.strategy; - this.stateBasedImmutableWhenCommitting = other.stateBasedImmutableWhenCommitting; - this.stateBasedNodeSharingStrategy = other.stateBasedNodeSharingStrategy; - this.hashProvider = other.hashProvider; - this.deltaStorageStrategy = other.deltaStorageStrategy; - } - protected boolean defaultSet = false; - protected V defaultValue = null; - protected StoreStrategy strategy = StoreStrategy.DELTA; - protected Boolean stateBasedImmutableWhenCommitting = false; - protected StateStorageStrategy stateBasedNodeSharingStrategy = StateStorageStrategy.SHARED_NODE_CACHE_IN_GROUP; - protected ContinousHashProvider hashProvider = null; - protected DeltaStorageStrategy deltaStorageStrategy = DeltaStorageStrategy.LIST; - - public VersionedMapStoreBuilder setDefaultValue(V defaultValue) { - var result = new VersionedMapStoreBuilder<>(this); - result.defaultValue = defaultValue; - result.defaultSet = true; - return result; - } - - public VersionedMapStoreBuilder setStrategy(StoreStrategy strategy) { - var result = new VersionedMapStoreBuilder<>(this); - result.strategy = strategy; - return result; - } - - public VersionedMapStoreBuilder setHashProvider(ContinousHashProvider hashProvider) { - var result = new VersionedMapStoreBuilder<>(this); - result.hashProvider = hashProvider; - return result; - } - - public VersionedMapStoreBuilder setStateBasedImmutableWhenCommitting(boolean toImmutableWhenCommitting) { - var result = new VersionedMapStoreBuilder<>(this); - result.stateBasedImmutableWhenCommitting = toImmutableWhenCommitting; - return result; - } - - public VersionedMapStoreBuilder setStateBasedNodeSharingStrategy(StateStorageStrategy strategy) { - var result = new VersionedMapStoreBuilder<>(this); - result.stateBasedNodeSharingStrategy = strategy; - return result; - } - - public VersionedMapStoreBuilder setDeltaStorageStrategy(DeltaStorageStrategy deltaStorageStrategy) { - var result = new VersionedMapStoreBuilder<>(this); - result.deltaStorageStrategy = deltaStorageStrategy; - return result; - } - - public VersionedMapStore buildOne() { - if(!defaultSet) { - throw new IllegalStateException("Default value is missing!"); - } - return switch (strategy) { - case DELTA -> new VersionedMapStoreDeltaImpl<>( - this.deltaStorageStrategy == DeltaStorageStrategy.SET, - this.defaultValue); - case STATE -> new VersionedMapStoreImpl<>( - this.hashProvider, - this.defaultValue, - new VersionedMapStoreConfiguration( - this.stateBasedImmutableWhenCommitting, - this.stateBasedNodeSharingStrategy != StateStorageStrategy.NO_NODE_CACHE, - this.stateBasedNodeSharingStrategy == StateStorageStrategy.SHARED_NODE_CACHE_IN_GROUP)); - }; - } - - public List> buildGroup(int amount) { - if(!defaultSet) { - throw new IllegalStateException("Default value is missing!"); - } - if (this.strategy == StoreStrategy.STATE && - this.stateBasedNodeSharingStrategy == StateStorageStrategy.SHARED_NODE_CACHE_IN_GROUP) { - return VersionedMapStoreImpl.createSharedVersionedMapStores( - amount, - this.hashProvider, - this.defaultValue, - new VersionedMapStoreConfiguration( - this.stateBasedImmutableWhenCommitting, - true, - true)); - } else { - List> result = new ArrayList<>(amount); - for (int i = 0; i < amount; i++) { - result.add(buildOne()); - } - return result; - } - } - - @Override - public String toString() { - return "VersionedMapStoreBuilder{" + - "defaultValue=" + defaultValue + - ", strategy=" + strategy + - ", stateBasedImmutableWhenCommitting=" + stateBasedImmutableWhenCommitting + - ", stateBasedNodeSharingStrategy=" + stateBasedNodeSharingStrategy + - ", hashProvider=" + hashProvider + - ", deltaStorageStrategy=" + deltaStorageStrategy + - '}'; - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactory.java new file mode 100644 index 00000000..5f882a3a --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactory.java @@ -0,0 +1,19 @@ +package tools.refinery.store.map; + +import java.util.List; + +public interface VersionedMapStoreFactory { + /** + * Constructs a new instance of {@link VersionedMap}. + * @return The new instance. + */ + VersionedMapStore createOne(); + + /** + * Constructs a group of {@link VersionedMap}s with the same configuration. If possible, the stores share + * resources with each other. + * @param amount The amount of new instances to be created. + * @return A list of new stores with the given number of elements. + */ + List> createGroup(int amount); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java new file mode 100644 index 00000000..9cf17b49 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java @@ -0,0 +1,24 @@ +package tools.refinery.store.map; + +public interface VersionedMapStoreFactoryBuilder { + enum StoreStrategy { + STATE, DELTA + } + + enum DeltaTransactionStrategy { + LIST, SET + } + + enum SharingStrategy { + NO_NODE_CACHE, SHARED_NODE_CACHE, SHARED_NODE_CACHE_IN_GROUP + } + + VersionedMapStoreFactoryBuilder defaultValue(V defaultValue); + VersionedMapStoreFactoryBuilder strategy(StoreStrategy strategy); + VersionedMapStoreFactoryBuilder stateBasedImmutableWhenCommitting(boolean transformToImmutable); + VersionedMapStoreFactoryBuilder stateBasedSharingStrategy(SharingStrategy sharingStrategy); + VersionedMapStoreFactoryBuilder stateBasedHashProvider(ContinousHashProvider hashProvider); + VersionedMapStoreFactoryBuilder deltaTransactionStrategy(DeltaTransactionStrategy deltaStrategy); + + VersionedMapStoreFactory build(); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaBasedVersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaBasedVersionedMapStoreFactory.java new file mode 100644 index 00000000..29ec0da1 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaBasedVersionedMapStoreFactory.java @@ -0,0 +1,34 @@ +package tools.refinery.store.map.internal; + +import tools.refinery.store.map.VersionedMapStore; +import tools.refinery.store.map.VersionedMapStoreDeltaImpl; +import tools.refinery.store.map.VersionedMapStoreFactory; +import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; + +import java.util.ArrayList; +import java.util.List; + +public class DeltaBasedVersionedMapStoreFactory implements VersionedMapStoreFactory { + private final V defaultValue; + private final boolean summarizeChanges; + + public DeltaBasedVersionedMapStoreFactory(V defaultValue, + VersionedMapStoreFactoryBuilder.DeltaTransactionStrategy deltaTransactionStrategy) { + this.defaultValue = defaultValue; + this.summarizeChanges = deltaTransactionStrategy == VersionedMapStoreFactoryBuilder.DeltaTransactionStrategy.SET; + } + + @Override + public VersionedMapStore createOne() { + return new VersionedMapStoreDeltaImpl<>(summarizeChanges, defaultValue); + } + + @Override + public List> createGroup(int amount) { + List> result = new ArrayList<>(amount); + for(int i=0; i(summarizeChanges,defaultValue)); + } + return result; + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/StateBasedVersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/StateBasedVersionedMapStoreFactory.java new file mode 100644 index 00000000..80dc347f --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/StateBasedVersionedMapStoreFactory.java @@ -0,0 +1,33 @@ +package tools.refinery.store.map.internal; + +import tools.refinery.store.map.*; + +import java.util.List; + +public class StateBasedVersionedMapStoreFactory implements VersionedMapStoreFactory { + private final V defaultValue; + private final ContinousHashProvider continousHashProvider; + private final VersionedMapStoreConfiguration config; + + public StateBasedVersionedMapStoreFactory(V defaultValue, Boolean transformToImmutable, VersionedMapStoreFactoryBuilder.SharingStrategy sharingStrategy, ContinousHashProvider continousHashProvider) { + this.defaultValue = defaultValue; + this.continousHashProvider = continousHashProvider; + + this.config = new VersionedMapStoreConfiguration( + transformToImmutable, + sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE || sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE_IN_GROUP, + sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE_IN_GROUP); + } + + @Override + public VersionedMapStore createOne() { + return new VersionedMapStoreImpl<>(continousHashProvider, defaultValue, config); + + } + + @Override + public List> createGroup(int amount) { + return VersionedMapStoreImpl.createSharedVersionedMapStores(amount, continousHashProvider, defaultValue, + config); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java new file mode 100644 index 00000000..3719eef5 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java @@ -0,0 +1,132 @@ +package tools.refinery.store.map.internal; + +import tools.refinery.store.map.ContinousHashProvider; +import tools.refinery.store.map.VersionedMapStoreFactory; +import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; + +public class VersionedMapStoreFactoryBuilderImpl implements VersionedMapStoreFactoryBuilder { + + private boolean defaultSet = false; + private V defaultValue; + private StoreStrategy strategy = null; + private Boolean transformToImmutable = null; + private SharingStrategy sharingStrategy = null; + private ContinousHashProvider continousHashProvider = null; + private DeltaTransactionStrategy deltaTransactionStrategy = null; + + private StoreStrategy checkStrategy() { + StoreStrategy currentStrategy = strategy; + currentStrategy = mergeStrategies(currentStrategy, transformToImmutable, StoreStrategy.STATE); + currentStrategy = mergeStrategies(currentStrategy, sharingStrategy, StoreStrategy.STATE); + currentStrategy = mergeStrategies(currentStrategy, continousHashProvider, StoreStrategy.STATE); + currentStrategy = mergeStrategies(currentStrategy, deltaTransactionStrategy, StoreStrategy.DELTA); + return currentStrategy; + } + + private StoreStrategy mergeStrategies(StoreStrategy old, StoreStrategy newStrategy) { + if (old != null && newStrategy != null && old != newStrategy) { + throw new IllegalArgumentException("Mixed strategy parametrization in VersionedMap builder!"); + } + + if (old != null) { + return old; + } else { + return newStrategy; + } + } + + private StoreStrategy mergeStrategies(StoreStrategy old, Object parameter, StoreStrategy newStrategy) { + if (parameter != null) { + return mergeStrategies(old, newStrategy); + } else { + return old; + } + } + + @Override + public VersionedMapStoreFactoryBuilder defaultValue(V defaultValue) { + this.defaultSet = true; + this.defaultValue = defaultValue; + return this; + } + + @Override + public VersionedMapStoreFactoryBuilder strategy(StoreStrategy strategy) { + this.strategy = strategy; + checkStrategy(); + return this; + } + + @Override + public VersionedMapStoreFactoryBuilder stateBasedImmutableWhenCommitting(boolean transformToImmutable) { + this.transformToImmutable = transformToImmutable; + checkStrategy(); + return this; + } + + @Override + public VersionedMapStoreFactoryBuilder stateBasedSharingStrategy(SharingStrategy sharingStrategy) { + this.sharingStrategy = sharingStrategy; + checkStrategy(); + return this; + } + + @Override + public VersionedMapStoreFactoryBuilder stateBasedHashProvider(ContinousHashProvider hashProvider) { + this.continousHashProvider = hashProvider; + checkStrategy(); + return this; + } + + @Override + public VersionedMapStoreFactoryBuilder deltaTransactionStrategy(DeltaTransactionStrategy deltaTransactionStrategy) { + this.deltaTransactionStrategy = deltaTransactionStrategy; + checkStrategy(); + return this; + } + + private T getOrDefault(T value, T defaultValue) { + if(value != null) { + return value; + } else { + return defaultValue; + } + } + + @Override + public VersionedMapStoreFactory build() { + if (!defaultSet) { + throw new IllegalArgumentException("Default value is missing!"); + } + var strategyToUse = checkStrategy(); + if (strategyToUse == null) { + return new DeltaBasedVersionedMapStoreFactory<>(defaultValue, + getOrDefault(deltaTransactionStrategy, DeltaTransactionStrategy.LIST)); + } + return switch (strategyToUse) { + case STATE -> { + if(continousHashProvider == null) { + throw new IllegalArgumentException("Continuous hash provider is missing!"); + } + yield new StateBasedVersionedMapStoreFactory<>(defaultValue, + getOrDefault(transformToImmutable,true), + getOrDefault(sharingStrategy, SharingStrategy.SHARED_NODE_CACHE_IN_GROUP), + continousHashProvider); + } + case DELTA -> new DeltaBasedVersionedMapStoreFactory<>(defaultValue, + getOrDefault(deltaTransactionStrategy, DeltaTransactionStrategy.LIST)); + }; + } + + @Override + public String toString() { + return "VersionedMapStoreBuilder{" + + "defaultValue=" + defaultValue + + ", strategy=" + strategy + + ", stateBasedImmutableWhenCommitting=" + transformToImmutable + + ", stateBasedNodeSharingStrategy=" + sharingStrategy + + ", hashProvider=" + continousHashProvider + + ", deltaStorageStrategy=" + deltaTransactionStrategy + + '}'; + } +} diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java index 05cf5a74..993e5531 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java @@ -1,7 +1,8 @@ package tools.refinery.store.map.tests; import org.junit.jupiter.api.Test; -import tools.refinery.store.map.VersionedMapStoreBuilder; +import tools.refinery.store.map.VersionedMapStore; +import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; import tools.refinery.store.map.internal.InOrderMapCursor; import tools.refinery.store.map.internal.VersionedMapImpl; import tools.refinery.store.map.tests.utils.MapTestEnvironment; @@ -11,13 +12,14 @@ import static org.junit.jupiter.api.Assertions.*; class InOrderCursorTest { @Test void testCursor() { - var store = VersionedMapStoreBuilder.builder() - .setStrategy(VersionedMapStoreBuilder.StoreStrategy.STATE) - .setStateBasedImmutableWhenCommitting(true) - .setHashProvider(MapTestEnvironment.prepareHashProvider(false)) - .setStateBasedNodeSharingStrategy(VersionedMapStoreBuilder.StateStorageStrategy.SHARED_NODE_CACHE) - .setDefaultValue("x") - .buildOne(); + var store = VersionedMapStore.builder() + .strategy(VersionedMapStoreFactoryBuilder.StoreStrategy.STATE) + .stateBasedImmutableWhenCommitting(true) + .stateBasedHashProvider(MapTestEnvironment.prepareHashProvider(false)) + .stateBasedSharingStrategy(VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE) + .defaultValue("x") + .build() + .createOne(); VersionedMapImpl map = (VersionedMapImpl) store.createMap(); checkMove(map,0); diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java index 2216db76..6889fd07 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java @@ -2,7 +2,6 @@ package tools.refinery.store.map.tests; import org.junit.jupiter.api.Test; import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.VersionedMapStoreBuilder; import tools.refinery.store.map.VersionedMapStoreImpl; import tools.refinery.store.model.TupleHashProvider; import tools.refinery.store.tuple.Tuple; @@ -23,7 +22,7 @@ class MapUnitTests { @Test void deltaRestoreTest() { VersionedMapStore store = - VersionedMapStoreBuilder.builder().setDefaultValue("x").buildOne(); + VersionedMapStore.builder().defaultValue("x").build().createOne(); var map = store.createMap(); map.put(1,"val"); var version1 = map.commit(); @@ -36,7 +35,7 @@ class MapUnitTests { @Test void deltaRestoreTest2() { VersionedMapStore store = - VersionedMapStoreBuilder.builder().setDefaultValue("x").buildOne(); + VersionedMapStore.builder().defaultValue("x").build().createOne(); var map = store.createMap(); map.put(1,"x"); var version1 = map.commit(); @@ -48,7 +47,7 @@ class MapUnitTests { @Test void deltaRestoreTest3() { VersionedMapStore store = - VersionedMapStoreBuilder.builder().setDefaultValue("x").buildOne(); + VersionedMapStore.builder().defaultValue("x").build().createOne(); var map = store.createMap(); map.commit(); map.put(1,"1"); @@ -71,7 +70,7 @@ class MapUnitTests { @Test void deltaRestoreTest4() { VersionedMapStore store = - VersionedMapStoreBuilder.builder().setDefaultValue("x").buildOne(); + VersionedMapStore.builder().defaultValue("x").build().createOne(); var map = store.createMap(); map.commit(); map.put(1,"1"); diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java index 14a9e2e0..7977f772 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java @@ -1,30 +1,29 @@ package tools.refinery.store.map.tests.fuzz; -import static org.junit.jupiter.api.Assertions.fail; - -import java.util.Random; -import java.util.stream.Stream; - import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; - import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.VersionedMapStoreBuilder; +import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; +import java.util.Random; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.fail; import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; class CommitFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, - boolean nullDefault, int commitFrequency, VersionedMapStoreBuilder builder) { + boolean nullDefault, int commitFrequency, + VersionedMapStoreFactoryBuilder builder) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); - VersionedMapStore store = builder.setDefaultValue(values[0]).buildOne(); + VersionedMapStore store = builder.defaultValue(values[0]).build().createOne(); var sut = store.createMap(); MapTestEnvironment e = new MapTestEnvironment<>(sut); @@ -61,7 +60,7 @@ class CommitFuzzTest { @Timeout(value = 10) @Tag("fuzz") void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, VersionedMapStoreBuilder builder) { + int seed, VersionedMapStoreFactoryBuilder builder) { runFuzzTest("CommitS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, builder); } @@ -76,7 +75,7 @@ class CommitFuzzTest { @Tag("fuzz") @Tag("slow") void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, VersionedMapStoreBuilder builder) { + int seed, VersionedMapStoreFactoryBuilder builder) { runFuzzTest("CommitS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, builder); } diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java index b462ed40..99e76649 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java @@ -5,7 +5,10 @@ import org.junit.jupiter.api.Timeout; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import tools.refinery.store.map.*; +import tools.refinery.store.map.Cursor; +import tools.refinery.store.map.VersionedMap; +import tools.refinery.store.map.VersionedMapStore; +import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; @@ -16,12 +19,13 @@ import java.util.List; import java.util.Random; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.fail; import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; class ContentEqualsFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, - boolean nullDefault, int commitFrequency, VersionedMapStoreBuilder builder) { + boolean nullDefault, int commitFrequency, + VersionedMapStoreFactoryBuilder builder) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); @@ -30,10 +34,10 @@ class ContentEqualsFuzzTest { iterativeRandomPutsAndCommitsThenCompare(scenario, builder, steps, maxKey, values, r, commitFrequency); } - private void iterativeRandomPutsAndCommitsThenCompare(String scenario, VersionedMapStoreBuilder builder, + private void iterativeRandomPutsAndCommitsThenCompare(String scenario, VersionedMapStoreFactoryBuilder builder, int steps, int maxKey, String[] values, Random r, int commitFrequency) { - VersionedMapStore store1 = builder.setDefaultValue(values[0]).buildOne(); + VersionedMapStore store1 = builder.defaultValue(values[0]).build().createOne(); VersionedMap sut1 = store1.createMap(); // Fill one map @@ -63,7 +67,7 @@ class ContentEqualsFuzzTest { // Randomize the order of the content Collections.shuffle(content, r); - VersionedMapStore store2 = builder.setDefaultValue(values[0]).buildOne(); + VersionedMapStore store2 = builder.defaultValue(values[0]).build().createOne(); VersionedMap sut2 = store2.createMap(); int index2 = 1; for (SimpleEntry entry : content) { @@ -88,7 +92,7 @@ class ContentEqualsFuzzTest { @Timeout(value = 10) @Tag("fuzz") void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, VersionedMapStoreBuilder builder) { + int seed, VersionedMapStoreFactoryBuilder builder) { runFuzzTest("CompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, builder); } @@ -103,7 +107,7 @@ class ContentEqualsFuzzTest { @Tag("fuzz") @Tag("slow") void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean defaultNull, int commitFrequency, - int seed, VersionedMapStoreBuilder builder) { + int seed, VersionedMapStoreFactoryBuilder builder) { runFuzzTest("CompareS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, defaultNull, commitFrequency, builder); } diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java index b087906d..3a8852a9 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.params.provider.MethodSource; import tools.refinery.store.map.DiffCursor; import tools.refinery.store.map.VersionedMap; import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.VersionedMapStoreBuilder; +import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; @@ -21,10 +21,10 @@ import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; class DiffCursorFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, boolean commitBeforeDiffCursor, - VersionedMapStoreBuilder builder) { + VersionedMapStoreFactoryBuilder builder) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); - VersionedMapStore store = builder.setDefaultValue(values[0]).buildOne(); + VersionedMapStore store = builder.defaultValue(values[0]).build().createOne(); iterativeRandomPutsAndCommitsThenDiffCursor(scenario, store, steps, maxKey, values, seed, commitFrequency, commitBeforeDiffCursor); } @@ -109,7 +109,7 @@ class DiffCursorFuzzTest { @Tag("fuzz") void parametrizedFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, int seed, boolean commitBeforeDiffCursor, - VersionedMapStoreBuilder builder) { + VersionedMapStoreFactoryBuilder builder) { runFuzzTest("DiffCursorS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, commitBeforeDiffCursor, builder); } @@ -124,7 +124,7 @@ class DiffCursorFuzzTest { @Tag("fuzz") @Tag("slow") void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, boolean commitBeforeDiffCursor, VersionedMapStoreBuilder builder) { + int seed, boolean commitBeforeDiffCursor, VersionedMapStoreFactoryBuilder builder) { runFuzzTest("DiffCursorS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, commitBeforeDiffCursor, builder); } diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java index ec2224b4..ea58e1b7 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java @@ -1,32 +1,31 @@ package tools.refinery.store.map.tests.fuzz; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; -import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; - -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.stream.Stream; - import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; - import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.VersionedMapStoreBuilder; +import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; + class MultiThreadFuzzTest { public static final int noThreads = 10; - private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, VersionedMapStoreBuilder builder) { + private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, VersionedMapStoreFactoryBuilder builder) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); - VersionedMapStore store = builder.setDefaultValue(values[0]).buildOne(); + VersionedMapStore store = builder.defaultValue(values[0]).build().createOne(); // initialize runnables MultiThreadTestRunnable[] runnables = new MultiThreadTestRunnable[noThreads]; @@ -73,7 +72,7 @@ class MultiThreadFuzzTest { @Timeout(value = 10) @Tag("fuzz") void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean defaultNull, - int commitFrequency, int seed, VersionedMapStoreBuilder builder) { + int commitFrequency, int seed, VersionedMapStoreFactoryBuilder builder) { runFuzzTest("MultiThreadS" + steps + "K" + noKeys + "V" + noValues + defaultNull + "CF" + commitFrequency + "s" + seed, seed, steps, noKeys, noValues, defaultNull, commitFrequency, builder); } @@ -88,7 +87,7 @@ class MultiThreadFuzzTest { @Tag("fuzz") @Tag("slow") void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, - int commitFrequency, int seed, VersionedMapStoreBuilder builder) { + int commitFrequency, int seed, VersionedMapStoreFactoryBuilder builder) { runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, builder); } diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java index bdf72ce4..61b17362 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java @@ -18,10 +18,10 @@ import tools.refinery.store.map.tests.utils.MapTestEnvironment; class MutableFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, - boolean nullDefault, VersionedMapStoreBuilder builder) { + boolean nullDefault, VersionedMapStoreFactoryBuilder builder) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); - VersionedMapStore store = builder.setDefaultValue(values[0]).buildOne(); + VersionedMapStore store = builder.defaultValue(values[0]).build().createOne(); VersionedMap sut = store.createMap(); MapTestEnvironment e = new MapTestEnvironment<>(sut); @@ -56,7 +56,7 @@ class MutableFuzzTest { @Timeout(value = 10) @Tag("fuzz") void parametrizedFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean defaultNull, int seed, - VersionedMapStoreBuilder builder) { + VersionedMapStoreFactoryBuilder builder) { runFuzzTest( "MutableS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, defaultNull, builder); @@ -72,7 +72,7 @@ class MutableFuzzTest { @Tag("fuzz") @Tag("slow") void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int seed, - VersionedMapStoreBuilder builder) { + VersionedMapStoreFactoryBuilder builder) { runFuzzTest( "MutableS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, builder); diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java index 568aaac9..1661cccb 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java @@ -1,31 +1,31 @@ package tools.refinery.store.map.tests.fuzz; -import static org.junit.jupiter.api.Assertions.fail; -import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; - -import java.util.HashMap; -import java.util.Map; -import java.util.Random; -import java.util.stream.Stream; - import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; - -import tools.refinery.store.map.*; -import tools.refinery.store.map.internal.VersionedMapImpl; +import tools.refinery.store.map.VersionedMap; +import tools.refinery.store.map.VersionedMapStore; +import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.fail; +import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; + class RestoreFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, - VersionedMapStoreBuilder builder) { + VersionedMapStoreFactoryBuilder builder) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); - VersionedMapStore store = builder.setDefaultValue(values[0]).buildOne(); + VersionedMapStore store = builder.defaultValue(values[0]).build().createOne(); iterativeRandomPutsAndCommitsThenRestore(scenario, store, steps, maxKey, values, seed, commitFrequency); } @@ -84,7 +84,7 @@ class RestoreFuzzTest { @Timeout(value = 10) @Tag("smoke") void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, VersionedMapStoreBuilder builder) { + int seed, VersionedMapStoreFactoryBuilder builder) { runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, builder); } @@ -99,7 +99,7 @@ class RestoreFuzzTest { @Tag("smoke") @Tag("slow") void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, int commitFrequency, - int seed, VersionedMapStoreBuilder builder) { + int seed, VersionedMapStoreFactoryBuilder builder) { runFuzzTest("RestoreS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, builder); } diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java index e7d49227..0e1f9f9f 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java @@ -6,7 +6,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.VersionedMapStoreBuilder; +import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; @@ -15,10 +15,10 @@ import java.util.stream.Stream; import static tools.refinery.store.map.tests.fuzz.utils.FuzzTestCollections.*; class SingleThreadFuzzTest { - private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, VersionedMapStoreBuilder builder) { + private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, VersionedMapStoreFactoryBuilder builder) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); - VersionedMapStore store = builder.setDefaultValue(values[0]).buildOne(); + VersionedMapStore store = builder.defaultValue(values[0]).build().createOne(); // initialize runnables MultiThreadTestRunnable runnable = new MultiThreadTestRunnable(scenario, store, steps, maxKey, values, seed, commitFrequency); @@ -35,7 +35,7 @@ class SingleThreadFuzzTest { @Timeout(value = 10) @Tag("fuzz") void parametrizedFastFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean defaultNull, - int commitFrequency, int seed, VersionedMapStoreBuilder builder) { + int commitFrequency, int seed, VersionedMapStoreFactoryBuilder builder) { runFuzzTest("SingleThreadS" + steps + "K" + noKeys + "V" + noValues + defaultNull + "CF" + commitFrequency + "s" + seed, seed, steps, noKeys, noValues, defaultNull, commitFrequency, builder); } @@ -50,7 +50,7 @@ class SingleThreadFuzzTest { @Tag("fuzz") @Tag("slow") void parametrizedSlowFuzz(int ignoredTests, int steps, int noKeys, int noValues, boolean nullDefault, - int commitFrequency, int seed, VersionedMapStoreBuilder builder) { + int commitFrequency, int seed, VersionedMapStoreFactoryBuilder builder) { runFuzzTest("SingleThreadS" + steps + "K" + noKeys + "V" + noValues + "s" + seed, seed, steps, noKeys, noValues, nullDefault, commitFrequency, builder); } diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java index b344d9b9..94c9cba7 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java @@ -1,6 +1,7 @@ package tools.refinery.store.map.tests.fuzz.utils; -import tools.refinery.store.map.VersionedMapStoreBuilder; +import tools.refinery.store.map.VersionedMapStore; +import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; import tools.refinery.store.map.tests.utils.MapTestEnvironment; public final class FuzzTestCollections { @@ -12,34 +13,27 @@ public final class FuzzTestCollections { public static final Object[] randomSeedOptions = {1}; public static final Object[] storeConfigs = { // State based - VersionedMapStoreBuilder.builder() - .setStrategy(VersionedMapStoreBuilder.StoreStrategy.STATE) - .setStateBasedImmutableWhenCommitting(true) - .setHashProvider(MapTestEnvironment.prepareHashProvider(false)) - .setStateBasedNodeSharingStrategy(VersionedMapStoreBuilder.StateStorageStrategy - .SHARED_NODE_CACHE), - VersionedMapStoreBuilder.builder() - .setStrategy(VersionedMapStoreBuilder.StoreStrategy.STATE) - .setStateBasedImmutableWhenCommitting(true) - .setHashProvider(MapTestEnvironment.prepareHashProvider(true)) - .setStateBasedNodeSharingStrategy(VersionedMapStoreBuilder.StateStorageStrategy - .SHARED_NODE_CACHE), - VersionedMapStoreBuilder.builder() - .setStrategy(VersionedMapStoreBuilder.StoreStrategy.STATE) - .setStateBasedImmutableWhenCommitting(false) - .setHashProvider(MapTestEnvironment.prepareHashProvider(false)) - .setStateBasedNodeSharingStrategy(VersionedMapStoreBuilder.StateStorageStrategy.SHARED_NODE_CACHE), - VersionedMapStoreBuilder.builder() - .setStrategy(VersionedMapStoreBuilder.StoreStrategy.STATE) - .setStateBasedImmutableWhenCommitting(false) - .setHashProvider(MapTestEnvironment.prepareHashProvider(false)) - .setStateBasedNodeSharingStrategy(VersionedMapStoreBuilder.StateStorageStrategy.NO_NODE_CACHE), + VersionedMapStore.builder() + .stateBasedImmutableWhenCommitting(true) + .stateBasedHashProvider(MapTestEnvironment.prepareHashProvider(false)) + .stateBasedSharingStrategy(VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE), + VersionedMapStore.builder() + .stateBasedImmutableWhenCommitting(true) + .stateBasedHashProvider(MapTestEnvironment.prepareHashProvider(true)) + .stateBasedSharingStrategy(VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE), + VersionedMapStore.builder() + .stateBasedImmutableWhenCommitting(false) + .stateBasedHashProvider(MapTestEnvironment.prepareHashProvider(false)) + .stateBasedSharingStrategy(VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE), + VersionedMapStore.builder() + .stateBasedImmutableWhenCommitting(false) + .stateBasedHashProvider(MapTestEnvironment.prepareHashProvider(false)) + .stateBasedSharingStrategy(VersionedMapStoreFactoryBuilder.SharingStrategy.NO_NODE_CACHE), + // Delta based - VersionedMapStoreBuilder.builder() - .setStrategy(VersionedMapStoreBuilder.StoreStrategy.DELTA) - .setDeltaStorageStrategy(VersionedMapStoreBuilder.DeltaStorageStrategy.SET), - VersionedMapStoreBuilder.builder() - .setStrategy(VersionedMapStoreBuilder.StoreStrategy.DELTA) - .setDeltaStorageStrategy(VersionedMapStoreBuilder.DeltaStorageStrategy.LIST) + VersionedMapStore.builder() + .deltaTransactionStrategy(VersionedMapStoreFactoryBuilder.DeltaTransactionStrategy.SET), + VersionedMapStore.builder() + .deltaTransactionStrategy(VersionedMapStoreFactoryBuilder.DeltaTransactionStrategy.LIST) }; } -- cgit v1.2.3-70-g09d2 From 38c66c13d3003df1f901258b4ffc69b1fac980e8 Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Fri, 21 Jul 2023 21:16:43 +0200 Subject: decreasing steps in fast fuzz tests --- .../java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java | 2 +- .../java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java index 3a8852a9..e02448cf 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java @@ -115,7 +115,7 @@ class DiffCursorFuzzTest { } static Stream parametrizedFuzz() { - return FuzzTestUtils.permutationWithSize(new Object[]{500}, keyCounts, valueCounts, nullDefaultOptions, + return FuzzTestUtils.permutationWithSize(new Object[]{100}, keyCounts, valueCounts, nullDefaultOptions, commitFrequencyOptions, randomSeedOptions, new Object[]{false,true}, storeConfigs); } diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java index 92208e48..89c01690 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java @@ -8,7 +8,7 @@ import java.util.stream.Stream; import org.junit.jupiter.params.provider.Arguments; public final class FuzzTestUtils { - public static final int FAST_STEP_COUNT = 500; + public static final int FAST_STEP_COUNT = 250; public static final int SLOW_STEP_COUNT = 32 * 32 * 32 * 32; private FuzzTestUtils() { -- cgit v1.2.3-70-g09d2 From 3455aeab401aeab4f01d8ef67a73b965b0a0dde0 Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Mon, 24 Jul 2023 15:08:46 +0200 Subject: Added missing copyright headers. --- .../java/tools/refinery/store/map/VersionedMapStoreDeltaImpl.java | 5 +++++ .../main/java/tools/refinery/store/map/VersionedMapStoreFactory.java | 5 +++++ .../tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java | 5 +++++ .../store/map/internal/DeltaBasedVersionedMapStoreFactory.java | 5 +++++ .../main/java/tools/refinery/store/map/internal/DeltaDiffCursor.java | 5 +++++ .../java/tools/refinery/store/map/internal/InOrderMapCursor.java | 5 +++++ .../java/tools/refinery/store/map/internal/IteratorAsCursor.java | 5 +++++ .../src/main/java/tools/refinery/store/map/internal/MapDelta.java | 5 +++++ .../main/java/tools/refinery/store/map/internal/MapTransaction.java | 5 +++++ .../store/map/internal/StateBasedVersionedMapStoreFactory.java | 5 +++++ .../refinery/store/map/internal/UncommittedDeltaArrayStore.java | 5 +++++ .../tools/refinery/store/map/internal/UncommittedDeltaMapStore.java | 5 +++++ .../tools/refinery/store/map/internal/UncommittedDeltaStore.java | 5 +++++ .../tools/refinery/store/map/internal/VersionedMapDeltaImpl.java | 5 +++++ .../store/map/internal/VersionedMapStoreFactoryBuilderImpl.java | 5 +++++ .../test/java/tools/refinery/store/map/tests/InOrderCursorTest.java | 5 +++++ .../tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java | 5 +++++ .../refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java | 5 +++++ 18 files changed, 90 insertions(+) (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreDeltaImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreDeltaImpl.java index 31cdbf95..0c61bd09 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreDeltaImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreDeltaImpl.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map; import java.util.*; diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactory.java index 5f882a3a..baf6ab50 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactory.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactory.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map; import java.util.List; diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java index 9cf17b49..6329a2f6 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map; public interface VersionedMapStoreFactoryBuilder { diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaBasedVersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaBasedVersionedMapStoreFactory.java index 29ec0da1..fe490f46 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaBasedVersionedMapStoreFactory.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaBasedVersionedMapStoreFactory.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map.internal; import tools.refinery.store.map.VersionedMapStore; diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaDiffCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaDiffCursor.java index 8ddca8ec..cc9003e3 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaDiffCursor.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaDiffCursor.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map.internal; import tools.refinery.store.map.AnyVersionedMap; diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/InOrderMapCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/InOrderMapCursor.java index c559d9ad..cb3f366f 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/InOrderMapCursor.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/InOrderMapCursor.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map.internal; import tools.refinery.store.map.AnyVersionedMap; diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/IteratorAsCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/IteratorAsCursor.java index c1a0aec4..d1ab8bb1 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/IteratorAsCursor.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/IteratorAsCursor.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map.internal; import java.util.*; diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDelta.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDelta.java index 86e9fe62..2674236c 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDelta.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDelta.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map.internal; public record MapDelta(K key, V oldValue, V newValue) { diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapTransaction.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapTransaction.java index 5996048e..d63522cd 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapTransaction.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapTransaction.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map.internal; import java.util.Arrays; diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/StateBasedVersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/StateBasedVersionedMapStoreFactory.java index 80dc347f..1c3ab27b 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/StateBasedVersionedMapStoreFactory.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/StateBasedVersionedMapStoreFactory.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map.internal; import tools.refinery.store.map.*; diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaArrayStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaArrayStore.java index 3b3f94ae..ba59cfef 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaArrayStore.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaArrayStore.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map.internal; import java.util.ArrayList; diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaMapStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaMapStore.java index 31423b1c..61a34351 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaMapStore.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaMapStore.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map.internal; import java.util.*; diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaStore.java index 7b017c8e..438b5561 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaStore.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaStore.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map.internal; public interface UncommittedDeltaStore { diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapDeltaImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapDeltaImpl.java index d09e54ba..ae47feda 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapDeltaImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapDeltaImpl.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map.internal; import java.util.*; diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java index 3719eef5..cf117d95 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map.internal; import tools.refinery.store.map.ContinousHashProvider; diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java index 993e5531..4ada4ea4 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map.tests; import org.junit.jupiter.api.Test; diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java index 0e1f9f9f..1337cf3a 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SingleThreadFuzzTest.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map.tests.fuzz; import org.junit.jupiter.api.Tag; diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java index 94c9cba7..4c3ecb09 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ package tools.refinery.store.map.tests.fuzz.utils; import tools.refinery.store.map.VersionedMapStore; -- cgit v1.2.3-70-g09d2 From 26592fc70a026b850616fc4bc9be5a46ab1179a9 Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Mon, 24 Jul 2023 16:51:47 +0200 Subject: Refactoring packages related to VersionedMapDeltaImpl + VersionedMapStoreStateImpl, update builder. - details of the maps goes to internal packages - ModelStoreBuilderImpl uses VersionedMapStoreFactoryBuilder --- .../map/benchmarks/ImmutablePutExecutionPlan.java | 17 +- .../refinery/store/map/ContinousHashProvider.java | 74 --- .../refinery/store/map/ContinuousHashProvider.java | 74 +++ .../tools/refinery/store/map/IteratorAsCursor.java | 64 +++ .../store/map/VersionedMapStoreConfiguration.java | 53 --- .../store/map/VersionedMapStoreDeltaImpl.java | 99 ---- .../store/map/VersionedMapStoreFactoryBuilder.java | 2 +- .../refinery/store/map/VersionedMapStoreImpl.java | 129 ------ .../DeltaBasedVersionedMapStoreFactory.java | 39 -- .../store/map/internal/DeltaDiffCursor.java | 147 ------ .../refinery/store/map/internal/HashClash.java | 23 - .../refinery/store/map/internal/ImmutableNode.java | 413 ----------------- .../store/map/internal/InOrderMapCursor.java | 146 ------ .../store/map/internal/IteratorAsCursor.java | 66 --- .../refinery/store/map/internal/MapCursor.java | 95 ---- .../refinery/store/map/internal/MapDelta.java | 20 - .../refinery/store/map/internal/MapDiffCursor.java | 264 ----------- .../store/map/internal/MapTransaction.java | 39 -- .../refinery/store/map/internal/MutableNode.java | 499 --------------------- .../tools/refinery/store/map/internal/Node.java | 131 ------ .../refinery/store/map/internal/OldValueBox.java | 24 - .../StateBasedVersionedMapStoreFactory.java | 38 -- .../map/internal/UncommittedDeltaArrayStore.java | 36 -- .../map/internal/UncommittedDeltaMapStore.java | 53 --- .../store/map/internal/UncommittedDeltaStore.java | 29 -- .../store/map/internal/VersionedMapDeltaImpl.java | 219 --------- .../store/map/internal/VersionedMapImpl.java | 171 ------- .../VersionedMapStoreFactoryBuilderImpl.java | 18 +- .../delta/DeltaBasedVersionedMapStoreFactory.java | 38 ++ .../store/map/internal/delta/DeltaDiffCursor.java | 147 ++++++ .../store/map/internal/delta/MapDelta.java | 20 + .../store/map/internal/delta/MapTransaction.java | 39 ++ .../internal/delta/UncommittedDeltaArrayStore.java | 36 ++ .../internal/delta/UncommittedDeltaMapStore.java | 53 +++ .../map/internal/delta/UncommittedDeltaStore.java | 29 ++ .../map/internal/delta/VersionedMapDeltaImpl.java | 220 +++++++++ .../internal/delta/VersionedMapStoreDeltaImpl.java | 101 +++++ .../store/map/internal/state/ImmutableNode.java | 413 +++++++++++++++++ .../store/map/internal/state/InOrderMapCursor.java | 146 ++++++ .../store/map/internal/state/MapCursor.java | 95 ++++ .../store/map/internal/state/MapDiffCursor.java | 264 +++++++++++ .../store/map/internal/state/MutableNode.java | 499 +++++++++++++++++++++ .../refinery/store/map/internal/state/Node.java | 131 ++++++ .../store/map/internal/state/OldValueBox.java | 24 + .../state/StateBasedVersionedMapStoreFactory.java | 38 ++ .../map/internal/state/VersionedMapStateImpl.java | 171 +++++++ .../state/VersionedMapStoreStateConfiguration.java | 56 +++ .../internal/state/VersionedMapStoreStateImpl.java | 129 ++++++ .../refinery/store/model/TupleHashProvider.java | 4 +- .../store/model/TupleHashProviderBitMagic.java | 34 -- .../model/internal/ModelStoreBuilderImpl.java | 12 +- .../store/map/tests/InOrderCursorTest.java | 8 +- .../refinery/store/map/tests/MapUnitTests.java | 4 +- .../fuzz/MutableImmutableCompareFuzzTest.java | 18 +- .../store/map/tests/fuzz/SharedStoreFuzzTest.java | 18 +- .../store/map/tests/utils/MapTestEnvironment.java | 6 +- .../store/model/hashtests/HashEfficiencyTest.java | 13 +- 57 files changed, 2849 insertions(+), 2899 deletions(-) delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/ContinousHashProvider.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/ContinuousHashProvider.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/IteratorAsCursor.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreConfiguration.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreDeltaImpl.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaBasedVersionedMapStoreFactory.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaDiffCursor.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/InOrderMapCursor.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/IteratorAsCursor.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDelta.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/MapTransaction.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/OldValueBox.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/StateBasedVersionedMapStoreFactory.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaArrayStore.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaMapStore.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaStore.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapDeltaImpl.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaBasedVersionedMapStoreFactory.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaDiffCursor.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapDelta.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapTransaction.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaArrayStore.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaMapStore.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaStore.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapStoreDeltaImpl.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/state/ImmutableNode.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/state/InOrderMapCursor.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapCursor.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapDiffCursor.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MutableNode.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/state/Node.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/state/OldValueBox.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/state/StateBasedVersionedMapStoreFactory.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStateImpl.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateConfiguration.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateImpl.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutExecutionPlan.java b/subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutExecutionPlan.java index 7e89cd06..4708e6d3 100644 --- a/subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutExecutionPlan.java +++ b/subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutExecutionPlan.java @@ -5,12 +5,13 @@ */ package tools.refinery.store.map.benchmarks; +import java.util.Objects; import java.util.Random; -import tools.refinery.store.map.ContinousHashProvider; +import tools.refinery.store.map.ContinuousHashProvider; import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.VersionedMapStoreImpl; -import tools.refinery.store.map.internal.VersionedMapImpl; +import tools.refinery.store.map.internal.state.VersionedMapStoreStateImpl; +import tools.refinery.store.map.internal.state.VersionedMapStateImpl; import tools.refinery.store.map.tests.utils.MapTestEnvironment; import org.openjdk.jmh.annotations.Level; @@ -35,7 +36,7 @@ public class ImmutablePutExecutionPlan { private String[] values; - private ContinousHashProvider hashProvider = MapTestEnvironment.prepareHashProvider(false); + private ContinuousHashProvider hashProvider = MapTestEnvironment.prepareHashProvider(false); @Setup(Level.Trial) public void setUpTrial() { @@ -43,9 +44,9 @@ public class ImmutablePutExecutionPlan { values = MapTestEnvironment.prepareValues(nValues, true); } - public VersionedMapImpl createSut() { - VersionedMapStore store = new VersionedMapStoreImpl(hashProvider, values[0]); - return (VersionedMapImpl) store.createMap(); + public VersionedMapStateImpl createSut() { + VersionedMapStore store = new VersionedMapStoreStateImpl<>(hashProvider, values[0]); + return (VersionedMapStateImpl) store.createMap(); } public Integer nextKey() { @@ -53,7 +54,7 @@ public class ImmutablePutExecutionPlan { } public boolean isDefault(String value) { - return value == values[0]; + return Objects.equals(value,values[0]); } public String nextValue() { diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/ContinousHashProvider.java b/subprojects/store/src/main/java/tools/refinery/store/map/ContinousHashProvider.java deleted file mode 100644 index 8e451230..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/ContinousHashProvider.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map; - -import tools.refinery.store.map.internal.Node; - -/** - * A class representing an equivalence relation for a type {@code K} with a - * continuous hash function. - * - * @author Oszkar Semerath - * - * @param Target java type. - */ -public interface ContinousHashProvider { - public static final int EFFECTIVE_BITS = Node.EFFECTIVE_BITS; - public static final int EFFECTIVE_BIT_MASK = (1 << (EFFECTIVE_BITS)) - 1; - - /** - * Maximal practical depth for differentiating keys. If two keys have the same - * hash code until that depth, the algorithm can stop. - */ - public static final int MAX_PRACTICAL_DEPTH = 500; - - /** - * Provides a hash code for a object {@code key} with a given {@code index}. It - * has the following contracts: - *
    - *
  • If {@link #equals}{@code (key1,key2)}, then - * {@code getHash(key1, index) == getHash(key2, index)} for all values of - * {@code index}.
  • - *
  • If {@code getHash(key1,index) == getHash(key2, index)} for all values of - * {@code index}, then {@link #equals}{@code (key1, key2)}
  • - *
  • In current implementation, we use only the least significant - * {@link #EFFECTIVE_BITS} - *
- * Check {@link #equals} for further details. - * - * @param key The target data object. - * @param index The depth of the the hash code. Needs to be non-negative. - * @return A hash code. - */ - public int getHash(K key, int index); - - public default int getEffectiveHash(K key, int index) { - return getHash(key, index) & EFFECTIVE_BIT_MASK; - } - - public default int compare(K key1, K key2) { - if (key1.equals(key2)) { - return 0; - } else { - for (int i = 0; i < ContinousHashProvider.MAX_PRACTICAL_DEPTH; i++) { - int hash1 = getEffectiveHash(key1, i); - int hash2 = getEffectiveHash(key2, i); - for(int j = 0; j>>j*Node.BRANCHING_FACTOR_BITS) & factorMask; - int hashFragment2 = (hash2>>>j*Node.BRANCHING_FACTOR_BITS) & factorMask; - var result = Integer.compare(hashFragment1, hashFragment2); - if (result != 0) { - return result; - } - } - } - throw new IllegalArgumentException("Two different keys (" + key1 + " and " + key2 - + ") have the same hashcode over the practical depth limitation (" - + ContinousHashProvider.MAX_PRACTICAL_DEPTH + ")!"); - } - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/ContinuousHashProvider.java b/subprojects/store/src/main/java/tools/refinery/store/map/ContinuousHashProvider.java new file mode 100644 index 00000000..abc044d0 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/ContinuousHashProvider.java @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map; + +import tools.refinery.store.map.internal.state.Node; + +/** + * A class representing an equivalence relation for a type {@code K} with a + * continuous hash function. + * + * @author Oszkar Semerath + * + * @param Target java type. + */ +public interface ContinuousHashProvider { + public static final int EFFECTIVE_BITS = Node.EFFECTIVE_BITS; + public static final int EFFECTIVE_BIT_MASK = (1 << (EFFECTIVE_BITS)) - 1; + + /** + * Maximal practical depth for differentiating keys. If two keys have the same + * hash code until that depth, the algorithm can stop. + */ + public static final int MAX_PRACTICAL_DEPTH = 500; + + /** + * Provides a hash code for a object {@code key} with a given {@code index}. It + * has the following contracts: + *
    + *
  • If {@link #equals}{@code (key1,key2)}, then + * {@code getHash(key1, index) == getHash(key2, index)} for all values of + * {@code index}.
  • + *
  • If {@code getHash(key1,index) == getHash(key2, index)} for all values of + * {@code index}, then {@link #equals}{@code (key1, key2)}
  • + *
  • In current implementation, we use only the least significant + * {@link #EFFECTIVE_BITS} + *
+ * Check {@link #equals} for further details. + * + * @param key The target data object. + * @param index The depth of the hash code. Needs to be non-negative. + * @return A hash code. + */ + public int getHash(K key, int index); + + public default int getEffectiveHash(K key, int index) { + return getHash(key, index) & EFFECTIVE_BIT_MASK; + } + + public default int compare(K key1, K key2) { + if (key1.equals(key2)) { + return 0; + } else { + for (int i = 0; i < ContinuousHashProvider.MAX_PRACTICAL_DEPTH; i++) { + int hash1 = getEffectiveHash(key1, i); + int hash2 = getEffectiveHash(key2, i); + for(int j = 0; j>>j*Node.BRANCHING_FACTOR_BITS) & factorMask; + int hashFragment2 = (hash2>>>j*Node.BRANCHING_FACTOR_BITS) & factorMask; + var result = Integer.compare(hashFragment1, hashFragment2); + if (result != 0) { + return result; + } + } + } + throw new IllegalArgumentException("Two different keys (" + key1 + " and " + key2 + + ") have the same hashcode over the practical depth limitation (" + + ContinuousHashProvider.MAX_PRACTICAL_DEPTH + ")!"); + } + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/IteratorAsCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/IteratorAsCursor.java new file mode 100644 index 00000000..29b03edf --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/IteratorAsCursor.java @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map; + +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class IteratorAsCursor implements Cursor { + final Iterator> iterator; + final VersionedMap source; + + private boolean terminated; + private K key; + private V value; + + public IteratorAsCursor(VersionedMap source, Map current) { + this.iterator = current.entrySet().iterator(); + this.source = source; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public boolean isTerminated() { + return terminated; + } + + @Override + public boolean move() { + terminated = !iterator.hasNext(); + if (terminated) { + this.key = null; + this.value = null; + } else { + Entry next = iterator.next(); + this.key = next.getKey(); + this.value = next.getValue(); + } + return !terminated; + } + + @Override + public boolean isDirty() { + return false; + } + + @Override + public Set getDependingMaps() { + return Set.of(this.source); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreConfiguration.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreConfiguration.java deleted file mode 100644 index b00cd961..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreConfiguration.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map; - -public class VersionedMapStoreConfiguration { - - public VersionedMapStoreConfiguration() { - - } - public VersionedMapStoreConfiguration(boolean immutableWhenCommitting, boolean sharedNodeCacheInStore, - boolean sharedNodeCacheInStoreGroups) { - super(); - this.immutableWhenCommitting = immutableWhenCommitting; - this.sharedNodeCacheInStore = sharedNodeCacheInStore; - this.sharedNodeCacheInStoreGroups = sharedNodeCacheInStoreGroups; - } - - /** - * If true root is replaced with immutable node when committed. Frees up memory - * by releasing immutable nodes, but it may decrease performance by recreating - * immutable nodes upon changes (some evidence). - */ - private boolean immutableWhenCommitting = true; - public boolean isImmutableWhenCommitting() { - return immutableWhenCommitting; - } - - /** - * If true, all sub-nodes are cached within a {@link VersionedMapStore}. It - * decreases the memory requirements. It may increase performance by discovering - * existing immutable copy of a node (some evidence). Additional overhead may - * decrease performance (no example found). The option permits the efficient - * implementation of version deletion. - */ - private boolean sharedNodeCacheInStore = true; - public boolean isSharedNodeCacheInStore() { - return sharedNodeCacheInStore; - } - - /** - * If true, all sub-nodes are cached within a group of - * {@link VersionedMapStoreImpl#createSharedVersionedMapStores(int, ContinousHashProvider, Object, VersionedMapStoreConfiguration)}. - * If {@link VersionedMapStoreConfiguration#sharedNodeCacheInStore} is - * false, then it has currently no impact. - */ - private boolean sharedNodeCacheInStoreGroups = true; - public boolean isSharedNodeCacheInStoreGroups() { - return sharedNodeCacheInStoreGroups; - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreDeltaImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreDeltaImpl.java deleted file mode 100644 index 0c61bd09..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreDeltaImpl.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map; - -import java.util.*; - -import tools.refinery.store.map.internal.*; - -public class VersionedMapStoreDeltaImpl implements VersionedMapStore { - // Configuration - protected final boolean summarizeChanges; - - // Static data - protected final V defaultValue; - - // Dynamic data - protected final Map> states = new HashMap<>(); - protected long nextID = 0; - - public VersionedMapStoreDeltaImpl(boolean summarizeChanges, V defaultValue) { - this.summarizeChanges = summarizeChanges; - this.defaultValue = defaultValue; - } - - @Override - public VersionedMap createMap() { - return new VersionedMapDeltaImpl<>(this, this.summarizeChanges, this.defaultValue); - } - - @Override - public VersionedMap createMap(long state) { - VersionedMapDeltaImpl result = new VersionedMapDeltaImpl<>(this, this.summarizeChanges, this.defaultValue); - result.restore(state); - return result; - } - - public synchronized MapTransaction appendTransaction(MapDelta[] deltas, MapTransaction previous, long[] versionContainer) { - long version = nextID++; - versionContainer[0] = version; - if (deltas == null) { - states.put(version, previous); - return previous; - } else { - MapTransaction transaction = new MapTransaction<>(deltas, version, previous); - states.put(version, transaction); - return transaction; - } - } - - private synchronized MapTransaction getState(long state) { - return states.get(state); - } - - public MapTransaction getPath(long to, List[]> forwardTransactions) { - final MapTransaction target = getState(to); - MapTransaction toTransaction = target; - while (toTransaction != null) { - forwardTransactions.add(toTransaction.deltas()); - toTransaction = toTransaction.parent(); - } - return target; - } - - public MapTransaction getPath(long from, long to, - List[]> backwardTransactions, - List[]> forwardTransactions) { - MapTransaction fromTransaction = getState(from); - final MapTransaction target = getState(to); - MapTransaction toTransaction = target; - - while (fromTransaction != toTransaction) { - if (fromTransaction == null || (toTransaction != null && fromTransaction.version() < toTransaction.version())) { - forwardTransactions.add(toTransaction.deltas()); - toTransaction = toTransaction.parent(); - } else { - backwardTransactions.add(fromTransaction.deltas()); - fromTransaction = fromTransaction.parent(); - } - } - return target; - } - - - @Override - public synchronized Set getStates() { - return new HashSet<>(states.keySet()); - } - - @Override - public DiffCursor getDiffCursor(long fromState, long toState) { - List[]> backwardTransactions = new ArrayList<>(); - List[]> forwardTransactions = new ArrayList<>(); - getPath(fromState, toState, backwardTransactions, forwardTransactions); - return new DeltaDiffCursor<>(backwardTransactions, forwardTransactions); - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java index 6329a2f6..6b4fc2a0 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java @@ -22,7 +22,7 @@ public interface VersionedMapStoreFactoryBuilder { VersionedMapStoreFactoryBuilder strategy(StoreStrategy strategy); VersionedMapStoreFactoryBuilder stateBasedImmutableWhenCommitting(boolean transformToImmutable); VersionedMapStoreFactoryBuilder stateBasedSharingStrategy(SharingStrategy sharingStrategy); - VersionedMapStoreFactoryBuilder stateBasedHashProvider(ContinousHashProvider hashProvider); + VersionedMapStoreFactoryBuilder stateBasedHashProvider(ContinuousHashProvider hashProvider); VersionedMapStoreFactoryBuilder deltaTransactionStrategy(DeltaTransactionStrategy deltaStrategy); VersionedMapStoreFactory build(); diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java deleted file mode 100644 index a934d59e..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map; - -import tools.refinery.store.map.internal.*; - -import java.util.*; - -public class VersionedMapStoreImpl implements VersionedMapStore { - // Configuration - private final boolean immutableWhenCommitting; - - // Static data - protected final ContinousHashProvider hashProvider; - protected final V defaultValue; - - // Dynamic data - protected final Map> states = new HashMap<>(); - protected final Map, ImmutableNode> nodeCache; - protected long nextID = 0; - - public VersionedMapStoreImpl(ContinousHashProvider hashProvider, V defaultValue, - VersionedMapStoreConfiguration config) { - this.immutableWhenCommitting = config.isImmutableWhenCommitting(); - this.hashProvider = hashProvider; - this.defaultValue = defaultValue; - if (config.isSharedNodeCacheInStore()) { - nodeCache = new HashMap<>(); - } else { - nodeCache = null; - } - } - - private VersionedMapStoreImpl(ContinousHashProvider hashProvider, V defaultValue, - Map, ImmutableNode> nodeCache, VersionedMapStoreConfiguration config) { - this.immutableWhenCommitting = config.isImmutableWhenCommitting(); - this.hashProvider = hashProvider; - this.defaultValue = defaultValue; - this.nodeCache = nodeCache; - } - - public VersionedMapStoreImpl(ContinousHashProvider hashProvider, V defaultValue) { - this(hashProvider, defaultValue, new VersionedMapStoreConfiguration()); - } - - public static List> createSharedVersionedMapStores(int amount, - ContinousHashProvider hashProvider, V defaultValue, - VersionedMapStoreConfiguration config) { - List> result = new ArrayList<>(amount); - if (config.isSharedNodeCacheInStoreGroups()) { - Map, ImmutableNode> nodeCache; - if (config.isSharedNodeCacheInStore()) { - nodeCache = new HashMap<>(); - } else { - nodeCache = null; - } - for (int i = 0; i < amount; i++) { - result.add(new VersionedMapStoreImpl<>(hashProvider, defaultValue, nodeCache, config)); - } - } else { - for (int i = 0; i < amount; i++) { - result.add(new VersionedMapStoreImpl<>(hashProvider, defaultValue, config)); - } - } - return result; - } - - public static List> createSharedVersionedMapStores(int amount, - ContinousHashProvider hashProvider, V defaultValue) { - return createSharedVersionedMapStores(amount, hashProvider, defaultValue, new VersionedMapStoreConfiguration()); - } - - @Override - public synchronized Set getStates() { - return new HashSet<>(states.keySet()); - } - - @Override - public VersionedMap createMap() { - return new VersionedMapImpl<>(this, hashProvider, defaultValue); - } - - @Override - public VersionedMap createMap(long state) { - ImmutableNode data = revert(state); - return new VersionedMapImpl<>(this, hashProvider, defaultValue, data); - } - - public synchronized ImmutableNode revert(long state) { - if (states.containsKey(state)) { - return states.get(state); - } else { - ArrayList existingKeys = new ArrayList<>(states.keySet()); - Collections.sort(existingKeys); - throw new IllegalArgumentException("Store does not contain state " + state + "! Available states: " - + Arrays.toString(existingKeys.toArray())); - } - } - - public synchronized long commit(Node data, VersionedMapImpl mapToUpdateRoot) { - ImmutableNode immutable; - if (data != null) { - immutable = data.toImmutable(this.nodeCache); - } else { - immutable = null; - } - - if (nextID == Long.MAX_VALUE) - throw new IllegalStateException("Map store run out of Id-s"); - long id = nextID++; - this.states.put(id, immutable); - if (this.immutableWhenCommitting) { - mapToUpdateRoot.setRoot(immutable); - } - return id; - } - - @Override - public DiffCursor getDiffCursor(long fromState, long toState) { - VersionedMapImpl map1 = (VersionedMapImpl) createMap(fromState); - VersionedMapImpl map2 = (VersionedMapImpl) createMap(toState); - InOrderMapCursor cursor1 = new InOrderMapCursor<>(map1); - InOrderMapCursor cursor2 = new InOrderMapCursor<>(map2); - return new MapDiffCursor<>(this.defaultValue, cursor1, cursor2); - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaBasedVersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaBasedVersionedMapStoreFactory.java deleted file mode 100644 index fe490f46..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaBasedVersionedMapStoreFactory.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.VersionedMapStoreDeltaImpl; -import tools.refinery.store.map.VersionedMapStoreFactory; -import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; - -import java.util.ArrayList; -import java.util.List; - -public class DeltaBasedVersionedMapStoreFactory implements VersionedMapStoreFactory { - private final V defaultValue; - private final boolean summarizeChanges; - - public DeltaBasedVersionedMapStoreFactory(V defaultValue, - VersionedMapStoreFactoryBuilder.DeltaTransactionStrategy deltaTransactionStrategy) { - this.defaultValue = defaultValue; - this.summarizeChanges = deltaTransactionStrategy == VersionedMapStoreFactoryBuilder.DeltaTransactionStrategy.SET; - } - - @Override - public VersionedMapStore createOne() { - return new VersionedMapStoreDeltaImpl<>(summarizeChanges, defaultValue); - } - - @Override - public List> createGroup(int amount) { - List> result = new ArrayList<>(amount); - for(int i=0; i(summarizeChanges,defaultValue)); - } - return result; - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaDiffCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaDiffCursor.java deleted file mode 100644 index cc9003e3..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/DeltaDiffCursor.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -import tools.refinery.store.map.AnyVersionedMap; -import tools.refinery.store.map.DiffCursor; - -import java.util.Collections; -import java.util.List; -import java.util.Set; - -public class DeltaDiffCursor implements DiffCursor { - final List[]> backwardTransactions; - final List[]> forwardTransactions; - - boolean started; - /** - * Denotes the direction of traversal. False means backwards, true means - * forward. - */ - boolean direction; - int listIndex; - int arrayIndex; - - public DeltaDiffCursor(List[]> backwardTransactions, List[]> forwardTransactions) { - this.backwardTransactions = backwardTransactions; - this.forwardTransactions = forwardTransactions; - - if (!backwardTransactions.isEmpty()) { - direction = false; - listIndex = 0; - arrayIndex = backwardTransactions.get(listIndex).length - 1; - } else if (!forwardTransactions.isEmpty()) { - direction = true; - listIndex = forwardTransactions.size() - 1; - arrayIndex = 0; - } else { - direction = true; - listIndex = -1; - } - started = false; - } - - protected MapDelta getCurrentDelta() { - final List[]> list; - if (!direction) { - list = this.backwardTransactions; - } else { - list = this.forwardTransactions; - } - return list.get(listIndex)[arrayIndex]; - } - - @Override - public K getKey() { - return getCurrentDelta().getKey(); - } - - @Override - public V getValue() { - return getToValue(); - } - - @Override - public boolean isTerminated() { - return this.direction && listIndex == -1; - } - - - @Override - public boolean move() { - if(!started) { - started = true; - return !isTerminated(); - } else if (isTerminated()) { - return false; - } else { - if (this.direction) { - if (arrayIndex+1 < forwardTransactions.get(listIndex).length) { - arrayIndex++; - return true; - } else { - if (listIndex-1 >= 0) { - listIndex--; - arrayIndex = 0; - return true; - } else { - listIndex = -1; - return false; - } - } - } else { - if (arrayIndex > 0) { - arrayIndex--; - return true; - } else { - if (listIndex+1 < backwardTransactions.size()) { - listIndex++; - this.arrayIndex = backwardTransactions.get(listIndex).length - 1; - return true; - } else { - this.direction = true; - if (!this.forwardTransactions.isEmpty()) { - listIndex = forwardTransactions.size() - 1; - arrayIndex = 0; - return true; - } else { - listIndex = -1; - return false; - } - } - } - } - } - } - - @Override - public boolean isDirty() { - return false; - } - - @Override - public Set getDependingMaps() { - return Collections.emptySet(); - } - - @Override - public V getFromValue() { - if(this.direction) { - return getCurrentDelta().getOldValue(); - } else { - return getCurrentDelta().getNewValue(); - } - } - - @Override - public V getToValue() { - if(this.direction) { - return getCurrentDelta().getNewValue(); - } else { - return getCurrentDelta().getOldValue(); - } - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java deleted file mode 100644 index a357fbce..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -enum HashClash { - /** - * Not stuck. - */ - NONE, - - /** - * Clashed, next we should return the key of cursor 1. - */ - STUCK_CURSOR_1, - - /** - * Clashed, next we should return the key of cursor 2. - */ - STUCK_CURSOR_2 -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java deleted file mode 100644 index d052318f..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java +++ /dev/null @@ -1,413 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -import java.util.Arrays; -import java.util.Map; - -import tools.refinery.store.map.ContinousHashProvider; - -public class ImmutableNode extends Node { - /** - * Bitmap defining the stored key and values. - */ - final int dataMap; - /** - * Bitmap defining the positions of further nodes. - */ - final int nodeMap; - /** - * Stores Keys, Values, and sub-nodes. Structure: (K,V)*,NODE; NODES are stored - * backwards. - */ - final Object[] content; - - /** - * Hash code derived from immutable hash code - */ - final int precalculatedHash; - - private ImmutableNode(int dataMap, int nodeMap, Object[] content, int precalculatedHash) { - super(); - this.dataMap = dataMap; - this.nodeMap = nodeMap; - this.content = content; - this.precalculatedHash = precalculatedHash; - } - - /** - * Constructor that copies a mutable node to an immutable. - * - * @param node A mutable node. - * @param cache A cache of existing immutable nodes. It can be used to search - * and place reference immutable nodes. It can be null, if no cache - * available. - * @return an immutable version of the input node. - */ - static ImmutableNode constructImmutable(MutableNode node, Map, ImmutableNode> cache) { - // 1. try to return from cache - if (cache != null) { - ImmutableNode cachedResult = cache.get(node); - if (cachedResult != null) { - // 1.1 Already cached, return from cache. - return cachedResult; - } - } - - // 2. otherwise construct a new ImmutableNode - int size = 0; - for (int i = 0; i < node.content.length; i++) { - if (node.content[i] != null) { - size++; - } - } - - int datas = 0; - int nodes = 0; - int resultDataMap = 0; - int resultNodeMap = 0; - final Object[] resultContent = new Object[size]; - int bitPosition = 1; - for (int i = 0; i < FACTOR; i++) { - Object key = node.content[i * 2]; - if (key != null) { - resultDataMap |= bitPosition; - resultContent[datas * 2] = key; - resultContent[datas * 2 + 1] = node.content[i * 2 + 1]; - datas++; - } else { - @SuppressWarnings("unchecked") var subnode = (Node) node.content[i * 2 + 1]; - if (subnode != null) { - ImmutableNode immutableSubNode = subnode.toImmutable(cache); - resultNodeMap |= bitPosition; - resultContent[size - 1 - nodes] = immutableSubNode; - nodes++; - } - } - bitPosition <<= 1; - } - final int resultHash = node.hashCode(); - var newImmutable = new ImmutableNode(resultDataMap, resultNodeMap, resultContent, resultHash); - - // 3. save new immutable. - if (cache != null) { - cache.put(newImmutable, newImmutable); - } - return newImmutable; - } - - private int index(int bitmap, int bitpos) { - return Integer.bitCount(bitmap & (bitpos - 1)); - } - - @Override - public V getValue(K key, ContinousHashProvider hashProvider, V defaultValue, int hash, int depth) { - int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); - int bitposition = 1 << selectedHashFragment; - // If the key is stored as a data - if ((dataMap & bitposition) != 0) { - int keyIndex = 2 * index(dataMap, bitposition); - @SuppressWarnings("unchecked") K keyCandidate = (K) content[keyIndex]; - if (keyCandidate.equals(key)) { - @SuppressWarnings("unchecked") V value = (V) content[keyIndex + 1]; - return value; - } else { - return defaultValue; - } - } - // the key is stored as a node - else if ((nodeMap & bitposition) != 0) { - int keyIndex = content.length - 1 - index(nodeMap, bitposition); - @SuppressWarnings("unchecked") var subNode = (ImmutableNode) content[keyIndex]; - int newDepth = incrementDepth(depth); - int newHash = newHash(hashProvider, key, hash, newDepth); - return subNode.getValue(key, hashProvider, defaultValue, newHash, newDepth); - } - // the key is not stored at all - else { - return defaultValue; - } - } - - @Override - public Node putValue(K key, V value, OldValueBox oldValue, ContinousHashProvider hashProvider, V defaultValue, int hash, int depth) { - int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); - int bitPosition = 1 << selectedHashFragment; - if ((dataMap & bitPosition) != 0) { - int keyIndex = 2 * index(dataMap, bitPosition); - @SuppressWarnings("unchecked") K keyCandidate = (K) content[keyIndex]; - if (keyCandidate.equals(key)) { - if (value == defaultValue) { - // delete - MutableNode mutable = this.toMutable(); - return mutable.removeEntry(selectedHashFragment, oldValue); - } else if (value == content[keyIndex + 1]) { - // dont change - oldValue.setOldValue(value); - return this; - } else { - // update existing value - MutableNode mutable = this.toMutable(); - return mutable.updateValue(value, oldValue, selectedHashFragment); - } - } else { - if (value == defaultValue) { - // dont change - oldValue.setOldValue(defaultValue); - return this; - } else { - // add new key + value - MutableNode mutable = this.toMutable(); - return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth); - } - } - } else if ((nodeMap & bitPosition) != 0) { - int keyIndex = content.length - 1 - index(nodeMap, bitPosition); - @SuppressWarnings("unchecked") var subNode = (ImmutableNode) content[keyIndex]; - int newDepth = incrementDepth(depth); - int newHash = newHash(hashProvider, key, hash, newDepth); - var newsubNode = subNode.putValue(key, value, oldValue, hashProvider, defaultValue, newHash, newDepth); - - if (subNode == newsubNode) { - // nothing changed - return this; - } else { - MutableNode mutable = toMutable(); - return mutable.updateWithSubNode(selectedHashFragment, newsubNode, - (value == null && defaultValue == null) || (value != null && value.equals(defaultValue))); - } - } else { - // add new key + value - MutableNode mutable = this.toMutable(); - return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth); - } - } - - @Override - public long getSize() { - int result = Integer.bitCount(this.dataMap); - for (int subnodeIndex = 0; subnodeIndex < Integer.bitCount(this.nodeMap); subnodeIndex++) { - @SuppressWarnings("unchecked") var subnode = (ImmutableNode) this.content[this.content.length - 1 - subnodeIndex]; - result += subnode.getSize(); - } - return result; - } - - @Override - protected MutableNode toMutable() { - return new MutableNode<>(this); - } - - @Override - public ImmutableNode toImmutable(Map, ImmutableNode> cache) { - return this; - } - - @Override - protected MutableNode isMutable() { - return null; - } - - @SuppressWarnings("unchecked") - @Override - boolean moveToNext(MapCursor cursor) { - // 1. try to move to data - int datas = Integer.bitCount(this.dataMap); - if (cursor.dataIndex != MapCursor.INDEX_FINISH) { - int newDataIndex = cursor.dataIndex + 1; - if (newDataIndex < datas) { - cursor.dataIndex = newDataIndex; - cursor.key = (K) this.content[newDataIndex * 2]; - cursor.value = (V) this.content[newDataIndex * 2 + 1]; - return true; - } else { - cursor.dataIndex = MapCursor.INDEX_FINISH; - } - } - - // 2. look inside the subnodes - int nodes = Integer.bitCount(this.nodeMap); - if(cursor.nodeIndexStack.peek()==null) { - throw new IllegalStateException("Cursor moved to the next state when the state is empty."); - } - int newNodeIndex = cursor.nodeIndexStack.peek() + 1; - if (newNodeIndex < nodes) { - // 2.1 found next subnode, move down to the subnode - Node subnode = (Node) this.content[this.content.length - 1 - newNodeIndex]; - cursor.dataIndex = MapCursor.INDEX_START; - cursor.nodeIndexStack.pop(); - cursor.nodeIndexStack.push(newNodeIndex); - cursor.nodeIndexStack.push(MapCursor.INDEX_START); - cursor.nodeStack.push(subnode); - return subnode.moveToNext(cursor); - } else { - // 3. no subnode found, move up - cursor.nodeStack.pop(); - cursor.nodeIndexStack.pop(); - if (!cursor.nodeStack.isEmpty()) { - Node supernode = cursor.nodeStack.peek(); - return supernode.moveToNext(cursor); - } else { - cursor.key = null; - cursor.value = null; - return false; - } - } - } - - @Override - @SuppressWarnings("unchecked") - boolean moveToNextInorder(InOrderMapCursor cursor) { - if(cursor.nodeIndexStack.peek()==null) { - throw new IllegalStateException("Cursor moved to the next state when the state is empty."); - } - - int position = cursor.nodeIndexStack.peek(); - for (int index = position + 1; index < FACTOR; index++) { - final int mask = 1< subnode = (Node) this.content[this.content.length - 1 - index(nodeMap, mask)]; - cursor.nodeIndexStack.pop(); - cursor.nodeIndexStack.push(index); - cursor.nodeIndexStack.push(InOrderMapCursor.INDEX_START); - cursor.nodeStack.push(subnode); - - return subnode.moveToNextInorder(cursor); - } - } - - // nothing found - cursor.nodeStack.pop(); - cursor.nodeIndexStack.pop(); - if (!cursor.nodeStack.isEmpty()) { - Node supernode = cursor.nodeStack.peek(); - return supernode.moveToNextInorder(cursor); - } else { - cursor.key = null; - cursor.value = null; - return false; - } - } - - @Override - public void prettyPrint(StringBuilder builder, int depth, int code) { - builder.append("\t".repeat(Math.max(0, depth))); - if (code >= 0) { - builder.append(code); - builder.append(":"); - } - builder.append("Immutable("); - boolean hadContent = false; - int dataMask = 1; - for (int i = 0; i < FACTOR; i++) { - if ((dataMask & dataMap) != 0) { - if (hadContent) { - builder.append(","); - } - builder.append(i); - builder.append(":["); - builder.append(content[2 * index(dataMap, dataMask)].toString()); - builder.append("]->["); - builder.append(content[2 * index(dataMap, dataMask) + 1].toString()); - builder.append("]"); - hadContent = true; - } - dataMask <<= 1; - } - builder.append(")"); - int nodeMask = 1; - for (int i = 0; i < FACTOR; i++) { - if ((nodeMask & nodeMap) != 0) { - @SuppressWarnings("unchecked") Node subNode = (Node) content[content.length - 1 - index(nodeMap, nodeMask)]; - builder.append("\n"); - subNode.prettyPrint(builder, incrementDepth(depth), i); - } - nodeMask <<= 1; - } - } - - @Override - public void checkIntegrity(ContinousHashProvider hashProvider, V defaultValue, int depth) { - if (depth > 0) { - boolean orphaned = Integer.bitCount(dataMap) == 1 && nodeMap == 0; - if (orphaned) { - throw new IllegalStateException("Orphaned node! " + dataMap + ": " + content[0]); - } - } - // check the place of data - - // check subnodes - for (int i = 0; i < Integer.bitCount(nodeMap); i++) { - @SuppressWarnings("unchecked") var subnode = (Node) this.content[this.content.length - 1 - i]; - if (!(subnode instanceof ImmutableNode)) { - throw new IllegalStateException("Immutable node contains mutable subnodes!"); - } else { - subnode.checkIntegrity(hashProvider, defaultValue, incrementDepth(depth)); - } - } - } - - @Override - public int hashCode() { - return this.precalculatedHash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (obj instanceof ImmutableNode other) { - return precalculatedHash == other.precalculatedHash && dataMap == other.dataMap && nodeMap == other.nodeMap && Arrays.deepEquals(content, other.content); - } else if (obj instanceof MutableNode mutableObj) { - return ImmutableNode.compareImmutableMutable(this, mutableObj); - } else { - return false; - } - } - - public static boolean compareImmutableMutable(ImmutableNode immutable, MutableNode mutable) { - int datas = 0; - int nodes = 0; - final int immutableLength = immutable.content.length; - for (int i = 0; i < FACTOR; i++) { - Object key = mutable.content[i * 2]; - // For each key candidate - if (key != null) { - // Check whether a new Key-Value pair can fit into the immutable container - if (datas * 2 + nodes + 2 <= immutableLength) { - if (!immutable.content[datas * 2].equals(key) || !immutable.content[datas * 2 + 1].equals(mutable.content[i * 2 + 1])) { - return false; - } - } else return false; - datas++; - } else { - var mutableSubnode = (Node) mutable.content[i * 2 + 1]; - if (mutableSubnode != null) { - if (datas * 2 + nodes + 1 <= immutableLength) { - Object immutableSubNode = immutable.content[immutableLength - 1 - nodes]; - if (!mutableSubnode.equals(immutableSubNode)) { - return false; - } - nodes++; - } else { - return false; - } - } - } - } - - return datas * 2 + nodes == immutable.content.length; - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/InOrderMapCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/InOrderMapCursor.java deleted file mode 100644 index cb3f366f..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/InOrderMapCursor.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -import tools.refinery.store.map.AnyVersionedMap; -import tools.refinery.store.map.ContentHashCode; -import tools.refinery.store.map.Cursor; -import tools.refinery.store.map.VersionedMap; - -import java.util.*; - -public class InOrderMapCursor implements Cursor { - // Constants - static final int INDEX_START = -1; - - // Tree stack - ArrayDeque> nodeStack; - ArrayDeque nodeIndexStack; - - - // Values - K key; - V value; - - // Hash code for checking concurrent modifications - final VersionedMap map; - final int creationHash; - - public InOrderMapCursor(VersionedMapImpl map) { - // Initializing tree stack - super(); - this.nodeStack = new ArrayDeque<>(); - this.nodeIndexStack = new ArrayDeque<>(); - if (map.root != null) { - this.nodeStack.add(map.root); - this.nodeIndexStack.push(INDEX_START); - } - - // Initializing cache - this.key = null; - this.value = null; - - // Initializing state - this.map = map; - this.creationHash = map.contentHashCode(ContentHashCode.APPROXIMATE_FAST); - } - - public K getKey() { - return key; - } - - public V getValue() { - return value; - } - - public boolean isTerminated() { - return this.nodeStack.isEmpty(); - } - - public boolean move() { - if (isDirty()) { - throw new ConcurrentModificationException(); - } - if (!isTerminated()) { - var node = this.nodeStack.peek(); - if (node == null) { - throw new IllegalStateException("Cursor is not terminated but the current node is missing"); - } - boolean result = node.moveToNextInorder(this); - if (this.nodeIndexStack.size() != this.nodeStack.size()) { - throw new IllegalArgumentException("Node stack is corrupted by illegal moves!"); - } - return result; - } - return false; - } - - public boolean skipCurrentNode() { - nodeStack.pop(); - nodeIndexStack.pop(); - return move(); - } - - @Override - public boolean isDirty() { - return this.map.contentHashCode(ContentHashCode.APPROXIMATE_FAST) != this.creationHash; - } - - @Override - public Set getDependingMaps() { - return Set.of(this.map); - } - - public static boolean sameSubNode(InOrderMapCursor cursor1, InOrderMapCursor cursor2) { - Node nodeOfCursor1 = cursor1.nodeStack.peek(); - Node nodeOfCursor2 = cursor2.nodeStack.peek(); - return Objects.equals(nodeOfCursor1, nodeOfCursor2); - } - - /** - * Compares the state of two cursors started on two {@link VersionedMap} of the same - * {@link tools.refinery.store.map.VersionedMapStore}. - * @param Key type - * @param Value type - * @param cursor1 first cursor - * @param cursor2 second cursor - * @return Positive number if cursor 1 is behind, negative number if cursor 2 is behind, and 0 if they are at the - * same position. - */ - public static int comparePosition(InOrderMapCursor cursor1, InOrderMapCursor cursor2) { - // If the state does not determine the order, then compare @nodeIndexStack. - Iterator nodeIndexStack1 = cursor1.nodeIndexStack.descendingIterator(); - Iterator nodeIndexStack2 = cursor2.nodeIndexStack.descendingIterator(); - - while(nodeIndexStack1.hasNext() && nodeIndexStack2.hasNext()){ - final int index1 = nodeIndexStack1.next(); - final int index2 = nodeIndexStack2.next(); - if(index1 < index2) { - return 1; - } else if(index1 > index2) { - return -1; - } - } - - return 0; - } - - /** - * Compares the depth of two cursors started on @{@link VersionedMap} of the same - * {@link tools.refinery.store.map.VersionedMapStore}. - * @param Key type - * @param Value type - * @param cursor1 first cursor - * @param cursor2 second cursor - * @return Positive number if cursor 1 is deeper, negative number if cursor 2 is deeper, and 0 if they are at the - * same depth. - */ - public static int compareDepth(InOrderMapCursor cursor1, InOrderMapCursor cursor2) { - int d1 = cursor1.nodeIndexStack.size(); - int d2 = cursor2.nodeIndexStack.size(); - return Integer.compare(d1, d2); - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/IteratorAsCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/IteratorAsCursor.java deleted file mode 100644 index d1ab8bb1..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/IteratorAsCursor.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -import java.util.*; -import java.util.Map.Entry; - -import tools.refinery.store.map.AnyVersionedMap; -import tools.refinery.store.map.Cursor; -import tools.refinery.store.map.VersionedMap; - -public class IteratorAsCursor implements Cursor { - final Iterator> iterator; - final VersionedMap source; - - private boolean terminated; - private K key; - private V value; - - public IteratorAsCursor(VersionedMap source, Map current) { - this.iterator = current.entrySet().iterator(); - this.source = source; - } - - @Override - public K getKey() { - return key; - } - - @Override - public V getValue() { - return value; - } - - @Override - public boolean isTerminated() { - return terminated; - } - - @Override - public boolean move() { - terminated = !iterator.hasNext(); - if (terminated) { - this.key = null; - this.value = null; - } else { - Entry next = iterator.next(); - this.key = next.getKey(); - this.value = next.getValue(); - } - return !terminated; - } - - @Override - public boolean isDirty() { - return false; - } - - @Override - public Set getDependingMaps() { - return Set.of(this.source); - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java deleted file mode 100644 index d42519b2..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -import tools.refinery.store.map.AnyVersionedMap; -import tools.refinery.store.map.ContentHashCode; -import tools.refinery.store.map.Cursor; -import tools.refinery.store.map.VersionedMap; - -import java.util.ArrayDeque; -import java.util.ConcurrentModificationException; -import java.util.Set; - -public class MapCursor implements Cursor { - // Constants - static final int INDEX_START = -1; - static final int INDEX_FINISH = -2; - - // Tree stack - ArrayDeque> nodeStack; - ArrayDeque nodeIndexStack; - int dataIndex; - - // Values - K key; - V value; - - // Hash code for checking concurrent modifications - final VersionedMap map; - final int creationHash; - - public MapCursor(Node root, VersionedMap map) { - // Initializing tree stack - super(); - this.nodeStack = new ArrayDeque<>(); - this.nodeIndexStack = new ArrayDeque<>(); - if (root != null) { - this.nodeStack.add(root); - this.nodeIndexStack.push(INDEX_START); - } - - this.dataIndex = INDEX_START; - - // Initializing cache - this.key = null; - this.value = null; - - // Initializing state - this.map = map; - this.creationHash = map.contentHashCode(ContentHashCode.APPROXIMATE_FAST); - } - - public K getKey() { - return key; - } - - public V getValue() { - return value; - } - - public boolean isTerminated() { - return this.nodeStack.isEmpty(); - } - - public boolean move() { - if (isDirty()) { - throw new ConcurrentModificationException(); - } - if (!isTerminated()) { - var node = this.nodeStack.peek(); - if (node == null) { - throw new IllegalStateException("Cursor is not terminated but the current node is missing"); - } - boolean result = node.moveToNext(this); - if (this.nodeIndexStack.size() != this.nodeStack.size()) { - throw new IllegalArgumentException("Node stack is corrupted by illegal moves!"); - } - return result; - } - return false; - } - - @Override - public boolean isDirty() { - return this.map.contentHashCode(ContentHashCode.APPROXIMATE_FAST) != this.creationHash; - } - - @Override - public Set getDependingMaps() { - return Set.of(this.map); - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDelta.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDelta.java deleted file mode 100644 index 2674236c..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDelta.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -public record MapDelta(K key, V oldValue, V newValue) { - public K getKey() { - return key; - } - - public V getOldValue() { - return oldValue; - } - - public V getNewValue() { - return newValue; - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java deleted file mode 100644 index fb1d5d2b..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -import tools.refinery.store.map.AnyVersionedMap; -import tools.refinery.store.map.Cursor; -import tools.refinery.store.map.DiffCursor; - -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * A cursor representing the difference between two states of a map. - * - * @author Oszkar Semerath - */ -public class MapDiffCursor implements DiffCursor, Cursor { - private enum State { - /** - * initialized state. - */ - INIT, - /** - * Unstable state. - */ - MOVING_MOVING_SAME_KEY_SAME_VALUE, - /** - * Both cursors are moving, and they are on the same sub-node. - */ - MOVING_MOVING_SAME_NODE, - /** - * Both cursors are moving, cursor 1 is behind. - */ - MOVING_MOVING_BEHIND1, - /** - * Both cursors are moving, cursor 2 is behind. - */ - MOVING_MOVING_BEHIND2, - /** - * Both cursors are moving, cursor 1 is on the same key as cursor 2, values are different - */ - MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE, - /** - * Cursor 1 is moving, Cursor 2 is terminated. - */ - MOVING_TERMINATED, - /** - * Cursor 1 is terminated , Cursor 2 is moving. - */ - TERMINATED_MOVING, - /** - * Both cursors are terminated. - */ - TERMINATED_TERMINATED, - /** - * Both Cursors are moving, and they are on an incomparable position. - * It is resolved by showing Cursor 1. - */ - MOVING_MOVING_HASH1, - /** - * Both Cursors are moving, and they are on an incomparable position. - * It is resolved by showing Cursor 2. - */ - MOVING_MOVING_HASH2 - } - - /** - * Default nodeId representing missing elements. - */ - private final V defaultValue; - private final InOrderMapCursor cursor1; - private final InOrderMapCursor cursor2; - - // State - State state = State.INIT; - - // Values - private K key; - private V fromValue; - private V toValue; - - - public MapDiffCursor(V defaultValue, InOrderMapCursor cursor1, InOrderMapCursor cursor2) { - super(); - this.defaultValue = defaultValue; - this.cursor1 = cursor1; - this.cursor2 = cursor2; - } - - @Override - public K getKey() { - return key; - } - - @Override - public V getFromValue() { - return fromValue; - } - - @Override - public V getToValue() { - return toValue; - } - - @Override - public V getValue() { - return getToValue(); - } - - public boolean isTerminated() { - return this.state == State.TERMINATED_TERMINATED; - } - - @Override - public boolean isDirty() { - return this.cursor1.isDirty() || this.cursor2.isDirty(); - } - - @Override - public Set getDependingMaps() { - return Stream.concat(cursor1.getDependingMaps().stream(), cursor2.getDependingMaps().stream()).map(AnyVersionedMap.class::cast).collect(Collectors.toUnmodifiableSet()); - } - - private boolean isInStableState() { - return this.state != State.MOVING_MOVING_SAME_KEY_SAME_VALUE - && this.state != State.MOVING_MOVING_SAME_NODE && this.state != State.INIT; - } - - private boolean updateAndReturnWithResult() { - return switch (this.state) { - case INIT -> throw new IllegalStateException("DiffCursor terminated without starting!"); - case MOVING_MOVING_SAME_KEY_SAME_VALUE, MOVING_MOVING_SAME_NODE -> - throw new IllegalStateException("DiffCursor terminated in unstable state!"); - case MOVING_MOVING_BEHIND1, MOVING_TERMINATED, MOVING_MOVING_HASH1 -> { - this.key = this.cursor1.getKey(); - this.fromValue = this.cursor1.getValue(); - this.toValue = this.defaultValue; - yield true; - } - case MOVING_MOVING_BEHIND2, TERMINATED_MOVING, MOVING_MOVING_HASH2 -> { - this.key = this.cursor2.getKey(); - this.fromValue = this.defaultValue; - this.toValue = cursor2.getValue(); - yield true; - } - case MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE -> { - this.key = this.cursor1.getKey(); - this.fromValue = this.cursor1.getValue(); - this.toValue = this.cursor2.getValue(); - yield true; - } - case TERMINATED_TERMINATED -> { - this.key = null; - this.fromValue = null; - this.toValue = null; - yield false; - } - }; - } - - public boolean move() { - do { - this.state = moveOne(this.state); - } while (!isInStableState()); - return updateAndReturnWithResult(); - } - - private State moveOne(State currentState) { - return switch (currentState) { - case INIT, MOVING_MOVING_HASH2, MOVING_MOVING_SAME_KEY_SAME_VALUE, MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE -> { - boolean cursor1Moved = cursor1.move(); - boolean cursor2Moved = cursor2.move(); - yield recalculateStateAfterCursorMovement(cursor1Moved, cursor2Moved); - } - case MOVING_MOVING_SAME_NODE -> { - boolean cursor1Moved = cursor1.skipCurrentNode(); - boolean cursor2Moved = cursor2.skipCurrentNode(); - yield recalculateStateAfterCursorMovement(cursor1Moved, cursor2Moved); - } - case MOVING_MOVING_BEHIND1 -> { - boolean cursorMoved = cursor1.move(); - if (cursorMoved) { - yield recalculateStateBasedOnCursorRelation(); - } else { - yield State.TERMINATED_MOVING; - } - } - case MOVING_MOVING_BEHIND2 -> { - boolean cursorMoved = cursor2.move(); - if (cursorMoved) { - yield recalculateStateBasedOnCursorRelation(); - } else { - yield State.MOVING_TERMINATED; - } - } - case TERMINATED_MOVING -> { - boolean cursorMoved = cursor2.move(); - if (cursorMoved) { - yield State.TERMINATED_MOVING; - } else { - yield State.TERMINATED_TERMINATED; - } - } - case MOVING_TERMINATED -> { - boolean cursorMoved = cursor1.move(); - if (cursorMoved) { - yield State.MOVING_TERMINATED; - } else { - yield State.TERMINATED_TERMINATED; - } - } - case MOVING_MOVING_HASH1 -> State.MOVING_MOVING_HASH2; - case TERMINATED_TERMINATED -> throw new IllegalStateException("Trying to move while terminated!"); - }; - } - - private State recalculateStateAfterCursorMovement(boolean cursor1Moved, boolean cursor2Moved) { - if (cursor1Moved && cursor2Moved) { - return recalculateStateBasedOnCursorRelation(); - } else if (cursor1Moved) { - return State.MOVING_TERMINATED; - } else if (cursor2Moved) { - return State.TERMINATED_MOVING; - } else { - return State.TERMINATED_TERMINATED; - } - } - - private State recalculateStateBasedOnCursorRelation() { - final int relation = InOrderMapCursor.comparePosition(cursor1, cursor2); - - if (relation > 0) { - return State.MOVING_MOVING_BEHIND1; - } else if (relation < 0) { - return State.MOVING_MOVING_BEHIND2; - } - - if (InOrderMapCursor.sameSubNode(cursor1, cursor2)) { - return State.MOVING_MOVING_SAME_NODE; - } else if (Objects.equals(cursor1.getKey(), cursor2.getKey())) { - if (Objects.equals(cursor1.getValue(), cursor2.getValue())) { - return State.MOVING_MOVING_SAME_KEY_SAME_VALUE; - } else { - return State.MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE; - } - } - - final int depth = InOrderMapCursor.compareDepth(cursor1, cursor2); - - if (depth > 0) { - return State.MOVING_MOVING_BEHIND1; - } else if (depth < 0) { - return State.MOVING_MOVING_BEHIND2; - } else { - return State.MOVING_MOVING_HASH1; - } - - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapTransaction.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapTransaction.java deleted file mode 100644 index d63522cd..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapTransaction.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -import java.util.Arrays; -import java.util.Objects; - -public record MapTransaction(MapDelta[] deltas, long version, MapTransaction parent) { - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.hashCode(deltas); - result = prime * result + Objects.hash(parent, version); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - @SuppressWarnings("unchecked") - MapTransaction other = (MapTransaction) obj; - return Arrays.equals(deltas, other.deltas) && Objects.equals(parent, other.parent) && version == other.version; - } - - @Override - public String toString() { - return "MapTransaction " + version + " " + Arrays.toString(deltas); - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java deleted file mode 100644 index bb85deb9..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java +++ /dev/null @@ -1,499 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -import tools.refinery.store.map.ContinousHashProvider; - -import java.util.Arrays; -import java.util.Map; - -public class MutableNode extends Node { - int cachedHash; - protected boolean cachedHashValid; - protected Object[] content; - - protected MutableNode() { - this.content = new Object[2 * FACTOR]; - invalidateHash(); - } - - public static MutableNode initialize(K key, V value, ContinousHashProvider hashProvider, V defaultValue) { - if (value == defaultValue) { - return null; - } else { - int hash = hashProvider.getHash(key, 0); - int fragment = hashFragment(hash, 0); - MutableNode res = new MutableNode<>(); - res.content[2 * fragment] = key; - res.content[2 * fragment + 1] = value; - res.invalidateHash(); - return res; - } - } - - /** - * Constructs a {@link MutableNode} as a copy of an {@link ImmutableNode} - * - * @param node to be transformed - */ - protected MutableNode(ImmutableNode node) { - this.content = new Object[2 * FACTOR]; - int dataUsed = 0; - int nodeUsed = 0; - for (int i = 0; i < FACTOR; i++) { - int bitPosition = 1 << i; - if ((node.dataMap & bitPosition) != 0) { - content[2 * i] = node.content[dataUsed * 2]; - content[2 * i + 1] = node.content[dataUsed * 2 + 1]; - dataUsed++; - } else if ((node.nodeMap & bitPosition) != 0) { - content[2 * i + 1] = node.content[node.content.length - 1 - nodeUsed]; - nodeUsed++; - } - } - this.cachedHashValid = false; - } - - @Override - public V getValue(K key, ContinousHashProvider hashProvider, V defaultValue, int hash, int depth) { - int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); - @SuppressWarnings("unchecked") K keyCandidate = (K) this.content[2 * selectedHashFragment]; - if (keyCandidate != null) { - if (keyCandidate.equals(key)) { - @SuppressWarnings("unchecked") V value = (V) this.content[2 * selectedHashFragment + 1]; - return value; - } else { - return defaultValue; - } - } else { - @SuppressWarnings("unchecked") var nodeCandidate = (Node) content[2 * selectedHashFragment + 1]; - if (nodeCandidate != null) { - int newDepth = incrementDepth(depth); - int newHash = newHash(hashProvider, key, hash, newDepth); - return nodeCandidate.getValue(key, hashProvider, defaultValue, newHash, newDepth); - } else { - return defaultValue; - } - } - } - - @Override - public Node putValue(K key, V value, OldValueBox oldValueBox, ContinousHashProvider hashProvider, V defaultValue, int hash, int depth) { - int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); - @SuppressWarnings("unchecked") K keyCandidate = (K) content[2 * selectedHashFragment]; - if (keyCandidate != null) { - // If it has key - if (keyCandidate.equals(key)) { - // The key is equals to an existing key -> update entry - if (value == defaultValue) { - return removeEntry(selectedHashFragment, oldValueBox); - } else { - return updateValue(value, oldValueBox, selectedHashFragment); - } - } else { - // The key is not equivalent to an existing key on the same hash bin - // -> split entry if it is necessary - if (value == defaultValue) { - // Value is default -> do not need to add new node - oldValueBox.setOldValue(defaultValue); - return this; - } else { - // Value is not default -> Split entry data to a new node - oldValueBox.setOldValue(defaultValue); - return moveDownAndSplit(hashProvider, key, value, keyCandidate, hash, depth, selectedHashFragment); - } - } - } - // If it does not have key, check for value - @SuppressWarnings("unchecked") var nodeCandidate = (Node) content[2 * selectedHashFragment + 1]; - if (nodeCandidate != null) { - // If it has value, it is a sub-node -> update that - int newDepth = incrementDepth(depth); - var newNode = nodeCandidate.putValue(key, value, oldValueBox, hashProvider, defaultValue, newHash(hashProvider, key, hash, newDepth), newDepth); - return updateWithSubNode(selectedHashFragment, newNode, (value == null && defaultValue == null) || (value != null && value.equals(defaultValue))); - } else { - // If it does not have value, put it in the empty place - if (value == defaultValue) { - // don't need to add new key-value pair - oldValueBox.setOldValue(defaultValue); - return this; - } else { - return addEntry(key, value, oldValueBox, selectedHashFragment, defaultValue); - } - } - } - - private Node addEntry(K key, V value, OldValueBox oldValueBox, int selectedHashFragment, V defaultValue) { - content[2 * selectedHashFragment] = key; - oldValueBox.setOldValue(defaultValue); - content[2 * selectedHashFragment + 1] = value; - invalidateHash(); - return this; - } - - /** - * Updates an entry in a selected hash-fragment to a non-default value. - * - * @param value new value - * @param selectedHashFragment position of the value - * @return updated node - */ - @SuppressWarnings("unchecked") - Node updateValue(V value, OldValueBox oldValue, int selectedHashFragment) { - oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]); - content[2 * selectedHashFragment + 1] = value; - invalidateHash(); - return this; - } - - /** - * Updates an entry in a selected hash-fragment with a subtree. - * - * @param selectedHashFragment position of the value - * @param newNode the subtree - * @return updated node - */ - Node updateWithSubNode(int selectedHashFragment, Node newNode, boolean deletionHappened) { - if (deletionHappened) { - if (newNode == null) { - // Check whether this node become empty - content[2 * selectedHashFragment + 1] = null; // i.e. the new node - if (hasContent()) { - invalidateHash(); - return this; - } else { - return null; - } - } else { - // check whether newNode is orphan - MutableNode immutableNewNode = newNode.isMutable(); - if (immutableNewNode != null) { - int orphaned = immutableNewNode.isOrphaned(); - if (orphaned >= 0) { - // orphan sub-node data is replaced with data - content[2 * selectedHashFragment] = immutableNewNode.content[orphaned * 2]; - content[2 * selectedHashFragment + 1] = immutableNewNode.content[orphaned * 2 + 1]; - invalidateHash(); - return this; - } - } - } - } - // normal behaviour - content[2 * selectedHashFragment + 1] = newNode; - invalidateHash(); - return this; - } - - private boolean hasContent() { - for (Object element : this.content) { - if (element != null) return true; - } - return false; - } - - @Override - protected MutableNode isMutable() { - return this; - } - - protected int isOrphaned() { - int dataFound = -2; - for (int i = 0; i < FACTOR; i++) { - if (content[i * 2] != null) { - if (dataFound >= 0) { - return -1; - } else { - dataFound = i; - } - } else if (content[i * 2 + 1] != null) { - return -3; - } - } - return dataFound; - } - - @SuppressWarnings("unchecked") - private Node moveDownAndSplit(ContinousHashProvider hashProvider, K newKey, V newValue, K previousKey, int hashOfNewKey, int depth, int selectedHashFragmentOfCurrentDepth) { - V previousValue = (V) content[2 * selectedHashFragmentOfCurrentDepth + 1]; - - MutableNode newSubNode = newNodeWithTwoEntries(hashProvider, previousKey, previousValue, hashProvider.getHash(previousKey, hashDepth(depth)), newKey, newValue, hashOfNewKey, incrementDepth(depth)); - - content[2 * selectedHashFragmentOfCurrentDepth] = null; - content[2 * selectedHashFragmentOfCurrentDepth + 1] = newSubNode; - invalidateHash(); - return this; - } - - // Pass everything as parameters for performance. - @SuppressWarnings("squid:S107") - private MutableNode newNodeWithTwoEntries(ContinousHashProvider hashProvider, K key1, V value1, int oldHash1, K key2, V value2, int oldHash2, int newDepth) { - int newHash1 = newHash(hashProvider, key1, oldHash1, newDepth); - int newHash2 = newHash(hashProvider, key2, oldHash2, newDepth); - int newFragment1 = hashFragment(newHash1, shiftDepth(newDepth)); - int newFragment2 = hashFragment(newHash2, shiftDepth(newDepth)); - - MutableNode subNode = new MutableNode<>(); - if (newFragment1 != newFragment2) { - subNode.content[newFragment1 * 2] = key1; - subNode.content[newFragment1 * 2 + 1] = value1; - - subNode.content[newFragment2 * 2] = key2; - subNode.content[newFragment2 * 2 + 1] = value2; - } else { - MutableNode subSubNode = newNodeWithTwoEntries(hashProvider, key1, value1, newHash1, key2, value2, newHash2, incrementDepth(newDepth)); - subNode.content[newFragment1 * 2 + 1] = subSubNode; - } - subNode.invalidateHash(); - return subNode; - } - - @SuppressWarnings("unchecked") - Node removeEntry(int selectedHashFragment, OldValueBox oldValue) { - content[2 * selectedHashFragment] = null; - oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]); - content[2 * selectedHashFragment + 1] = null; - if (hasContent()) { - invalidateHash(); - return this; - } else { - return null; - } - } - - @SuppressWarnings("unchecked") - @Override - public long getSize() { - int size = 0; - for (int i = 0; i < FACTOR; i++) { - if (content[i * 2] != null) { - size++; - } else { - Node nodeCandidate = (Node) content[i * 2 + 1]; - if (nodeCandidate != null) { - size += nodeCandidate.getSize(); - } - } - } - return size; - } - - @Override - protected MutableNode toMutable() { - return this; - } - - @Override - public ImmutableNode toImmutable(Map, ImmutableNode> cache) { - return ImmutableNode.constructImmutable(this, cache); - } - - @SuppressWarnings("unchecked") - @Override - boolean moveToNext(MapCursor cursor) { - // 1. try to move to data - if (cursor.dataIndex != MapCursor.INDEX_FINISH) { - for (int index = cursor.dataIndex + 1; index < FACTOR; index++) { - if (this.content[index * 2] != null) { - // 1.1 found next data - cursor.dataIndex = index; - cursor.key = (K) this.content[index * 2]; - cursor.value = (V) this.content[index * 2 + 1]; - return true; - } - } - cursor.dataIndex = MapCursor.INDEX_FINISH; - } - - // 2. look inside the sub-nodes - if(cursor.nodeIndexStack.peek()==null) { - throw new IllegalStateException("Cursor moved to the next state when the state is empty."); - } - for (int index = cursor.nodeIndexStack.peek() + 1; index < FACTOR; index++) { - if (this.content[index * 2] == null && this.content[index * 2 + 1] != null) { - // 2.1 found next sub-node, move down to the sub-node - Node subnode = (Node) this.content[index * 2 + 1]; - - cursor.dataIndex = MapCursor.INDEX_START; - cursor.nodeIndexStack.pop(); - cursor.nodeIndexStack.push(index); - cursor.nodeIndexStack.push(MapCursor.INDEX_START); - cursor.nodeStack.push(subnode); - - return subnode.moveToNext(cursor); - } - } - // 3. no sub-node found, move up - cursor.nodeStack.pop(); - cursor.nodeIndexStack.pop(); - if (!cursor.nodeStack.isEmpty()) { - Node supernode = cursor.nodeStack.peek(); - return supernode.moveToNext(cursor); - } else { - cursor.key = null; - cursor.value = null; - return false; - } - } - - @Override - @SuppressWarnings("unchecked") - boolean moveToNextInorder(InOrderMapCursor cursor) { - if(cursor.nodeIndexStack.peek()==null || cursor.nodeStack.peek()==null) { - throw new IllegalStateException("Cursor moved to the next state when the state is empty."); - } - - int position = cursor.nodeIndexStack.peek(); - - for (int index = position + 1; index < FACTOR; index++) { - // data found - if (this.content[index * 2] != null) { - cursor.nodeIndexStack.pop(); - cursor.nodeIndexStack.push(index); - - cursor.key = (K) this.content[index * 2]; - cursor.value = (V) this.content[index * 2 + 1]; - return true; - } else if (this.content[index * 2 +1] != null) { - // sub-node found - Node subnode = (Node) this.content[index * 2 +1]; - cursor.nodeIndexStack.pop(); - cursor.nodeIndexStack.push(index); - cursor.nodeIndexStack.push(InOrderMapCursor.INDEX_START); - cursor.nodeStack.push(subnode); - - return subnode.moveToNextInorder(cursor); - } - } - - // nothing found - cursor.nodeStack.pop(); - cursor.nodeIndexStack.pop(); - if (!cursor.nodeStack.isEmpty()) { - Node supernode = cursor.nodeStack.peek(); - return supernode.moveToNextInorder(cursor); - } else { - cursor.key = null; - cursor.value = null; - return false; - } - } - - @Override - public void prettyPrint(StringBuilder builder, int depth, int code) { - builder.append("\t".repeat(Math.max(0, depth))); - if (code >= 0) { - builder.append(code); - builder.append(":"); - } - builder.append("Mutable("); - // print content - boolean hadContent = false; - for (int i = 0; i < FACTOR; i++) { - if (content[2 * i] != null) { - if (hadContent) { - builder.append(","); - } - builder.append(i); - builder.append(":["); - builder.append(content[2 * i].toString()); - builder.append("]->["); - builder.append(content[2 * i + 1].toString()); - builder.append("]"); - hadContent = true; - } - } - builder.append(")"); - // print sub-nodes - for (int i = 0; i < FACTOR; i++) { - if (content[2 * i] == null && content[2 * i + 1] != null) { - @SuppressWarnings("unchecked") Node subNode = (Node) content[2 * i + 1]; - builder.append("\n"); - subNode.prettyPrint(builder, incrementDepth(depth), i); - } - } - } - - @Override - public void checkIntegrity(ContinousHashProvider hashProvider, V defaultValue, int depth) { - // check for orphan nodes - if (depth > 0) { - int orphaned = isOrphaned(); - if (orphaned >= 0) { - throw new IllegalStateException("Orphaned node! " + orphaned + ": " + content[2 * orphaned]); - } - } - // check the place of data - for (int i = 0; i < FACTOR; i++) { - if (this.content[2 * i] != null) { - @SuppressWarnings("unchecked") K key = (K) this.content[2 * i]; - @SuppressWarnings("unchecked") V value = (V) this.content[2 * i + 1]; - - if (value == defaultValue) { - throw new IllegalStateException("Node contains default value!"); - } - int hashCode = hashProvider.getHash(key, hashDepth(depth)); - int shiftDepth = shiftDepth(depth); - int selectedHashFragment = hashFragment(hashCode, shiftDepth); - if (i != selectedHashFragment) { - throw new IllegalStateException("Key " + key + " with hash code " + hashCode + " is in bad place! Fragment=" + selectedHashFragment + ", Place=" + i); - } - } - } - // check sub-nodes - for (int i = 0; i < FACTOR; i++) { - if (this.content[2 * i + 1] != null && this.content[2 * i] == null) { - @SuppressWarnings("unchecked") var subNode = (Node) this.content[2 * i + 1]; - subNode.checkIntegrity(hashProvider, defaultValue, incrementDepth(depth)); - } - } - // check the hash - if (cachedHashValid) { - int oldHash = this.cachedHash; - invalidateHash(); - int newHash = hashCode(); - if (oldHash != newHash) { - throw new IllegalStateException("Hash code was not up to date! (old=" + oldHash + ",new=" + newHash + ")"); - } - } - } - - protected void invalidateHash() { - this.cachedHashValid = false; - } - - @Override - public int hashCode() { - if (!this.cachedHashValid) { - this.cachedHash = Arrays.hashCode(content); - this.cachedHashValid = true; - } - return this.cachedHash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (obj instanceof MutableNode mutableObj) { - if (obj.hashCode() != this.hashCode()) { - return false; - } else { - for (int i = 0; i < FACTOR * 2; i++) { - Object thisContent = this.content[i]; - if (thisContent != null && !thisContent.equals(mutableObj.content[i])) { - return false; - } - } - return true; - } - } else if (obj instanceof ImmutableNode immutableObj) { - return ImmutableNode.compareImmutableMutable(immutableObj, this); - } else { - return false; - } - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java deleted file mode 100644 index 4b44f760..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -import java.util.Map; - -import tools.refinery.store.map.ContinousHashProvider; - -public abstract class Node { - public static final int BRANCHING_FACTOR_BITS = 5; - public static final int FACTOR = 1 << BRANCHING_FACTOR_BITS; - protected static final int NUMBER_OF_FACTORS = Integer.SIZE / BRANCHING_FACTOR_BITS; - protected static final int FACTOR_MASK = FACTOR - 1; - public static final int EFFECTIVE_BITS = BRANCHING_FACTOR_BITS * NUMBER_OF_FACTORS; - public static final int FACTOR_SELECTION_BITS = 32 - Integer.numberOfLeadingZeros(NUMBER_OF_FACTORS); - public static final int FACTOR_SELECTION_MASK = (1 << FACTOR_SELECTION_BITS) - 1; - public static final int INCREMENT_BIG_STEP = (1 << FACTOR_SELECTION_BITS) - NUMBER_OF_FACTORS; - - /** - * Increments the depth of the search in the tree. The depth parameter has two - * components: the least few bits selects the fragment of the hashcode, the - * other part selects the continuous hash. - * - * @param depth parameter encoding the fragment and the depth - * @return new depth. - */ - protected int incrementDepth(int depth) { - int newDepth = depth + 1; - if ((newDepth & FACTOR_SELECTION_MASK) == NUMBER_OF_FACTORS) { - newDepth += INCREMENT_BIG_STEP; - } - return newDepth; - } - - /** - * Calculates the index for the continuous hash. - * - * @param depth The depth of the node in the tree. - * @return The index of the continuous hash. - */ - protected static int hashDepth(int depth) { - return depth >> FACTOR_SELECTION_BITS; - } - - /** - * Calculates the which segment of a single hash should be used. - * - * @param depth The depth of the node in the tree. - * @return The segment of a hash code. - */ - protected static int shiftDepth(int depth) { - return depth & FACTOR_SELECTION_MASK; - } - - /** - * Selects a segments from a complete hash for a given depth. - * - * @param hash A complete hash. - * @param shiftDepth The index of the segment. - * @return The segment as an integer. - */ - protected static int hashFragment(int hash, int shiftDepth) { - if (shiftDepth < 0 || Node.NUMBER_OF_FACTORS < shiftDepth) - throw new IllegalArgumentException("Invalid shift depth! valid interval=[0;5], input=" + shiftDepth); - return (hash >>> shiftDepth * BRANCHING_FACTOR_BITS) & FACTOR_MASK; - } - - /** - * Returns the hash code for a given depth. It may calculate new hash code, or - * reuse a hash code calculated for depth-1. - * - * @param key The key. - * @param hash Hash code for depth-1 - * @param depth The depth. - * @return The new hash code. - */ - protected int newHash(final ContinousHashProvider hashProvider, K key, int hash, int depth) { - final int shiftDepth = shiftDepth(depth); - if (shiftDepth == 0) { - final int hashDepth = hashDepth(depth); - if (hashDepth >= ContinousHashProvider.MAX_PRACTICAL_DEPTH) { - throw new IllegalArgumentException( - "Key " + key + " have the clashing hashcode over the practical depth limitation (" - + ContinousHashProvider.MAX_PRACTICAL_DEPTH + ")!"); - } - return hashProvider.getHash(key, hashDepth); - } else { - return hash; - } - } - - public abstract V getValue(K key, ContinousHashProvider hashProvider, V defaultValue, int hash, - int depth); - - public abstract Node putValue(K key, V value, OldValueBox old, - ContinousHashProvider hashProvider, V defaultValue, int hash, int depth); - - public abstract long getSize(); - - abstract MutableNode toMutable(); - - public abstract ImmutableNode toImmutable(Map, ImmutableNode> cache); - - protected abstract MutableNode isMutable(); - - /** - * Moves a {@link MapCursor} to its next position. - * - * @param cursor the cursor - * @return Whether there was a next value to move on. - */ - abstract boolean moveToNext(MapCursor cursor); - abstract boolean moveToNextInorder(InOrderMapCursor cursor); - - ///////// FOR printing - public abstract void prettyPrint(StringBuilder builder, int depth, int code); - - - @Override - public String toString() { - StringBuilder stringBuilder = new StringBuilder(); - prettyPrint(stringBuilder, 0, -1); - return stringBuilder.toString(); - } - - public void checkIntegrity(ContinousHashProvider hashProvider, V defaultValue, int depth) { - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/OldValueBox.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/OldValueBox.java deleted file mode 100644 index 354af51d..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/OldValueBox.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -public class OldValueBox{ - V oldValue; - boolean isSet = false; - - public V getOldValue() { - if(!isSet) throw new IllegalStateException(); - isSet = false; - return oldValue; - } - - public void setOldValue(V ouldValue) { - if(isSet) throw new IllegalStateException(); - this.oldValue = ouldValue; - isSet = true; - } - -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/StateBasedVersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/StateBasedVersionedMapStoreFactory.java deleted file mode 100644 index 1c3ab27b..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/StateBasedVersionedMapStoreFactory.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -import tools.refinery.store.map.*; - -import java.util.List; - -public class StateBasedVersionedMapStoreFactory implements VersionedMapStoreFactory { - private final V defaultValue; - private final ContinousHashProvider continousHashProvider; - private final VersionedMapStoreConfiguration config; - - public StateBasedVersionedMapStoreFactory(V defaultValue, Boolean transformToImmutable, VersionedMapStoreFactoryBuilder.SharingStrategy sharingStrategy, ContinousHashProvider continousHashProvider) { - this.defaultValue = defaultValue; - this.continousHashProvider = continousHashProvider; - - this.config = new VersionedMapStoreConfiguration( - transformToImmutable, - sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE || sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE_IN_GROUP, - sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE_IN_GROUP); - } - - @Override - public VersionedMapStore createOne() { - return new VersionedMapStoreImpl<>(continousHashProvider, defaultValue, config); - - } - - @Override - public List> createGroup(int amount) { - return VersionedMapStoreImpl.createSharedVersionedMapStores(amount, continousHashProvider, defaultValue, - config); - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaArrayStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaArrayStore.java deleted file mode 100644 index ba59cfef..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaArrayStore.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -import java.util.ArrayList; -import java.util.List; - -public class UncommittedDeltaArrayStore implements UncommittedDeltaStore { - final List> uncommittedOldValues = new ArrayList<>(); - - @Override - public void processChange(K key, V oldValue, V newValue) { - uncommittedOldValues.add(new MapDelta<>(key, oldValue, newValue)); - } - - @Override - public MapDelta[] extractDeltas() { - if (uncommittedOldValues.isEmpty()) { - return null; - } else { - @SuppressWarnings("unchecked") - MapDelta[] result = uncommittedOldValues.toArray(new MapDelta[0]); - return result; - } - } - - @Override - public MapDelta[] extractAndDeleteDeltas() { - MapDelta[] res = extractDeltas(); - this.uncommittedOldValues.clear(); - return res; - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaMapStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaMapStore.java deleted file mode 100644 index 61a34351..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaMapStore.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -import java.util.*; -import java.util.Map.Entry; - -import tools.refinery.store.map.VersionedMap; - -public class UncommittedDeltaMapStore implements UncommittedDeltaStore { - final VersionedMap source; - final Map uncommittedOldValues = new HashMap<>(); - - public UncommittedDeltaMapStore(VersionedMap source) { - this.source = source; - } - - @Override - public void processChange(K key, V oldValue, V newValue) { - if(!uncommittedOldValues.containsKey(key)) { - this.uncommittedOldValues.put(key,oldValue); - } - } - - @Override - public MapDelta[] extractDeltas() { - if (uncommittedOldValues.isEmpty()) { - return null; - } else { - @SuppressWarnings("unchecked") - MapDelta[] deltas = new MapDelta[uncommittedOldValues.size()]; - int i = 0; - for (Entry entry : uncommittedOldValues.entrySet()) { - final K key = entry.getKey(); - final V oldValue = entry.getValue(); - final V newValue = source.get(key); - deltas[i++] = new MapDelta<>(key, oldValue, newValue); - } - - return deltas; - } - } - - @Override - public MapDelta[] extractAndDeleteDeltas() { - MapDelta[] res = extractDeltas(); - this.uncommittedOldValues.clear(); - return res; - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaStore.java deleted file mode 100644 index 438b5561..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/UncommittedDeltaStore.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -public interface UncommittedDeltaStore { - void processChange(K key, V oldValue, V newValue); - - MapDelta[] extractDeltas(); - - MapDelta[] extractAndDeleteDeltas(); - - default void checkIntegrity() { - MapDelta[] extractedDeltas = extractDeltas(); - if(extractedDeltas != null) { - for(var uncommittedOldValue : extractedDeltas) { - if(uncommittedOldValue == null) { - throw new IllegalArgumentException("Null entry in deltas!"); - } - if(uncommittedOldValue.getKey() == null) { - throw new IllegalStateException("Null key in deltas!"); - } - } - } - } - -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapDeltaImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapDeltaImpl.java deleted file mode 100644 index ae47feda..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapDeltaImpl.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -import java.util.*; - -import tools.refinery.store.map.*; - -public class VersionedMapDeltaImpl implements VersionedMap { - protected final VersionedMapStoreDeltaImpl store; - - final Map current; - - final UncommittedDeltaStore uncommittedStore; - MapTransaction previous; - - protected final V defaultValue; - - public VersionedMapDeltaImpl(VersionedMapStoreDeltaImpl store, boolean summarizeChanges, V defaultValue) { - this.store = store; - this.defaultValue = defaultValue; - - current = new HashMap<>(); - if (summarizeChanges) { - this.uncommittedStore = new UncommittedDeltaMapStore<>(this); - } else { - this.uncommittedStore = new UncommittedDeltaArrayStore<>(); - } - } - - @Override - public V getDefaultValue() { - return defaultValue; - } - - @Override - public long commit() { - MapDelta[] deltas = uncommittedStore.extractAndDeleteDeltas(); - long[] versionContainer = new long[1]; - this.previous = this.store.appendTransaction(deltas, previous, versionContainer); - return versionContainer[0]; - } - - @Override - public void restore(long state) { - // 1. restore uncommitted states - MapDelta[] uncommitted = this.uncommittedStore.extractAndDeleteDeltas(); - if (uncommitted != null) { - backward(uncommitted); - } - - // 2. get common ancestor - final MapTransaction parent; - List[]> forward = new ArrayList<>(); - if (this.previous == null) { - parent = this.store.getPath(state, forward); - this.forward(forward); - } else { - List[]> backward = new ArrayList<>(); - parent = this.store.getPath(this.previous.version(), state, backward, forward); - this.backward(backward); - this.forward(forward); - } - this.previous = parent; - } - - protected void forward(List[]> changes) { - for (int i = changes.size() - 1; i >= 0; i--) { - forward(changes.get(i)); - } - } - - protected void backward(List[]> changes) { - for (int i = 0; i < changes.size(); i++) { - backward(changes.get(i)); - } - } - - protected void forward(MapDelta[] changes) { - for (int i = 0; i < changes.length; i++) { - final MapDelta change = changes[i]; - K key = change.getKey(); - V newValue = change.getNewValue(); - - if(newValue == defaultValue) { - current.remove(key); - } else { - current.put(key,newValue); - } - } - } - - protected void backward(MapDelta[] changes) { - for (int i = changes.length - 1; i >= 0; i--) { - final MapDelta change = changes[i]; - K key = change.getKey(); - V oldValue = change.oldValue(); - - if(oldValue == defaultValue) { - current.remove(key); - } else { - current.put(key,oldValue); - } - } - } - - @Override - public V get(K key) { - return current.getOrDefault(key, defaultValue); - } - - @Override - public Cursor getAll() { - return new IteratorAsCursor<>(this, current); - } - - @Override - public V put(K key, V value) { - final V oldValue; - if (Objects.equals(value, defaultValue)) { - final V res = current.remove(key); - if (res == null) { - // no changes: default > default - oldValue = defaultValue; - } else { - oldValue = res; - } - } else { - final var mapValue = current.put(key, value); - if (mapValue == null) { - oldValue = defaultValue; - } else { - oldValue = mapValue; - } - } - if(!Objects.equals(oldValue,value)) { - uncommittedStore.processChange(key, oldValue, value); - } - return oldValue; - } - - @Override - public void putAll(Cursor cursor) { - if (cursor.getDependingMaps().contains(this)) { - List keys = new ArrayList<>(); - List values = new ArrayList<>(); - while (cursor.move()) { - keys.add(cursor.getKey()); - values.add(cursor.getValue()); - } - for (int i = 0; i < keys.size(); i++) { - this.put(keys.get(i), values.get(i)); - } - } else { - while (cursor.move()) { - this.put(cursor.getKey(), cursor.getValue()); - } - } - } - - @Override - public long getSize() { - return current.size(); - } - - @Override - public DiffCursor getDiffCursor(long state) { - MapDelta[] backward = this.uncommittedStore.extractDeltas(); - List[]> backwardTransactions = new ArrayList<>(); - List[]> forwardTransactions = new ArrayList<>(); - - if (backward != null) { - backwardTransactions.add(backward); - } - - if (this.previous != null) { - store.getPath(this.previous.version(), state, backwardTransactions, forwardTransactions); - } else { - store.getPath(state, forwardTransactions); - } - - return new DeltaDiffCursor<>(backwardTransactions, forwardTransactions); - } - - @Override - public int contentHashCode(ContentHashCode mode) { - return this.current.hashCode(); - } - - @Override - public boolean contentEquals(AnyVersionedMap other) { - if (other instanceof VersionedMapDeltaImpl versioned) { - if (versioned == this) { - return true; - } else { - return Objects.equals(this.defaultValue, versioned.defaultValue) && Objects.equals(this.current, versioned.current); - } - } else { - throw new UnsupportedOperationException("Comparing different map implementations is ineffective."); - } - } - - @Override - public void checkIntegrity() { - this.uncommittedStore.checkIntegrity(); - - for (var entry : this.current.entrySet()) { - var value = entry.getValue(); - if (value == this.defaultValue) { - throw new IllegalStateException("Default value stored in map!"); - } else if (value == null) { - throw new IllegalStateException("null value stored in map!"); - } - } - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java deleted file mode 100644 index c107f7e0..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.map.internal; - -import tools.refinery.store.map.*; - -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; - -/** - * Not threadSafe in itself - * - * @param - * @param - * @author Oszkar Semerath - */ -public class VersionedMapImpl implements VersionedMap { - protected final VersionedMapStoreImpl store; - - protected final ContinousHashProvider hashProvider; - protected final V defaultValue; - protected Node root; - - private final OldValueBox oldValueBox = new OldValueBox<>(); - - public VersionedMapImpl( - VersionedMapStoreImpl store, - ContinousHashProvider hashProvider, - V defaultValue) { - this.store = store; - this.hashProvider = hashProvider; - this.defaultValue = defaultValue; - this.root = null; - } - - public VersionedMapImpl( - VersionedMapStoreImpl store, - ContinousHashProvider hashProvider, - V defaultValue, Node data) { - this.store = store; - this.hashProvider = hashProvider; - this.defaultValue = defaultValue; - this.root = data; - } - - @Override - public V getDefaultValue() { - return defaultValue; - } - - public ContinousHashProvider getHashProvider() { - return hashProvider; - } - - @Override - public V put(K key, V value) { - if (root != null) { - root = root.putValue(key, value, oldValueBox, hashProvider, defaultValue, hashProvider.getHash(key, 0), 0); - return oldValueBox.getOldValue(); - } else { - root = MutableNode.initialize(key, value, hashProvider, defaultValue); - return defaultValue; - } - } - - @Override - public void putAll(Cursor cursor) { - if (cursor.getDependingMaps().contains(this)) { - List keys = new LinkedList<>(); - List values = new LinkedList<>(); - while (cursor.move()) { - keys.add(cursor.getKey()); - values.add(cursor.getValue()); - } - Iterator keyIterator = keys.iterator(); - Iterator valueIterator = values.iterator(); - while (keyIterator.hasNext()) { - var key = keyIterator.next(); - var value = valueIterator.next(); - this.put(key,value); - } - } else { - while (cursor.move()) { - this.put(cursor.getKey(), cursor.getValue()); - } - } - } - - @Override - public V get(K key) { - if (root != null) { - return root.getValue(key, hashProvider, defaultValue, hashProvider.getHash(key, 0), 0); - } else { - return defaultValue; - } - } - - @Override - public long getSize() { - if (root == null) { - return 0; - } else { - return root.getSize(); - } - } - - @Override - public Cursor getAll() { - return new MapCursor<>(this.root, this); - } - - @Override - public DiffCursor getDiffCursor(long toVersion) { - InOrderMapCursor fromCursor = new InOrderMapCursor<>(this); - VersionedMapImpl toMap = (VersionedMapImpl) this.store.createMap(toVersion); - InOrderMapCursor toCursor = new InOrderMapCursor<>(toMap); - return new MapDiffCursor<>(this.defaultValue, fromCursor, toCursor); - } - - - @Override - public long commit() { - return this.store.commit(root, this); - } - - public void setRoot(Node root) { - this.root = root; - } - - @Override - public void restore(long state) { - root = this.store.revert(state); - } - - public String prettyPrint() { - if (this.root != null) { - StringBuilder s = new StringBuilder(); - this.root.prettyPrint(s, 0, -1); - return s.toString(); - } else { - return "empty tree"; - } - } - - @Override - public void checkIntegrity() { - if (this.root != null) { - this.root.checkIntegrity(hashProvider, defaultValue, 0); - } - } - - @Override - public int contentHashCode(ContentHashCode mode) { - // Calculating the root hashCode is always fast, because {@link Node} caches its hashCode. - if(root == null) { - return 0; - } else { - return root.hashCode(); - } - } - - @Override - public boolean contentEquals(AnyVersionedMap other) { - return other instanceof VersionedMapImpl otherImpl && Objects.equals(root, otherImpl.root); - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java index cf117d95..47470236 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java @@ -5,9 +5,11 @@ */ package tools.refinery.store.map.internal; -import tools.refinery.store.map.ContinousHashProvider; +import tools.refinery.store.map.ContinuousHashProvider; import tools.refinery.store.map.VersionedMapStoreFactory; import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; +import tools.refinery.store.map.internal.delta.DeltaBasedVersionedMapStoreFactory; +import tools.refinery.store.map.internal.state.StateBasedVersionedMapStoreFactory; public class VersionedMapStoreFactoryBuilderImpl implements VersionedMapStoreFactoryBuilder { @@ -16,14 +18,14 @@ public class VersionedMapStoreFactoryBuilderImpl implements VersionedMapSt private StoreStrategy strategy = null; private Boolean transformToImmutable = null; private SharingStrategy sharingStrategy = null; - private ContinousHashProvider continousHashProvider = null; + private ContinuousHashProvider continuousHashProvider = null; private DeltaTransactionStrategy deltaTransactionStrategy = null; private StoreStrategy checkStrategy() { StoreStrategy currentStrategy = strategy; currentStrategy = mergeStrategies(currentStrategy, transformToImmutable, StoreStrategy.STATE); currentStrategy = mergeStrategies(currentStrategy, sharingStrategy, StoreStrategy.STATE); - currentStrategy = mergeStrategies(currentStrategy, continousHashProvider, StoreStrategy.STATE); + currentStrategy = mergeStrategies(currentStrategy, continuousHashProvider, StoreStrategy.STATE); currentStrategy = mergeStrategies(currentStrategy, deltaTransactionStrategy, StoreStrategy.DELTA); return currentStrategy; } @@ -77,8 +79,8 @@ public class VersionedMapStoreFactoryBuilderImpl implements VersionedMapSt } @Override - public VersionedMapStoreFactoryBuilder stateBasedHashProvider(ContinousHashProvider hashProvider) { - this.continousHashProvider = hashProvider; + public VersionedMapStoreFactoryBuilder stateBasedHashProvider(ContinuousHashProvider hashProvider) { + this.continuousHashProvider = hashProvider; checkStrategy(); return this; } @@ -110,13 +112,13 @@ public class VersionedMapStoreFactoryBuilderImpl implements VersionedMapSt } return switch (strategyToUse) { case STATE -> { - if(continousHashProvider == null) { + if(continuousHashProvider == null) { throw new IllegalArgumentException("Continuous hash provider is missing!"); } yield new StateBasedVersionedMapStoreFactory<>(defaultValue, getOrDefault(transformToImmutable,true), getOrDefault(sharingStrategy, SharingStrategy.SHARED_NODE_CACHE_IN_GROUP), - continousHashProvider); + continuousHashProvider); } case DELTA -> new DeltaBasedVersionedMapStoreFactory<>(defaultValue, getOrDefault(deltaTransactionStrategy, DeltaTransactionStrategy.LIST)); @@ -130,7 +132,7 @@ public class VersionedMapStoreFactoryBuilderImpl implements VersionedMapSt ", strategy=" + strategy + ", stateBasedImmutableWhenCommitting=" + transformToImmutable + ", stateBasedNodeSharingStrategy=" + sharingStrategy + - ", hashProvider=" + continousHashProvider + + ", hashProvider=" + continuousHashProvider + ", deltaStorageStrategy=" + deltaTransactionStrategy + '}'; } diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaBasedVersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaBasedVersionedMapStoreFactory.java new file mode 100644 index 00000000..cedcdc0b --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaBasedVersionedMapStoreFactory.java @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.delta; + +import tools.refinery.store.map.VersionedMapStore; +import tools.refinery.store.map.VersionedMapStoreFactory; +import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; + +import java.util.ArrayList; +import java.util.List; + +public class DeltaBasedVersionedMapStoreFactory implements VersionedMapStoreFactory { + private final V defaultValue; + private final boolean summarizeChanges; + + public DeltaBasedVersionedMapStoreFactory(V defaultValue, + VersionedMapStoreFactoryBuilder.DeltaTransactionStrategy deltaTransactionStrategy) { + this.defaultValue = defaultValue; + this.summarizeChanges = deltaTransactionStrategy == VersionedMapStoreFactoryBuilder.DeltaTransactionStrategy.SET; + } + + @Override + public VersionedMapStore createOne() { + return new VersionedMapStoreDeltaImpl<>(summarizeChanges, defaultValue); + } + + @Override + public List> createGroup(int amount) { + List> result = new ArrayList<>(amount); + for(int i=0; i(summarizeChanges,defaultValue)); + } + return result; + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaDiffCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaDiffCursor.java new file mode 100644 index 00000000..ce10b246 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/DeltaDiffCursor.java @@ -0,0 +1,147 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.delta; + +import tools.refinery.store.map.AnyVersionedMap; +import tools.refinery.store.map.DiffCursor; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class DeltaDiffCursor implements DiffCursor { + final List[]> backwardTransactions; + final List[]> forwardTransactions; + + boolean started; + /** + * Denotes the direction of traversal. False means backwards, true means + * forward. + */ + boolean direction; + int listIndex; + int arrayIndex; + + public DeltaDiffCursor(List[]> backwardTransactions, List[]> forwardTransactions) { + this.backwardTransactions = backwardTransactions; + this.forwardTransactions = forwardTransactions; + + if (!backwardTransactions.isEmpty()) { + direction = false; + listIndex = 0; + arrayIndex = backwardTransactions.get(listIndex).length - 1; + } else if (!forwardTransactions.isEmpty()) { + direction = true; + listIndex = forwardTransactions.size() - 1; + arrayIndex = 0; + } else { + direction = true; + listIndex = -1; + } + started = false; + } + + protected MapDelta getCurrentDelta() { + final List[]> list; + if (!direction) { + list = this.backwardTransactions; + } else { + list = this.forwardTransactions; + } + return list.get(listIndex)[arrayIndex]; + } + + @Override + public K getKey() { + return getCurrentDelta().getKey(); + } + + @Override + public V getValue() { + return getToValue(); + } + + @Override + public boolean isTerminated() { + return this.direction && listIndex == -1; + } + + + @Override + public boolean move() { + if(!started) { + started = true; + return !isTerminated(); + } else if (isTerminated()) { + return false; + } else { + if (this.direction) { + if (arrayIndex+1 < forwardTransactions.get(listIndex).length) { + arrayIndex++; + return true; + } else { + if (listIndex-1 >= 0) { + listIndex--; + arrayIndex = 0; + return true; + } else { + listIndex = -1; + return false; + } + } + } else { + if (arrayIndex > 0) { + arrayIndex--; + return true; + } else { + if (listIndex+1 < backwardTransactions.size()) { + listIndex++; + this.arrayIndex = backwardTransactions.get(listIndex).length - 1; + return true; + } else { + this.direction = true; + if (!this.forwardTransactions.isEmpty()) { + listIndex = forwardTransactions.size() - 1; + arrayIndex = 0; + return true; + } else { + listIndex = -1; + return false; + } + } + } + } + } + } + + @Override + public boolean isDirty() { + return false; + } + + @Override + public Set getDependingMaps() { + return Collections.emptySet(); + } + + @Override + public V getFromValue() { + if(this.direction) { + return getCurrentDelta().getOldValue(); + } else { + return getCurrentDelta().getNewValue(); + } + } + + @Override + public V getToValue() { + if(this.direction) { + return getCurrentDelta().getNewValue(); + } else { + return getCurrentDelta().getOldValue(); + } + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapDelta.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapDelta.java new file mode 100644 index 00000000..0c0cc906 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapDelta.java @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.delta; + +public record MapDelta(K key, V oldValue, V newValue) { + public K getKey() { + return key; + } + + public V getOldValue() { + return oldValue; + } + + public V getNewValue() { + return newValue; + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapTransaction.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapTransaction.java new file mode 100644 index 00000000..7f9ccd7f --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapTransaction.java @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.delta; + +import java.util.Arrays; +import java.util.Objects; + +public record MapTransaction(MapDelta[] deltas, long version, MapTransaction parent) { + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(deltas); + result = prime * result + Objects.hash(parent, version); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + @SuppressWarnings("unchecked") + MapTransaction other = (MapTransaction) obj; + return Arrays.equals(deltas, other.deltas) && Objects.equals(parent, other.parent) && version == other.version; + } + + @Override + public String toString() { + return "MapTransaction " + version + " " + Arrays.toString(deltas); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaArrayStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaArrayStore.java new file mode 100644 index 00000000..1f6a9000 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaArrayStore.java @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.delta; + +import java.util.ArrayList; +import java.util.List; + +public class UncommittedDeltaArrayStore implements UncommittedDeltaStore { + final List> uncommittedOldValues = new ArrayList<>(); + + @Override + public void processChange(K key, V oldValue, V newValue) { + uncommittedOldValues.add(new MapDelta<>(key, oldValue, newValue)); + } + + @Override + public MapDelta[] extractDeltas() { + if (uncommittedOldValues.isEmpty()) { + return null; + } else { + @SuppressWarnings("unchecked") + MapDelta[] result = uncommittedOldValues.toArray(new MapDelta[0]); + return result; + } + } + + @Override + public MapDelta[] extractAndDeleteDeltas() { + MapDelta[] res = extractDeltas(); + this.uncommittedOldValues.clear(); + return res; + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaMapStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaMapStore.java new file mode 100644 index 00000000..644884a6 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaMapStore.java @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.delta; + +import java.util.*; +import java.util.Map.Entry; + +import tools.refinery.store.map.VersionedMap; + +public class UncommittedDeltaMapStore implements UncommittedDeltaStore { + final VersionedMap source; + final Map uncommittedOldValues = new HashMap<>(); + + public UncommittedDeltaMapStore(VersionedMap source) { + this.source = source; + } + + @Override + public void processChange(K key, V oldValue, V newValue) { + if(!uncommittedOldValues.containsKey(key)) { + this.uncommittedOldValues.put(key,oldValue); + } + } + + @Override + public MapDelta[] extractDeltas() { + if (uncommittedOldValues.isEmpty()) { + return null; + } else { + @SuppressWarnings("unchecked") + MapDelta[] deltas = new MapDelta[uncommittedOldValues.size()]; + int i = 0; + for (Entry entry : uncommittedOldValues.entrySet()) { + final K key = entry.getKey(); + final V oldValue = entry.getValue(); + final V newValue = source.get(key); + deltas[i++] = new MapDelta<>(key, oldValue, newValue); + } + + return deltas; + } + } + + @Override + public MapDelta[] extractAndDeleteDeltas() { + MapDelta[] res = extractDeltas(); + this.uncommittedOldValues.clear(); + return res; + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaStore.java new file mode 100644 index 00000000..ecd33c5f --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/UncommittedDeltaStore.java @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.delta; + +public interface UncommittedDeltaStore { + void processChange(K key, V oldValue, V newValue); + + MapDelta[] extractDeltas(); + + MapDelta[] extractAndDeleteDeltas(); + + default void checkIntegrity() { + MapDelta[] extractedDeltas = extractDeltas(); + if(extractedDeltas != null) { + for(var uncommittedOldValue : extractedDeltas) { + if(uncommittedOldValue == null) { + throw new IllegalArgumentException("Null entry in deltas!"); + } + if(uncommittedOldValue.getKey() == null) { + throw new IllegalStateException("Null key in deltas!"); + } + } + } + } + +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java new file mode 100644 index 00000000..5bb864ac --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java @@ -0,0 +1,220 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.delta; + +import java.util.*; + +import tools.refinery.store.map.*; +import tools.refinery.store.map.IteratorAsCursor; + +public class VersionedMapDeltaImpl implements VersionedMap { + protected final VersionedMapStoreDeltaImpl store; + + final Map current; + + final UncommittedDeltaStore uncommittedStore; + MapTransaction previous; + + protected final V defaultValue; + + public VersionedMapDeltaImpl(VersionedMapStoreDeltaImpl store, boolean summarizeChanges, V defaultValue) { + this.store = store; + this.defaultValue = defaultValue; + + current = new HashMap<>(); + if (summarizeChanges) { + this.uncommittedStore = new UncommittedDeltaMapStore<>(this); + } else { + this.uncommittedStore = new UncommittedDeltaArrayStore<>(); + } + } + + @Override + public V getDefaultValue() { + return defaultValue; + } + + @Override + public long commit() { + MapDelta[] deltas = uncommittedStore.extractAndDeleteDeltas(); + long[] versionContainer = new long[1]; + this.previous = this.store.appendTransaction(deltas, previous, versionContainer); + return versionContainer[0]; + } + + @Override + public void restore(long state) { + // 1. restore uncommitted states + MapDelta[] uncommitted = this.uncommittedStore.extractAndDeleteDeltas(); + if (uncommitted != null) { + backward(uncommitted); + } + + // 2. get common ancestor + final MapTransaction parent; + List[]> forward = new ArrayList<>(); + if (this.previous == null) { + parent = this.store.getPath(state, forward); + this.forward(forward); + } else { + List[]> backward = new ArrayList<>(); + parent = this.store.getPath(this.previous.version(), state, backward, forward); + this.backward(backward); + this.forward(forward); + } + this.previous = parent; + } + + protected void forward(List[]> changes) { + for (int i = changes.size() - 1; i >= 0; i--) { + forward(changes.get(i)); + } + } + + protected void backward(List[]> changes) { + for (int i = 0; i < changes.size(); i++) { + backward(changes.get(i)); + } + } + + protected void forward(MapDelta[] changes) { + for (int i = 0; i < changes.length; i++) { + final MapDelta change = changes[i]; + K key = change.getKey(); + V newValue = change.getNewValue(); + + if(newValue == defaultValue) { + current.remove(key); + } else { + current.put(key,newValue); + } + } + } + + protected void backward(MapDelta[] changes) { + for (int i = changes.length - 1; i >= 0; i--) { + final MapDelta change = changes[i]; + K key = change.getKey(); + V oldValue = change.oldValue(); + + if(oldValue == defaultValue) { + current.remove(key); + } else { + current.put(key,oldValue); + } + } + } + + @Override + public V get(K key) { + return current.getOrDefault(key, defaultValue); + } + + @Override + public Cursor getAll() { + return new IteratorAsCursor<>(this, current); + } + + @Override + public V put(K key, V value) { + final V oldValue; + if (Objects.equals(value, defaultValue)) { + final V res = current.remove(key); + if (res == null) { + // no changes: default > default + oldValue = defaultValue; + } else { + oldValue = res; + } + } else { + final var mapValue = current.put(key, value); + if (mapValue == null) { + oldValue = defaultValue; + } else { + oldValue = mapValue; + } + } + if(!Objects.equals(oldValue,value)) { + uncommittedStore.processChange(key, oldValue, value); + } + return oldValue; + } + + @Override + public void putAll(Cursor cursor) { + if (cursor.getDependingMaps().contains(this)) { + List keys = new ArrayList<>(); + List values = new ArrayList<>(); + while (cursor.move()) { + keys.add(cursor.getKey()); + values.add(cursor.getValue()); + } + for (int i = 0; i < keys.size(); i++) { + this.put(keys.get(i), values.get(i)); + } + } else { + while (cursor.move()) { + this.put(cursor.getKey(), cursor.getValue()); + } + } + } + + @Override + public long getSize() { + return current.size(); + } + + @Override + public DiffCursor getDiffCursor(long state) { + MapDelta[] backward = this.uncommittedStore.extractDeltas(); + List[]> backwardTransactions = new ArrayList<>(); + List[]> forwardTransactions = new ArrayList<>(); + + if (backward != null) { + backwardTransactions.add(backward); + } + + if (this.previous != null) { + store.getPath(this.previous.version(), state, backwardTransactions, forwardTransactions); + } else { + store.getPath(state, forwardTransactions); + } + + return new DeltaDiffCursor<>(backwardTransactions, forwardTransactions); + } + + @Override + public int contentHashCode(ContentHashCode mode) { + return this.current.hashCode(); + } + + @Override + public boolean contentEquals(AnyVersionedMap other) { + if (other instanceof VersionedMapDeltaImpl versioned) { + if (versioned == this) { + return true; + } else { + return Objects.equals(this.defaultValue, versioned.defaultValue) && Objects.equals(this.current, versioned.current); + } + } else { + throw new UnsupportedOperationException("Comparing different map implementations is ineffective."); + } + } + + @Override + public void checkIntegrity() { + this.uncommittedStore.checkIntegrity(); + + for (var entry : this.current.entrySet()) { + var value = entry.getValue(); + if (value == this.defaultValue) { + throw new IllegalStateException("Default value stored in map!"); + } else if (value == null) { + throw new IllegalStateException("null value stored in map!"); + } + } + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapStoreDeltaImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapStoreDeltaImpl.java new file mode 100644 index 00000000..7f56ea77 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapStoreDeltaImpl.java @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.delta; + +import tools.refinery.store.map.DiffCursor; +import tools.refinery.store.map.VersionedMap; +import tools.refinery.store.map.VersionedMapStore; + +import java.util.*; + +public class VersionedMapStoreDeltaImpl implements VersionedMapStore { + // Configuration + protected final boolean summarizeChanges; + + // Static data + protected final V defaultValue; + + // Dynamic data + protected final Map> states = new HashMap<>(); + protected long nextID = 0; + + public VersionedMapStoreDeltaImpl(boolean summarizeChanges, V defaultValue) { + this.summarizeChanges = summarizeChanges; + this.defaultValue = defaultValue; + } + + @Override + public VersionedMap createMap() { + return new VersionedMapDeltaImpl<>(this, this.summarizeChanges, this.defaultValue); + } + + @Override + public VersionedMap createMap(long state) { + VersionedMapDeltaImpl result = new VersionedMapDeltaImpl<>(this, this.summarizeChanges, this.defaultValue); + result.restore(state); + return result; + } + + public synchronized MapTransaction appendTransaction(MapDelta[] deltas, MapTransaction previous, long[] versionContainer) { + long version = nextID++; + versionContainer[0] = version; + if (deltas == null) { + states.put(version, previous); + return previous; + } else { + MapTransaction transaction = new MapTransaction<>(deltas, version, previous); + states.put(version, transaction); + return transaction; + } + } + + private synchronized MapTransaction getState(long state) { + return states.get(state); + } + + public MapTransaction getPath(long to, List[]> forwardTransactions) { + final MapTransaction target = getState(to); + MapTransaction toTransaction = target; + while (toTransaction != null) { + forwardTransactions.add(toTransaction.deltas()); + toTransaction = toTransaction.parent(); + } + return target; + } + + public MapTransaction getPath(long from, long to, + List[]> backwardTransactions, + List[]> forwardTransactions) { + MapTransaction fromTransaction = getState(from); + final MapTransaction target = getState(to); + MapTransaction toTransaction = target; + + while (fromTransaction != toTransaction) { + if (fromTransaction == null || (toTransaction != null && fromTransaction.version() < toTransaction.version())) { + forwardTransactions.add(toTransaction.deltas()); + toTransaction = toTransaction.parent(); + } else { + backwardTransactions.add(fromTransaction.deltas()); + fromTransaction = fromTransaction.parent(); + } + } + return target; + } + + + @Override + public synchronized Set getStates() { + return new HashSet<>(states.keySet()); + } + + @Override + public DiffCursor getDiffCursor(long fromState, long toState) { + List[]> backwardTransactions = new ArrayList<>(); + List[]> forwardTransactions = new ArrayList<>(); + getPath(fromState, toState, backwardTransactions, forwardTransactions); + return new DeltaDiffCursor<>(backwardTransactions, forwardTransactions); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/ImmutableNode.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/ImmutableNode.java new file mode 100644 index 00000000..722f9ed7 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/ImmutableNode.java @@ -0,0 +1,413 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.state; + +import java.util.Arrays; +import java.util.Map; + +import tools.refinery.store.map.ContinuousHashProvider; + +public class ImmutableNode extends Node { + /** + * Bitmap defining the stored key and values. + */ + final int dataMap; + /** + * Bitmap defining the positions of further nodes. + */ + final int nodeMap; + /** + * Stores Keys, Values, and sub-nodes. Structure: (K,V)*,NODE; NODES are stored + * backwards. + */ + final Object[] content; + + /** + * Hash code derived from immutable hash code + */ + final int precalculatedHash; + + private ImmutableNode(int dataMap, int nodeMap, Object[] content, int precalculatedHash) { + super(); + this.dataMap = dataMap; + this.nodeMap = nodeMap; + this.content = content; + this.precalculatedHash = precalculatedHash; + } + + /** + * Constructor that copies a mutable node to an immutable. + * + * @param node A mutable node. + * @param cache A cache of existing immutable nodes. It can be used to search + * and place reference immutable nodes. It can be null, if no cache + * available. + * @return an immutable version of the input node. + */ + static ImmutableNode constructImmutable(MutableNode node, Map, ImmutableNode> cache) { + // 1. try to return from cache + if (cache != null) { + ImmutableNode cachedResult = cache.get(node); + if (cachedResult != null) { + // 1.1 Already cached, return from cache. + return cachedResult; + } + } + + // 2. otherwise construct a new ImmutableNode + int size = 0; + for (int i = 0; i < node.content.length; i++) { + if (node.content[i] != null) { + size++; + } + } + + int datas = 0; + int nodes = 0; + int resultDataMap = 0; + int resultNodeMap = 0; + final Object[] resultContent = new Object[size]; + int bitPosition = 1; + for (int i = 0; i < FACTOR; i++) { + Object key = node.content[i * 2]; + if (key != null) { + resultDataMap |= bitPosition; + resultContent[datas * 2] = key; + resultContent[datas * 2 + 1] = node.content[i * 2 + 1]; + datas++; + } else { + @SuppressWarnings("unchecked") var subnode = (Node) node.content[i * 2 + 1]; + if (subnode != null) { + ImmutableNode immutableSubNode = subnode.toImmutable(cache); + resultNodeMap |= bitPosition; + resultContent[size - 1 - nodes] = immutableSubNode; + nodes++; + } + } + bitPosition <<= 1; + } + final int resultHash = node.hashCode(); + var newImmutable = new ImmutableNode(resultDataMap, resultNodeMap, resultContent, resultHash); + + // 3. save new immutable. + if (cache != null) { + cache.put(newImmutable, newImmutable); + } + return newImmutable; + } + + private int index(int bitmap, int bitpos) { + return Integer.bitCount(bitmap & (bitpos - 1)); + } + + @Override + public V getValue(K key, ContinuousHashProvider hashProvider, V defaultValue, int hash, int depth) { + int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); + int bitposition = 1 << selectedHashFragment; + // If the key is stored as a data + if ((dataMap & bitposition) != 0) { + int keyIndex = 2 * index(dataMap, bitposition); + @SuppressWarnings("unchecked") K keyCandidate = (K) content[keyIndex]; + if (keyCandidate.equals(key)) { + @SuppressWarnings("unchecked") V value = (V) content[keyIndex + 1]; + return value; + } else { + return defaultValue; + } + } + // the key is stored as a node + else if ((nodeMap & bitposition) != 0) { + int keyIndex = content.length - 1 - index(nodeMap, bitposition); + @SuppressWarnings("unchecked") var subNode = (ImmutableNode) content[keyIndex]; + int newDepth = incrementDepth(depth); + int newHash = newHash(hashProvider, key, hash, newDepth); + return subNode.getValue(key, hashProvider, defaultValue, newHash, newDepth); + } + // the key is not stored at all + else { + return defaultValue; + } + } + + @Override + public Node putValue(K key, V value, OldValueBox oldValue, ContinuousHashProvider hashProvider, V defaultValue, int hash, int depth) { + int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); + int bitPosition = 1 << selectedHashFragment; + if ((dataMap & bitPosition) != 0) { + int keyIndex = 2 * index(dataMap, bitPosition); + @SuppressWarnings("unchecked") K keyCandidate = (K) content[keyIndex]; + if (keyCandidate.equals(key)) { + if (value == defaultValue) { + // delete + MutableNode mutable = this.toMutable(); + return mutable.removeEntry(selectedHashFragment, oldValue); + } else if (value == content[keyIndex + 1]) { + // don't change + oldValue.setOldValue(value); + return this; + } else { + // update existing value + MutableNode mutable = this.toMutable(); + return mutable.updateValue(value, oldValue, selectedHashFragment); + } + } else { + if (value == defaultValue) { + // don't change + oldValue.setOldValue(defaultValue); + return this; + } else { + // add new key + value + MutableNode mutable = this.toMutable(); + return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth); + } + } + } else if ((nodeMap & bitPosition) != 0) { + int keyIndex = content.length - 1 - index(nodeMap, bitPosition); + @SuppressWarnings("unchecked") var subNode = (ImmutableNode) content[keyIndex]; + int newDepth = incrementDepth(depth); + int newHash = newHash(hashProvider, key, hash, newDepth); + var newsubNode = subNode.putValue(key, value, oldValue, hashProvider, defaultValue, newHash, newDepth); + + if (subNode == newsubNode) { + // nothing changed + return this; + } else { + MutableNode mutable = toMutable(); + return mutable.updateWithSubNode(selectedHashFragment, newsubNode, + (value == null && defaultValue == null) || (value != null && value.equals(defaultValue))); + } + } else { + // add new key + value + MutableNode mutable = this.toMutable(); + return mutable.putValue(key, value, oldValue, hashProvider, defaultValue, hash, depth); + } + } + + @Override + public long getSize() { + int result = Integer.bitCount(this.dataMap); + for (int subnodeIndex = 0; subnodeIndex < Integer.bitCount(this.nodeMap); subnodeIndex++) { + @SuppressWarnings("unchecked") var subnode = (ImmutableNode) this.content[this.content.length - 1 - subnodeIndex]; + result += subnode.getSize(); + } + return result; + } + + @Override + protected MutableNode toMutable() { + return new MutableNode<>(this); + } + + @Override + public ImmutableNode toImmutable(Map, ImmutableNode> cache) { + return this; + } + + @Override + protected MutableNode isMutable() { + return null; + } + + @SuppressWarnings("unchecked") + @Override + boolean moveToNext(MapCursor cursor) { + // 1. try to move to data + int datas = Integer.bitCount(this.dataMap); + if (cursor.dataIndex != MapCursor.INDEX_FINISH) { + int newDataIndex = cursor.dataIndex + 1; + if (newDataIndex < datas) { + cursor.dataIndex = newDataIndex; + cursor.key = (K) this.content[newDataIndex * 2]; + cursor.value = (V) this.content[newDataIndex * 2 + 1]; + return true; + } else { + cursor.dataIndex = MapCursor.INDEX_FINISH; + } + } + + // 2. look inside the subnodes + int nodes = Integer.bitCount(this.nodeMap); + if(cursor.nodeIndexStack.peek()==null) { + throw new IllegalStateException("Cursor moved to the next state when the state is empty."); + } + int newNodeIndex = cursor.nodeIndexStack.peek() + 1; + if (newNodeIndex < nodes) { + // 2.1 found next subnode, move down to the subnode + Node subnode = (Node) this.content[this.content.length - 1 - newNodeIndex]; + cursor.dataIndex = MapCursor.INDEX_START; + cursor.nodeIndexStack.pop(); + cursor.nodeIndexStack.push(newNodeIndex); + cursor.nodeIndexStack.push(MapCursor.INDEX_START); + cursor.nodeStack.push(subnode); + return subnode.moveToNext(cursor); + } else { + // 3. no subnode found, move up + cursor.nodeStack.pop(); + cursor.nodeIndexStack.pop(); + if (!cursor.nodeStack.isEmpty()) { + Node supernode = cursor.nodeStack.peek(); + return supernode.moveToNext(cursor); + } else { + cursor.key = null; + cursor.value = null; + return false; + } + } + } + + @Override + @SuppressWarnings("unchecked") + boolean moveToNextInorder(InOrderMapCursor cursor) { + if(cursor.nodeIndexStack.peek()==null) { + throw new IllegalStateException("Cursor moved to the next state when the state is empty."); + } + + int position = cursor.nodeIndexStack.peek(); + for (int index = position + 1; index < FACTOR; index++) { + final int mask = 1< subnode = (Node) this.content[this.content.length - 1 - index(nodeMap, mask)]; + cursor.nodeIndexStack.pop(); + cursor.nodeIndexStack.push(index); + cursor.nodeIndexStack.push(InOrderMapCursor.INDEX_START); + cursor.nodeStack.push(subnode); + + return subnode.moveToNextInorder(cursor); + } + } + + // nothing found + cursor.nodeStack.pop(); + cursor.nodeIndexStack.pop(); + if (!cursor.nodeStack.isEmpty()) { + Node supernode = cursor.nodeStack.peek(); + return supernode.moveToNextInorder(cursor); + } else { + cursor.key = null; + cursor.value = null; + return false; + } + } + + @Override + public void prettyPrint(StringBuilder builder, int depth, int code) { + builder.append("\t".repeat(Math.max(0, depth))); + if (code >= 0) { + builder.append(code); + builder.append(":"); + } + builder.append("Immutable("); + boolean hadContent = false; + int dataMask = 1; + for (int i = 0; i < FACTOR; i++) { + if ((dataMask & dataMap) != 0) { + if (hadContent) { + builder.append(","); + } + builder.append(i); + builder.append(":["); + builder.append(content[2 * index(dataMap, dataMask)].toString()); + builder.append("]->["); + builder.append(content[2 * index(dataMap, dataMask) + 1].toString()); + builder.append("]"); + hadContent = true; + } + dataMask <<= 1; + } + builder.append(")"); + int nodeMask = 1; + for (int i = 0; i < FACTOR; i++) { + if ((nodeMask & nodeMap) != 0) { + @SuppressWarnings("unchecked") Node subNode = (Node) content[content.length - 1 - index(nodeMap, nodeMask)]; + builder.append("\n"); + subNode.prettyPrint(builder, incrementDepth(depth), i); + } + nodeMask <<= 1; + } + } + + @Override + public void checkIntegrity(ContinuousHashProvider hashProvider, V defaultValue, int depth) { + if (depth > 0) { + boolean orphaned = Integer.bitCount(dataMap) == 1 && nodeMap == 0; + if (orphaned) { + throw new IllegalStateException("Orphaned node! " + dataMap + ": " + content[0]); + } + } + // check the place of data + + // check subnodes + for (int i = 0; i < Integer.bitCount(nodeMap); i++) { + @SuppressWarnings("unchecked") var subnode = (Node) this.content[this.content.length - 1 - i]; + if (!(subnode instanceof ImmutableNode)) { + throw new IllegalStateException("Immutable node contains mutable subnodes!"); + } else { + subnode.checkIntegrity(hashProvider, defaultValue, incrementDepth(depth)); + } + } + } + + @Override + public int hashCode() { + return this.precalculatedHash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (obj instanceof ImmutableNode other) { + return precalculatedHash == other.precalculatedHash && dataMap == other.dataMap && nodeMap == other.nodeMap && Arrays.deepEquals(content, other.content); + } else if (obj instanceof MutableNode mutableObj) { + return ImmutableNode.compareImmutableMutable(this, mutableObj); + } else { + return false; + } + } + + public static boolean compareImmutableMutable(ImmutableNode immutable, MutableNode mutable) { + int datas = 0; + int nodes = 0; + final int immutableLength = immutable.content.length; + for (int i = 0; i < FACTOR; i++) { + Object key = mutable.content[i * 2]; + // For each key candidate + if (key != null) { + // Check whether a new Key-Value pair can fit into the immutable container + if (datas * 2 + nodes + 2 <= immutableLength) { + if (!immutable.content[datas * 2].equals(key) || !immutable.content[datas * 2 + 1].equals(mutable.content[i * 2 + 1])) { + return false; + } + } else return false; + datas++; + } else { + var mutableSubnode = (Node) mutable.content[i * 2 + 1]; + if (mutableSubnode != null) { + if (datas * 2 + nodes + 1 <= immutableLength) { + Object immutableSubNode = immutable.content[immutableLength - 1 - nodes]; + if (!mutableSubnode.equals(immutableSubNode)) { + return false; + } + nodes++; + } else { + return false; + } + } + } + } + + return datas * 2 + nodes == immutable.content.length; + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/InOrderMapCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/InOrderMapCursor.java new file mode 100644 index 00000000..7cc91004 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/InOrderMapCursor.java @@ -0,0 +1,146 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.state; + +import tools.refinery.store.map.AnyVersionedMap; +import tools.refinery.store.map.ContentHashCode; +import tools.refinery.store.map.Cursor; +import tools.refinery.store.map.VersionedMap; + +import java.util.*; + +public class InOrderMapCursor implements Cursor { + // Constants + static final int INDEX_START = -1; + + // Tree stack + ArrayDeque> nodeStack; + ArrayDeque nodeIndexStack; + + + // Values + K key; + V value; + + // Hash code for checking concurrent modifications + final VersionedMap map; + final int creationHash; + + public InOrderMapCursor(VersionedMapStateImpl map) { + // Initializing tree stack + super(); + this.nodeStack = new ArrayDeque<>(); + this.nodeIndexStack = new ArrayDeque<>(); + if (map.root != null) { + this.nodeStack.add(map.root); + this.nodeIndexStack.push(INDEX_START); + } + + // Initializing cache + this.key = null; + this.value = null; + + // Initializing state + this.map = map; + this.creationHash = map.contentHashCode(ContentHashCode.APPROXIMATE_FAST); + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public boolean isTerminated() { + return this.nodeStack.isEmpty(); + } + + public boolean move() { + if (isDirty()) { + throw new ConcurrentModificationException(); + } + if (!isTerminated()) { + var node = this.nodeStack.peek(); + if (node == null) { + throw new IllegalStateException("Cursor is not terminated but the current node is missing"); + } + boolean result = node.moveToNextInorder(this); + if (this.nodeIndexStack.size() != this.nodeStack.size()) { + throw new IllegalArgumentException("Node stack is corrupted by illegal moves!"); + } + return result; + } + return false; + } + + public boolean skipCurrentNode() { + nodeStack.pop(); + nodeIndexStack.pop(); + return move(); + } + + @Override + public boolean isDirty() { + return this.map.contentHashCode(ContentHashCode.APPROXIMATE_FAST) != this.creationHash; + } + + @Override + public Set getDependingMaps() { + return Set.of(this.map); + } + + public static boolean sameSubNode(InOrderMapCursor cursor1, InOrderMapCursor cursor2) { + Node nodeOfCursor1 = cursor1.nodeStack.peek(); + Node nodeOfCursor2 = cursor2.nodeStack.peek(); + return Objects.equals(nodeOfCursor1, nodeOfCursor2); + } + + /** + * Compares the state of two cursors started on two {@link VersionedMap} of the same + * {@link tools.refinery.store.map.VersionedMapStore}. + * @param Key type + * @param Value type + * @param cursor1 first cursor + * @param cursor2 second cursor + * @return Positive number if cursor 1 is behind, negative number if cursor 2 is behind, and 0 if they are at the + * same position. + */ + public static int comparePosition(InOrderMapCursor cursor1, InOrderMapCursor cursor2) { + // If the state does not determine the order, then compare @nodeIndexStack. + Iterator nodeIndexStack1 = cursor1.nodeIndexStack.descendingIterator(); + Iterator nodeIndexStack2 = cursor2.nodeIndexStack.descendingIterator(); + + while(nodeIndexStack1.hasNext() && nodeIndexStack2.hasNext()){ + final int index1 = nodeIndexStack1.next(); + final int index2 = nodeIndexStack2.next(); + if(index1 < index2) { + return 1; + } else if(index1 > index2) { + return -1; + } + } + + return 0; + } + + /** + * Compares the depth of two cursors started on @{@link VersionedMap} of the same + * {@link tools.refinery.store.map.VersionedMapStore}. + * @param Key type + * @param Value type + * @param cursor1 first cursor + * @param cursor2 second cursor + * @return Positive number if cursor 1 is deeper, negative number if cursor 2 is deeper, and 0 if they are at the + * same depth. + */ + public static int compareDepth(InOrderMapCursor cursor1, InOrderMapCursor cursor2) { + int d1 = cursor1.nodeIndexStack.size(); + int d2 = cursor2.nodeIndexStack.size(); + return Integer.compare(d1, d2); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapCursor.java new file mode 100644 index 00000000..0db50d04 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapCursor.java @@ -0,0 +1,95 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.state; + +import tools.refinery.store.map.AnyVersionedMap; +import tools.refinery.store.map.ContentHashCode; +import tools.refinery.store.map.Cursor; +import tools.refinery.store.map.VersionedMap; + +import java.util.ArrayDeque; +import java.util.ConcurrentModificationException; +import java.util.Set; + +public class MapCursor implements Cursor { + // Constants + static final int INDEX_START = -1; + static final int INDEX_FINISH = -2; + + // Tree stack + ArrayDeque> nodeStack; + ArrayDeque nodeIndexStack; + int dataIndex; + + // Values + K key; + V value; + + // Hash code for checking concurrent modifications + final VersionedMap map; + final int creationHash; + + public MapCursor(Node root, VersionedMap map) { + // Initializing tree stack + super(); + this.nodeStack = new ArrayDeque<>(); + this.nodeIndexStack = new ArrayDeque<>(); + if (root != null) { + this.nodeStack.add(root); + this.nodeIndexStack.push(INDEX_START); + } + + this.dataIndex = INDEX_START; + + // Initializing cache + this.key = null; + this.value = null; + + // Initializing state + this.map = map; + this.creationHash = map.contentHashCode(ContentHashCode.APPROXIMATE_FAST); + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public boolean isTerminated() { + return this.nodeStack.isEmpty(); + } + + public boolean move() { + if (isDirty()) { + throw new ConcurrentModificationException(); + } + if (!isTerminated()) { + var node = this.nodeStack.peek(); + if (node == null) { + throw new IllegalStateException("Cursor is not terminated but the current node is missing"); + } + boolean result = node.moveToNext(this); + if (this.nodeIndexStack.size() != this.nodeStack.size()) { + throw new IllegalArgumentException("Node stack is corrupted by illegal moves!"); + } + return result; + } + return false; + } + + @Override + public boolean isDirty() { + return this.map.contentHashCode(ContentHashCode.APPROXIMATE_FAST) != this.creationHash; + } + + @Override + public Set getDependingMaps() { + return Set.of(this.map); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapDiffCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapDiffCursor.java new file mode 100644 index 00000000..083bf8cf --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MapDiffCursor.java @@ -0,0 +1,264 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.state; + +import tools.refinery.store.map.AnyVersionedMap; +import tools.refinery.store.map.Cursor; +import tools.refinery.store.map.DiffCursor; + +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * A cursor representing the difference between two states of a map. + * + * @author Oszkar Semerath + */ +public class MapDiffCursor implements DiffCursor, Cursor { + private enum State { + /** + * initialized state. + */ + INIT, + /** + * Unstable state. + */ + MOVING_MOVING_SAME_KEY_SAME_VALUE, + /** + * Both cursors are moving, and they are on the same sub-node. + */ + MOVING_MOVING_SAME_NODE, + /** + * Both cursors are moving, cursor 1 is behind. + */ + MOVING_MOVING_BEHIND1, + /** + * Both cursors are moving, cursor 2 is behind. + */ + MOVING_MOVING_BEHIND2, + /** + * Both cursors are moving, cursor 1 is on the same key as cursor 2, values are different + */ + MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE, + /** + * Cursor 1 is moving, Cursor 2 is terminated. + */ + MOVING_TERMINATED, + /** + * Cursor 1 is terminated , Cursor 2 is moving. + */ + TERMINATED_MOVING, + /** + * Both cursors are terminated. + */ + TERMINATED_TERMINATED, + /** + * Both Cursors are moving, and they are on an incomparable position. + * It is resolved by showing Cursor 1. + */ + MOVING_MOVING_HASH1, + /** + * Both Cursors are moving, and they are on an incomparable position. + * It is resolved by showing Cursor 2. + */ + MOVING_MOVING_HASH2 + } + + /** + * Default nodeId representing missing elements. + */ + private final V defaultValue; + private final InOrderMapCursor cursor1; + private final InOrderMapCursor cursor2; + + // State + State state = State.INIT; + + // Values + private K key; + private V fromValue; + private V toValue; + + + public MapDiffCursor(V defaultValue, InOrderMapCursor cursor1, InOrderMapCursor cursor2) { + super(); + this.defaultValue = defaultValue; + this.cursor1 = cursor1; + this.cursor2 = cursor2; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getFromValue() { + return fromValue; + } + + @Override + public V getToValue() { + return toValue; + } + + @Override + public V getValue() { + return getToValue(); + } + + public boolean isTerminated() { + return this.state == State.TERMINATED_TERMINATED; + } + + @Override + public boolean isDirty() { + return this.cursor1.isDirty() || this.cursor2.isDirty(); + } + + @Override + public Set getDependingMaps() { + return Stream.concat(cursor1.getDependingMaps().stream(), cursor2.getDependingMaps().stream()).map(AnyVersionedMap.class::cast).collect(Collectors.toUnmodifiableSet()); + } + + private boolean isInStableState() { + return this.state != State.MOVING_MOVING_SAME_KEY_SAME_VALUE + && this.state != State.MOVING_MOVING_SAME_NODE && this.state != State.INIT; + } + + private boolean updateAndReturnWithResult() { + return switch (this.state) { + case INIT -> throw new IllegalStateException("DiffCursor terminated without starting!"); + case MOVING_MOVING_SAME_KEY_SAME_VALUE, MOVING_MOVING_SAME_NODE -> + throw new IllegalStateException("DiffCursor terminated in unstable state!"); + case MOVING_MOVING_BEHIND1, MOVING_TERMINATED, MOVING_MOVING_HASH1 -> { + this.key = this.cursor1.getKey(); + this.fromValue = this.cursor1.getValue(); + this.toValue = this.defaultValue; + yield true; + } + case MOVING_MOVING_BEHIND2, TERMINATED_MOVING, MOVING_MOVING_HASH2 -> { + this.key = this.cursor2.getKey(); + this.fromValue = this.defaultValue; + this.toValue = cursor2.getValue(); + yield true; + } + case MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE -> { + this.key = this.cursor1.getKey(); + this.fromValue = this.cursor1.getValue(); + this.toValue = this.cursor2.getValue(); + yield true; + } + case TERMINATED_TERMINATED -> { + this.key = null; + this.fromValue = null; + this.toValue = null; + yield false; + } + }; + } + + public boolean move() { + do { + this.state = moveOne(this.state); + } while (!isInStableState()); + return updateAndReturnWithResult(); + } + + private State moveOne(State currentState) { + return switch (currentState) { + case INIT, MOVING_MOVING_HASH2, MOVING_MOVING_SAME_KEY_SAME_VALUE, MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE -> { + boolean cursor1Moved = cursor1.move(); + boolean cursor2Moved = cursor2.move(); + yield recalculateStateAfterCursorMovement(cursor1Moved, cursor2Moved); + } + case MOVING_MOVING_SAME_NODE -> { + boolean cursor1Moved = cursor1.skipCurrentNode(); + boolean cursor2Moved = cursor2.skipCurrentNode(); + yield recalculateStateAfterCursorMovement(cursor1Moved, cursor2Moved); + } + case MOVING_MOVING_BEHIND1 -> { + boolean cursorMoved = cursor1.move(); + if (cursorMoved) { + yield recalculateStateBasedOnCursorRelation(); + } else { + yield State.TERMINATED_MOVING; + } + } + case MOVING_MOVING_BEHIND2 -> { + boolean cursorMoved = cursor2.move(); + if (cursorMoved) { + yield recalculateStateBasedOnCursorRelation(); + } else { + yield State.MOVING_TERMINATED; + } + } + case TERMINATED_MOVING -> { + boolean cursorMoved = cursor2.move(); + if (cursorMoved) { + yield State.TERMINATED_MOVING; + } else { + yield State.TERMINATED_TERMINATED; + } + } + case MOVING_TERMINATED -> { + boolean cursorMoved = cursor1.move(); + if (cursorMoved) { + yield State.MOVING_TERMINATED; + } else { + yield State.TERMINATED_TERMINATED; + } + } + case MOVING_MOVING_HASH1 -> State.MOVING_MOVING_HASH2; + case TERMINATED_TERMINATED -> throw new IllegalStateException("Trying to move while terminated!"); + }; + } + + private State recalculateStateAfterCursorMovement(boolean cursor1Moved, boolean cursor2Moved) { + if (cursor1Moved && cursor2Moved) { + return recalculateStateBasedOnCursorRelation(); + } else if (cursor1Moved) { + return State.MOVING_TERMINATED; + } else if (cursor2Moved) { + return State.TERMINATED_MOVING; + } else { + return State.TERMINATED_TERMINATED; + } + } + + private State recalculateStateBasedOnCursorRelation() { + final int relation = InOrderMapCursor.comparePosition(cursor1, cursor2); + + if (relation > 0) { + return State.MOVING_MOVING_BEHIND1; + } else if (relation < 0) { + return State.MOVING_MOVING_BEHIND2; + } + + if (InOrderMapCursor.sameSubNode(cursor1, cursor2)) { + return State.MOVING_MOVING_SAME_NODE; + } else if (Objects.equals(cursor1.getKey(), cursor2.getKey())) { + if (Objects.equals(cursor1.getValue(), cursor2.getValue())) { + return State.MOVING_MOVING_SAME_KEY_SAME_VALUE; + } else { + return State.MOVING_MOVING_SAME_KEY_DIFFERENT_VALUE; + } + } + + final int depth = InOrderMapCursor.compareDepth(cursor1, cursor2); + + if (depth > 0) { + return State.MOVING_MOVING_BEHIND1; + } else if (depth < 0) { + return State.MOVING_MOVING_BEHIND2; + } else { + return State.MOVING_MOVING_HASH1; + } + + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MutableNode.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MutableNode.java new file mode 100644 index 00000000..408ed62c --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/MutableNode.java @@ -0,0 +1,499 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.state; + +import tools.refinery.store.map.ContinuousHashProvider; + +import java.util.Arrays; +import java.util.Map; + +public class MutableNode extends Node { + int cachedHash; + protected boolean cachedHashValid; + protected Object[] content; + + protected MutableNode() { + this.content = new Object[2 * FACTOR]; + invalidateHash(); + } + + public static MutableNode initialize(K key, V value, ContinuousHashProvider hashProvider, V defaultValue) { + if (value == defaultValue) { + return null; + } else { + int hash = hashProvider.getHash(key, 0); + int fragment = hashFragment(hash, 0); + MutableNode res = new MutableNode<>(); + res.content[2 * fragment] = key; + res.content[2 * fragment + 1] = value; + res.invalidateHash(); + return res; + } + } + + /** + * Constructs a {@link MutableNode} as a copy of an {@link ImmutableNode} + * + * @param node to be transformed + */ + protected MutableNode(ImmutableNode node) { + this.content = new Object[2 * FACTOR]; + int dataUsed = 0; + int nodeUsed = 0; + for (int i = 0; i < FACTOR; i++) { + int bitPosition = 1 << i; + if ((node.dataMap & bitPosition) != 0) { + content[2 * i] = node.content[dataUsed * 2]; + content[2 * i + 1] = node.content[dataUsed * 2 + 1]; + dataUsed++; + } else if ((node.nodeMap & bitPosition) != 0) { + content[2 * i + 1] = node.content[node.content.length - 1 - nodeUsed]; + nodeUsed++; + } + } + this.cachedHashValid = false; + } + + @Override + public V getValue(K key, ContinuousHashProvider hashProvider, V defaultValue, int hash, int depth) { + int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); + @SuppressWarnings("unchecked") K keyCandidate = (K) this.content[2 * selectedHashFragment]; + if (keyCandidate != null) { + if (keyCandidate.equals(key)) { + @SuppressWarnings("unchecked") V value = (V) this.content[2 * selectedHashFragment + 1]; + return value; + } else { + return defaultValue; + } + } else { + @SuppressWarnings("unchecked") var nodeCandidate = (Node) content[2 * selectedHashFragment + 1]; + if (nodeCandidate != null) { + int newDepth = incrementDepth(depth); + int newHash = newHash(hashProvider, key, hash, newDepth); + return nodeCandidate.getValue(key, hashProvider, defaultValue, newHash, newDepth); + } else { + return defaultValue; + } + } + } + + @Override + public Node putValue(K key, V value, OldValueBox oldValueBox, ContinuousHashProvider hashProvider, V defaultValue, int hash, int depth) { + int selectedHashFragment = hashFragment(hash, shiftDepth(depth)); + @SuppressWarnings("unchecked") K keyCandidate = (K) content[2 * selectedHashFragment]; + if (keyCandidate != null) { + // If it has key + if (keyCandidate.equals(key)) { + // The key is equals to an existing key -> update entry + if (value == defaultValue) { + return removeEntry(selectedHashFragment, oldValueBox); + } else { + return updateValue(value, oldValueBox, selectedHashFragment); + } + } else { + // The key is not equivalent to an existing key on the same hash bin + // -> split entry if it is necessary + if (value == defaultValue) { + // Value is default -> do not need to add new node + oldValueBox.setOldValue(defaultValue); + return this; + } else { + // Value is not default -> Split entry data to a new node + oldValueBox.setOldValue(defaultValue); + return moveDownAndSplit(hashProvider, key, value, keyCandidate, hash, depth, selectedHashFragment); + } + } + } + // If it does not have key, check for value + @SuppressWarnings("unchecked") var nodeCandidate = (Node) content[2 * selectedHashFragment + 1]; + if (nodeCandidate != null) { + // If it has value, it is a sub-node -> update that + int newDepth = incrementDepth(depth); + var newNode = nodeCandidate.putValue(key, value, oldValueBox, hashProvider, defaultValue, newHash(hashProvider, key, hash, newDepth), newDepth); + return updateWithSubNode(selectedHashFragment, newNode, (value == null && defaultValue == null) || (value != null && value.equals(defaultValue))); + } else { + // If it does not have value, put it in the empty place + if (value == defaultValue) { + // don't need to add new key-value pair + oldValueBox.setOldValue(defaultValue); + return this; + } else { + return addEntry(key, value, oldValueBox, selectedHashFragment, defaultValue); + } + } + } + + private Node addEntry(K key, V value, OldValueBox oldValueBox, int selectedHashFragment, V defaultValue) { + content[2 * selectedHashFragment] = key; + oldValueBox.setOldValue(defaultValue); + content[2 * selectedHashFragment + 1] = value; + invalidateHash(); + return this; + } + + /** + * Updates an entry in a selected hash-fragment to a non-default value. + * + * @param value new value + * @param selectedHashFragment position of the value + * @return updated node + */ + @SuppressWarnings("unchecked") + Node updateValue(V value, OldValueBox oldValue, int selectedHashFragment) { + oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]); + content[2 * selectedHashFragment + 1] = value; + invalidateHash(); + return this; + } + + /** + * Updates an entry in a selected hash-fragment with a subtree. + * + * @param selectedHashFragment position of the value + * @param newNode the subtree + * @return updated node + */ + Node updateWithSubNode(int selectedHashFragment, Node newNode, boolean deletionHappened) { + if (deletionHappened) { + if (newNode == null) { + // Check whether this node become empty + content[2 * selectedHashFragment + 1] = null; // i.e. the new node + if (hasContent()) { + invalidateHash(); + return this; + } else { + return null; + } + } else { + // check whether newNode is orphan + MutableNode immutableNewNode = newNode.isMutable(); + if (immutableNewNode != null) { + int orphaned = immutableNewNode.isOrphaned(); + if (orphaned >= 0) { + // orphan sub-node data is replaced with data + content[2 * selectedHashFragment] = immutableNewNode.content[orphaned * 2]; + content[2 * selectedHashFragment + 1] = immutableNewNode.content[orphaned * 2 + 1]; + invalidateHash(); + return this; + } + } + } + } + // normal behaviour + content[2 * selectedHashFragment + 1] = newNode; + invalidateHash(); + return this; + } + + private boolean hasContent() { + for (Object element : this.content) { + if (element != null) return true; + } + return false; + } + + @Override + protected MutableNode isMutable() { + return this; + } + + protected int isOrphaned() { + int dataFound = -2; + for (int i = 0; i < FACTOR; i++) { + if (content[i * 2] != null) { + if (dataFound >= 0) { + return -1; + } else { + dataFound = i; + } + } else if (content[i * 2 + 1] != null) { + return -3; + } + } + return dataFound; + } + + @SuppressWarnings("unchecked") + private Node moveDownAndSplit(ContinuousHashProvider hashProvider, K newKey, V newValue, K previousKey, int hashOfNewKey, int depth, int selectedHashFragmentOfCurrentDepth) { + V previousValue = (V) content[2 * selectedHashFragmentOfCurrentDepth + 1]; + + MutableNode newSubNode = newNodeWithTwoEntries(hashProvider, previousKey, previousValue, hashProvider.getHash(previousKey, hashDepth(depth)), newKey, newValue, hashOfNewKey, incrementDepth(depth)); + + content[2 * selectedHashFragmentOfCurrentDepth] = null; + content[2 * selectedHashFragmentOfCurrentDepth + 1] = newSubNode; + invalidateHash(); + return this; + } + + // Pass everything as parameters for performance. + @SuppressWarnings("squid:S107") + private MutableNode newNodeWithTwoEntries(ContinuousHashProvider hashProvider, K key1, V value1, int oldHash1, K key2, V value2, int oldHash2, int newDepth) { + int newHash1 = newHash(hashProvider, key1, oldHash1, newDepth); + int newHash2 = newHash(hashProvider, key2, oldHash2, newDepth); + int newFragment1 = hashFragment(newHash1, shiftDepth(newDepth)); + int newFragment2 = hashFragment(newHash2, shiftDepth(newDepth)); + + MutableNode subNode = new MutableNode<>(); + if (newFragment1 != newFragment2) { + subNode.content[newFragment1 * 2] = key1; + subNode.content[newFragment1 * 2 + 1] = value1; + + subNode.content[newFragment2 * 2] = key2; + subNode.content[newFragment2 * 2 + 1] = value2; + } else { + MutableNode subSubNode = newNodeWithTwoEntries(hashProvider, key1, value1, newHash1, key2, value2, newHash2, incrementDepth(newDepth)); + subNode.content[newFragment1 * 2 + 1] = subSubNode; + } + subNode.invalidateHash(); + return subNode; + } + + @SuppressWarnings("unchecked") + Node removeEntry(int selectedHashFragment, OldValueBox oldValue) { + content[2 * selectedHashFragment] = null; + oldValue.setOldValue((V) content[2 * selectedHashFragment + 1]); + content[2 * selectedHashFragment + 1] = null; + if (hasContent()) { + invalidateHash(); + return this; + } else { + return null; + } + } + + @SuppressWarnings("unchecked") + @Override + public long getSize() { + int size = 0; + for (int i = 0; i < FACTOR; i++) { + if (content[i * 2] != null) { + size++; + } else { + Node nodeCandidate = (Node) content[i * 2 + 1]; + if (nodeCandidate != null) { + size += nodeCandidate.getSize(); + } + } + } + return size; + } + + @Override + protected MutableNode toMutable() { + return this; + } + + @Override + public ImmutableNode toImmutable(Map, ImmutableNode> cache) { + return ImmutableNode.constructImmutable(this, cache); + } + + @SuppressWarnings("unchecked") + @Override + boolean moveToNext(MapCursor cursor) { + // 1. try to move to data + if (cursor.dataIndex != MapCursor.INDEX_FINISH) { + for (int index = cursor.dataIndex + 1; index < FACTOR; index++) { + if (this.content[index * 2] != null) { + // 1.1 found next data + cursor.dataIndex = index; + cursor.key = (K) this.content[index * 2]; + cursor.value = (V) this.content[index * 2 + 1]; + return true; + } + } + cursor.dataIndex = MapCursor.INDEX_FINISH; + } + + // 2. look inside the sub-nodes + if(cursor.nodeIndexStack.peek()==null) { + throw new IllegalStateException("Cursor moved to the next state when the state is empty."); + } + for (int index = cursor.nodeIndexStack.peek() + 1; index < FACTOR; index++) { + if (this.content[index * 2] == null && this.content[index * 2 + 1] != null) { + // 2.1 found next sub-node, move down to the sub-node + Node subnode = (Node) this.content[index * 2 + 1]; + + cursor.dataIndex = MapCursor.INDEX_START; + cursor.nodeIndexStack.pop(); + cursor.nodeIndexStack.push(index); + cursor.nodeIndexStack.push(MapCursor.INDEX_START); + cursor.nodeStack.push(subnode); + + return subnode.moveToNext(cursor); + } + } + // 3. no sub-node found, move up + cursor.nodeStack.pop(); + cursor.nodeIndexStack.pop(); + if (!cursor.nodeStack.isEmpty()) { + Node supernode = cursor.nodeStack.peek(); + return supernode.moveToNext(cursor); + } else { + cursor.key = null; + cursor.value = null; + return false; + } + } + + @Override + @SuppressWarnings("unchecked") + boolean moveToNextInorder(InOrderMapCursor cursor) { + if(cursor.nodeIndexStack.peek()==null || cursor.nodeStack.peek()==null) { + throw new IllegalStateException("Cursor moved to the next state when the state is empty."); + } + + int position = cursor.nodeIndexStack.peek(); + + for (int index = position + 1; index < FACTOR; index++) { + // data found + if (this.content[index * 2] != null) { + cursor.nodeIndexStack.pop(); + cursor.nodeIndexStack.push(index); + + cursor.key = (K) this.content[index * 2]; + cursor.value = (V) this.content[index * 2 + 1]; + return true; + } else if (this.content[index * 2 +1] != null) { + // sub-node found + Node subnode = (Node) this.content[index * 2 +1]; + cursor.nodeIndexStack.pop(); + cursor.nodeIndexStack.push(index); + cursor.nodeIndexStack.push(InOrderMapCursor.INDEX_START); + cursor.nodeStack.push(subnode); + + return subnode.moveToNextInorder(cursor); + } + } + + // nothing found + cursor.nodeStack.pop(); + cursor.nodeIndexStack.pop(); + if (!cursor.nodeStack.isEmpty()) { + Node supernode = cursor.nodeStack.peek(); + return supernode.moveToNextInorder(cursor); + } else { + cursor.key = null; + cursor.value = null; + return false; + } + } + + @Override + public void prettyPrint(StringBuilder builder, int depth, int code) { + builder.append("\t".repeat(Math.max(0, depth))); + if (code >= 0) { + builder.append(code); + builder.append(":"); + } + builder.append("Mutable("); + // print content + boolean hadContent = false; + for (int i = 0; i < FACTOR; i++) { + if (content[2 * i] != null) { + if (hadContent) { + builder.append(","); + } + builder.append(i); + builder.append(":["); + builder.append(content[2 * i].toString()); + builder.append("]->["); + builder.append(content[2 * i + 1].toString()); + builder.append("]"); + hadContent = true; + } + } + builder.append(")"); + // print sub-nodes + for (int i = 0; i < FACTOR; i++) { + if (content[2 * i] == null && content[2 * i + 1] != null) { + @SuppressWarnings("unchecked") Node subNode = (Node) content[2 * i + 1]; + builder.append("\n"); + subNode.prettyPrint(builder, incrementDepth(depth), i); + } + } + } + + @Override + public void checkIntegrity(ContinuousHashProvider hashProvider, V defaultValue, int depth) { + // check for orphan nodes + if (depth > 0) { + int orphaned = isOrphaned(); + if (orphaned >= 0) { + throw new IllegalStateException("Orphaned node! " + orphaned + ": " + content[2 * orphaned]); + } + } + // check the place of data + for (int i = 0; i < FACTOR; i++) { + if (this.content[2 * i] != null) { + @SuppressWarnings("unchecked") K key = (K) this.content[2 * i]; + @SuppressWarnings("unchecked") V value = (V) this.content[2 * i + 1]; + + if (value == defaultValue) { + throw new IllegalStateException("Node contains default value!"); + } + int hashCode = hashProvider.getHash(key, hashDepth(depth)); + int shiftDepth = shiftDepth(depth); + int selectedHashFragment = hashFragment(hashCode, shiftDepth); + if (i != selectedHashFragment) { + throw new IllegalStateException("Key " + key + " with hash code " + hashCode + " is in bad place! Fragment=" + selectedHashFragment + ", Place=" + i); + } + } + } + // check sub-nodes + for (int i = 0; i < FACTOR; i++) { + if (this.content[2 * i + 1] != null && this.content[2 * i] == null) { + @SuppressWarnings("unchecked") var subNode = (Node) this.content[2 * i + 1]; + subNode.checkIntegrity(hashProvider, defaultValue, incrementDepth(depth)); + } + } + // check the hash + if (cachedHashValid) { + int oldHash = this.cachedHash; + invalidateHash(); + int newHash = hashCode(); + if (oldHash != newHash) { + throw new IllegalStateException("Hash code was not up to date! (old=" + oldHash + ",new=" + newHash + ")"); + } + } + } + + protected void invalidateHash() { + this.cachedHashValid = false; + } + + @Override + public int hashCode() { + if (!this.cachedHashValid) { + this.cachedHash = Arrays.hashCode(content); + this.cachedHashValid = true; + } + return this.cachedHash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (obj instanceof MutableNode mutableObj) { + if (obj.hashCode() != this.hashCode()) { + return false; + } else { + for (int i = 0; i < FACTOR * 2; i++) { + Object thisContent = this.content[i]; + if (thisContent != null && !thisContent.equals(mutableObj.content[i])) { + return false; + } + } + return true; + } + } else if (obj instanceof ImmutableNode immutableObj) { + return ImmutableNode.compareImmutableMutable(immutableObj, this); + } else { + return false; + } + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/Node.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/Node.java new file mode 100644 index 00000000..0ecf2712 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/Node.java @@ -0,0 +1,131 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.state; + +import java.util.Map; + +import tools.refinery.store.map.ContinuousHashProvider; + +public abstract class Node { + public static final int BRANCHING_FACTOR_BITS = 5; + public static final int FACTOR = 1 << BRANCHING_FACTOR_BITS; + protected static final int NUMBER_OF_FACTORS = Integer.SIZE / BRANCHING_FACTOR_BITS; + protected static final int FACTOR_MASK = FACTOR - 1; + public static final int EFFECTIVE_BITS = BRANCHING_FACTOR_BITS * NUMBER_OF_FACTORS; + public static final int FACTOR_SELECTION_BITS = 32 - Integer.numberOfLeadingZeros(NUMBER_OF_FACTORS); + public static final int FACTOR_SELECTION_MASK = (1 << FACTOR_SELECTION_BITS) - 1; + public static final int INCREMENT_BIG_STEP = (1 << FACTOR_SELECTION_BITS) - NUMBER_OF_FACTORS; + + /** + * Increments the depth of the search in the tree. The depth parameter has two + * components: the least few bits selects the fragment of the hashcode, the + * other part selects the continuous hash. + * + * @param depth parameter encoding the fragment and the depth + * @return new depth. + */ + protected int incrementDepth(int depth) { + int newDepth = depth + 1; + if ((newDepth & FACTOR_SELECTION_MASK) == NUMBER_OF_FACTORS) { + newDepth += INCREMENT_BIG_STEP; + } + return newDepth; + } + + /** + * Calculates the index for the continuous hash. + * + * @param depth The depth of the node in the tree. + * @return The index of the continuous hash. + */ + protected static int hashDepth(int depth) { + return depth >> FACTOR_SELECTION_BITS; + } + + /** + * Calculates the which segment of a single hash should be used. + * + * @param depth The depth of the node in the tree. + * @return The segment of a hash code. + */ + protected static int shiftDepth(int depth) { + return depth & FACTOR_SELECTION_MASK; + } + + /** + * Selects a segments from a complete hash for a given depth. + * + * @param hash A complete hash. + * @param shiftDepth The index of the segment. + * @return The segment as an integer. + */ + protected static int hashFragment(int hash, int shiftDepth) { + if (shiftDepth < 0 || Node.NUMBER_OF_FACTORS < shiftDepth) + throw new IllegalArgumentException("Invalid shift depth! valid interval=[0;5], input=" + shiftDepth); + return (hash >>> shiftDepth * BRANCHING_FACTOR_BITS) & FACTOR_MASK; + } + + /** + * Returns the hash code for a given depth. It may calculate new hash code, or + * reuse a hash code calculated for depth-1. + * + * @param key The key. + * @param hash Hash code for depth-1 + * @param depth The depth. + * @return The new hash code. + */ + protected int newHash(final ContinuousHashProvider hashProvider, K key, int hash, int depth) { + final int shiftDepth = shiftDepth(depth); + if (shiftDepth == 0) { + final int hashDepth = hashDepth(depth); + if (hashDepth >= ContinuousHashProvider.MAX_PRACTICAL_DEPTH) { + throw new IllegalArgumentException( + "Key " + key + " have the clashing hashcode over the practical depth limitation (" + + ContinuousHashProvider.MAX_PRACTICAL_DEPTH + ")!"); + } + return hashProvider.getHash(key, hashDepth); + } else { + return hash; + } + } + + public abstract V getValue(K key, ContinuousHashProvider hashProvider, V defaultValue, int hash, + int depth); + + public abstract Node putValue(K key, V value, OldValueBox old, + ContinuousHashProvider hashProvider, V defaultValue, int hash, int depth); + + public abstract long getSize(); + + abstract MutableNode toMutable(); + + public abstract ImmutableNode toImmutable(Map, ImmutableNode> cache); + + protected abstract MutableNode isMutable(); + + /** + * Moves a {@link MapCursor} to its next position. + * + * @param cursor the cursor + * @return Whether there was a next value to move on. + */ + abstract boolean moveToNext(MapCursor cursor); + abstract boolean moveToNextInorder(InOrderMapCursor cursor); + + ///////// FOR printing + public abstract void prettyPrint(StringBuilder builder, int depth, int code); + + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + prettyPrint(stringBuilder, 0, -1); + return stringBuilder.toString(); + } + + public void checkIntegrity(ContinuousHashProvider hashProvider, V defaultValue, int depth) { + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/OldValueBox.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/OldValueBox.java new file mode 100644 index 00000000..1b0a4b0c --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/OldValueBox.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.state; + +public class OldValueBox{ + V oldValue; + boolean isSet = false; + + public V getOldValue() { + if(!isSet) throw new IllegalStateException(); + isSet = false; + return oldValue; + } + + public void setOldValue(V ouldValue) { + if(isSet) throw new IllegalStateException(); + this.oldValue = ouldValue; + isSet = true; + } + +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/StateBasedVersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/StateBasedVersionedMapStoreFactory.java new file mode 100644 index 00000000..9409ace0 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/StateBasedVersionedMapStoreFactory.java @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.state; + +import tools.refinery.store.map.*; + +import java.util.List; + +public class StateBasedVersionedMapStoreFactory implements VersionedMapStoreFactory { + private final V defaultValue; + private final ContinuousHashProvider continuousHashProvider; + private final VersionedMapStoreStateConfiguration config; + + public StateBasedVersionedMapStoreFactory(V defaultValue, Boolean transformToImmutable, VersionedMapStoreFactoryBuilder.SharingStrategy sharingStrategy, ContinuousHashProvider continuousHashProvider) { + this.defaultValue = defaultValue; + this.continuousHashProvider = continuousHashProvider; + + this.config = new VersionedMapStoreStateConfiguration( + transformToImmutable, + sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE || sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE_IN_GROUP, + sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE_IN_GROUP); + } + + @Override + public VersionedMapStore createOne() { + return new VersionedMapStoreStateImpl<>(continuousHashProvider, defaultValue, config); + + } + + @Override + public List> createGroup(int amount) { + return VersionedMapStoreStateImpl.createSharedVersionedMapStores(amount, continuousHashProvider, defaultValue, + config); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStateImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStateImpl.java new file mode 100644 index 00000000..817fc70b --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStateImpl.java @@ -0,0 +1,171 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.state; + +import tools.refinery.store.map.*; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +/** + * Not threadSafe in itself + * + * @param + * @param + * @author Oszkar Semerath + */ +public class VersionedMapStateImpl implements VersionedMap { + protected final VersionedMapStoreStateImpl store; + + protected final ContinuousHashProvider hashProvider; + protected final V defaultValue; + protected Node root; + + private final OldValueBox oldValueBox = new OldValueBox<>(); + + public VersionedMapStateImpl( + VersionedMapStoreStateImpl store, + ContinuousHashProvider hashProvider, + V defaultValue) { + this.store = store; + this.hashProvider = hashProvider; + this.defaultValue = defaultValue; + this.root = null; + } + + public VersionedMapStateImpl( + VersionedMapStoreStateImpl store, + ContinuousHashProvider hashProvider, + V defaultValue, Node data) { + this.store = store; + this.hashProvider = hashProvider; + this.defaultValue = defaultValue; + this.root = data; + } + + @Override + public V getDefaultValue() { + return defaultValue; + } + + public ContinuousHashProvider getHashProvider() { + return hashProvider; + } + + @Override + public V put(K key, V value) { + if (root != null) { + root = root.putValue(key, value, oldValueBox, hashProvider, defaultValue, hashProvider.getHash(key, 0), 0); + return oldValueBox.getOldValue(); + } else { + root = MutableNode.initialize(key, value, hashProvider, defaultValue); + return defaultValue; + } + } + + @Override + public void putAll(Cursor cursor) { + if (cursor.getDependingMaps().contains(this)) { + List keys = new LinkedList<>(); + List values = new LinkedList<>(); + while (cursor.move()) { + keys.add(cursor.getKey()); + values.add(cursor.getValue()); + } + Iterator keyIterator = keys.iterator(); + Iterator valueIterator = values.iterator(); + while (keyIterator.hasNext()) { + var key = keyIterator.next(); + var value = valueIterator.next(); + this.put(key,value); + } + } else { + while (cursor.move()) { + this.put(cursor.getKey(), cursor.getValue()); + } + } + } + + @Override + public V get(K key) { + if (root != null) { + return root.getValue(key, hashProvider, defaultValue, hashProvider.getHash(key, 0), 0); + } else { + return defaultValue; + } + } + + @Override + public long getSize() { + if (root == null) { + return 0; + } else { + return root.getSize(); + } + } + + @Override + public Cursor getAll() { + return new MapCursor<>(this.root, this); + } + + @Override + public DiffCursor getDiffCursor(long toVersion) { + InOrderMapCursor fromCursor = new InOrderMapCursor<>(this); + VersionedMapStateImpl toMap = (VersionedMapStateImpl) this.store.createMap(toVersion); + InOrderMapCursor toCursor = new InOrderMapCursor<>(toMap); + return new MapDiffCursor<>(this.defaultValue, fromCursor, toCursor); + } + + + @Override + public long commit() { + return this.store.commit(root, this); + } + + public void setRoot(Node root) { + this.root = root; + } + + @Override + public void restore(long state) { + root = this.store.revert(state); + } + + public String prettyPrint() { + if (this.root != null) { + StringBuilder s = new StringBuilder(); + this.root.prettyPrint(s, 0, -1); + return s.toString(); + } else { + return "empty tree"; + } + } + + @Override + public void checkIntegrity() { + if (this.root != null) { + this.root.checkIntegrity(hashProvider, defaultValue, 0); + } + } + + @Override + public int contentHashCode(ContentHashCode mode) { + // Calculating the root hashCode is always fast, because {@link Node} caches its hashCode. + if(root == null) { + return 0; + } else { + return root.hashCode(); + } + } + + @Override + public boolean contentEquals(AnyVersionedMap other) { + return other instanceof VersionedMapStateImpl otherImpl && Objects.equals(root, otherImpl.root); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateConfiguration.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateConfiguration.java new file mode 100644 index 00000000..45f30cc7 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateConfiguration.java @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.state; + +import tools.refinery.store.map.ContinuousHashProvider; +import tools.refinery.store.map.VersionedMapStore; + +public class VersionedMapStoreStateConfiguration { + + public VersionedMapStoreStateConfiguration() { + + } + public VersionedMapStoreStateConfiguration(boolean immutableWhenCommitting, boolean sharedNodeCacheInStore, + boolean sharedNodeCacheInStoreGroups) { + super(); + this.immutableWhenCommitting = immutableWhenCommitting; + this.sharedNodeCacheInStore = sharedNodeCacheInStore; + this.sharedNodeCacheInStoreGroups = sharedNodeCacheInStoreGroups; + } + + /** + * If true root is replaced with immutable node when committed. Frees up memory + * by releasing immutable nodes, but it may decrease performance by recreating + * immutable nodes upon changes (some evidence). + */ + private boolean immutableWhenCommitting = true; + public boolean isImmutableWhenCommitting() { + return immutableWhenCommitting; + } + + /** + * If true, all sub-nodes are cached within a {@link VersionedMapStore}. It + * decreases the memory requirements. It may increase performance by discovering + * existing immutable copy of a node (some evidence). Additional overhead may + * decrease performance (no example found). The option permits the efficient + * implementation of version deletion. + */ + private boolean sharedNodeCacheInStore = true; + public boolean isSharedNodeCacheInStore() { + return sharedNodeCacheInStore; + } + + /** + * If true, all sub-nodes are cached within a group of + * {@link VersionedMapStoreStateImpl#createSharedVersionedMapStores(int, ContinuousHashProvider, Object, VersionedMapStoreStateConfiguration)}. + * If {@link VersionedMapStoreStateConfiguration#sharedNodeCacheInStore} is + * false, then it has currently no impact. + */ + private boolean sharedNodeCacheInStoreGroups = true; + public boolean isSharedNodeCacheInStoreGroups() { + return sharedNodeCacheInStoreGroups; + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateImpl.java new file mode 100644 index 00000000..0651772a --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateImpl.java @@ -0,0 +1,129 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map.internal.state; + +import tools.refinery.store.map.*; + +import java.util.*; + +public class VersionedMapStoreStateImpl implements VersionedMapStore { + // Configuration + private final boolean immutableWhenCommitting; + + // Static data + protected final ContinuousHashProvider hashProvider; + protected final V defaultValue; + + // Dynamic data + protected final Map> states = new HashMap<>(); + protected final Map, ImmutableNode> nodeCache; + protected long nextID = 0; + + public VersionedMapStoreStateImpl(ContinuousHashProvider hashProvider, V defaultValue, + VersionedMapStoreStateConfiguration config) { + this.immutableWhenCommitting = config.isImmutableWhenCommitting(); + this.hashProvider = hashProvider; + this.defaultValue = defaultValue; + if (config.isSharedNodeCacheInStore()) { + nodeCache = new HashMap<>(); + } else { + nodeCache = null; + } + } + + private VersionedMapStoreStateImpl(ContinuousHashProvider hashProvider, V defaultValue, + Map, ImmutableNode> nodeCache, VersionedMapStoreStateConfiguration config) { + this.immutableWhenCommitting = config.isImmutableWhenCommitting(); + this.hashProvider = hashProvider; + this.defaultValue = defaultValue; + this.nodeCache = nodeCache; + } + + public VersionedMapStoreStateImpl(ContinuousHashProvider hashProvider, V defaultValue) { + this(hashProvider, defaultValue, new VersionedMapStoreStateConfiguration()); + } + + public static List> createSharedVersionedMapStores(int amount, + ContinuousHashProvider hashProvider, V defaultValue, + VersionedMapStoreStateConfiguration config) { + List> result = new ArrayList<>(amount); + if (config.isSharedNodeCacheInStoreGroups()) { + Map, ImmutableNode> nodeCache; + if (config.isSharedNodeCacheInStore()) { + nodeCache = new HashMap<>(); + } else { + nodeCache = null; + } + for (int i = 0; i < amount; i++) { + result.add(new VersionedMapStoreStateImpl<>(hashProvider, defaultValue, nodeCache, config)); + } + } else { + for (int i = 0; i < amount; i++) { + result.add(new VersionedMapStoreStateImpl<>(hashProvider, defaultValue, config)); + } + } + return result; + } + + public static List> createSharedVersionedMapStores(int amount, + ContinuousHashProvider hashProvider, V defaultValue) { + return createSharedVersionedMapStores(amount, hashProvider, defaultValue, new VersionedMapStoreStateConfiguration()); + } + + @Override + public synchronized Set getStates() { + return new HashSet<>(states.keySet()); + } + + @Override + public VersionedMap createMap() { + return new VersionedMapStateImpl<>(this, hashProvider, defaultValue); + } + + @Override + public VersionedMap createMap(long state) { + ImmutableNode data = revert(state); + return new VersionedMapStateImpl<>(this, hashProvider, defaultValue, data); + } + + public synchronized ImmutableNode revert(long state) { + if (states.containsKey(state)) { + return states.get(state); + } else { + ArrayList existingKeys = new ArrayList<>(states.keySet()); + Collections.sort(existingKeys); + throw new IllegalArgumentException("Store does not contain state " + state + "! Available states: " + + Arrays.toString(existingKeys.toArray())); + } + } + + public synchronized long commit(Node data, VersionedMapStateImpl mapToUpdateRoot) { + ImmutableNode immutable; + if (data != null) { + immutable = data.toImmutable(this.nodeCache); + } else { + immutable = null; + } + + if (nextID == Long.MAX_VALUE) + throw new IllegalStateException("Map store run out of Id-s"); + long id = nextID++; + this.states.put(id, immutable); + if (this.immutableWhenCommitting) { + mapToUpdateRoot.setRoot(immutable); + } + return id; + } + + @Override + public DiffCursor getDiffCursor(long fromState, long toState) { + VersionedMapStateImpl map1 = (VersionedMapStateImpl) createMap(fromState); + VersionedMapStateImpl map2 = (VersionedMapStateImpl) createMap(toState); + InOrderMapCursor cursor1 = new InOrderMapCursor<>(map1); + InOrderMapCursor cursor2 = new InOrderMapCursor<>(map2); + return new MapDiffCursor<>(this.defaultValue, cursor1, cursor2); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java b/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java index fdd4425e..3c94541f 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java @@ -5,12 +5,12 @@ */ package tools.refinery.store.model; -import tools.refinery.store.map.ContinousHashProvider; +import tools.refinery.store.map.ContinuousHashProvider; import tools.refinery.store.tuple.Tuple; import tools.refinery.store.tuple.Tuple1; import tools.refinery.store.tuple.Tuple2; -public class TupleHashProvider implements ContinousHashProvider { +public class TupleHashProvider implements ContinuousHashProvider { protected static final int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java b/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java deleted file mode 100644 index 14116a90..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.model; - -import tools.refinery.store.map.ContinousHashProvider; -import tools.refinery.store.tuple.Tuple; - -public class TupleHashProviderBitMagic implements ContinousHashProvider { - - @Override - public int getHash(Tuple key, int index) { - if(key.getSize() == 1) { - return key.get(0); - } - - int result = 0; - final int startBitIndex = index*30; - final int finalBitIndex = startBitIndex+30; - final int arity = key.getSize(); - - for(int i = startBitIndex; i<=finalBitIndex; i++) { - final int selectedKey = key.get(i%arity); - final int selectedPosition = 1<<(i/arity); - if((selectedKey&selectedPosition) != 0) { - result |= 1<<(i%30); - } - } - - return result; - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java index aafbe130..2cc7b19c 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java @@ -8,10 +8,10 @@ package tools.refinery.store.model.internal; import tools.refinery.store.adapter.AdapterUtils; import tools.refinery.store.adapter.ModelAdapterBuilder; import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.VersionedMapStoreImpl; +import tools.refinery.store.map.VersionedMapStoreFactory; +import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; import tools.refinery.store.model.ModelStore; import tools.refinery.store.model.ModelStoreBuilder; -import tools.refinery.store.model.TupleHashProvider; import tools.refinery.store.representation.AnySymbol; import tools.refinery.store.representation.Symbol; import tools.refinery.store.tuple.Tuple; @@ -77,8 +77,12 @@ public class ModelStoreBuilderImpl implements ModelStoreBuilder { private void createStores(Map> stores, SymbolEquivalenceClass equivalenceClass, List symbols) { int size = symbols.size(); - var storeGroup = VersionedMapStoreImpl.createSharedVersionedMapStores(size, TupleHashProvider.INSTANCE, - equivalenceClass.defaultValue()); + VersionedMapStoreFactory mapFactory = VersionedMapStore + .builder() + .strategy(VersionedMapStoreFactoryBuilder.StoreStrategy.DELTA) + .defaultValue(equivalenceClass.defaultValue()) + .build(); + var storeGroup = mapFactory.createGroup(size); for (int i = 0; i < size; i++) { stores.put(symbols.get(i), storeGroup.get(i)); } diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java index 4ada4ea4..98756460 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/InOrderCursorTest.java @@ -8,8 +8,8 @@ package tools.refinery.store.map.tests; import org.junit.jupiter.api.Test; import tools.refinery.store.map.VersionedMapStore; import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; -import tools.refinery.store.map.internal.InOrderMapCursor; -import tools.refinery.store.map.internal.VersionedMapImpl; +import tools.refinery.store.map.internal.state.InOrderMapCursor; +import tools.refinery.store.map.internal.state.VersionedMapStateImpl; import tools.refinery.store.map.tests.utils.MapTestEnvironment; import static org.junit.jupiter.api.Assertions.*; @@ -26,7 +26,7 @@ class InOrderCursorTest { .build() .createOne(); - VersionedMapImpl map = (VersionedMapImpl) store.createMap(); + VersionedMapStateImpl map = (VersionedMapStateImpl) store.createMap(); checkMove(map,0); map.put(1,"A"); @@ -44,7 +44,7 @@ class InOrderCursorTest { } - private void checkMove(VersionedMapImpl map, int num) { + private void checkMove(VersionedMapStateImpl map, int num) { InOrderMapCursor cursor = new InOrderMapCursor<>(map); for(int i=0; i store = new VersionedMapStoreImpl<>(TupleHashProvider.INSTANCE, false); + VersionedMapStore store = new VersionedMapStoreStateImpl<>(TupleHashProvider.INSTANCE, false); var map = store.createMap(); var out1 = map.put(Tuple.of(0), true); assertEquals(false, out1); diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java index 420dade6..abfb4791 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java @@ -17,10 +17,10 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import tools.refinery.store.map.ContinousHashProvider; +import tools.refinery.store.map.ContinuousHashProvider; import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.VersionedMapStoreImpl; -import tools.refinery.store.map.internal.VersionedMapImpl; +import tools.refinery.store.map.internal.state.VersionedMapStoreStateImpl; +import tools.refinery.store.map.internal.state.VersionedMapStateImpl; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; @@ -28,11 +28,11 @@ class MutableImmutableCompareFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, boolean evilHash) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); - ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); + ContinuousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); - VersionedMapStore store = new VersionedMapStoreImpl<>(chp, values[0]); - VersionedMapImpl immutable = (VersionedMapImpl) store.createMap(); - VersionedMapImpl mutable = (VersionedMapImpl) store.createMap(); + VersionedMapStore store = new VersionedMapStoreStateImpl<>(chp, values[0]); + VersionedMapStateImpl immutable = (VersionedMapStateImpl) store.createMap(); + VersionedMapStateImpl mutable = (VersionedMapStateImpl) store.createMap(); Random r = new Random(seed); @@ -40,8 +40,8 @@ class MutableImmutableCompareFuzzTest { commitFrequency); } - private void iterativeRandomPutsAndCommitsAndCompare(String scenario, VersionedMapImpl immutable, - VersionedMapImpl mutable, int steps, int maxKey, String[] values, Random r, + private void iterativeRandomPutsAndCommitsAndCompare(String scenario, VersionedMapStateImpl immutable, + VersionedMapStateImpl mutable, int steps, int maxKey, String[] values, Random r, int commitFrequency) { for (int i = 0; i < steps; i++) { int index = i + 1; diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java index 680d962d..b17766b7 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java @@ -18,10 +18,10 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import tools.refinery.store.map.ContinousHashProvider; +import tools.refinery.store.map.ContinuousHashProvider; import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.VersionedMapStoreImpl; -import tools.refinery.store.map.internal.VersionedMapImpl; +import tools.refinery.store.map.internal.state.VersionedMapStoreStateImpl; +import tools.refinery.store.map.internal.state.VersionedMapStateImpl; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; @@ -31,9 +31,9 @@ class SharedStoreFuzzTest { private void runFuzzTest(String scenario, int seed, int steps, int maxKey, int maxValue, boolean nullDefault, int commitFrequency, boolean evilHash) { String[] values = MapTestEnvironment.prepareValues(maxValue, nullDefault); - ContinousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); + ContinuousHashProvider chp = MapTestEnvironment.prepareHashProvider(evilHash); - List> stores = VersionedMapStoreImpl.createSharedVersionedMapStores(5, chp, values[0]); + List> stores = VersionedMapStoreStateImpl.createSharedVersionedMapStores(5, chp, values[0]); iterativeRandomPutsAndCommitsThenRestore(scenario, stores, steps, maxKey, values, seed, commitFrequency); } @@ -42,9 +42,9 @@ class SharedStoreFuzzTest { int steps, int maxKey, String[] values, int seed, int commitFrequency) { // 1. maps with versions Random r = new Random(seed); - List> versioneds = new LinkedList<>(); + List> versioneds = new LinkedList<>(); for (VersionedMapStore store : stores) { - versioneds.add((VersionedMapImpl) store.createMap()); + versioneds.add((VersionedMapStateImpl) store.createMap()); } List> index2Version = new LinkedList<>(); @@ -66,9 +66,9 @@ class SharedStoreFuzzTest { } } // 2. create a non-versioned and - List> reference = new LinkedList<>(); + List> reference = new LinkedList<>(); for (VersionedMapStore store : stores) { - reference.add((VersionedMapImpl) store.createMap()); + reference.add((VersionedMapStateImpl) store.createMap()); } r = new Random(seed); diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java index e7348370..401f2866 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java @@ -6,7 +6,7 @@ package tools.refinery.store.map.tests.utils; import tools.refinery.store.map.*; -import tools.refinery.store.map.internal.VersionedMapImpl; +import tools.refinery.store.map.internal.state.VersionedMapStateImpl; import java.util.*; import java.util.Map.Entry; @@ -28,7 +28,7 @@ public class MapTestEnvironment { return values; } - public static ContinousHashProvider prepareHashProvider(final boolean evil) { + public static ContinuousHashProvider prepareHashProvider(final boolean evil) { // Use maxPrime = 2147483629 return (key, index) -> { @@ -187,7 +187,7 @@ public class MapTestEnvironment { //System.out.println(cursor.getKey() + " " + ((VersionedMapImpl) versionedMap).getHashProvider() // .getHash(cursor.getKey(), 0)); if (previous != null) { - int comparisonResult = ((VersionedMapImpl) versionedMap).getHashProvider().compare(previous, + int comparisonResult = ((VersionedMapStateImpl) versionedMap).getHashProvider().compare(previous, cursor.getKey()); assertTrue(comparisonResult < 0, scenario + " Cursor order is not incremental!"); } diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/hashtests/HashEfficiencyTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/hashtests/HashEfficiencyTest.java index 4d4f5e26..5b595da7 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/model/hashtests/HashEfficiencyTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/model/hashtests/HashEfficiencyTest.java @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package tools.refinery.store.model.hashtests; +package tools.refinery.store.model.hashTests; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -14,10 +14,9 @@ import java.util.Random; import org.junit.jupiter.api.Test; -import tools.refinery.store.map.ContinousHashProvider; +import tools.refinery.store.map.ContinuousHashProvider; import tools.refinery.store.tuple.Tuple; import tools.refinery.store.model.TupleHashProvider; -import tools.refinery.store.model.TupleHashProviderBitMagic; class HashEfficiencyTest { @@ -95,7 +94,7 @@ class HashEfficiencyTest { List p = nRandoms(2, amount, 1);; assertEquals(amount,p.size()); } - private static double calculateHashClashes(List tuples, ContinousHashProvider chp) { + private static double calculateHashClashes(List tuples, ContinuousHashProvider chp) { int sumClashes = 0; for(int i = 0; i chp, Tuple a, Tuple b) { + private static int calculateHashClash(ContinuousHashProvider chp, Tuple a, Tuple b) { if(a.equals(b)) return 0; final int bits = 5; final int segments = Integer.SIZE/bits; @@ -131,11 +130,9 @@ class HashEfficiencyTest { } public static void main(String[] args) { List hashNames = new LinkedList<>(); - List> hashes = new LinkedList<>(); + List> hashes = new LinkedList<>(); hashNames.add("PrimeGroup"); hashes.add(new TupleHashProvider()); - hashNames.add("BitMagic"); - hashes.add(new TupleHashProviderBitMagic()); int[] arities = new int[] {2,3,4,5}; int[] sizes = new int[] {32*32,32*32*8}; -- cgit v1.2.3-70-g09d2 From c272a44efcff7d35a6ba31ef3cd12d1eb17640a0 Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Wed, 26 Jul 2023 17:48:25 +0200 Subject: Versioned.commit + Versioned.restore uses Version instead of long. When a Version is collected by gc, the store lets the state get collected by gc as well. --- .../java/tools/refinery/store/map/Version.java | 26 +++++++++ .../java/tools/refinery/store/map/Versioned.java | 18 +++++- .../tools/refinery/store/map/VersionedMap.java | 2 +- .../refinery/store/map/VersionedMapStore.java | 8 +-- .../store/map/VersionedMapStoreFactoryBuilder.java | 1 + .../VersionedMapStoreFactoryBuilderImpl.java | 23 ++++++-- .../store/map/internal/delta/MapTransaction.java | 10 ++-- .../map/internal/delta/VersionedMapDeltaImpl.java | 34 ++++++++--- .../internal/delta/VersionedMapStoreDeltaImpl.java | 41 ++++++-------- .../store/map/internal/state/ImmutableNode.java | 3 +- .../state/StateBasedVersionedMapStoreFactory.java | 11 +++- .../map/internal/state/VersionedMapStateImpl.java | 6 +- .../state/VersionedMapStoreStateConfiguration.java | 8 ++- .../internal/state/VersionedMapStoreStateImpl.java | 44 ++++++--------- .../tools/refinery/store/model/Interpretation.java | 3 +- .../java/tools/refinery/store/model/Model.java | 8 +-- .../tools/refinery/store/model/ModelListener.java | 4 +- .../tools/refinery/store/model/ModelStore.java | 8 +-- .../refinery/store/model/internal/ModelImpl.java | 66 +++++++++++----------- .../model/internal/ModelStoreBuilderImpl.java | 6 +- .../store/model/internal/ModelStoreImpl.java | 37 ++++++------ .../store/model/internal/ModelVersion.java | 39 +++++++++++++ .../model/internal/VersionedInterpretation.java | 14 ++--- .../store/map/tests/fuzz/DiffCursorFuzzTest.java | 21 +++---- .../map/tests/fuzz/MultiThreadTestRunnable.java | 16 +++--- .../store/map/tests/fuzz/RestoreFuzzTest.java | 5 +- .../store/map/tests/fuzz/SharedStoreFuzzTest.java | 5 +- .../map/tests/fuzz/utils/FuzzTestCollections.java | 13 ++++- .../store/map/tests/utils/MapTestEnvironment.java | 2 +- .../refinery/store/model/tests/ModelTest.java | 5 +- 30 files changed, 298 insertions(+), 189 deletions(-) create mode 100644 subprojects/store/src/main/java/tools/refinery/store/map/Version.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelVersion.java (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/Version.java b/subprojects/store/src/main/java/tools/refinery/store/map/Version.java new file mode 100644 index 00000000..fa2734e4 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/Version.java @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.map; + +/** + * Interface denoting versions of {@link Versioned}. + */ +public interface Version { + /** + * Hashcode should be updated in accordance with equals. + * @return a hashcode of the object. + */ + int hashCode(); + + /** + * Equivalence of two {@link Version}. This equivalence must satisfy the following constraint (in addition to the + * constraints of {@link Object#equals(Object)}: if {@code v1} and {@code v2} are {@link Version}s, and {@code v1 + * .equals(v2)}, then {@code versioned.restore(v1)} must be {@code equals} to {@code versioned.restore(v2)}. + * @param o the other object. + * @return weather the two versions are equals. + */ + boolean equals(Object o); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java b/subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java index 55720db3..da12b0a9 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java @@ -5,8 +5,20 @@ */ package tools.refinery.store.map; +/** + * Object that can save and restore its state. + */ public interface Versioned { - public long commit(); - //maybe revert()? - public void restore(long state); + /** + * Saves the state of the object. + * @return an object that marks the version of the object at the time the function was called. + */ + Version commit(); + + /** + * Restores the state of the object. + * @param state a {@link Version} object that marks the version. The state must be a {@link Version} object + * returned by a previous {@link #commit()}! + */ + void restore(Version state); } diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java index c8226c3e..28194b58 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java @@ -16,5 +16,5 @@ public non-sealed interface VersionedMap extends AnyVersionedMap { void putAll(Cursor cursor); - DiffCursor getDiffCursor(long state); + DiffCursor getDiffCursor(Version state); } diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java index b24c404c..55cf08a5 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java @@ -7,17 +7,13 @@ package tools.refinery.store.map; import tools.refinery.store.map.internal.VersionedMapStoreFactoryBuilderImpl; -import java.util.Set; - public interface VersionedMapStore { VersionedMap createMap(); - VersionedMap createMap(long state); - - Set getStates(); + VersionedMap createMap(Version state); - DiffCursor getDiffCursor(long fromState, long toState); + DiffCursor getDiffCursor(Version fromState, Version toState); static VersionedMapStoreFactoryBuilder builder() { return new VersionedMapStoreFactoryBuilderImpl<>(); diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java index 6b4fc2a0..0ac196f2 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreFactoryBuilder.java @@ -20,6 +20,7 @@ public interface VersionedMapStoreFactoryBuilder { VersionedMapStoreFactoryBuilder defaultValue(V defaultValue); VersionedMapStoreFactoryBuilder strategy(StoreStrategy strategy); + VersionedMapStoreFactoryBuilder versionFreeing(boolean enabled); VersionedMapStoreFactoryBuilder stateBasedImmutableWhenCommitting(boolean transformToImmutable); VersionedMapStoreFactoryBuilder stateBasedSharingStrategy(SharingStrategy sharingStrategy); VersionedMapStoreFactoryBuilder stateBasedHashProvider(ContinuousHashProvider hashProvider); diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java index 47470236..9f419ce1 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapStoreFactoryBuilderImpl.java @@ -18,6 +18,7 @@ public class VersionedMapStoreFactoryBuilderImpl implements VersionedMapSt private StoreStrategy strategy = null; private Boolean transformToImmutable = null; private SharingStrategy sharingStrategy = null; + private Boolean enableVersionFreeing = null; private ContinuousHashProvider continuousHashProvider = null; private DeltaTransactionStrategy deltaTransactionStrategy = null; @@ -64,6 +65,13 @@ public class VersionedMapStoreFactoryBuilderImpl implements VersionedMapSt return this; } + @Override + public VersionedMapStoreFactoryBuilder versionFreeing(boolean enabled) { + this.enableVersionFreeing = enabled; + checkStrategy(); + return this; + } + @Override public VersionedMapStoreFactoryBuilder stateBasedImmutableWhenCommitting(boolean transformToImmutable) { this.transformToImmutable = transformToImmutable; @@ -118,6 +126,7 @@ public class VersionedMapStoreFactoryBuilderImpl implements VersionedMapSt yield new StateBasedVersionedMapStoreFactory<>(defaultValue, getOrDefault(transformToImmutable,true), getOrDefault(sharingStrategy, SharingStrategy.SHARED_NODE_CACHE_IN_GROUP), + getOrDefault(enableVersionFreeing, true), continuousHashProvider); } case DELTA -> new DeltaBasedVersionedMapStoreFactory<>(defaultValue, @@ -127,13 +136,15 @@ public class VersionedMapStoreFactoryBuilderImpl implements VersionedMapSt @Override public String toString() { - return "VersionedMapStoreBuilder{" + - "defaultValue=" + defaultValue + + return "VersionedMapStoreFactoryBuilderImpl{" + + "defaultSet=" + defaultSet + + ", defaultValue=" + defaultValue + ", strategy=" + strategy + - ", stateBasedImmutableWhenCommitting=" + transformToImmutable + - ", stateBasedNodeSharingStrategy=" + sharingStrategy + - ", hashProvider=" + continuousHashProvider + - ", deltaStorageStrategy=" + deltaTransactionStrategy + + ", transformToImmutable=" + transformToImmutable + + ", sharingStrategy=" + sharingStrategy + + ", enableVersionFreeing=" + enableVersionFreeing + + ", continuousHashProvider=" + continuousHashProvider + + ", deltaTransactionStrategy=" + deltaTransactionStrategy + '}'; } } diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapTransaction.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapTransaction.java index 7f9ccd7f..6f3fa6d7 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapTransaction.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/MapTransaction.java @@ -5,17 +5,19 @@ */ package tools.refinery.store.map.internal.delta; +import tools.refinery.store.map.Version; + import java.util.Arrays; import java.util.Objects; -public record MapTransaction(MapDelta[] deltas, long version, MapTransaction parent) { +public record MapTransaction(MapDelta[] deltas, MapTransaction parent, int depth) implements Version { @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(deltas); - result = prime * result + Objects.hash(parent, version); + result = prime * result + Objects.hash(parent, depth); return result; } @@ -29,11 +31,11 @@ public record MapTransaction(MapDelta[] deltas, long version, MapTra return false; @SuppressWarnings("unchecked") MapTransaction other = (MapTransaction) obj; - return Arrays.equals(deltas, other.deltas) && Objects.equals(parent, other.parent) && version == other.version; + return depth == other.depth && Objects.equals(parent, other.parent) && Arrays.equals(deltas, other.deltas); } @Override public String toString() { - return "MapTransaction " + version + " " + Arrays.toString(deltas); + return "MapTransaction " + depth + " " + Arrays.toString(deltas); } } diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java index 5bb864ac..c19cc817 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java @@ -38,15 +38,15 @@ public class VersionedMapDeltaImpl implements VersionedMap { } @Override - public long commit() { + public Version commit() { MapDelta[] deltas = uncommittedStore.extractAndDeleteDeltas(); - long[] versionContainer = new long[1]; - this.previous = this.store.appendTransaction(deltas, previous, versionContainer); - return versionContainer[0]; + final MapTransaction committedTransaction = this.store.appendTransaction(deltas, previous); + this.previous = committedTransaction; + return committedTransaction; } @Override - public void restore(long state) { + public void restore(Version state) { // 1. restore uncommitted states MapDelta[] uncommitted = this.uncommittedStore.extractAndDeleteDeltas(); if (uncommitted != null) { @@ -61,7 +61,7 @@ public class VersionedMapDeltaImpl implements VersionedMap { this.forward(forward); } else { List[]> backward = new ArrayList<>(); - parent = this.store.getPath(this.previous.version(), state, backward, forward); + parent = this.store.getPath(this.previous, state, backward, forward); this.backward(backward); this.forward(forward); } @@ -75,12 +75,16 @@ public class VersionedMapDeltaImpl implements VersionedMap { } protected void backward(List[]> changes) { + //Currently, this loop statement is faster. + //noinspection ForLoopReplaceableByForEach for (int i = 0; i < changes.size(); i++) { backward(changes.get(i)); } } protected void forward(MapDelta[] changes) { + //Currently, this loop statement is faster. + //noinspection ForLoopReplaceableByForEach for (int i = 0; i < changes.length; i++) { final MapDelta change = changes[i]; K key = change.getKey(); @@ -168,7 +172,7 @@ public class VersionedMapDeltaImpl implements VersionedMap { } @Override - public DiffCursor getDiffCursor(long state) { + public DiffCursor getDiffCursor(Version state) { MapDelta[] backward = this.uncommittedStore.extractDeltas(); List[]> backwardTransactions = new ArrayList<>(); List[]> forwardTransactions = new ArrayList<>(); @@ -178,7 +182,7 @@ public class VersionedMapDeltaImpl implements VersionedMap { } if (this.previous != null) { - store.getPath(this.previous.version(), state, backwardTransactions, forwardTransactions); + store.getPath(this.previous, state, backwardTransactions, forwardTransactions); } else { store.getPath(state, forwardTransactions); } @@ -216,5 +220,19 @@ public class VersionedMapDeltaImpl implements VersionedMap { throw new IllegalStateException("null value stored in map!"); } } + MapTransaction transaction = this.previous; + while(transaction != null) { + MapTransaction parent = transaction.parent(); + if(parent != null) { + if(parent.depth() != transaction.depth()-1) { + throw new IllegalStateException("Parent depths are inconsistent!"); + } + } else { + if(transaction.depth() != 0) { + throw new IllegalArgumentException("Root depth is not 0!"); + } + } + transaction = transaction.parent(); + } } } diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapStoreDeltaImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapStoreDeltaImpl.java index 7f56ea77..ed169409 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapStoreDeltaImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapStoreDeltaImpl.java @@ -6,6 +6,7 @@ package tools.refinery.store.map.internal.delta; import tools.refinery.store.map.DiffCursor; +import tools.refinery.store.map.Version; import tools.refinery.store.map.VersionedMap; import tools.refinery.store.map.VersionedMapStore; @@ -18,10 +19,6 @@ public class VersionedMapStoreDeltaImpl implements VersionedMapStore // Static data protected final V defaultValue; - // Dynamic data - protected final Map> states = new HashMap<>(); - protected long nextID = 0; - public VersionedMapStoreDeltaImpl(boolean summarizeChanges, V defaultValue) { this.summarizeChanges = summarizeChanges; this.defaultValue = defaultValue; @@ -33,30 +30,32 @@ public class VersionedMapStoreDeltaImpl implements VersionedMapStore } @Override - public VersionedMap createMap(long state) { + public VersionedMap createMap(Version state) { VersionedMapDeltaImpl result = new VersionedMapDeltaImpl<>(this, this.summarizeChanges, this.defaultValue); result.restore(state); return result; } - public synchronized MapTransaction appendTransaction(MapDelta[] deltas, MapTransaction previous, long[] versionContainer) { - long version = nextID++; - versionContainer[0] = version; + public MapTransaction appendTransaction(MapDelta[] deltas, MapTransaction previous) { if (deltas == null) { - states.put(version, previous); return previous; } else { - MapTransaction transaction = new MapTransaction<>(deltas, version, previous); - states.put(version, transaction); - return transaction; + final int depth; + if(previous != null) { + depth = previous.depth()+1; + } else { + depth = 0; + } + return new MapTransaction<>(deltas, previous, depth); } } - private synchronized MapTransaction getState(long state) { - return states.get(state); + @SuppressWarnings("unchecked") + private MapTransaction getState(Version state) { + return (MapTransaction) state; } - public MapTransaction getPath(long to, List[]> forwardTransactions) { + public MapTransaction getPath(Version to, List[]> forwardTransactions) { final MapTransaction target = getState(to); MapTransaction toTransaction = target; while (toTransaction != null) { @@ -66,7 +65,7 @@ public class VersionedMapStoreDeltaImpl implements VersionedMapStore return target; } - public MapTransaction getPath(long from, long to, + public MapTransaction getPath(Version from, Version to, List[]> backwardTransactions, List[]> forwardTransactions) { MapTransaction fromTransaction = getState(from); @@ -74,7 +73,7 @@ public class VersionedMapStoreDeltaImpl implements VersionedMapStore MapTransaction toTransaction = target; while (fromTransaction != toTransaction) { - if (fromTransaction == null || (toTransaction != null && fromTransaction.version() < toTransaction.version())) { + if (fromTransaction == null || (toTransaction != null && fromTransaction.depth() < toTransaction.depth())) { forwardTransactions.add(toTransaction.deltas()); toTransaction = toTransaction.parent(); } else { @@ -85,14 +84,8 @@ public class VersionedMapStoreDeltaImpl implements VersionedMapStore return target; } - - @Override - public synchronized Set getStates() { - return new HashSet<>(states.keySet()); - } - @Override - public DiffCursor getDiffCursor(long fromState, long toState) { + public DiffCursor getDiffCursor(Version fromState, Version toState) { List[]> backwardTransactions = new ArrayList<>(); List[]> forwardTransactions = new ArrayList<>(); getPath(fromState, toState, backwardTransactions, forwardTransactions); diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/ImmutableNode.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/ImmutableNode.java index 722f9ed7..5b1d8b77 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/ImmutableNode.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/ImmutableNode.java @@ -9,8 +9,9 @@ import java.util.Arrays; import java.util.Map; import tools.refinery.store.map.ContinuousHashProvider; +import tools.refinery.store.map.Version; -public class ImmutableNode extends Node { +public class ImmutableNode extends Node implements Version { /** * Bitmap defining the stored key and values. */ diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/StateBasedVersionedMapStoreFactory.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/StateBasedVersionedMapStoreFactory.java index 9409ace0..ccc791a8 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/StateBasedVersionedMapStoreFactory.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/StateBasedVersionedMapStoreFactory.java @@ -14,14 +14,19 @@ public class StateBasedVersionedMapStoreFactory implements VersionedMapSto private final ContinuousHashProvider continuousHashProvider; private final VersionedMapStoreStateConfiguration config; - public StateBasedVersionedMapStoreFactory(V defaultValue, Boolean transformToImmutable, VersionedMapStoreFactoryBuilder.SharingStrategy sharingStrategy, ContinuousHashProvider continuousHashProvider) { + public StateBasedVersionedMapStoreFactory(V defaultValue, Boolean transformToImmutable, + VersionedMapStoreFactoryBuilder.SharingStrategy sharingStrategy, + boolean versionFreeingEnabled, + ContinuousHashProvider continuousHashProvider) { this.defaultValue = defaultValue; this.continuousHashProvider = continuousHashProvider; this.config = new VersionedMapStoreStateConfiguration( transformToImmutable, - sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE || sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE_IN_GROUP, - sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE_IN_GROUP); + sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE + || sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE_IN_GROUP, + sharingStrategy == VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE_IN_GROUP, + versionFreeingEnabled); } @Override diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStateImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStateImpl.java index 817fc70b..57eeccf6 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStateImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStateImpl.java @@ -115,7 +115,7 @@ public class VersionedMapStateImpl implements VersionedMap { } @Override - public DiffCursor getDiffCursor(long toVersion) { + public DiffCursor getDiffCursor(Version toVersion) { InOrderMapCursor fromCursor = new InOrderMapCursor<>(this); VersionedMapStateImpl toMap = (VersionedMapStateImpl) this.store.createMap(toVersion); InOrderMapCursor toCursor = new InOrderMapCursor<>(toMap); @@ -124,7 +124,7 @@ public class VersionedMapStateImpl implements VersionedMap { @Override - public long commit() { + public Version commit() { return this.store.commit(root, this); } @@ -133,7 +133,7 @@ public class VersionedMapStateImpl implements VersionedMap { } @Override - public void restore(long state) { + public void restore(Version state) { root = this.store.revert(state); } diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateConfiguration.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateConfiguration.java index 45f30cc7..6650f565 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateConfiguration.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateConfiguration.java @@ -14,11 +14,12 @@ public class VersionedMapStoreStateConfiguration { } public VersionedMapStoreStateConfiguration(boolean immutableWhenCommitting, boolean sharedNodeCacheInStore, - boolean sharedNodeCacheInStoreGroups) { + boolean sharedNodeCacheInStoreGroups, boolean versionFreeingEnabled) { super(); this.immutableWhenCommitting = immutableWhenCommitting; this.sharedNodeCacheInStore = sharedNodeCacheInStore; this.sharedNodeCacheInStoreGroups = sharedNodeCacheInStoreGroups; + this.versionFreeingEnabled = versionFreeingEnabled; } /** @@ -53,4 +54,9 @@ public class VersionedMapStoreStateConfiguration { public boolean isSharedNodeCacheInStoreGroups() { return sharedNodeCacheInStoreGroups; } + + private boolean versionFreeingEnabled = true; + public boolean isVersionFreeingEnabled() { + return versionFreeingEnabled; + } } diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateImpl.java index 0651772a..8ff3f8e7 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/state/VersionedMapStoreStateImpl.java @@ -17,10 +17,7 @@ public class VersionedMapStoreStateImpl implements VersionedMapStore protected final ContinuousHashProvider hashProvider; protected final V defaultValue; - // Dynamic data - protected final Map> states = new HashMap<>(); protected final Map, ImmutableNode> nodeCache; - protected long nextID = 0; public VersionedMapStoreStateImpl(ContinuousHashProvider hashProvider, V defaultValue, VersionedMapStoreStateConfiguration config) { @@ -28,7 +25,7 @@ public class VersionedMapStoreStateImpl implements VersionedMapStore this.hashProvider = hashProvider; this.defaultValue = defaultValue; if (config.isSharedNodeCacheInStore()) { - nodeCache = new HashMap<>(); + nodeCache = createNoteCache(config); } else { nodeCache = null; } @@ -53,7 +50,7 @@ public class VersionedMapStoreStateImpl implements VersionedMapStore if (config.isSharedNodeCacheInStoreGroups()) { Map, ImmutableNode> nodeCache; if (config.isSharedNodeCacheInStore()) { - nodeCache = new HashMap<>(); + nodeCache = createNoteCache(config); } else { nodeCache = null; } @@ -68,39 +65,36 @@ public class VersionedMapStoreStateImpl implements VersionedMapStore return result; } + private static Map createNoteCache(VersionedMapStoreStateConfiguration config) { + if(config.isVersionFreeingEnabled()) { + return new WeakHashMap<>(); + } else { + return new HashMap<>(); + } + } + public static List> createSharedVersionedMapStores(int amount, ContinuousHashProvider hashProvider, V defaultValue) { return createSharedVersionedMapStores(amount, hashProvider, defaultValue, new VersionedMapStoreStateConfiguration()); } - @Override - public synchronized Set getStates() { - return new HashSet<>(states.keySet()); - } - @Override public VersionedMap createMap() { return new VersionedMapStateImpl<>(this, hashProvider, defaultValue); } @Override - public VersionedMap createMap(long state) { + public VersionedMap createMap(Version state) { ImmutableNode data = revert(state); return new VersionedMapStateImpl<>(this, hashProvider, defaultValue, data); } - public synchronized ImmutableNode revert(long state) { - if (states.containsKey(state)) { - return states.get(state); - } else { - ArrayList existingKeys = new ArrayList<>(states.keySet()); - Collections.sort(existingKeys); - throw new IllegalArgumentException("Store does not contain state " + state + "! Available states: " - + Arrays.toString(existingKeys.toArray())); - } + @SuppressWarnings("unchecked") + public synchronized ImmutableNode revert(Version state) { + return (ImmutableNode) state; } - public synchronized long commit(Node data, VersionedMapStateImpl mapToUpdateRoot) { + public synchronized Version commit(Node data, VersionedMapStateImpl mapToUpdateRoot) { ImmutableNode immutable; if (data != null) { immutable = data.toImmutable(this.nodeCache); @@ -108,18 +102,14 @@ public class VersionedMapStoreStateImpl implements VersionedMapStore immutable = null; } - if (nextID == Long.MAX_VALUE) - throw new IllegalStateException("Map store run out of Id-s"); - long id = nextID++; - this.states.put(id, immutable); if (this.immutableWhenCommitting) { mapToUpdateRoot.setRoot(immutable); } - return id; + return immutable; } @Override - public DiffCursor getDiffCursor(long fromState, long toState) { + public DiffCursor getDiffCursor(Version fromState, Version toState) { VersionedMapStateImpl map1 = (VersionedMapStateImpl) createMap(fromState); VersionedMapStateImpl map2 = (VersionedMapStateImpl) createMap(toState); InOrderMapCursor cursor1 = new InOrderMapCursor<>(map1); diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java index 26ad9a69..72f188d3 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java @@ -7,6 +7,7 @@ package tools.refinery.store.model; import tools.refinery.store.map.Cursor; import tools.refinery.store.map.DiffCursor; +import tools.refinery.store.map.Version; import tools.refinery.store.representation.Symbol; import tools.refinery.store.tuple.Tuple; @@ -22,7 +23,7 @@ public non-sealed interface Interpretation extends AnyInterpretation { void putAll(Cursor cursor); - DiffCursor getDiffCursor(long to); + DiffCursor getDiffCursor(Version to); void addListener(InterpretationListener listener, boolean alsoWhenRestoring); diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/Model.java b/subprojects/store/src/main/java/tools/refinery/store/model/Model.java index d58d91c3..a028b81b 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/Model.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/Model.java @@ -6,6 +6,7 @@ package tools.refinery.store.model; import tools.refinery.store.adapter.ModelAdapter; +import tools.refinery.store.map.Version; import tools.refinery.store.map.Versioned; import tools.refinery.store.representation.AnySymbol; import tools.refinery.store.representation.Symbol; @@ -13,11 +14,10 @@ import tools.refinery.store.representation.Symbol; import java.util.Optional; public interface Model extends Versioned { - long NO_STATE_ID = -1; - + Version NO_STATE_ID = null; ModelStore getStore(); - long getState(); + Version getState(); boolean hasUncommittedChanges(); @@ -27,7 +27,7 @@ public interface Model extends Versioned { Interpretation getInterpretation(Symbol symbol); - ModelDiffCursor getDiffCursor(long to); + ModelDiffCursor getDiffCursor(Version to); Optional tryGetAdapter(Class adapterType); diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java b/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java index a9ad8cfd..703ee10a 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java @@ -5,6 +5,8 @@ */ package tools.refinery.store.model; +import tools.refinery.store.map.Version; + public interface ModelListener { default void beforeCommit() { } @@ -12,7 +14,7 @@ public interface ModelListener { default void afterCommit() { } - default void beforeRestore(long state) { + default void beforeRestore(Version state) { } default void afterRestore() { diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java index b10eb8a4..89382b3a 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java @@ -6,23 +6,21 @@ package tools.refinery.store.model; import tools.refinery.store.adapter.ModelStoreAdapter; +import tools.refinery.store.map.Version; import tools.refinery.store.model.internal.ModelStoreBuilderImpl; import tools.refinery.store.representation.AnySymbol; import java.util.Collection; import java.util.Optional; -import java.util.Set; public interface ModelStore { Collection getSymbols(); Model createEmptyModel(); - Model createModelForState(long state); + Model createModelForState(Version state); - Set getStates(); - - ModelDiffCursor getDiffCursor(long from, long to); + ModelDiffCursor getDiffCursor(Version from, Version to); Optional tryGetAdapter(Class adapterType); diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java index c5475a1a..c2ad9257 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java @@ -8,6 +8,7 @@ package tools.refinery.store.model.internal; import tools.refinery.store.adapter.AdapterUtils; import tools.refinery.store.adapter.ModelAdapter; import tools.refinery.store.map.DiffCursor; +import tools.refinery.store.map.Version; import tools.refinery.store.model.*; import tools.refinery.store.representation.AnySymbol; import tools.refinery.store.representation.Symbol; @@ -17,21 +18,21 @@ import java.util.*; public class ModelImpl implements Model { private final ModelStore store; - private long state; - private Map> interpretations; + private Version state; + private LinkedHashMap> interpretations; private final List adapters; private final List listeners = new ArrayList<>(); private boolean uncommittedChanges; private ModelAction pendingAction = ModelAction.NONE; - private long restoringToState = NO_STATE_ID; + private Version restoringToState = null; - ModelImpl(ModelStore store, long state, int adapterCount) { + ModelImpl(ModelStore store, Version state, int adapterCount) { this.store = store; this.state = state; adapters = new ArrayList<>(adapterCount); } - void setInterpretations(Map> interpretations) { + void setInterpretations(LinkedHashMap> interpretations) { this.interpretations = interpretations; } @@ -41,7 +42,7 @@ public class ModelImpl implements Model { } @Override - public long getState() { + public Version getState() { return state; } @@ -57,7 +58,7 @@ public class ModelImpl implements Model { } @Override - public ModelDiffCursor getDiffCursor(long to) { + public ModelDiffCursor getDiffCursor(Version to) { var diffCursors = new HashMap>(interpretations.size()); for (var entry : interpretations.entrySet()) { diffCursors.put(entry.getKey(), entry.getValue().getDiffCursor(to)); @@ -65,7 +66,7 @@ public class ModelImpl implements Model { return new ModelDiffCursor(diffCursors); } - private void setState(long state) { + private void setState(Version state) { this.state = state; uncommittedChanges = false; } @@ -82,11 +83,11 @@ public class ModelImpl implements Model { } private boolean hasPendingAction() { - return pendingAction != ModelAction.NONE || restoringToState != NO_STATE_ID; + return pendingAction != ModelAction.NONE || restoringToState != null; } @Override - public long commit() { + public Version commit() { if (hasPendingAction()) { throw pendingActionError("commit"); } @@ -94,43 +95,40 @@ public class ModelImpl implements Model { try { int listenerCount = listeners.size(); int i = listenerCount; - long version = 0; + + // Before commit message to listeners while (i > 0) { i--; listeners.get(i).beforeCommit(); } - boolean versionSet = false; - for (var interpretation : interpretations.values()) { - long newVersion = interpretation.commit(); - if (versionSet) { - if (version != newVersion) { - throw new IllegalStateException("Interpretations in model have different versions (%d and %d)" - .formatted(version, newVersion)); - } - } else { - version = newVersion; - versionSet = true; - } + + // Doing the commit on the interpretations + Version[] interpretationVersions = new Version[interpretations.size()]; + int j = 0; + for(var interpretationEntry : interpretations.entrySet()) { + interpretationVersions[j++] = interpretationEntry.getValue().commit(); } - setState(version); + ModelVersion modelVersion = new ModelVersion(interpretationVersions); + setState(modelVersion); + + // After commit message to listeners while (i < listenerCount) { listeners.get(i).afterCommit(); i++; } - return version; + + return modelVersion; } finally { pendingAction = ModelAction.NONE; } } @Override - public void restore(long version) { + public void restore(Version version) { if (hasPendingAction()) { - throw pendingActionError("restore to %d".formatted(version)); - } - if (!store.getStates().contains(version)) { - throw new IllegalArgumentException("Store does not contain state %d".formatted(version)); + throw pendingActionError("restore to %s".formatted(version)); } + pendingAction = ModelAction.RESTORE; restoringToState = version; try { @@ -140,9 +138,11 @@ public class ModelImpl implements Model { i--; listeners.get(i).beforeRestore(version); } + int j = 0; for (var interpretation : interpretations.values()) { - interpretation.restore(version); + interpretation.restore(ModelVersion.getInternalVersion(version,j++)); } + setState(version); while (i < listenerCount) { listeners.get(i).afterRestore(); @@ -150,7 +150,7 @@ public class ModelImpl implements Model { } } finally { pendingAction = ModelAction.NONE; - restoringToState = NO_STATE_ID; + restoringToState = null; } } @@ -159,7 +159,7 @@ public class ModelImpl implements Model { case NONE -> throw new IllegalArgumentException("Trying to throw pending action error when there is no " + "pending action"); case COMMIT -> "commit"; - case RESTORE -> "restore to %d".formatted(restoringToState); + case RESTORE -> "restore to %s".formatted(restoringToState); }; return new IllegalStateException("Cannot %s due to pending %s".formatted(currentActionName, pendingActionName)); } diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java index 2cc7b19c..65fa8d24 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java @@ -19,8 +19,8 @@ import tools.refinery.store.tuple.Tuple; import java.util.*; public class ModelStoreBuilderImpl implements ModelStoreBuilder { - private final Set allSymbols = new HashSet<>(); - private final Map, List> equivalenceClasses = new HashMap<>(); + private final LinkedHashSet allSymbols = new LinkedHashSet<>(); + private final LinkedHashMap, List> equivalenceClasses = new LinkedHashMap<>(); private final List adapters = new ArrayList<>(); @Override @@ -59,7 +59,7 @@ public class ModelStoreBuilderImpl implements ModelStoreBuilder { @Override public ModelStore build() { - var stores = new HashMap>(allSymbols.size()); + var stores = new LinkedHashMap>(allSymbols.size()); for (var entry : equivalenceClasses.entrySet()) { createStores(stores, entry.getKey(), entry.getValue()); } diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java index 60b735e6..a320a618 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java @@ -8,8 +8,8 @@ package tools.refinery.store.model.internal; import tools.refinery.store.adapter.AdapterUtils; import tools.refinery.store.adapter.ModelStoreAdapter; import tools.refinery.store.map.DiffCursor; +import tools.refinery.store.map.Version; import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.model.Model; import tools.refinery.store.model.ModelDiffCursor; import tools.refinery.store.model.ModelStore; import tools.refinery.store.representation.AnySymbol; @@ -18,10 +18,10 @@ import tools.refinery.store.tuple.Tuple; import java.util.*; public class ModelStoreImpl implements ModelStore { - private final Map> stores; + private final LinkedHashMap> stores; private final List adapters; - ModelStoreImpl(Map> stores, int adapterCount) { + ModelStoreImpl(LinkedHashMap> stores, int adapterCount) { this.stores = stores; adapters = new ArrayList<>(adapterCount); } @@ -31,14 +31,14 @@ public class ModelStoreImpl implements ModelStore { return Collections.unmodifiableCollection(stores.keySet()); } - private ModelImpl createModelWithoutInterpretations(long state) { + private ModelImpl createModelWithoutInterpretations(Version state) { return new ModelImpl(this, state, adapters.size()); } @Override public ModelImpl createEmptyModel() { - var model = createModelWithoutInterpretations(Model.NO_STATE_ID); - var interpretations = new HashMap>(stores.size()); + var model = createModelWithoutInterpretations(null); + var interpretations = new LinkedHashMap>(stores.size()); for (var entry : this.stores.entrySet()) { var symbol = entry.getKey(); interpretations.put(symbol, VersionedInterpretation.of(model, symbol, entry.getValue())); @@ -49,13 +49,21 @@ public class ModelStoreImpl implements ModelStore { } @Override - public synchronized ModelImpl createModelForState(long state) { + public synchronized ModelImpl createModelForState(Version state) { var model = createModelWithoutInterpretations(state); - var interpretations = new HashMap>(stores.size()); + var interpretations = new LinkedHashMap>(stores.size()); + + int i=0; for (var entry : this.stores.entrySet()) { var symbol = entry.getKey(); - interpretations.put(symbol, VersionedInterpretation.of(model, symbol, entry.getValue(), state)); + interpretations.put(symbol, + VersionedInterpretation.of( + model, + symbol, + entry.getValue(), + ModelVersion.getInternalVersion(state,i++))); } + model.setInterpretations(interpretations); adaptModel(model); return model; @@ -69,16 +77,7 @@ public class ModelStoreImpl implements ModelStore { } @Override - public synchronized Set getStates() { - var iterator = stores.values().iterator(); - if (iterator.hasNext()) { - return Set.copyOf(iterator.next().getStates()); - } - return Set.of(0L); - } - - @Override - public synchronized ModelDiffCursor getDiffCursor(long from, long to) { + public synchronized ModelDiffCursor getDiffCursor(Version from, Version to) { var diffCursors = new HashMap>(); for (var entry : stores.entrySet()) { var representation = entry.getKey(); diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelVersion.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelVersion.java new file mode 100644 index 00000000..cf3b7fc6 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelVersion.java @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.model.internal; + +import tools.refinery.store.map.Version; + +import java.util.Arrays; + +public record ModelVersion(Version[] mapVersions) implements Version{ + + public static Version getInternalVersion(Version modelVersion, int interpretationIndex) { + return ((ModelVersion)modelVersion).mapVersions()[interpretationIndex]; + } + + @Override + public int hashCode() { + return Arrays.hashCode(mapVersions); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ModelVersion that = (ModelVersion) o; + + return Arrays.equals(mapVersions, that.mapVersions); + } + + @Override + public String toString() { + return "ModelVersion{" + + "mapVersions=" + Arrays.toString(mapVersions) + + '}'; + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java index 404be65f..76e3baea 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java @@ -5,10 +5,7 @@ */ package tools.refinery.store.model.internal; -import tools.refinery.store.map.Cursor; -import tools.refinery.store.map.DiffCursor; -import tools.refinery.store.map.VersionedMap; -import tools.refinery.store.map.VersionedMapStore; +import tools.refinery.store.map.*; import tools.refinery.store.model.Interpretation; import tools.refinery.store.model.InterpretationListener; import tools.refinery.store.model.Model; @@ -110,15 +107,14 @@ public class VersionedInterpretation implements Interpretation { } @Override - public DiffCursor getDiffCursor(long to) { + public DiffCursor getDiffCursor(Version to) { return map.getDiffCursor(to); } - public long commit() { + Version commit() { return map.commit(); } - - public void restore(long state) { + void restore(Version state) { if (!restoreListeners.isEmpty()) { var diffCursor = getDiffCursor(state); while (diffCursor.move()) { @@ -150,7 +146,7 @@ public class VersionedInterpretation implements Interpretation { } static VersionedInterpretation of(ModelImpl model, AnySymbol symbol, VersionedMapStore store, - long state) { + Version state) { @SuppressWarnings("unchecked") var typedSymbol = (Symbol) symbol; var map = store.createMap(state); diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java index 5a4f8038..94259edc 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java @@ -10,13 +10,12 @@ import org.junit.jupiter.api.Timeout; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import tools.refinery.store.map.DiffCursor; -import tools.refinery.store.map.VersionedMap; -import tools.refinery.store.map.VersionedMapStore; -import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; +import tools.refinery.store.map.*; import tools.refinery.store.map.tests.fuzz.utils.FuzzTestUtils; import tools.refinery.store.map.tests.utils.MapTestEnvironment; +import java.util.HashMap; +import java.util.Map; import java.util.Random; import java.util.stream.Stream; @@ -39,6 +38,7 @@ class DiffCursorFuzzTest { int commitFrequency, boolean commitBeforeDiffCursor) { int largestCommit = -1; + Map index2Version = new HashMap<>(); { // 1. build a map with versions @@ -55,8 +55,9 @@ class DiffCursorFuzzTest { fail(scenario + ":" + index + ": exception happened: " + exception); } if (index % commitFrequency == 0) { - long version = versioned.commit(); - largestCommit = (int) version; + Version version = versioned.commit(); + index2Version.put(index,version); + largestCommit = index; } if (index % 10000 == 0) System.out.println(scenario + ":" + index + "/" + steps + " building finished"); @@ -73,20 +74,20 @@ class DiffCursorFuzzTest { int index = i + 1; if (index % diffTravelFrequency == 0) { // diff-travel - long travelToVersion = r2.nextInt(largestCommit + 1); + int travelToVersion = r2.nextInt(largestCommit + 1); - VersionedMap oracle = store.createMap(travelToVersion); + VersionedMap oracle = store.createMap(index2Version.get(travelToVersion)); if(commitBeforeDiffCursor) { moving.commit(); } - DiffCursor diffCursor = moving.getDiffCursor(travelToVersion); + DiffCursor diffCursor = moving.getDiffCursor(index2Version.get(travelToVersion)); moving.putAll(diffCursor); moving.commit(); MapTestEnvironment.compareTwoMaps(scenario + ":c" + index, oracle, moving); - moving.restore(travelToVersion); + moving.restore(index2Version.get(travelToVersion)); } else { // random puts diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java index 9b2e591a..dfe46bae 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Map; import java.util.Random; +import tools.refinery.store.map.Version; import tools.refinery.store.map.VersionedMap; import tools.refinery.store.map.VersionedMapStore; import tools.refinery.store.map.tests.utils.MapTestEnvironment; @@ -61,7 +62,7 @@ public class MultiThreadTestRunnable implements Runnable { // 1. build a map with versions Random r = new Random(seed); VersionedMap versioned = store.createMap(); - Map index2Version = new HashMap<>(); + Map index2Version = new HashMap<>(); for (int i = 0; i < steps; i++) { int index = i + 1; @@ -74,7 +75,7 @@ public class MultiThreadTestRunnable implements Runnable { logAndThrowError(scenario + ":" + index + ": exception happened: " + exception); } if (index % commitFrequency == 0) { - long version = versioned.commit(); + Version version = versioned.commit(); index2Version.put(i, version); } MapTestEnvironment.printStatus(scenario, index, steps, "building"); @@ -100,13 +101,12 @@ public class MultiThreadTestRunnable implements Runnable { MapTestEnvironment.compareTwoMaps(scenario + ":" + index, reference, versioned, null); // go back to a random state (probably created by another thread) - List states = new ArrayList<>(store.getStates()); - states.sort(Long::compare); + List states = new ArrayList<>(index2Version.values()); + //states.sort(Long::compare); Collections.shuffle(states, r2); - for (Long state : states.subList(0, Math.min(states.size(), 100))) { - long x = state; - versioned.restore(x); - var clean = store.createMap(x); + for (Version state : states.subList(0, Math.min(states.size(), 100))) { + versioned.restore(state); + var clean = store.createMap(state); MapTestEnvironment.compareTwoMaps(scenario + ":" + index, clean, versioned, null); } versioned.restore(index2Version.get(i)); diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java index 0b399c3a..5c768788 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Timeout; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import tools.refinery.store.map.Version; import tools.refinery.store.map.VersionedMap; import tools.refinery.store.map.VersionedMapStore; import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; @@ -40,7 +41,7 @@ class RestoreFuzzTest { // 1. build a map with versions Random r = new Random(seed); VersionedMap versioned = store.createMap(); - Map index2Version = new HashMap<>(); + Map index2Version = new HashMap<>(); for (int i = 0; i < steps; i++) { int index = i + 1; @@ -53,7 +54,7 @@ class RestoreFuzzTest { fail(scenario + ":" + index + ": exception happened: " + exception); } if (index % commitFrequency == 0) { - long version = versioned.commit(); + Version version = versioned.commit(); index2Version.put(i, version); } MapTestEnvironment.printStatus(scenario, index, steps, "building"); diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java index b17766b7..299c94b1 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java @@ -19,6 +19,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import tools.refinery.store.map.ContinuousHashProvider; +import tools.refinery.store.map.Version; import tools.refinery.store.map.VersionedMapStore; import tools.refinery.store.map.internal.state.VersionedMapStoreStateImpl; import tools.refinery.store.map.internal.state.VersionedMapStateImpl; @@ -47,7 +48,7 @@ class SharedStoreFuzzTest { versioneds.add((VersionedMapStateImpl) store.createMap()); } - List> index2Version = new LinkedList<>(); + List> index2Version = new LinkedList<>(); for (int i = 0; i < stores.size(); i++) { index2Version.add(new HashMap<>()); } @@ -59,7 +60,7 @@ class SharedStoreFuzzTest { String nextValue = values[r.nextInt(values.length)]; versioneds.get(storeIndex).put(nextKey, nextValue); if (stepIndex % commitFrequency == 0) { - long version = versioneds.get(storeIndex).commit(); + Version version = versioneds.get(storeIndex).commit(); index2Version.get(storeIndex).put(i, version); } MapTestEnvironment.printStatus(scenario, stepIndex, steps, "building"); diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java index 4c3ecb09..ec04904e 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestCollections.java @@ -18,26 +18,35 @@ public final class FuzzTestCollections { public static final Object[] randomSeedOptions = {1}; public static final Object[] storeConfigs = { // State based + // Default VersionedMapStore.builder() - .stateBasedImmutableWhenCommitting(true) .stateBasedHashProvider(MapTestEnvironment.prepareHashProvider(false)) .stateBasedSharingStrategy(VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE), + // Evil hash code test VersionedMapStore.builder() - .stateBasedImmutableWhenCommitting(true) .stateBasedHashProvider(MapTestEnvironment.prepareHashProvider(true)) .stateBasedSharingStrategy(VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE), + // No weak hashmap test + VersionedMapStore.builder() + .versionFreeing(false) + .stateBasedHashProvider(MapTestEnvironment.prepareHashProvider(false)) + .stateBasedSharingStrategy(VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE), + // Copy when committing, do not hurt the work copy, share between saves. VersionedMapStore.builder() .stateBasedImmutableWhenCommitting(false) .stateBasedHashProvider(MapTestEnvironment.prepareHashProvider(false)) .stateBasedSharingStrategy(VersionedMapStoreFactoryBuilder.SharingStrategy.SHARED_NODE_CACHE), + // Copy when committing, do not hurt the work copy, do not share between states. VersionedMapStore.builder() .stateBasedImmutableWhenCommitting(false) .stateBasedHashProvider(MapTestEnvironment.prepareHashProvider(false)) .stateBasedSharingStrategy(VersionedMapStoreFactoryBuilder.SharingStrategy.NO_NODE_CACHE), // Delta based + // Set based transactions VersionedMapStore.builder() .deltaTransactionStrategy(VersionedMapStoreFactoryBuilder.DeltaTransactionStrategy.SET), + // List based transactions VersionedMapStore.builder() .deltaTransactionStrategy(VersionedMapStoreFactoryBuilder.DeltaTransactionStrategy.LIST) }; diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java index 401f2866..b84df280 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java @@ -125,7 +125,7 @@ public class MapTestEnvironment { } } - public long commit(){ + public Version commit(){ return sut.commit(); } diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java index 56b75804..dc7b776e 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java @@ -6,6 +6,7 @@ package tools.refinery.store.model.tests; import org.junit.jupiter.api.Test; +import tools.refinery.store.map.Version; import tools.refinery.store.model.Model; import tools.refinery.store.model.ModelStore; import tools.refinery.store.representation.Symbol; @@ -120,7 +121,7 @@ class ModelTest { assertTrue(model.hasUncommittedChanges()); assertEquals(Model.NO_STATE_ID, model.getState()); - long state1 = model.commit(); + Version state1 = model.commit(); assertFalse(model.hasUncommittedChanges()); assertEquals(state1, model.getState()); @@ -134,7 +135,7 @@ class ModelTest { assertTrue(model.hasUncommittedChanges()); assertEquals(state1, model.getState()); - long state2 = model.commit(); + Version state2 = model.commit(); assertFalse(model.hasUncommittedChanges()); assertEquals(state2, model.getState()); -- cgit v1.2.3-70-g09d2 From 4220b2af314201714c5a53e856dff8317557ba76 Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Thu, 3 Aug 2023 19:16:03 +0200 Subject: Intermediate commit with Lazy NeighbourhoodCalculator and StateEquivalenceChecker prototypes --- .../tools/refinery/store/statecoding/Morphism.java | 11 + .../refinery/store/statecoding/ObjectCode.java | 11 + .../store/statecoding/StateCodeCalculator.java | 2 +- .../store/statecoding/StateCoderAdapter.java | 13 +- .../store/statecoding/StateCoderResult.java | 9 + .../store/statecoding/StateCoderStoreAdapter.java | 3 + .../store/statecoding/StateEquivalenceChecker.java | 22 ++ .../internal/StateCoderAdapterImpl.java | 14 +- .../internal/StateCoderStoreAdapterImpl.java | 29 +++ .../CollectionNeighbourhoodCalculator.java | 131 ++++++++++++ .../neighbourhood/LazyNeighbourhoodCalculator.java | 235 +++++++++++++++++++++ .../neighbourhood/NeighbourhoodCalculator.java | 45 ++-- .../statecoding/neighbourhood/ObjectCode.java | 55 ----- .../statecoding/neighbourhood/ObjectCodeImpl.java | 66 ++++++ .../stateequivalence/CombinationNodePairing.java | 96 +++++++++ .../statecoding/stateequivalence/NodePairing.java | 33 +++ .../stateequivalence/PermutationMorphism.java | 64 ++++++ .../StateEquivalenceCheckerImpl.java | 152 +++++++++++++ .../stateequivalence/TrivialNodePairing.java | 36 ++++ .../store/statecoding/ExperimentalSetupTest.java | 107 ++++++++++ .../store/statecoding/StateCoderBuildTest.java | 80 +++++++ 21 files changed, 1129 insertions(+), 85 deletions(-) create mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/Morphism.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/ObjectCode.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderResult.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/CollectionNeighbourhoodCalculator.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCode.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/TrivialNodePairing.java create mode 100644 subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java create mode 100644 subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/Morphism.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/Morphism.java new file mode 100644 index 00000000..d4ac3b58 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/Morphism.java @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding; + +public interface Morphism { + int get(int object); + int getSize(); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/ObjectCode.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/ObjectCode.java new file mode 100644 index 00000000..840253ea --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/ObjectCode.java @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding; + +public interface ObjectCode { + long get(int object); + int getSize(); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculator.java index 479b61ed..b7f1d81a 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculator.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculator.java @@ -6,5 +6,5 @@ package tools.refinery.store.statecoding; public interface StateCodeCalculator { - + StateCoderResult calculateCodes(); } diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java index 8795fb68..8cfd24d5 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java @@ -6,7 +6,18 @@ package tools.refinery.store.statecoding; import tools.refinery.store.adapter.ModelAdapter; +import tools.refinery.store.statecoding.internal.StateCoderBuilderImpl; public interface StateCoderAdapter extends ModelAdapter { - int calculateHashCode(); + StateCoderResult calculateStateCode(); + default int calculateModelCode() { + return calculateStateCode().modelCode(); + } + default ObjectCode calculateObjectCode() { + return calculateStateCode().objectCode(); + } + + static StateCoderBuilderImpl builder() { + return new StateCoderBuilderImpl(); + } } diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderResult.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderResult.java new file mode 100644 index 00000000..d2aff836 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderResult.java @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding; + +public record StateCoderResult(int modelCode, ObjectCode objectCode) { +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderStoreAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderStoreAdapter.java index 5946a162..c6509521 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderStoreAdapter.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderStoreAdapter.java @@ -6,9 +6,12 @@ package tools.refinery.store.statecoding; import tools.refinery.store.adapter.ModelStoreAdapter; +import tools.refinery.store.map.Version; import tools.refinery.store.model.Model; public interface StateCoderStoreAdapter extends ModelStoreAdapter { + StateEquivalenceChecker.EquivalenceResult checkEquivalence(Version v1, Version v2); + @Override StateCoderAdapter createModelAdapter(Model model); } diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java new file mode 100644 index 00000000..6d8dc6c7 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding; + +import tools.refinery.store.model.Interpretation; + +import java.util.List; + +public interface StateEquivalenceChecker { + enum EquivalenceResult { + ISOMORPHIC, UNKNOWN, DIFFERENT + } + + EquivalenceResult constructMorphism( + List> interpretations1, + ObjectCode code1, List> interpretations2, + ObjectCode code2); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java index 689db2e3..b66fc86d 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java @@ -9,8 +9,10 @@ import tools.refinery.store.adapter.ModelStoreAdapter; import tools.refinery.store.model.Interpretation; import tools.refinery.store.model.Model; import tools.refinery.store.representation.Symbol; +import tools.refinery.store.statecoding.StateCodeCalculator; import tools.refinery.store.statecoding.StateCoderAdapter; -import tools.refinery.store.statecoding.neighbourhood.NeighbourhoodCalculator; +import tools.refinery.store.statecoding.StateCoderResult; +import tools.refinery.store.statecoding.neighbourhood.LazyNeighbourhoodCalculator; import java.util.Collection; import java.util.List; @@ -18,14 +20,14 @@ import java.util.List; public class StateCoderAdapterImpl implements StateCoderAdapter { final ModelStoreAdapter storeAdapter; final Model model; - final NeighbourhoodCalculator calculator; + final StateCodeCalculator calculator; StateCoderAdapterImpl(ModelStoreAdapter storeAdapter, Model model, Collection> symbols) { this.storeAdapter = storeAdapter; this.model = model; List> interpretations = symbols.stream().map(model::getInterpretation).toList(); - calculator = new NeighbourhoodCalculator(interpretations); + calculator = new LazyNeighbourhoodCalculator(interpretations); } @Override @@ -39,9 +41,7 @@ public class StateCoderAdapterImpl implements StateCoderAdapter { } @Override - public int calculateHashCode() { - return calculator.calculate(); + public StateCoderResult calculateStateCode() { + return calculator.calculateCodes(); } - - } diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java index 77d36e96..5374755d 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java @@ -5,18 +5,24 @@ */ package tools.refinery.store.statecoding.internal; +import tools.refinery.store.map.Version; import tools.refinery.store.model.Model; import tools.refinery.store.model.ModelStore; import tools.refinery.store.representation.Symbol; import tools.refinery.store.statecoding.StateCoderAdapter; import tools.refinery.store.statecoding.StateCoderStoreAdapter; +import tools.refinery.store.statecoding.StateEquivalenceChecker; +import tools.refinery.store.statecoding.stateequivalence.StateEquivalenceCheckerImpl; import java.util.Collection; +import java.util.Objects; public class StateCoderStoreAdapterImpl implements StateCoderStoreAdapter { final ModelStore store; final Collection> symbols; + final StateEquivalenceChecker equivalenceChecker = new StateEquivalenceCheckerImpl(); + StateCoderStoreAdapterImpl(ModelStore store, Collection> symbols) { this.store = store; this.symbols = symbols; @@ -27,8 +33,31 @@ public class StateCoderStoreAdapterImpl implements StateCoderStoreAdapter { return store; } + @Override + public StateEquivalenceChecker.EquivalenceResult checkEquivalence(Version v1, Version v2) { + if(Objects.equals(v1,v2)) { + return StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC; + } + var model1 = this.getStore().createModelForState(v1); + var model2 = this.getStore().createModelForState(v2); + + var s1 = model1.getAdapter(StateCoderAdapter.class).calculateStateCode(); + var s2 = model2.getAdapter(StateCoderAdapter.class).calculateStateCode(); + + if(s1.modelCode() != s2.modelCode()) { + return StateEquivalenceChecker.EquivalenceResult.DIFFERENT; + } + + var i1 = symbols.stream().map(model1::getInterpretation).toList(); + var i2 = symbols.stream().map(model2::getInterpretation).toList(); + + return equivalenceChecker.constructMorphism(i1,s1.objectCode(),i2,s2.objectCode()); + } + @Override public StateCoderAdapter createModelAdapter(Model model) { return new StateCoderAdapterImpl(this,model,symbols); } + + } diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/CollectionNeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/CollectionNeighbourhoodCalculator.java new file mode 100644 index 00000000..058750ee --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/CollectionNeighbourhoodCalculator.java @@ -0,0 +1,131 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding.neighbourhood; + +import org.eclipse.collections.api.set.primitive.MutableLongSet; +import org.eclipse.collections.impl.set.mutable.primitive.LongHashSet; +import tools.refinery.store.model.Interpretation; +import tools.refinery.store.statecoding.StateCodeCalculator; +import tools.refinery.store.statecoding.StateCoderResult; +import tools.refinery.store.tuple.Tuple; +import tools.refinery.store.tuple.Tuple0; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Random; + +public class CollectionNeighbourhoodCalculator implements StateCodeCalculator { + protected final List> nullImpactValues; + protected final LinkedHashMap, long[]> impactValues; + + public CollectionNeighbourhoodCalculator(List> interpretations) { + this.nullImpactValues = new ArrayList<>(); + this.impactValues = new LinkedHashMap<>(); + Random random = new Random(1); + + for (Interpretation interpretation : interpretations) { + int arity = interpretation.getSymbol().arity(); + if (arity == 0) { + nullImpactValues.add(interpretation); + } else { + long[] impact = new long[arity]; + for (int i = 0; i < arity; i++) { + impact[i] = random.nextLong(); + } + impactValues.put(interpretation, impact); + } + } + } + + @Override + public StateCoderResult calculateCodes() { + ObjectCodeImpl previous = new ObjectCodeImpl(); + ObjectCodeImpl next = new ObjectCodeImpl(); + + int previousSize = 1; + long lastSum; + boolean grows; + + do{ + for (var impactValueEntry : this.impactValues.entrySet()) { + Interpretation interpretation = impactValueEntry.getKey(); + long[] impact = impactValueEntry.getValue(); + var cursor = interpretation.getAll(); + while (cursor.move()) { + Tuple tuple = cursor.getKey(); + Object value = cursor.getValue(); + long tupleHash = getTupleHash(tuple, value, previous); + addHash(next, tuple, impact, tupleHash); + } + } + + previous = next; + next = null; + lastSum = 0; + MutableLongSet codes = new LongHashSet(); + for (int i = 0; i < previous.getSize(); i++) { + long objectHash = previous.get(i); + codes.add(objectHash); + + final long shifted1 = objectHash>>> 32; + final long shifted2 = objectHash << 32; + lastSum += shifted1 + shifted2; + } + int nextSize = codes.size(); + grows = previousSize < nextSize; + previousSize = nextSize; + + if(grows) { + next = new ObjectCodeImpl(previous); + } + } while (grows); + + long result = 1; + for (var nullImpactValue : nullImpactValues) { + result = result * 31 + nullImpactValue.get(Tuple0.INSTANCE).hashCode(); + } + result += lastSum; + + return new StateCoderResult((int) result, previous); + } + + protected long getTupleHash(Tuple tuple, Object value, ObjectCodeImpl objectCodeImpl) { + long result = value.hashCode(); + int arity = tuple.getSize(); + if (arity == 1) { + result = result * 31 + objectCodeImpl.get(tuple.get(0)); + } else if (arity == 2) { + result = result * 31 + objectCodeImpl.get(tuple.get(0)); + result = result * 31 + objectCodeImpl.get(tuple.get(1)); + if (tuple.get(0) == tuple.get(1)) { + result++; + } + } else if (arity > 2) { + for (int i = 0; i < arity; i++) { + result = result * 31 + objectCodeImpl.get(tuple.get(i)); + } + } + return result; + } + + protected void addHash(ObjectCodeImpl objectCodeImpl, Tuple tuple, long[] impact, long tupleHashCode) { + if (tuple.getSize() == 1) { + addHash(objectCodeImpl, tuple.get(0), impact[0], tupleHashCode); + } else if (tuple.getSize() == 2) { + addHash(objectCodeImpl, tuple.get(0), impact[0], tupleHashCode); + addHash(objectCodeImpl, tuple.get(1), impact[1], tupleHashCode); + } else if (tuple.getSize() > 2) { + for (int i = 0; i < tuple.getSize(); i++) { + addHash(objectCodeImpl, tuple.get(i), impact[i], tupleHashCode); + } + } + } + + protected void addHash(ObjectCodeImpl objectCodeImpl, int o, long impact, long tupleHash) { + objectCodeImpl.set(o, objectCodeImpl.get(o) + tupleHash * impact); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java new file mode 100644 index 00000000..79317679 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java @@ -0,0 +1,235 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding.neighbourhood; + +import org.eclipse.collections.api.map.primitive.LongIntMap; +import org.eclipse.collections.impl.map.mutable.primitive.LongIntHashMap; +import tools.refinery.store.map.Cursor; +import tools.refinery.store.model.Interpretation; +import tools.refinery.store.statecoding.StateCodeCalculator; +import tools.refinery.store.statecoding.StateCoderResult; +import tools.refinery.store.tuple.Tuple; +import tools.refinery.store.tuple.Tuple0; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Random; + +public class LazyNeighbourhoodCalculator implements StateCodeCalculator { + protected final List> nullImpactValues; + protected final LinkedHashMap, long[]> impactValues; + + public LazyNeighbourhoodCalculator(List> interpretations) { + this.nullImpactValues = new ArrayList<>(); + this.impactValues = new LinkedHashMap<>(); + Random random = new Random(1); + + for (Interpretation interpretation : interpretations) { + int arity = interpretation.getSymbol().arity(); + if (arity == 0) { + nullImpactValues.add(interpretation); + } else { + long[] impact = new long[arity]; + for (int i = 0; i < arity; i++) { + impact[i] = random.nextInt(); + } + impactValues.put(interpretation, impact); + } + } + } + + public StateCoderResult calculateCodes() { + ObjectCodeImpl previous = new ObjectCodeImpl(); + ObjectCodeImpl next = new ObjectCodeImpl(); + LongIntMap hash2Amount = new LongIntHashMap(); + + long lastSum; + int lastSize = 1; + boolean grows; + + do { + constructNextObjectCodes(previous, next, hash2Amount); + + LongIntHashMap nextHash2Amount = new LongIntHashMap(); + lastSum = calculateLastSum(previous, next, hash2Amount, nextHash2Amount); + + previous = next; + next = null; + + int nextSize = nextHash2Amount.size(); + grows = nextSize > lastSize; + lastSize = nextSize; + + if (grows) { + next = new ObjectCodeImpl(previous); + } + } while (grows); + + long result = 1; + for (var nullImpactValue : nullImpactValues) { + result = result * 31 + nullImpactValue.get(Tuple0.INSTANCE).hashCode(); + } + result += lastSum; + + return new StateCoderResult((int) result, previous); + } + + private long calculateLastSum(ObjectCodeImpl previous, ObjectCodeImpl next, LongIntMap hash2Amount, + LongIntHashMap nextHash2Amount) { + long lastSum = 0; + for (int i = 0; i < next.getSize(); i++) { + final long hash; + if (isUnique(hash2Amount, previous, i)) { + hash = previous.get(i); + next.set(i, hash); + } else { + hash = next.get(i); + } + + final int amount = nextHash2Amount.get(hash); + nextHash2Amount.put(hash, amount + 1); + + final long shifted1 = hash >>> 8; + final long shifted2 = hash << 8; + final long shifted3 = hash >> 2; + lastSum += shifted1*shifted3 + shifted2; + } + return lastSum; + } + + private void constructNextObjectCodes(ObjectCodeImpl previous, ObjectCodeImpl next, LongIntMap hash2Amount) { + for (var impactValueEntry : this.impactValues.entrySet()) { + Interpretation interpretation = impactValueEntry.getKey(); + var cursor = interpretation.getAll(); + int arity = interpretation.getSymbol().arity(); + long[] impactValue = impactValueEntry.getValue(); + + if (arity == 1) { + while (cursor.move()) { + lazyImpactCalculation1(hash2Amount, previous, next, impactValue, cursor); + } + } else if (arity == 2) { + while (cursor.move()) { + lazyImpactCalculation2(hash2Amount, previous, next, impactValue, cursor); + } + } else { + while (cursor.move()) { + lazyImpactCalculationN(hash2Amount, previous, next, impactValue, cursor); + } + } + } + } + + private boolean isUnique(LongIntMap hash2Amount, ObjectCodeImpl objectCodeImpl, int object) { + final long hash = objectCodeImpl.get(object); + if(hash == 0) { + return false; + } + final int amount = hash2Amount.get(hash); + return amount == 1; + } + + private void lazyImpactCalculation1(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues, Cursor cursor) { + + Tuple tuple = cursor.getKey(); + int o = tuple.get(0); + + if (isUnique(hash2Amount, previous, o)) { + next.ensureSize(o); + } else { + Object value = cursor.getValue(); + long tupleHash = getTupleHash1(tuple, value, previous); + + addHash(next, o, impactValues[0], tupleHash); + } + } + + private void lazyImpactCalculation2(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues, Cursor cursor) { + Tuple tuple = cursor.getKey(); + int o1 = tuple.get(0); + int o2 = tuple.get(1); + + boolean u1 = isUnique(hash2Amount, previous, o1); + boolean u2 = isUnique(hash2Amount, previous, o2); + + if (u1 && u2) { + next.ensureSize(o1); + next.ensureSize(o2); + } else { + Object value = cursor.getValue(); + long tupleHash = getTupleHash2(tuple, value, previous); + + if (!u1) { + addHash(next, o1, impactValues[0], tupleHash); + next.ensureSize(o2); + } + if (!u2) { + next.ensureSize(o1); + addHash(next, o2, impactValues[1], tupleHash); + } + } + } + + private void lazyImpactCalculationN(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues, Cursor cursor) { + Tuple tuple = cursor.getKey(); + + boolean[] uniques = new boolean[tuple.getSize()]; + boolean allUnique = true; + for (int i = 0; i < tuple.getSize(); i++) { + final boolean isUnique = isUnique(hash2Amount, previous, tuple.get(i)); + uniques[i] = isUnique; + allUnique &= isUnique; + } + + if (allUnique) { + for (int i = 0; i < tuple.getSize(); i++) { + next.ensureSize(tuple.get(i)); + } + } else { + Object value = cursor.getValue(); + long tupleHash = getTupleHashN(tuple, value, previous); + + for (int i = 0; i < tuple.getSize(); i++) { + int o = tuple.get(i); + if (!uniques[i]) { + addHash(next, o, impactValues[i], tupleHash); + } else { + next.ensureSize(o); + } + } + } + } + + private long getTupleHash1(Tuple tuple, Object value, ObjectCodeImpl objectCodeImpl) { + long result = value.hashCode(); + result = result * 31 + objectCodeImpl.get(tuple.get(0)); + return result; + } + + private long getTupleHash2(Tuple tuple, Object value, ObjectCodeImpl objectCodeImpl) { + long result = value.hashCode(); + result = result * 31 + objectCodeImpl.get(tuple.get(0)); + result = result * 31 + objectCodeImpl.get(tuple.get(1)); + if (tuple.get(0) == tuple.get(1)) { + result*=31; + } + return result; + } + + private long getTupleHashN(Tuple tuple, Object value, ObjectCodeImpl objectCodeImpl) { + long result = value.hashCode(); + for (int i = 0; i < tuple.getSize(); i++) { + result = result * 31 + objectCodeImpl.get(tuple.get(i)); + } + return result; + } + + protected void addHash(ObjectCodeImpl objectCodeImpl, int o, long impact, long tupleHash) { + long x = tupleHash * impact; + objectCodeImpl.set(o, objectCodeImpl.get(o) + x); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java index 24a7122e..212291c3 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java @@ -8,6 +8,8 @@ package tools.refinery.store.statecoding.neighbourhood; import org.eclipse.collections.api.set.primitive.MutableLongSet; import org.eclipse.collections.impl.set.mutable.primitive.LongHashSet; import tools.refinery.store.model.Interpretation; +import tools.refinery.store.statecoding.StateCodeCalculator; +import tools.refinery.store.statecoding.StateCoderResult; import tools.refinery.store.tuple.Tuple; import tools.refinery.store.tuple.Tuple0; @@ -16,9 +18,9 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Random; -public class NeighbourhoodCalculator { - final List> nullImpactValues; - final LinkedHashMap, long[]> impactValues; +public class NeighbourhoodCalculator implements StateCodeCalculator { + protected final List> nullImpactValues; + protected final LinkedHashMap, long[]> impactValues; public NeighbourhoodCalculator(List> interpretations) { this.nullImpactValues = new ArrayList<>(); @@ -39,9 +41,10 @@ public class NeighbourhoodCalculator { } } - public int calculate() { - ObjectCode previous = new ObjectCode(); - ObjectCode next = new ObjectCode(); + @Override + public StateCoderResult calculateCodes() { + ObjectCodeImpl previous = new ObjectCodeImpl(); + ObjectCodeImpl next = new ObjectCodeImpl(); int previousSize = 1; long lastSum; @@ -77,7 +80,7 @@ public class NeighbourhoodCalculator { previousSize = nextSize; if(grows) { - next = new ObjectCode(previous); + next = new ObjectCodeImpl(previous); } } while (grows); @@ -87,43 +90,43 @@ public class NeighbourhoodCalculator { } result += lastSum; - return (int) result; + return new StateCoderResult((int) result, previous); } - protected long getTupleHash(Tuple tuple, Object value, ObjectCode objectCode) { - long result = (long) value; + protected long getTupleHash(Tuple tuple, Object value, ObjectCodeImpl objectCodeImpl) { + long result = value.hashCode(); int arity = tuple.getSize(); if (arity == 1) { - result = result * 31 + objectCode.get(tuple.get(0)); + result = result * 31 + objectCodeImpl.get(tuple.get(0)); } else if (arity == 2) { - result = result * 31 + objectCode.get(tuple.get(0)); - result = result * 31 + objectCode.get(tuple.get(1)); + result = result * 31 + objectCodeImpl.get(tuple.get(0)); + result = result * 31 + objectCodeImpl.get(tuple.get(1)); if (tuple.get(0) == tuple.get(1)) { result++; } } else if (arity > 2) { for (int i = 0; i < arity; i++) { - result = result * 31 + objectCode.get(tuple.get(i)); + result = result * 31 + objectCodeImpl.get(tuple.get(i)); } } return result; } - protected void addHash(ObjectCode objectCode, Tuple tuple, long[] impact, long tupleHashCode) { + protected void addHash(ObjectCodeImpl objectCodeImpl, Tuple tuple, long[] impact, long tupleHashCode) { if (tuple.getSize() == 1) { - addHash(objectCode, tuple.get(0), impact[0], tupleHashCode); + addHash(objectCodeImpl, tuple.get(0), impact[0], tupleHashCode); } else if (tuple.getSize() == 2) { - addHash(objectCode, tuple.get(0), impact[0], tupleHashCode); - addHash(objectCode, tuple.get(1), impact[1], tupleHashCode); + addHash(objectCodeImpl, tuple.get(0), impact[0], tupleHashCode); + addHash(objectCodeImpl, tuple.get(1), impact[1], tupleHashCode); } else if (tuple.getSize() > 2) { for (int i = 0; i < tuple.getSize(); i++) { - addHash(objectCode, tuple.get(i), impact[i], tupleHashCode); + addHash(objectCodeImpl, tuple.get(i), impact[i], tupleHashCode); } } } - protected void addHash(ObjectCode objectCode, int o, long impact, long tupleHash) { - objectCode.set(o, objectCode.get(o) + tupleHash * impact); + protected void addHash(ObjectCodeImpl objectCodeImpl, int o, long impact, long tupleHash) { + objectCodeImpl.set(o, objectCodeImpl.get(o) + tupleHash * impact); } } diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCode.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCode.java deleted file mode 100644 index 594d2b3a..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCode.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.statecoding.neighbourhood; - -public class ObjectCode { - private long[] vector; - private int size; - - public ObjectCode() { - vector = new long[10]; - size = 0; - } - - public ObjectCode(ObjectCode sameSize) { - this.vector = new long[sameSize.size]; - this.size = sameSize.size; - } - - private void ensureSize(int object) { - if(object >= size) { - size = object+1; - } - - if(object >= vector.length) { - int newLength = vector.length*2; - while(object >= newLength) { - newLength*=2; - } - - long[] newVector = new long[newLength]; - System.arraycopy(vector, 0, newVector, 0, vector.length); - this.vector = newVector; - } - } - - public long get(int object) { - if(object < vector.length) { - return vector[object]; - } else { - return 0; - } - } - - public void set(int object, long value) { - ensureSize(object); - vector[object]=value; - } - - public int getSize() { - return this.size; - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java new file mode 100644 index 00000000..08e3a90b --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding.neighbourhood; + +import tools.refinery.store.statecoding.ObjectCode; + +import java.util.Arrays; + +public class ObjectCodeImpl implements ObjectCode { + private long[] vector; + private int size; + + public ObjectCodeImpl() { + vector = new long[10]; + size = 0; + } + + public ObjectCodeImpl(ObjectCodeImpl sameSize) { + this.vector = new long[sameSize.size]; + this.size = sameSize.size; + } + + public void ensureSize(int object) { + if(object >= size) { + size = object+1; + } + + if(object >= vector.length) { + int newLength = vector.length*2; + while(object >= newLength) { + newLength*=2; + } + + long[] newVector = new long[newLength]; + System.arraycopy(vector, 0, newVector, 0, vector.length); + this.vector = newVector; + } + } + + public long get(int object) { + if(object < vector.length) { + return vector[object]; + } else { + return 0; + } + } + + public void set(int object, long value) { + ensureSize(object); + vector[object]=value; + } + + public int getSize() { + return this.size; + } + + @Override + public String toString() { + return "ObjectCodeImpl{" + + "vector=" + Arrays.toString(Arrays.copyOf(vector,this.size)) + + '}'; + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java new file mode 100644 index 00000000..2877bd0f --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java @@ -0,0 +1,96 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding.stateequivalence; + +import org.eclipse.collections.api.factory.primitive.IntIntMaps; +import org.eclipse.collections.api.map.primitive.IntIntMap; +import org.eclipse.collections.api.set.primitive.IntSet; +import org.eclipse.collections.impl.list.Interval; +import org.eclipse.collections.impl.set.mutable.primitive.IntHashSet; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class CombinationNodePairing implements NodePairing { + private final int[] left; + private final int[] right; + + CombinationNodePairing(IntSet left, IntHashSet right) { + this.left = left.toArray(); + this.right = right.toArray(); + + Arrays.sort(this.left); + Arrays.sort(this.right); + } + + @Override + public int size() { + return left.length; + } + + static final int LIMIT = 5; + static final List> permutations = new ArrayList<>(); + + /** + * Generates and stores permutations up to a given size. If the number would be more than a limit, it provides a + * single permutation only. + * + * @param max The max number in the permutation + * @return A complete list of permutations of numbers 0...max, or a single permutation. + */ + public static List getPermutations(int max) { + if (max < permutations.size()) { + return permutations.get(max); + } + if (max == 0) { + List result = new ArrayList<>(); + result.add(new int[1]); + permutations.add(result); + return result; + } + List result = new ArrayList<>(); + List previousPermutations = getPermutations(max - 1); + for (var permutation : previousPermutations) { + for (int pos = 0; pos <= max; pos++) { + int[] newPermutation = new int[max + 1]; + if (pos >= 0) + System.arraycopy(permutation, 0, newPermutation, 0, pos); + newPermutation[pos] = max; + if (max - (pos + 1) >= 0) + System.arraycopy(permutation, pos + 1, newPermutation, pos + 1 + 1, max - (pos + 1)); + result.add(newPermutation); + } + } + permutations.add(result); + return result; + } + + @Override + public List permutations() { + final var interval = Interval.zeroTo(this.size() - 1); + + if (isComplete()) { + final List p = getPermutations(this.size() - 1); + return p.stream().map(x -> constructPermutationMap(interval, x)).toList(); + } else { + return List.of(constructTrivialMap(interval)); + } + } + + private IntIntMap constructTrivialMap(Interval interval) { + return IntIntMaps.immutable.from(interval, l -> left[l], r -> right[r]); + } + + private IntIntMap constructPermutationMap(Interval interval, int[] permutation) { + return IntIntMaps.immutable.from(interval, l -> left[l], r -> right[permutation[r]]); + } + + @Override + public boolean isComplete() { + return this.size() <= LIMIT; + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java new file mode 100644 index 00000000..7e5db7a3 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding.stateequivalence; + +import org.eclipse.collections.api.map.primitive.IntIntMap; +import org.eclipse.collections.impl.set.mutable.primitive.IntHashSet; + +import java.util.List; + +public interface NodePairing { + + int size(); + List permutations(); + + boolean isComplete(); + + static NodePairing constructNodePairing(IntHashSet left, IntHashSet right){ + if(left.size() != right.size()) { + return null; + } + + if(left.size() == 1) { + int leftValue = left.intIterator().next(); + int rightValue = right.intIterator().next(); + return new TrivialNodePairing(leftValue, rightValue); + } else { + return new CombinationNodePairing(left,right); + } + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java new file mode 100644 index 00000000..6be0f3e7 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding.stateequivalence; + +import org.eclipse.collections.api.map.primitive.IntIntMap; +import tools.refinery.store.statecoding.Morphism; + +import java.util.List; + +public class PermutationMorphism implements Morphism { + private final IntIntMap object2PermutationGroup; + private final List> permutationsGroups; + private final int[] selection; + private boolean hasNext; + + PermutationMorphism(IntIntMap object2PermutationGroup, + List> permutationsGroups) { + this.object2PermutationGroup = object2PermutationGroup; + this.permutationsGroups = permutationsGroups; + + this.selection = new int[this.permutationsGroups.size()]; + this.hasNext = true; + } + + public boolean next() { + return next(0); + } + + private boolean next(int position) { + if (position >= permutationsGroups.size()) { + this.hasNext = false; + return false; + } + if (selection[position] + 1 < permutationsGroups.get(position).size()) { + selection[position] = selection[position] + 1; + return true; + } else { + selection[position] = 0; + return next(position + 1); + } + } + + @Override + public int get(int object) { + if(!hasNext) { + throw new IllegalArgumentException("No next permutation!"); + } + + final int groupIndex = object2PermutationGroup.get(object); + final var selectedGroup = permutationsGroups.get(groupIndex); + final int permutationIndex = selection[groupIndex]; + final var selectedPermutation = selectedGroup.get(permutationIndex); + + return selectedPermutation.get(object); + } + + @Override + public int getSize() { + return object2PermutationGroup.size(); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java new file mode 100644 index 00000000..fd704086 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java @@ -0,0 +1,152 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding.stateequivalence; + +import org.eclipse.collections.api.map.primitive.IntIntMap; +import org.eclipse.collections.impl.map.mutable.primitive.IntIntHashMap; +import org.eclipse.collections.impl.map.mutable.primitive.LongObjectHashMap; +import org.eclipse.collections.impl.set.mutable.primitive.IntHashSet; +import tools.refinery.store.model.Interpretation; +import tools.refinery.store.statecoding.Morphism; +import tools.refinery.store.statecoding.ObjectCode; +import tools.refinery.store.statecoding.StateEquivalenceChecker; +import tools.refinery.store.tuple.Tuple; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class StateEquivalenceCheckerImpl implements StateEquivalenceChecker { + public static final int LIMIT = 1000; + + @Override + public EquivalenceResult constructMorphism(List> interpretations1, + ObjectCode code1, + List> interpretations2, + ObjectCode code2) { + if (code1.getSize() != code2.getSize()) { + return EquivalenceResult.DIFFERENT; + } + + IntIntHashMap object2PermutationGroup = new IntIntHashMap(); + List> permutationsGroups = new ArrayList<>(); + + final EquivalenceResult permutations = constructPermutationNavigation(indexByHash(code1), indexByHash(code2), + object2PermutationGroup, permutationsGroups); + + if (permutations == EquivalenceResult.DIFFERENT) { + return EquivalenceResult.DIFFERENT; + } + + boolean hasNext; + PermutationMorphism morphism = new PermutationMorphism(object2PermutationGroup, permutationsGroups); + int tried = 0; + do { + if (testMorphism(interpretations1, interpretations2, morphism)) { + return permutations; + } + + if(tried >= LIMIT) { + return EquivalenceResult.UNKNOWN; + } + + hasNext = morphism.next(); + tried++; + } while (hasNext); + + return EquivalenceResult.DIFFERENT; + } + + private LongObjectHashMap indexByHash(ObjectCode code) { + LongObjectHashMap result = new LongObjectHashMap<>(); + for (int o = 0; o < code.getSize(); o++) { + long hash = code.get(o); + var equivalenceClass = result.get(hash); + if (equivalenceClass == null) { + equivalenceClass = new IntHashSet(); + result.put(hash, equivalenceClass); + } + equivalenceClass.add(o); + } + return result; + } + + private EquivalenceResult constructPermutationNavigation(LongObjectHashMap map1, + LongObjectHashMap map2, + IntIntHashMap emptyMapToListOfOptions, + List> emptyListOfOptions) { + if (map1.size() != map2.size()) { + return EquivalenceResult.DIFFERENT; + } + + var iterator = map1.keySet().longIterator(); + + boolean allComplete = true; + + while (iterator.hasNext()) { + long hash = iterator.next(); + var set1 = map1.get(hash); + var set2 = map2.get(hash); + if (set2 == null) { + return EquivalenceResult.DIFFERENT; + } + + var pairing = NodePairing.constructNodePairing(set1, set2); + if (pairing == null) { + return EquivalenceResult.DIFFERENT; + } + + allComplete &= pairing.isComplete(); + + final int optionIndex = emptyListOfOptions.size(); + set1.forEach(key -> emptyMapToListOfOptions.put(key, optionIndex)); + emptyListOfOptions.add(pairing.permutations()); + } + if(allComplete) { + return EquivalenceResult.ISOMORPHIC; + } else { + return EquivalenceResult.UNKNOWN; + } + } + + private boolean testMorphism(List> s, List> t, Morphism m) { + for (int interpretationIndex = 0; interpretationIndex < s.size(); interpretationIndex++) { + var sI = s.get(interpretationIndex); + var tI = t.get(interpretationIndex); + + var cursor = sI.getAll(); + while (cursor.move()) { + final Tuple sTuple = cursor.getKey(); + final Object sValue = cursor.getValue(); + + final Tuple tTuple = apply(sTuple, m); + final Object tValue = tI.get(tTuple); + + if (!Objects.equals(sValue, tValue)) { + return false; + } + } + } + return true; + } + + private Tuple apply(Tuple t, Morphism m) { + final int arity = t.getSize(); + if (arity == 0) { + return Tuple.of(); + } else if (arity == 1) { + return Tuple.of(m.get(t.get(0))); + } else if (arity == 2) { + return Tuple.of(m.get(t.get(0)), m.get(t.get(1))); + } else { + int[] newTupleIndices = new int[arity]; + for (int i = 0; i < arity; i++) { + newTupleIndices[i] = m.get(t.get(i)); + } + return Tuple.of(newTupleIndices); + } + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/TrivialNodePairing.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/TrivialNodePairing.java new file mode 100644 index 00000000..f5eadfb9 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/TrivialNodePairing.java @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding.stateequivalence; + +import org.eclipse.collections.api.factory.primitive.IntIntMaps; +import org.eclipse.collections.api.map.primitive.IntIntMap; + +import java.util.List; + +public class TrivialNodePairing implements NodePairing { + final int left; + final int right; + + TrivialNodePairing(int left, int right) { + this.left = left; + this.right = right; + } + + @Override + public int size() { + return 1; + } + + @Override + public List permutations() { + return List.of(IntIntMaps.immutable.of(left,right)); + } + + @Override + public boolean isComplete() { + return true; + } +} diff --git a/subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java b/subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java new file mode 100644 index 00000000..87b1623c --- /dev/null +++ b/subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java @@ -0,0 +1,107 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding; + +import org.junit.jupiter.api.Test; +import tools.refinery.store.map.Version; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.tuple.Tuple; + +import java.util.*; + +class ExperimentalSetupTest { + public static void generate(int size) { + Symbol person = new Symbol<>("Person", 1, Boolean.class, false); + Symbol friend = new Symbol<>("friend", 2, Boolean.class, false); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(StateCoderAdapter + .builder()) + .build(); + + Set versions = new HashSet<>(); + Map> codes = new HashMap<>(); + + var empty = store.createEmptyModel(); + var pI = empty.getInterpretation(person); + + for (int i = 0; i < size; i++) { + pI.put(Tuple.of(i), true); + } + + var emptyVersion = empty.commit(); + versions.add(emptyVersion); + var emptyCode = empty.getAdapter(StateCoderAdapter.class).calculateModelCode(); + List emptyList = new ArrayList<>(); + emptyList.add(emptyVersion); + codes.put(emptyCode, emptyList); + + var storeAdapter = store.getAdapter(StateCoderStoreAdapter.class); + + int dif = 0; + int iso = 0; + int unk = 0; + + //int step = 0 + + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + var previousVersions = new HashSet<>(versions); + for (var version : previousVersions) { + + var model = store.createModelForState(version); + model.getInterpretation(friend).put(Tuple.of(i, j), true); + + Version version1 = model.commit(); + var stateCode = model.getAdapter(StateCoderAdapter.class).calculateStateCode(); + int code = stateCode.modelCode(); + //System.out.println(step+++" ->" +code); + if (codes.containsKey(code)) { + Version similar = codes.get(code).get(0); + + var outcome = storeAdapter.checkEquivalence(version1, similar); + if (outcome == StateEquivalenceChecker.EquivalenceResult.DIFFERENT) { + System.out.println(); + var c = model.getInterpretation(friend).getAll(); + while (c.move()) { + System.out.println(c.getKey().toString()); + } + System.out.println("vs"); + var c2 = store.createModelForState(similar).getInterpretation(friend).getAll(); + while (c2.move()) { + System.out.println(c2.getKey().toString()); + } + + dif++; + } else if (outcome == StateEquivalenceChecker.EquivalenceResult.UNKNOWN) { + unk++; + } else { + iso++; + } + } else { + versions.add(version1); + + List newList = new ArrayList<>(); + newList.add(version1); + codes.put(code, newList); + } + } + } + } + + System.out.printf("v=%d i=%d d=%d u=%d\n", versions.size(), iso, dif, unk); + } + + @Test + void runTests() { + for (int i = 0; i < 5; i++) { + System.out.println("size = " + i); + generate(i); + } + } +} diff --git a/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java b/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java new file mode 100644 index 00000000..b0b80af7 --- /dev/null +++ b/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding; + +import org.junit.jupiter.api.Test; +import tools.refinery.store.model.Interpretation; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.tuple.Tuple; + +import static org.junit.jupiter.api.Assertions.*; + +class StateCoderBuildTest { + Symbol person = new Symbol<>("Person", 1, Boolean.class, false); + Symbol age = new Symbol<>("age", 1, Integer.class, null); + Symbol friend = new Symbol<>("friend", 2, Boolean.class, false); + + @Test + void simpleStateCoderTest() { + var store = ModelStore.builder() + .symbols(person, age, friend) + .with(StateCoderAdapter + .builder()) + .build(); + + var model = store.createEmptyModel(); + var stateCoder = model.getAdapter(StateCoderAdapter.class); + assertNotNull(stateCoder); + + var personI = model.getInterpretation(person); + var friendI = model.getInterpretation(friend); + var ageI = model.getInterpretation(age); + fill(personI, friendI, ageI); + + stateCoder.calculateStateCode(); + } + + @Test + void excludeTest() { + var store = ModelStore.builder() + .symbols(person, age, friend) + .with(StateCoderAdapter.builder() + .exclude(person) + .exclude(age)) + .build(); + + var model = store.createEmptyModel(); + var stateCoder = model.getAdapter(StateCoderAdapter.class); + assertNotNull(stateCoder); + + var personI = model.getInterpretation(person); + var friendI = model.getInterpretation(friend); + var ageI = model.getInterpretation(age); + fill(personI, friendI, ageI); + + int code = stateCoder.calculateStateCode().modelCode(); + + ageI.put(Tuple.of(1),3); + assertEquals(code,stateCoder.calculateStateCode().modelCode()); + + ageI.put(Tuple.of(1),null); + assertEquals(code,stateCoder.calculateStateCode().modelCode()); + + personI.put(Tuple.of(2),false); + assertEquals(code,stateCoder.calculateStateCode().modelCode()); + } + + private static void fill(Interpretation personI, Interpretation friendI, Interpretation ageI) { + personI.put(Tuple.of(1), true); + personI.put(Tuple.of(2), true); + + ageI.put(Tuple.of(1), 5); + ageI.put(Tuple.of(2), 4); + + friendI.put(Tuple.of(1, 2), true); + } +} -- cgit v1.2.3-70-g09d2 From 8eb1b7a14972e52018a1bb69a53f2878e96b581e Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Mon, 7 Aug 2023 02:45:57 +0200 Subject: StateCoderBuilder improvement with individuals, and customizable calculators. --- .../statecoding/StateCodeCalculatorFactory.java | 15 +++ .../store/statecoding/StateCoderBuilder.java | 16 +++ .../store/statecoding/StateEquivalenceChecker.java | 2 + .../internal/StateCoderAdapterImpl.java | 12 +- .../internal/StateCoderBuilderImpl.java | 34 +++++- .../internal/StateCoderStoreAdapterImpl.java | 29 +++-- .../AbstractNeighbourhoodCalculator.java | 95 +++++++++++++++ .../neighbourhood/LazyNeighbourhoodCalculator.java | 100 ++++------------ .../LazyNeighbourhoodCalculatorFactory.java | 20 ++++ .../neighbourhood/NeighbourhoodCalculator.java | 132 --------------------- .../StateEquivalenceCheckerImpl.java | 42 ++++--- .../store/statecoding/StateCoderBuildTest.java | 51 +++++++- 12 files changed, 297 insertions(+), 251 deletions(-) create mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculatorFactory.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculatorFactory.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculatorFactory.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculatorFactory.java new file mode 100644 index 00000000..04e17a13 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCodeCalculatorFactory.java @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding; + +import org.eclipse.collections.api.set.primitive.IntSet; +import tools.refinery.store.model.Interpretation; + +import java.util.List; + +public interface StateCodeCalculatorFactory { + StateCodeCalculator create(List> interpretations, IntSet individuals); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderBuilder.java index 2f37584f..54650825 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderBuilder.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderBuilder.java @@ -8,7 +8,9 @@ package tools.refinery.store.statecoding; import tools.refinery.store.adapter.ModelAdapterBuilder; import tools.refinery.store.model.ModelStore; import tools.refinery.store.representation.AnySymbol; +import tools.refinery.store.tuple.Tuple1; +import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -24,6 +26,20 @@ public interface StateCoderBuilder extends ModelAdapterBuilder { return excludeAll(List.of(symbols)); } + StateCoderBuilder individual(Tuple1 tuple); + default StateCoderBuilder individual(Collection tuple1s) { + for(Tuple1 tuple : tuple1s){ + individual(tuple); + } + return this; + } + default StateCoderBuilder individuals(Tuple1... tuple1s) { + return individual(Arrays.stream(tuple1s).toList()); + } + + StateCoderBuilder stateCodeCalculatorFactory(StateCodeCalculatorFactory codeCalculatorFactory); + StateCoderBuilder stateEquivalenceChecker(StateEquivalenceChecker stateEquivalenceChecker); + @Override StateCoderStoreAdapter build(ModelStore store); } diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java index 6d8dc6c7..3fd8c8d8 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java @@ -5,6 +5,7 @@ */ package tools.refinery.store.statecoding; +import org.eclipse.collections.api.set.primitive.IntSet; import tools.refinery.store.model.Interpretation; import java.util.List; @@ -15,6 +16,7 @@ public interface StateEquivalenceChecker { } EquivalenceResult constructMorphism( + IntSet individuals, List> interpretations1, ObjectCode code1, List> interpretations2, diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java index b66fc86d..a2471916 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderAdapterImpl.java @@ -6,28 +6,20 @@ package tools.refinery.store.statecoding.internal; import tools.refinery.store.adapter.ModelStoreAdapter; -import tools.refinery.store.model.Interpretation; import tools.refinery.store.model.Model; -import tools.refinery.store.representation.Symbol; import tools.refinery.store.statecoding.StateCodeCalculator; import tools.refinery.store.statecoding.StateCoderAdapter; import tools.refinery.store.statecoding.StateCoderResult; -import tools.refinery.store.statecoding.neighbourhood.LazyNeighbourhoodCalculator; - -import java.util.Collection; -import java.util.List; public class StateCoderAdapterImpl implements StateCoderAdapter { final ModelStoreAdapter storeAdapter; final Model model; final StateCodeCalculator calculator; - StateCoderAdapterImpl(ModelStoreAdapter storeAdapter, Model model, Collection> symbols) { + StateCoderAdapterImpl(ModelStoreAdapter storeAdapter, StateCodeCalculator calculator, Model model) { this.storeAdapter = storeAdapter; this.model = model; - - List> interpretations = symbols.stream().map(model::getInterpretation).toList(); - calculator = new LazyNeighbourhoodCalculator(interpretations); + this.calculator = calculator; } @Override diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java index 700723f4..8268a826 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java @@ -5,19 +5,27 @@ */ package tools.refinery.store.statecoding.internal; +import org.eclipse.collections.impl.set.mutable.primitive.IntHashSet; import tools.refinery.store.model.ModelStore; import tools.refinery.store.model.ModelStoreBuilder; import tools.refinery.store.representation.AnySymbol; import tools.refinery.store.representation.Symbol; +import tools.refinery.store.statecoding.StateCodeCalculatorFactory; import tools.refinery.store.statecoding.StateCoderBuilder; import tools.refinery.store.statecoding.StateCoderStoreAdapter; +import tools.refinery.store.statecoding.StateEquivalenceChecker; +import tools.refinery.store.statecoding.neighbourhood.LazyNeighbourhoodCalculatorFactory; +import tools.refinery.store.statecoding.stateequivalence.StateEquivalenceCheckerImpl; +import tools.refinery.store.tuple.Tuple1; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Set; +import java.util.*; public class StateCoderBuilderImpl implements StateCoderBuilder { Set excluded = new HashSet<>(); + IntHashSet individuals = new IntHashSet(); + + StateCodeCalculatorFactory calculator = new LazyNeighbourhoodCalculatorFactory(); + StateEquivalenceChecker checker = new StateEquivalenceCheckerImpl(); @Override public StateCoderBuilder exclude(AnySymbol symbol) { @@ -25,6 +33,24 @@ public class StateCoderBuilderImpl implements StateCoderBuilder { return this; } + @Override + public StateCoderBuilder individual(Tuple1 tuple) { + individuals.add(tuple.get(0)); + return this; + } + + @Override + public StateCoderBuilder stateEquivalenceChecker(StateEquivalenceChecker stateEquivalenceChecker) { + this.checker = stateEquivalenceChecker; + return this; + } + + @Override + public StateCoderBuilder stateCodeCalculatorFactory(StateCodeCalculatorFactory codeCalculatorFactory) { + this.calculator = codeCalculatorFactory; + return this; + } + @Override public boolean isConfigured() { return true; @@ -43,6 +69,6 @@ public class StateCoderBuilderImpl implements StateCoderBuilder { symbols.add(typed); } } - return new StateCoderStoreAdapterImpl(store, symbols); + return new StateCoderStoreAdapterImpl(store, calculator, checker, symbols, individuals); } } diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java index 5374755d..89586bfb 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderStoreAdapterImpl.java @@ -5,14 +5,15 @@ */ package tools.refinery.store.statecoding.internal; +import org.eclipse.collections.api.set.primitive.IntSet; import tools.refinery.store.map.Version; import tools.refinery.store.model.Model; import tools.refinery.store.model.ModelStore; import tools.refinery.store.representation.Symbol; +import tools.refinery.store.statecoding.StateCodeCalculatorFactory; import tools.refinery.store.statecoding.StateCoderAdapter; import tools.refinery.store.statecoding.StateCoderStoreAdapter; import tools.refinery.store.statecoding.StateEquivalenceChecker; -import tools.refinery.store.statecoding.stateequivalence.StateEquivalenceCheckerImpl; import java.util.Collection; import java.util.Objects; @@ -20,12 +21,22 @@ import java.util.Objects; public class StateCoderStoreAdapterImpl implements StateCoderStoreAdapter { final ModelStore store; final Collection> symbols; + final IntSet individuals; - final StateEquivalenceChecker equivalenceChecker = new StateEquivalenceCheckerImpl(); + final StateEquivalenceChecker equivalenceChecker; + final StateCodeCalculatorFactory codeCalculatorFactory; - StateCoderStoreAdapterImpl(ModelStore store, Collection> symbols) { + StateCoderStoreAdapterImpl(ModelStore store, + StateCodeCalculatorFactory codeCalculatorFactory, + StateEquivalenceChecker equivalenceChecker, + Collection> symbols, + IntSet individuals) + { + this.codeCalculatorFactory = codeCalculatorFactory; + this.equivalenceChecker = equivalenceChecker; this.store = store; this.symbols = symbols; + this.individuals = individuals; } @Override @@ -35,7 +46,7 @@ public class StateCoderStoreAdapterImpl implements StateCoderStoreAdapter { @Override public StateEquivalenceChecker.EquivalenceResult checkEquivalence(Version v1, Version v2) { - if(Objects.equals(v1,v2)) { + if (Objects.equals(v1, v2)) { return StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC; } var model1 = this.getStore().createModelForState(v1); @@ -44,20 +55,20 @@ public class StateCoderStoreAdapterImpl implements StateCoderStoreAdapter { var s1 = model1.getAdapter(StateCoderAdapter.class).calculateStateCode(); var s2 = model2.getAdapter(StateCoderAdapter.class).calculateStateCode(); - if(s1.modelCode() != s2.modelCode()) { + if (s1.modelCode() != s2.modelCode()) { return StateEquivalenceChecker.EquivalenceResult.DIFFERENT; } var i1 = symbols.stream().map(model1::getInterpretation).toList(); var i2 = symbols.stream().map(model2::getInterpretation).toList(); - return equivalenceChecker.constructMorphism(i1,s1.objectCode(),i2,s2.objectCode()); + return equivalenceChecker.constructMorphism(individuals, i1, s1.objectCode(), i2, s2.objectCode()); } @Override public StateCoderAdapter createModelAdapter(Model model) { - return new StateCoderAdapterImpl(this,model,symbols); + var interpretations = symbols.stream().map(model::getInterpretation).toList(); + var coder = codeCalculatorFactory.create(interpretations, individuals); + return new StateCoderAdapterImpl(this, coder, model); } - - } diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java new file mode 100644 index 00000000..0de76519 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java @@ -0,0 +1,95 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding.neighbourhood; + +import org.eclipse.collections.api.set.primitive.IntSet; +import org.eclipse.collections.impl.map.mutable.primitive.IntLongHashMap; +import org.eclipse.collections.impl.map.mutable.primitive.LongIntHashMap; +import tools.refinery.store.model.Interpretation; +import tools.refinery.store.statecoding.ObjectCode; +import tools.refinery.store.tuple.Tuple; +import tools.refinery.store.tuple.Tuple0; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Random; + +public abstract class AbstractNeighbourhoodCalculator { + protected final List> nullImpactValues; + protected final LinkedHashMap, long[]> impactValues; + protected final IntLongHashMap individualHashValues; + + protected AbstractNeighbourhoodCalculator(List> interpretations, IntSet individuals) { + this.nullImpactValues = new ArrayList<>(); + this.impactValues = new LinkedHashMap<>(); + Random random = new Random(1); + + individualHashValues = new IntLongHashMap(); + var individualsInOrder = individuals.toSortedList(Integer::compare); + for(int i = 0; i interpretation : interpretations) { + int arity = interpretation.getSymbol().arity(); + if (arity == 0) { + nullImpactValues.add(interpretation); + } else { + long[] impact = new long[arity]; + for (int i = 0; i < arity; i++) { + impact[i] = random.nextInt(); + } + impactValues.put(interpretation, impact); + } + } + } + + protected void initializeWithIndividuals(ObjectCodeImpl previous, LongIntHashMap hash2Amount) { + for (var entry : individualHashValues.keyValuesView()) { + previous.set(entry.getOne(), entry.getTwo()); + hash2Amount.put(entry.getTwo(), 1); + } + } + + protected long getTupleHash1(Tuple tuple, Object value, ObjectCode objectCodeImpl) { + long result = value.hashCode(); + result = result * 31 + objectCodeImpl.get(tuple.get(0)); + return result; + } + + protected long getTupleHash2(Tuple tuple, Object value, ObjectCode objectCodeImpl) { + long result = value.hashCode(); + result = result * 31 + objectCodeImpl.get(tuple.get(0)); + result = result * 31 + objectCodeImpl.get(tuple.get(1)); + if (tuple.get(0) == tuple.get(1)) { + result *= 31; + } + return result; + } + + protected long getTupleHashN(Tuple tuple, Object value, ObjectCode objectCodeImpl) { + long result = value.hashCode(); + for (int i = 0; i < tuple.getSize(); i++) { + result = result * 31 + objectCodeImpl.get(tuple.get(i)); + } + return result; + } + + protected void addHash(ObjectCodeImpl objectCodeImpl, int o, long impact, long tupleHash) { + long x = tupleHash * impact; + objectCodeImpl.set(o, objectCodeImpl.get(o) + x); + } + + protected long calculateModelCode(long lastSum) { + long result = 1; + for (var nullImpactValue : nullImpactValues) { + result = result * 31 + nullImpactValue.get(Tuple0.INSTANCE).hashCode(); + } + result += lastSum; + return result; + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java index 79317679..98a75e08 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java @@ -6,74 +6,48 @@ package tools.refinery.store.statecoding.neighbourhood; import org.eclipse.collections.api.map.primitive.LongIntMap; +import org.eclipse.collections.api.set.primitive.IntSet; import org.eclipse.collections.impl.map.mutable.primitive.LongIntHashMap; import tools.refinery.store.map.Cursor; import tools.refinery.store.model.Interpretation; import tools.refinery.store.statecoding.StateCodeCalculator; import tools.refinery.store.statecoding.StateCoderResult; import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.tuple.Tuple0; -import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Random; -public class LazyNeighbourhoodCalculator implements StateCodeCalculator { - protected final List> nullImpactValues; - protected final LinkedHashMap, long[]> impactValues; - - public LazyNeighbourhoodCalculator(List> interpretations) { - this.nullImpactValues = new ArrayList<>(); - this.impactValues = new LinkedHashMap<>(); - Random random = new Random(1); - - for (Interpretation interpretation : interpretations) { - int arity = interpretation.getSymbol().arity(); - if (arity == 0) { - nullImpactValues.add(interpretation); - } else { - long[] impact = new long[arity]; - for (int i = 0; i < arity; i++) { - impact[i] = random.nextInt(); - } - impactValues.put(interpretation, impact); - } - } +public class LazyNeighbourhoodCalculator extends AbstractNeighbourhoodCalculator implements StateCodeCalculator { + public LazyNeighbourhoodCalculator(List> interpretations, IntSet individuals) { + super(interpretations, individuals); } public StateCoderResult calculateCodes() { ObjectCodeImpl previous = new ObjectCodeImpl(); - ObjectCodeImpl next = new ObjectCodeImpl(); - LongIntMap hash2Amount = new LongIntHashMap(); + LongIntHashMap hash2Amount = new LongIntHashMap(); + + initializeWithIndividuals(previous, hash2Amount); long lastSum; - int lastSize = 1; - boolean grows; + // All hash code is 0, except to the individuals. + int lastSize = hash2Amount.size() + 1; + boolean grows; do { + ObjectCodeImpl next = new ObjectCodeImpl(); constructNextObjectCodes(previous, next, hash2Amount); LongIntHashMap nextHash2Amount = new LongIntHashMap(); lastSum = calculateLastSum(previous, next, hash2Amount, nextHash2Amount); - previous = next; - next = null; - int nextSize = nextHash2Amount.size(); grows = nextSize > lastSize; lastSize = nextSize; - if (grows) { - next = new ObjectCodeImpl(previous); - } + previous = next; + hash2Amount = nextHash2Amount; } while (grows); - long result = 1; - for (var nullImpactValue : nullImpactValues) { - result = result * 31 + nullImpactValue.get(Tuple0.INSTANCE).hashCode(); - } - result += lastSum; + long result = calculateModelCode(lastSum); return new StateCoderResult((int) result, previous); } @@ -96,7 +70,7 @@ public class LazyNeighbourhoodCalculator implements StateCodeCalculator { final long shifted1 = hash >>> 8; final long shifted2 = hash << 8; final long shifted3 = hash >> 2; - lastSum += shifted1*shifted3 + shifted2; + lastSum += shifted1 * shifted3 + shifted2; } return lastSum; } @@ -126,7 +100,7 @@ public class LazyNeighbourhoodCalculator implements StateCodeCalculator { private boolean isUnique(LongIntMap hash2Amount, ObjectCodeImpl objectCodeImpl, int object) { final long hash = objectCodeImpl.get(object); - if(hash == 0) { + if (hash == 0) { return false; } final int amount = hash2Amount.get(hash); @@ -149,12 +123,12 @@ public class LazyNeighbourhoodCalculator implements StateCodeCalculator { } private void lazyImpactCalculation2(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues, Cursor cursor) { - Tuple tuple = cursor.getKey(); - int o1 = tuple.get(0); - int o2 = tuple.get(1); + final Tuple tuple = cursor.getKey(); + final int o1 = tuple.get(0); + final int o2 = tuple.get(1); - boolean u1 = isUnique(hash2Amount, previous, o1); - boolean u2 = isUnique(hash2Amount, previous, o2); + final boolean u1 = isUnique(hash2Amount, previous, o1); + final boolean u2 = isUnique(hash2Amount, previous, o2); if (u1 && u2) { next.ensureSize(o1); @@ -175,9 +149,9 @@ public class LazyNeighbourhoodCalculator implements StateCodeCalculator { } private void lazyImpactCalculationN(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues, Cursor cursor) { - Tuple tuple = cursor.getKey(); + final Tuple tuple = cursor.getKey(); - boolean[] uniques = new boolean[tuple.getSize()]; + final boolean[] uniques = new boolean[tuple.getSize()]; boolean allUnique = true; for (int i = 0; i < tuple.getSize(); i++) { final boolean isUnique = isUnique(hash2Amount, previous, tuple.get(i)); @@ -204,32 +178,4 @@ public class LazyNeighbourhoodCalculator implements StateCodeCalculator { } } - private long getTupleHash1(Tuple tuple, Object value, ObjectCodeImpl objectCodeImpl) { - long result = value.hashCode(); - result = result * 31 + objectCodeImpl.get(tuple.get(0)); - return result; - } - - private long getTupleHash2(Tuple tuple, Object value, ObjectCodeImpl objectCodeImpl) { - long result = value.hashCode(); - result = result * 31 + objectCodeImpl.get(tuple.get(0)); - result = result * 31 + objectCodeImpl.get(tuple.get(1)); - if (tuple.get(0) == tuple.get(1)) { - result*=31; - } - return result; - } - - private long getTupleHashN(Tuple tuple, Object value, ObjectCodeImpl objectCodeImpl) { - long result = value.hashCode(); - for (int i = 0; i < tuple.getSize(); i++) { - result = result * 31 + objectCodeImpl.get(tuple.get(i)); - } - return result; - } - - protected void addHash(ObjectCodeImpl objectCodeImpl, int o, long impact, long tupleHash) { - long x = tupleHash * impact; - objectCodeImpl.set(o, objectCodeImpl.get(o) + x); - } } diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculatorFactory.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculatorFactory.java new file mode 100644 index 00000000..2e499f95 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculatorFactory.java @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding.neighbourhood; + +import org.eclipse.collections.api.set.primitive.IntSet; +import tools.refinery.store.model.Interpretation; +import tools.refinery.store.statecoding.StateCodeCalculator; +import tools.refinery.store.statecoding.StateCodeCalculatorFactory; + +import java.util.List; + +public class LazyNeighbourhoodCalculatorFactory implements StateCodeCalculatorFactory { + @Override + public StateCodeCalculator create(List> interpretations, IntSet individuals) { + return new LazyNeighbourhoodCalculator(interpretations,individuals); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java deleted file mode 100644 index 212291c3..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.statecoding.neighbourhood; - -import org.eclipse.collections.api.set.primitive.MutableLongSet; -import org.eclipse.collections.impl.set.mutable.primitive.LongHashSet; -import tools.refinery.store.model.Interpretation; -import tools.refinery.store.statecoding.StateCodeCalculator; -import tools.refinery.store.statecoding.StateCoderResult; -import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.tuple.Tuple0; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Random; - -public class NeighbourhoodCalculator implements StateCodeCalculator { - protected final List> nullImpactValues; - protected final LinkedHashMap, long[]> impactValues; - - public NeighbourhoodCalculator(List> interpretations) { - this.nullImpactValues = new ArrayList<>(); - this.impactValues = new LinkedHashMap<>(); - Random random = new Random(1); - - for (Interpretation interpretation : interpretations) { - int arity = interpretation.getSymbol().arity(); - if (arity == 0) { - nullImpactValues.add(interpretation); - } else { - long[] impact = new long[arity]; - for (int i = 0; i < arity; i++) { - impact[i] = random.nextLong(); - } - impactValues.put(interpretation, impact); - } - } - } - - @Override - public StateCoderResult calculateCodes() { - ObjectCodeImpl previous = new ObjectCodeImpl(); - ObjectCodeImpl next = new ObjectCodeImpl(); - - int previousSize = 1; - long lastSum; - boolean grows; - - do{ - for (var impactValueEntry : this.impactValues.entrySet()) { - Interpretation interpretation = impactValueEntry.getKey(); - long[] impact = impactValueEntry.getValue(); - var cursor = interpretation.getAll(); - while (cursor.move()) { - Tuple tuple = cursor.getKey(); - Object value = cursor.getValue(); - long tupleHash = getTupleHash(tuple, value, previous); - addHash(next, tuple, impact, tupleHash); - } - } - - previous = next; - next = null; - lastSum = 0; - MutableLongSet codes = new LongHashSet(); - for (int i = 0; i < previous.getSize(); i++) { - long objectHash = previous.get(i); - codes.add(objectHash); - - final long shifted1 = objectHash>>> 32; - final long shifted2 = objectHash << 32; - lastSum += shifted1 + shifted2; - } - int nextSize = codes.size(); - grows = previousSize < nextSize; - previousSize = nextSize; - - if(grows) { - next = new ObjectCodeImpl(previous); - } - } while (grows); - - long result = 1; - for (var nullImpactValue : nullImpactValues) { - result = result * 31 + nullImpactValue.get(Tuple0.INSTANCE).hashCode(); - } - result += lastSum; - - return new StateCoderResult((int) result, previous); - } - - protected long getTupleHash(Tuple tuple, Object value, ObjectCodeImpl objectCodeImpl) { - long result = value.hashCode(); - int arity = tuple.getSize(); - if (arity == 1) { - result = result * 31 + objectCodeImpl.get(tuple.get(0)); - } else if (arity == 2) { - result = result * 31 + objectCodeImpl.get(tuple.get(0)); - result = result * 31 + objectCodeImpl.get(tuple.get(1)); - if (tuple.get(0) == tuple.get(1)) { - result++; - } - } else if (arity > 2) { - for (int i = 0; i < arity; i++) { - result = result * 31 + objectCodeImpl.get(tuple.get(i)); - } - } - return result; - } - - protected void addHash(ObjectCodeImpl objectCodeImpl, Tuple tuple, long[] impact, long tupleHashCode) { - if (tuple.getSize() == 1) { - addHash(objectCodeImpl, tuple.get(0), impact[0], tupleHashCode); - } else if (tuple.getSize() == 2) { - addHash(objectCodeImpl, tuple.get(0), impact[0], tupleHashCode); - addHash(objectCodeImpl, tuple.get(1), impact[1], tupleHashCode); - } else if (tuple.getSize() > 2) { - for (int i = 0; i < tuple.getSize(); i++) { - addHash(objectCodeImpl, tuple.get(i), impact[i], tupleHashCode); - } - } - } - - protected void addHash(ObjectCodeImpl objectCodeImpl, int o, long impact, long tupleHash) { - objectCodeImpl.set(o, objectCodeImpl.get(o) + tupleHash * impact); - } - -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java index fd704086..e58a2502 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java @@ -5,7 +5,9 @@ */ package tools.refinery.store.statecoding.stateequivalence; +import org.eclipse.collections.api.factory.primitive.IntIntMaps; import org.eclipse.collections.api.map.primitive.IntIntMap; +import org.eclipse.collections.api.set.primitive.IntSet; import org.eclipse.collections.impl.map.mutable.primitive.IntIntHashMap; import org.eclipse.collections.impl.map.mutable.primitive.LongObjectHashMap; import org.eclipse.collections.impl.set.mutable.primitive.IntHashSet; @@ -23,7 +25,8 @@ public class StateEquivalenceCheckerImpl implements StateEquivalenceChecker { public static final int LIMIT = 1000; @Override - public EquivalenceResult constructMorphism(List> interpretations1, + public EquivalenceResult constructMorphism(IntSet individuals, + List> interpretations1, ObjectCode code1, List> interpretations2, ObjectCode code2) { @@ -34,7 +37,8 @@ public class StateEquivalenceCheckerImpl implements StateEquivalenceChecker { IntIntHashMap object2PermutationGroup = new IntIntHashMap(); List> permutationsGroups = new ArrayList<>(); - final EquivalenceResult permutations = constructPermutationNavigation(indexByHash(code1), indexByHash(code2), + final EquivalenceResult permutations = constructPermutationNavigation(individuals, + indexByHash(code1, individuals), indexByHash(code2, individuals), object2PermutationGroup, permutationsGroups); if (permutations == EquivalenceResult.DIFFERENT) { @@ -60,24 +64,27 @@ public class StateEquivalenceCheckerImpl implements StateEquivalenceChecker { return EquivalenceResult.DIFFERENT; } - private LongObjectHashMap indexByHash(ObjectCode code) { + private LongObjectHashMap indexByHash(ObjectCode code, IntSet individuals) { LongObjectHashMap result = new LongObjectHashMap<>(); for (int o = 0; o < code.getSize(); o++) { - long hash = code.get(o); - var equivalenceClass = result.get(hash); - if (equivalenceClass == null) { - equivalenceClass = new IntHashSet(); - result.put(hash, equivalenceClass); + if(! individuals.contains(o)){ + long hash = code.get(o); + var equivalenceClass = result.get(hash); + if (equivalenceClass == null) { + equivalenceClass = new IntHashSet(); + result.put(hash, equivalenceClass); + } + equivalenceClass.add(o); } - equivalenceClass.add(o); } return result; } - private EquivalenceResult constructPermutationNavigation(LongObjectHashMap map1, - LongObjectHashMap map2, - IntIntHashMap emptyMapToListOfOptions, - List> emptyListOfOptions) { + private EquivalenceResult constructPermutationNavigation(IntSet individuals, + LongObjectHashMap map1, + LongObjectHashMap map2, + IntIntHashMap object2OptionIndex, + List> listOfOptions) { if (map1.size() != map2.size()) { return EquivalenceResult.DIFFERENT; } @@ -101,10 +108,13 @@ public class StateEquivalenceCheckerImpl implements StateEquivalenceChecker { allComplete &= pairing.isComplete(); - final int optionIndex = emptyListOfOptions.size(); - set1.forEach(key -> emptyMapToListOfOptions.put(key, optionIndex)); - emptyListOfOptions.add(pairing.permutations()); + final int optionIndex = listOfOptions.size(); + set1.forEach(key -> object2OptionIndex.put(key, optionIndex)); + listOfOptions.add(pairing.permutations()); } + + individuals.forEach(o -> listOfOptions.add(o,List.of(IntIntMaps.immutable.of(o,o)))); + if(allComplete) { return EquivalenceResult.ISOMORPHIC; } else { diff --git a/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java b/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java index b0b80af7..a4e953ea 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java @@ -19,11 +19,10 @@ class StateCoderBuildTest { Symbol friend = new Symbol<>("friend", 2, Boolean.class, false); @Test - void simpleStateCoderTest() { + void simpleStateCoderBuildTest() { var store = ModelStore.builder() .symbols(person, age, friend) - .with(StateCoderAdapter - .builder()) + .with(StateCoderAdapter.builder()) .build(); var model = store.createEmptyModel(); @@ -33,6 +32,7 @@ class StateCoderBuildTest { var personI = model.getInterpretation(person); var friendI = model.getInterpretation(friend); var ageI = model.getInterpretation(age); + fill(personI, friendI, ageI); stateCoder.calculateStateCode(); @@ -68,6 +68,51 @@ class StateCoderBuildTest { assertEquals(code,stateCoder.calculateStateCode().modelCode()); } + @Test + void notIndividualTest() { + var store = ModelStore.builder() + .symbols(friend) + .with(StateCoderAdapter.builder()) + .build(); + + var model = store.createEmptyModel(); + var stateCoder = model.getAdapter(StateCoderAdapter.class); + + var friendI = model.getInterpretation(friend); + + friendI.put(Tuple.of(1,2),true); + int code1 = stateCoder.calculateModelCode(); + + friendI.put(Tuple.of(1,2),false); + friendI.put(Tuple.of(2,1),true); + int code2 = stateCoder.calculateModelCode(); + + assertEquals(code1,code2); + } + + @Test + void individualTest() { + var store = ModelStore.builder() + .symbols(friend) + .with(StateCoderAdapter.builder() + .individual(Tuple.of(1))) + .build(); + + var model = store.createEmptyModel(); + var stateCoder = model.getAdapter(StateCoderAdapter.class); + + var friendI = model.getInterpretation(friend); + + friendI.put(Tuple.of(1,2),true); + int code1 = stateCoder.calculateModelCode(); + + friendI.put(Tuple.of(1,2),false); + friendI.put(Tuple.of(2,1),true); + int code2 = stateCoder.calculateModelCode(); + + assertNotEquals(code1,code2); + } + private static void fill(Interpretation personI, Interpretation friendI, Interpretation ageI) { personI.put(Tuple.of(1), true); personI.put(Tuple.of(2), true); -- cgit v1.2.3-70-g09d2 From 36550e2be2146b290210cb12c76d0341e499f849 Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Mon, 7 Aug 2023 16:07:09 +0200 Subject: Tests + small changes for AbstractNeighbourhoodCalculator and StateEquivalenceCheckerImpl --- .../AbstractNeighbourhoodCalculator.java | 15 +- .../statecoding/neighbourhood/ObjectCodeImpl.java | 3 +- .../StateEquivalenceCheckerImpl.java | 19 +- .../store/statecoding/EquivalenceTest.java | 178 +++++++++++++++++++ .../store/statecoding/StateCoderUnitTest.java | 195 +++++++++++++++++++++ 5 files changed, 390 insertions(+), 20 deletions(-) create mode 100644 subprojects/store/src/test/java/tools/refinery/store/statecoding/EquivalenceTest.java create mode 100644 subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderUnitTest.java (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java index 0de76519..c3f8a586 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java @@ -13,10 +13,7 @@ import tools.refinery.store.statecoding.ObjectCode; import tools.refinery.store.tuple.Tuple; import tools.refinery.store.tuple.Tuple0; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Random; +import java.util.*; public abstract class AbstractNeighbourhoodCalculator { protected final List> nullImpactValues; @@ -56,13 +53,13 @@ public abstract class AbstractNeighbourhoodCalculator { } protected long getTupleHash1(Tuple tuple, Object value, ObjectCode objectCodeImpl) { - long result = value.hashCode(); + long result = Objects.hashCode(value); result = result * 31 + objectCodeImpl.get(tuple.get(0)); return result; } protected long getTupleHash2(Tuple tuple, Object value, ObjectCode objectCodeImpl) { - long result = value.hashCode(); + long result = Objects.hashCode(value); result = result * 31 + objectCodeImpl.get(tuple.get(0)); result = result * 31 + objectCodeImpl.get(tuple.get(1)); if (tuple.get(0) == tuple.get(1)) { @@ -72,7 +69,7 @@ public abstract class AbstractNeighbourhoodCalculator { } protected long getTupleHashN(Tuple tuple, Object value, ObjectCode objectCodeImpl) { - long result = value.hashCode(); + long result = Objects.hashCode(value); for (int i = 0; i < tuple.getSize(); i++) { result = result * 31 + objectCodeImpl.get(tuple.get(i)); } @@ -85,9 +82,9 @@ public abstract class AbstractNeighbourhoodCalculator { } protected long calculateModelCode(long lastSum) { - long result = 1; + long result = 0; for (var nullImpactValue : nullImpactValues) { - result = result * 31 + nullImpactValue.get(Tuple0.INSTANCE).hashCode(); + result = result * 31 + Objects.hashCode(nullImpactValue.get(Tuple0.INSTANCE)); } result += lastSum; return result; diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java index 08e3a90b..c4d86cf1 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/ObjectCodeImpl.java @@ -50,7 +50,8 @@ public class ObjectCodeImpl implements ObjectCode { public void set(int object, long value) { ensureSize(object); - vector[object]=value; + final long valueToPut = value == 0 ? 1 : value; + vector[object]=valueToPut; } public int getSize() { diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java index e58a2502..34dba34e 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java @@ -29,11 +29,8 @@ public class StateEquivalenceCheckerImpl implements StateEquivalenceChecker { List> interpretations1, ObjectCode code1, List> interpretations2, - ObjectCode code2) { - if (code1.getSize() != code2.getSize()) { - return EquivalenceResult.DIFFERENT; - } - + ObjectCode code2) + { IntIntHashMap object2PermutationGroup = new IntIntHashMap(); List> permutationsGroups = new ArrayList<>(); @@ -69,12 +66,14 @@ public class StateEquivalenceCheckerImpl implements StateEquivalenceChecker { for (int o = 0; o < code.getSize(); o++) { if(! individuals.contains(o)){ long hash = code.get(o); - var equivalenceClass = result.get(hash); - if (equivalenceClass == null) { - equivalenceClass = new IntHashSet(); - result.put(hash, equivalenceClass); + if(hash != 0) { + var equivalenceClass = result.get(hash); + if (equivalenceClass == null) { + equivalenceClass = new IntHashSet(); + result.put(hash, equivalenceClass); + } + equivalenceClass.add(o); } - equivalenceClass.add(o); } } return result; diff --git a/subprojects/store/src/test/java/tools/refinery/store/statecoding/EquivalenceTest.java b/subprojects/store/src/test/java/tools/refinery/store/statecoding/EquivalenceTest.java new file mode 100644 index 00000000..8a9c0e9b --- /dev/null +++ b/subprojects/store/src/test/java/tools/refinery/store/statecoding/EquivalenceTest.java @@ -0,0 +1,178 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding; + +import org.junit.jupiter.api.Test; +import tools.refinery.store.map.Version; +import tools.refinery.store.model.Model; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.tuple.Tuple; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class EquivalenceTest { + Symbol person = new Symbol<>("Person", 1, Boolean.class, false); + Symbol age = new Symbol<>("age", 1, Integer.class, null); + Symbol friend = new Symbol<>("friend", 2, Boolean.class, false); + Symbol parents = new Symbol<>("parents", 3, Boolean.class, false); + Symbol population = new Symbol<>("population", 0, Integer.class, 0); + + private ModelStore createStore() { + return ModelStore.builder() + .symbols(person, age, friend, parents, population) + .with(StateCoderAdapter.builder()) + .build(); + } + + @Test + void emptyModelCode0() { + ModelStore store = createStore(); + var stateCoder = store.getAdapter(StateCoderStoreAdapter.class); + Model model = createStore().createEmptyModel(); + Version v1 = model.commit(); + Version v2 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC, stateCoder.checkEquivalence(v1, v2)); + + var personI = model.getInterpretation(person); + var friendI = model.getInterpretation(friend); + + personI.put(Tuple.of(1), true); + personI.put(Tuple.of(2), true); + friendI.put(Tuple.of(1, 2), true); + + Version v3 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v1, v3)); + } + + @Test + void nullRelationTest() { + ModelStore store = createStore(); + var stateCoder = store.getAdapter(StateCoderStoreAdapter.class); + Model model = createStore().createEmptyModel(); + + var populationI = model.getInterpretation(population); + + Version v1 = model.commit(); + + populationI.put(Tuple.of(), 1); + Version v2 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v1, v2)); + + populationI.put(Tuple.of(), 2); + Version v3 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v2, v3)); + } + + @Test + void unaryBooleanTest() { + ModelStore store = createStore(); + var stateCoder = store.getAdapter(StateCoderStoreAdapter.class); + Model model = createStore().createEmptyModel(); + + var personI = model.getInterpretation(person); + + Version v1 = model.commit(); + + personI.put(Tuple.of(1), true); + Version v2 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v1, v2)); + + personI.put(Tuple.of(2), true); + Version v3 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v2, v3)); + + personI.put(Tuple.of(1), false); + Version v4 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC, stateCoder.checkEquivalence(v2, v4)); + } + + @Test + void unaryIntTest() { + ModelStore store = createStore(); + var stateCoder = store.getAdapter(StateCoderStoreAdapter.class); + Model model = createStore().createEmptyModel(); + + var ageI = model.getInterpretation(age); + + ageI.put(Tuple.of(1), 3); + Version v1 = model.commit(); + + ageI.put(Tuple.of(1), 4); + Version v2 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v1, v2)); + + ageI.put(Tuple.of(2), 4); + Version v3 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v2, v3)); + + ageI.put(Tuple.of(1), null); + Version v4 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC, stateCoder.checkEquivalence(v2, v4)); + } + + @Test + void binaryTest() { + ModelStore store = createStore(); + var stateCoder = store.getAdapter(StateCoderStoreAdapter.class); + Model model = createStore().createEmptyModel(); + + var friendI = model.getInterpretation(friend); + + Version v1 = model.commit(); + + friendI.put(Tuple.of(1, 2), true); + Version v2 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v1, v2)); + + friendI.put(Tuple.of(2, 1), true); + Version v3 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v2, v3)); + + friendI.put(Tuple.of(1, 2), false); + Version v4 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC, stateCoder.checkEquivalence(v2, v4)); + } + + @Test + void NaryTest() { + ModelStore store = createStore(); + var stateCoder = store.getAdapter(StateCoderStoreAdapter.class); + Model model = createStore().createEmptyModel(); + + var parentsI = model.getInterpretation(parents); + + Version v1 = model.commit(); + + parentsI.put(Tuple.of(3, 1, 2), true); + Version v2 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v1, v2)); + + parentsI.put(Tuple.of(4, 1, 2), true); + Version v3 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.DIFFERENT, stateCoder.checkEquivalence(v2, v3)); + + parentsI.put(Tuple.of(3, 1, 2), false); + Version v4 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC, stateCoder.checkEquivalence(v2, v4)); + } +} diff --git a/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderUnitTest.java b/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderUnitTest.java new file mode 100644 index 00000000..d94df841 --- /dev/null +++ b/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderUnitTest.java @@ -0,0 +1,195 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.store.statecoding; + +import org.junit.jupiter.api.Test; +import tools.refinery.store.model.Model; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.tuple.Tuple; + +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.*; + +class StateCoderUnitTest { + Symbol person = new Symbol<>("Person", 1, Boolean.class, false); + Symbol age = new Symbol<>("age", 1, Integer.class, null); + Symbol friend = new Symbol<>("friend", 2, Boolean.class, false); + Symbol parents = new Symbol<>("parents", 3, Boolean.class, false); + Symbol population = new Symbol<>("population", 0, Integer.class, 0); + + private Model createEmptyModel() { + var store = ModelStore.builder() + .symbols(person, age, friend, parents, population) + .with(StateCoderAdapter.builder()) + .build(); + + return store.createEmptyModel(); + } + + @Test + void emptyModelCode0() { + Model model = createEmptyModel(); + var stateCoder = model.getAdapter(StateCoderAdapter.class); + + assertEquals(0, stateCoder.calculateModelCode()); + + var personI = model.getInterpretation(person); + var friendI = model.getInterpretation(friend); + + personI.put(Tuple.of(1), true); + personI.put(Tuple.of(2), true); + friendI.put(Tuple.of(1, 2), true); + + assertNotEquals(0, stateCoder.calculateModelCode()); + } + + @Test + void emptyObjectCode0() { + Model model = createEmptyModel(); + var stateCoder = model.getAdapter(StateCoderAdapter.class); + + var personI = model.getInterpretation(person); + var friendI = model.getInterpretation(friend); + + assertEquals(0, stateCoder.calculateObjectCode().get(1)); + assertEquals(0, stateCoder.calculateObjectCode().get(17)); + + personI.put(Tuple.of(1), true); + personI.put(Tuple.of(2), true); + friendI.put(Tuple.of(1, 2), true); + + assertNotEquals(0, stateCoder.calculateObjectCode().get(1)); + assertEquals(0, stateCoder.calculateObjectCode().get(17)); + } + + @Test + void nullRelationTest() { + Model model = createEmptyModel(); + var stateCoder = model.getAdapter(StateCoderAdapter.class); + + var populationI = model.getInterpretation(population); + + final int hashOf0 = Objects.hashCode(0); + + assertEquals(hashOf0, stateCoder.calculateModelCode()); + + populationI.put(Tuple.of(), 1); + int code1 = stateCoder.calculateModelCode(); + + assertNotEquals(hashOf0, stateCoder.calculateModelCode()); + + populationI.put(Tuple.of(), 2); + int code2 = stateCoder.calculateModelCode(); + + assertNotEquals(code1, code2); + + populationI.put(Tuple.of(), 1); + assertEquals(code1, stateCoder.calculateModelCode()); + } + + @Test + void unaryBooleanTest() { + Model model = createEmptyModel(); + var stateCoder = model.getAdapter(StateCoderAdapter.class); + + var personI = model.getInterpretation(person); + + assertEquals(0, stateCoder.calculateModelCode()); + + personI.put(Tuple.of(1), true); + int code1 = stateCoder.calculateModelCode(); + + assertNotEquals(0, stateCoder.calculateModelCode()); + + personI.put(Tuple.of(2), true); + int code2 = stateCoder.calculateModelCode(); + + assertNotEquals(code1, code2); + + personI.put(Tuple.of(1), false); + assertEquals(code1, stateCoder.calculateModelCode()); + } + + @Test + void unaryIntTest() { + Model model = createEmptyModel(); + var stateCoder = model.getAdapter(StateCoderAdapter.class); + + var ageI = model.getInterpretation(age); + + assertEquals(0, stateCoder.calculateModelCode()); + + ageI.put(Tuple.of(1), 4); + int code0 = stateCoder.calculateModelCode(); + + assertNotEquals(0, code0); + + ageI.put(Tuple.of(1), 5); + int code1 = stateCoder.calculateModelCode(); + + assertNotEquals(code0, code1); + + ageI.put(Tuple.of(2), 5); + int code2 = stateCoder.calculateModelCode(); + + assertNotEquals(code1, code2); + + ageI.put(Tuple.of(1), null); + assertEquals(code1, stateCoder.calculateModelCode()); + } + + @Test + void binaryTest() { + Model model = createEmptyModel(); + var stateCoder = model.getAdapter(StateCoderAdapter.class); + + var friendI = model.getInterpretation(friend); + + assertEquals(0, stateCoder.calculateModelCode()); + + friendI.put(Tuple.of(1, 2), true); + int code1 = stateCoder.calculateModelCode(); + + assertNotEquals(0, code1); + + friendI.put(Tuple.of(2, 1), true); + int code2 = stateCoder.calculateModelCode(); + + assertNotEquals(code1, code2); + + friendI.put(Tuple.of(1, 2), false); + int code3 = stateCoder.calculateModelCode(); + + assertEquals(code1, code3); + } + + @Test + void NaryTest() { + Model model = createEmptyModel(); + var stateCoder = model.getAdapter(StateCoderAdapter.class); + + var parentsI = model.getInterpretation(parents); + + assertEquals(0, stateCoder.calculateModelCode()); + + parentsI.put(Tuple.of(3, 1, 2), true); + int code1 = stateCoder.calculateModelCode(); + + assertNotEquals(0, code1); + + parentsI.put(Tuple.of(4, 1, 2), true); + int code2 = stateCoder.calculateModelCode(); + + assertNotEquals(code1, code2); + + parentsI.put(Tuple.of(3, 1, 2), false); + int code3 = stateCoder.calculateModelCode(); + + assertEquals(code1, code3); + } +} -- cgit v1.2.3-70-g09d2 From 52c8e89faa49988bc47a15256ed818f3b78bb56b Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Tue, 8 Aug 2023 19:07:27 +0200 Subject: Test cases for Equivalence accuracy measurements --- .../store/statecoding/ExperimentalSetupTest.java | 189 +++++++++++++++------ 1 file changed, 140 insertions(+), 49 deletions(-) (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java b/subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java index 87b1623c..25b5dca1 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java @@ -5,16 +5,52 @@ */ package tools.refinery.store.statecoding; +import org.eclipse.collections.impl.map.mutable.primitive.IntObjectHashMap; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import tools.refinery.store.map.Version; +import tools.refinery.store.model.Model; import tools.refinery.store.model.ModelStore; import tools.refinery.store.representation.Symbol; import tools.refinery.store.tuple.Tuple; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + class ExperimentalSetupTest { - public static void generate(int size) { + static class ExperimentalSetupResult { + int versions = 0; + int different = 0; + int isomorphic = 0; + int unknown = 0; + + double failureRatio() { + return (different + 0.0) / versions; + } + + @Override + public String toString() { + return "ExperimentalSetupResult{" + + "versions=" + versions + + ", different=" + different + + ", isomorphic=" + isomorphic + + ", unknown=" + unknown + + ", ratio= " + failureRatio() + + '}'; + } + } + + static int MAX = 100000; + + public static ExperimentalSetupResult generate(int size, boolean permuteTypes) { Symbol person = new Symbol<>("Person", 1, Boolean.class, false); Symbol friend = new Symbol<>("friend", 2, Boolean.class, false); @@ -25,13 +61,13 @@ class ExperimentalSetupTest { .build(); Set versions = new HashSet<>(); - Map> codes = new HashMap<>(); + IntObjectHashMap> codes = new IntObjectHashMap<>(); var empty = store.createEmptyModel(); - var pI = empty.getInterpretation(person); - - for (int i = 0; i < size; i++) { - pI.put(Tuple.of(i), true); + if (!permuteTypes) { + for (int i = 0; i < size; i++) { + empty.getInterpretation(person).put(Tuple.of(i), true); + } } var emptyVersion = empty.commit(); @@ -42,12 +78,27 @@ class ExperimentalSetupTest { codes.put(emptyCode, emptyList); var storeAdapter = store.getAdapter(StateCoderStoreAdapter.class); + var result = new ExperimentalSetupResult(); - int dif = 0; - int iso = 0; - int unk = 0; + int steps = 0; - //int step = 0 + if (permuteTypes) { + for (int i = 0; i < size; i++) { + var previousVersions = new HashSet<>(versions); + for (var version : previousVersions) { + var model = store.createModelForState(version); + model.getInterpretation(person).put(Tuple.of(i), true); + + saveAsNewVersion(versions, codes, storeAdapter, result, model); + + logProgress(steps++); + if (steps > MAX) { + result.versions = versions.size(); + return result; + } + } + } + } for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { @@ -57,51 +108,91 @@ class ExperimentalSetupTest { var model = store.createModelForState(version); model.getInterpretation(friend).put(Tuple.of(i, j), true); - Version version1 = model.commit(); - var stateCode = model.getAdapter(StateCoderAdapter.class).calculateStateCode(); - int code = stateCode.modelCode(); - //System.out.println(step+++" ->" +code); - if (codes.containsKey(code)) { - Version similar = codes.get(code).get(0); - - var outcome = storeAdapter.checkEquivalence(version1, similar); - if (outcome == StateEquivalenceChecker.EquivalenceResult.DIFFERENT) { - System.out.println(); - var c = model.getInterpretation(friend).getAll(); - while (c.move()) { - System.out.println(c.getKey().toString()); - } - System.out.println("vs"); - var c2 = store.createModelForState(similar).getInterpretation(friend).getAll(); - while (c2.move()) { - System.out.println(c2.getKey().toString()); - } - - dif++; - } else if (outcome == StateEquivalenceChecker.EquivalenceResult.UNKNOWN) { - unk++; - } else { - iso++; - } - } else { - versions.add(version1); - - List newList = new ArrayList<>(); - newList.add(version1); - codes.put(code, newList); + saveAsNewVersion(versions, codes, storeAdapter, result, model); + + logProgress(steps++); + if (steps > MAX) { + result.versions = versions.size(); + return result; } } } } - System.out.printf("v=%d i=%d d=%d u=%d\n", versions.size(), iso, dif, unk); + result.versions = versions.size(); + return result; } - @Test - void runTests() { - for (int i = 0; i < 5; i++) { - System.out.println("size = " + i); - generate(i); + private static void saveAsNewVersion(Set versions, IntObjectHashMap> codes, + StateCoderStoreAdapter storeAdapter, ExperimentalSetupResult result, Model model) { + Version version1 = model.commit(); + + var stateCode = model.getAdapter(StateCoderAdapter.class).calculateStateCode(); + int code = stateCode.modelCode(); + if (codes.containsKey(code)) { + Version similar = codes.get(code).get(0); + + var outcome = storeAdapter.checkEquivalence(version1, similar); + if (outcome == StateEquivalenceChecker.EquivalenceResult.DIFFERENT) { + result.different++; + } else if (outcome == StateEquivalenceChecker.EquivalenceResult.UNKNOWN) { + result.unknown++; + } else { + result.isomorphic++; + } + } else { + versions.add(version1); + + List newList = new ArrayList<>(); + newList.add(version1); + codes.put(code, newList); + } + } + + private static void logProgress(int steps) { + if (steps % 10000 == 0) { + System.out.println("Steps: " + steps + " / " + MAX); } } + + static final double limit = 0.01; + + @Test + void test0() { + assertEquals(1, generate(0, true).versions); + } + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3, 4}) + void testForSmallUntypedModels(int size) { + var res = generate(size, false); + System.out.println(res); + assertTrue(res.failureRatio() < limit); + } + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3}) + void testForSmallTypedModels(int size) { + var res = generate(size, true); + System.out.println(res); + assertTrue(res.failureRatio() < limit); + } + + @Test + @Tag("fuzz") + @Tag("slow") + void testForLargeTypedModels() { + var res = generate(10, true); + System.out.println(res); + assertTrue(res.failureRatio() < limit); + } + + @Test + @Tag("fuzz") + @Tag("slow") + void testForLargeUntypedModels() { + var res = generate(10, false); + System.out.println(res); + assertTrue(res.failureRatio() < limit); + } } -- cgit v1.2.3-70-g09d2 From db7a2892def64f0d155d4fc9525be11a1833f8f8 Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Tue, 8 Aug 2023 19:18:45 +0200 Subject: Added two build tests for custom coding and equivalence checking algorithms. --- .../store/statecoding/StateCoderBuildTest.java | 74 ++++++++++++++++++---- 1 file changed, 60 insertions(+), 14 deletions(-) (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java b/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java index a4e953ea..0b738005 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/statecoding/StateCoderBuildTest.java @@ -58,14 +58,14 @@ class StateCoderBuildTest { int code = stateCoder.calculateStateCode().modelCode(); - ageI.put(Tuple.of(1),3); - assertEquals(code,stateCoder.calculateStateCode().modelCode()); + ageI.put(Tuple.of(1), 3); + assertEquals(code, stateCoder.calculateStateCode().modelCode()); - ageI.put(Tuple.of(1),null); - assertEquals(code,stateCoder.calculateStateCode().modelCode()); + ageI.put(Tuple.of(1), null); + assertEquals(code, stateCoder.calculateStateCode().modelCode()); - personI.put(Tuple.of(2),false); - assertEquals(code,stateCoder.calculateStateCode().modelCode()); + personI.put(Tuple.of(2), false); + assertEquals(code, stateCoder.calculateStateCode().modelCode()); } @Test @@ -80,14 +80,14 @@ class StateCoderBuildTest { var friendI = model.getInterpretation(friend); - friendI.put(Tuple.of(1,2),true); + friendI.put(Tuple.of(1, 2), true); int code1 = stateCoder.calculateModelCode(); - friendI.put(Tuple.of(1,2),false); - friendI.put(Tuple.of(2,1),true); + friendI.put(Tuple.of(1, 2), false); + friendI.put(Tuple.of(2, 1), true); int code2 = stateCoder.calculateModelCode(); - assertEquals(code1,code2); + assertEquals(code1, code2); } @Test @@ -103,16 +103,62 @@ class StateCoderBuildTest { var friendI = model.getInterpretation(friend); - friendI.put(Tuple.of(1,2),true); + friendI.put(Tuple.of(1, 2), true); int code1 = stateCoder.calculateModelCode(); - friendI.put(Tuple.of(1,2),false); - friendI.put(Tuple.of(2,1),true); + friendI.put(Tuple.of(1, 2), false); + friendI.put(Tuple.of(2, 1), true); int code2 = stateCoder.calculateModelCode(); - assertNotEquals(code1,code2); + assertNotEquals(code1, code2); } + @Test + void customStateCoderTest() { + final boolean[] called = new boolean[]{false}; + StateCodeCalculator mock = () -> { + called[0] = true; + return null; + }; + + var store = ModelStore.builder() + .symbols(friend) + .with(StateCoderAdapter.builder() + .stateCodeCalculatorFactory((interpretations, individuals) -> mock)) + .build(); + + var model = store.createEmptyModel(); + var stateCoder = model.getAdapter(StateCoderAdapter.class); + + stateCoder.calculateStateCode(); + + assertTrue(called[0]); + } + + @Test + void customEquivalenceCheckerTest() { + final boolean[] called = new boolean[]{false}; + StateEquivalenceChecker mock = (p1, p2, p3, p4, p5) -> { + called[0] = true; + return StateEquivalenceChecker.EquivalenceResult.UNKNOWN; + }; + + var store = ModelStore.builder() + .symbols(friend) + .with(StateCoderAdapter.builder() + .stateEquivalenceChecker(mock)) + .build(); + + var model = store.createEmptyModel(); + var v1 = model.commit(); + var v2 = model.commit(); + + store.getAdapter(StateCoderStoreAdapter.class).checkEquivalence(v1, v2); + + assertTrue(called[0]); + } + + private static void fill(Interpretation personI, Interpretation friendI, Interpretation ageI) { personI.put(Tuple.of(1), true); personI.put(Tuple.of(2), true); -- cgit v1.2.3-70-g09d2 From e2268925741bd4c68dcead708b211bf5fd99440c Mon Sep 17 00:00:00 2001 From: OszkarSemerath Date: Tue, 8 Aug 2023 20:19:03 +0200 Subject: Added test for StateEquivalenceChecker Unknown outcome. --- .../StateEquivalenceCheckerImpl.java | 6 +++- .../store/statecoding/EquivalenceTest.java | 39 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) (limited to 'subprojects/store/src/test') diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java index 34dba34e..ef0d76a7 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java @@ -58,7 +58,11 @@ public class StateEquivalenceCheckerImpl implements StateEquivalenceChecker { tried++; } while (hasNext); - return EquivalenceResult.DIFFERENT; + if(permutations == EquivalenceResult.UNKNOWN) { + return EquivalenceResult.UNKNOWN; + } else { + return EquivalenceResult.DIFFERENT; + } } private LongObjectHashMap indexByHash(ObjectCode code, IntSet individuals) { diff --git a/subprojects/store/src/test/java/tools/refinery/store/statecoding/EquivalenceTest.java b/subprojects/store/src/test/java/tools/refinery/store/statecoding/EquivalenceTest.java index 8a9c0e9b..3c35849e 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/statecoding/EquivalenceTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/statecoding/EquivalenceTest.java @@ -10,6 +10,7 @@ import tools.refinery.store.map.Version; import tools.refinery.store.model.Model; import tools.refinery.store.model.ModelStore; import tools.refinery.store.representation.Symbol; +import tools.refinery.store.statecoding.neighbourhood.ObjectCodeImpl; import tools.refinery.store.tuple.Tuple; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -175,4 +176,42 @@ class EquivalenceTest { assertEquals(StateEquivalenceChecker.EquivalenceResult.ISOMORPHIC, stateCoder.checkEquivalence(v2, v4)); } + + @Test + void largeUnknownTest() { + final int limit = 100; + + StateCodeCalculator calculator = () -> { + var code = new ObjectCodeImpl(); + for (int i = 0; i < limit; i++) { + code.set(i, 1); + } + return new StateCoderResult(1, code); + }; + + ModelStore store = ModelStore.builder() + .symbols(person, age, friend, parents, population) + .with(StateCoderAdapter.builder() + .stateCodeCalculatorFactory((p1, p2) -> calculator)) + .build(); + + var stateCoder = store.getAdapter(StateCoderStoreAdapter.class); + Model model = createStore().createEmptyModel(); + + var personI = model.getInterpretation(person); + var friendI = model.getInterpretation(friend); + + for (int i = 0; i < limit; i++) { + personI.put(Tuple.of(i), true); + } + + friendI.put(Tuple.of(11,12),true); + var v1 = model.commit(); + + friendI.put(Tuple.of(11,12),false); + friendI.put(Tuple.of(21,22),false); + var v2 = model.commit(); + + assertEquals(StateEquivalenceChecker.EquivalenceResult.UNKNOWN, stateCoder.checkEquivalence(v1,v2)); + } } -- cgit v1.2.3-70-g09d2