From d91acf3690682d243dbc150df902525b6e545c2f Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Mon, 23 Jan 2023 20:27:55 +0100 Subject: refactor: Model store and query API Use Adapters to simplify API usage. --- .../language/semantics/model/ModelInitializer.java | 8 +- .../semantics/model/internal/DecisionTree.java | 2 +- .../model/internal/DecisionTreeCursor.java | 2 +- .../semantics/model/internal/DecisionTreeNode.java | 2 +- .../model/internal/DecisionTreeValue.java | 2 +- .../semantics/model/internal/IntermediateNode.java | 2 +- .../semantics/model/internal/TerminalNode.java | 2 +- .../semantics/model/tests/DecisionTreeTests.java | 2 +- subprojects/store-query-viatra/build.gradle | 2 +- .../store/query/viatra/ViatraModelQuery.java | 22 + .../query/viatra/ViatraModelQueryBuilder.java | 48 ++ .../query/viatra/ViatraModelQueryStoreAdapter.java | 8 + .../query/viatra/ViatraQueryableModelStore.java | 140 ----- .../query/viatra/internal/RawPatternMatcher.java | 63 --- .../query/viatra/internal/RelationalScope.java | 22 +- .../internal/ViatraModelQueryAdapterImpl.java | 117 +++++ .../internal/ViatraModelQueryBuilderImpl.java | 116 +++++ .../internal/ViatraModelQueryStoreAdapterImpl.java | 58 +++ .../viatra/internal/ViatraQueryableModel.java | 222 -------- .../UpperCardinalitySumAggregationOperator.java | 8 +- .../internal/context/RelationalEngineContext.java | 2 +- .../context/RelationalQueryMetaContext.java | 2 +- .../internal/context/RelationalRuntimeContext.java | 14 +- .../query/viatra/internal/pquery/DNF2PQuery.java | 34 +- .../query/viatra/internal/pquery/RawPQuery.java | 71 +++ .../viatra/internal/pquery/RawPatternMatcher.java | 72 +++ .../internal/pquery/RelationViewWrapper.java | 4 +- .../query/viatra/internal/pquery/SimplePQuery.java | 74 --- .../internal/update/ModelUpdateListener.java | 46 ++ .../viatra/internal/update/RelationViewFilter.java | 66 +++ .../update/RelationViewUpdateListener.java | 40 ++ .../TupleChangingRelationViewUpdateListener.java | 35 ++ .../TuplePreservingRelationViewUpdateListener.java | 24 + .../internal/viewupdate/ModelUpdateListener.java | 112 ---- .../viatra/internal/viewupdate/ViewUpdate.java | 32 -- .../internal/viewupdate/ViewUpdateBuffer.java | 47 -- .../internal/viewupdate/ViewUpdateTranslator.java | 73 --- .../refinery/store/query/viatra/QueryTest.java | 580 ++++++++++++--------- .../store/query/viatra/QueryTransactionTest.java | 57 +- ...ardinalitySumAggregationOperatorStreamTest.java | 4 +- ...UpperCardinalitySumAggregationOperatorTest.java | 4 +- .../store/adapter/AbstractModelAdapterBuilder.java | 27 + .../tools/refinery/store/adapter/AdapterList.java | 94 ++++ .../store/adapter/AnyModelAdapterType.java | 19 + .../tools/refinery/store/adapter/ModelAdapter.java | 9 + .../store/adapter/ModelAdapterBuilder.java | 17 + .../store/adapter/ModelAdapterBuilderFactory.java | 14 + .../refinery/store/adapter/ModelAdapterType.java | 79 +++ .../refinery/store/adapter/ModelStoreAdapter.java | 10 + .../refinery/store/model/AnyInterpretation.java | 11 + .../tools/refinery/store/model/Interpretation.java | 25 + .../store/model/InterpretationListener.java | 7 + .../java/tools/refinery/store/model/Model.java | 29 +- .../refinery/store/model/ModelDiffCursor.java | 24 +- .../tools/refinery/store/model/ModelListener.java | 15 + .../tools/refinery/store/model/ModelStore.java | 17 +- .../refinery/store/model/ModelStoreBuilder.java | 36 ++ .../tools/refinery/store/model/ModelStoreImpl.java | 115 ---- .../tools/refinery/store/model/RelationLike.java | 7 - .../refinery/store/model/TupleHashProvider.java | 11 +- .../refinery/store/model/internal/ModelAction.java | 7 + .../refinery/store/model/internal/ModelImpl.java | 211 ++++---- .../model/internal/ModelStoreBuilderImpl.java | 104 ++++ .../store/model/internal/ModelStoreImpl.java | 100 ++++ .../internal/SimilarRelationEquivalenceClass.java | 35 -- .../model/internal/SymbolEquivalenceClass.java | 9 + .../model/internal/VersionedInterpretation.java | 159 ++++++ .../model/representation/AnyAuxiliaryData.java | 4 - .../representation/AnyDataRepresentation.java | 9 - .../store/model/representation/AnyRelation.java | 9 - .../store/model/representation/AuxiliaryData.java | 23 - .../model/representation/DataRepresentation.java | 43 -- .../store/model/representation/Relation.java | 33 -- .../store/model/representation/TruthValue.java | 58 --- .../cardinality/CardinalityInterval.java | 21 - .../cardinality/CardinalityIntervals.java | 46 -- .../cardinality/EmptyCardinalityInterval.java | 59 --- .../cardinality/FiniteUpperCardinality.java | 55 -- .../cardinality/NonEmptyCardinalityInterval.java | 74 --- .../cardinality/UnboundedUpperCardinality.java | 42 -- .../cardinality/UpperCardinalities.java | 33 -- .../cardinality/UpperCardinality.java | 22 - .../main/java/tools/refinery/store/query/DNF.java | 8 +- .../tools/refinery/store/query/ModelQuery.java | 11 + .../refinery/store/query/ModelQueryAdapter.java | 11 + .../refinery/store/query/ModelQueryBuilder.java | 23 + .../store/query/ModelQueryStoreAdapter.java | 16 + .../tools/refinery/store/query/QueryableModel.java | 33 -- .../refinery/store/query/QueryableModelStore.java | 19 - .../java/tools/refinery/store/query/ResultSet.java | 25 + .../store/query/atom/AbstractSubstitutionAtom.java | 10 +- .../tools/refinery/store/query/atom/CallAtom.java | 18 +- .../refinery/store/query/atom/DNFCallAtom.java | 32 ++ .../refinery/store/query/atom/ModalRelation.java | 14 +- .../query/view/AbstractFilteredRelationView.java | 44 -- .../refinery/store/query/view/AnyRelationView.java | 8 +- .../store/query/view/FilteredRelationView.java | 24 +- .../store/query/view/FunctionalRelationView.java | 24 +- .../store/query/view/KeyOnlyRelationView.java | 18 +- .../refinery/store/query/view/RelationView.java | 34 +- .../query/view/TuplePreservingRelationView.java | 44 ++ .../refinery/store/representation/AnySymbol.java | 5 + .../refinery/store/representation/Symbol.java | 47 ++ .../refinery/store/representation/SymbolLike.java | 7 + .../refinery/store/representation/TruthValue.java | 58 +++ .../cardinality/CardinalityInterval.java | 21 + .../cardinality/CardinalityIntervals.java | 46 ++ .../cardinality/EmptyCardinalityInterval.java | 59 +++ .../cardinality/FiniteUpperCardinality.java | 55 ++ .../cardinality/NonEmptyCardinalityInterval.java | 74 +++ .../cardinality/UnboundedUpperCardinality.java | 42 ++ .../cardinality/UpperCardinalities.java | 33 ++ .../cardinality/UpperCardinality.java | 22 + .../refinery/store/map/tests/MapUnitTests.java | 9 +- .../store/model/hashTests/HashEfficiencyTest.java | 161 ------ .../store/model/hashtests/HashEfficiencyTest.java | 161 ++++++ .../cardinality/CardinalityIntervalTest.java | 122 ----- .../cardinality/CardinalityIntervalsTest.java | 21 - .../cardinality/EmptyCardinalityIntervalTest.java | 14 - .../cardinality/FiniteCardinalityIntervalTest.java | 20 - .../cardinality/FiniteUpperCardinalityTest.java | 12 - .../cardinality/UpperCardinalitiesTest.java | 25 - .../cardinality/UpperCardinalityTest.java | 110 ---- .../refinery/store/model/tests/ModelTest.java | 190 +++---- .../cardinality/CardinalityIntervalTest.java | 123 +++++ .../cardinality/CardinalityIntervalsTest.java | 23 + .../cardinality/EmptyCardinalityIntervalTest.java | 15 + .../cardinality/FiniteCardinalityIntervalTest.java | 23 + .../cardinality/FiniteUpperCardinalityTest.java | 13 + .../cardinality/UpperCardinalitiesTest.java | 28 + .../cardinality/UpperCardinalityTest.java | 112 ++++ 131 files changed, 3371 insertions(+), 2659 deletions(-) create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQuery.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraQueryableModelStore.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RawPatternMatcher.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraQueryableModel.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SimplePQuery.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ModelUpdateListener.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdate.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateBuffer.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateTranslator.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/adapter/AbstractModelAdapterBuilder.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/adapter/AdapterList.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/adapter/AnyModelAdapterType.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapter.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilder.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilderFactory.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterType.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/adapter/ModelStoreAdapter.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/AnyInterpretation.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/InterpretationListener.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreBuilder.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreImpl.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/RelationLike.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelAction.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/internal/SimilarRelationEquivalenceClass.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/internal/SymbolEquivalenceClass.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/representation/AnyAuxiliaryData.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/representation/AnyDataRepresentation.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/representation/AnyRelation.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/representation/AuxiliaryData.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/representation/DataRepresentation.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/representation/Relation.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/representation/TruthValue.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/CardinalityInterval.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervals.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/EmptyCardinalityInterval.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/FiniteUpperCardinality.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/NonEmptyCardinalityInterval.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UnboundedUpperCardinality.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UpperCardinalities.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UpperCardinality.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/query/ModelQuery.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/query/QueryableModel.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/query/QueryableModelStore.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/query/ResultSet.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFCallAtom.java delete mode 100644 subprojects/store/src/main/java/tools/refinery/store/query/view/AbstractFilteredRelationView.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/representation/AnySymbol.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/representation/Symbol.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/representation/SymbolLike.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/representation/TruthValue.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityInterval.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityIntervals.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/EmptyCardinalityInterval.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinality.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UnboundedUpperCardinality.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinalities.java create mode 100644 subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinality.java delete mode 100644 subprojects/store/src/test/java/tools/refinery/store/model/hashTests/HashEfficiencyTest.java create mode 100644 subprojects/store/src/test/java/tools/refinery/store/model/hashtests/HashEfficiencyTest.java delete mode 100644 subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervalTest.java delete mode 100644 subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervalsTest.java delete mode 100644 subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/EmptyCardinalityIntervalTest.java delete mode 100644 subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/FiniteCardinalityIntervalTest.java delete mode 100644 subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/FiniteUpperCardinalityTest.java delete mode 100644 subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/UpperCardinalitiesTest.java delete mode 100644 subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/UpperCardinalityTest.java create mode 100644 subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalTest.java create mode 100644 subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalsTest.java create mode 100644 subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/EmptyCardinalityIntervalTest.java create mode 100644 subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteCardinalityIntervalTest.java create mode 100644 subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinalityTest.java create mode 100644 subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalitiesTest.java create mode 100644 subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalityTest.java (limited to 'subprojects') diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java index 13bb20d7..a6712a89 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java @@ -7,8 +7,8 @@ import tools.refinery.language.model.problem.*; import tools.refinery.language.semantics.model.internal.DecisionTree; import tools.refinery.language.utils.ProblemDesugarer; import tools.refinery.language.utils.RelationInfo; -import tools.refinery.store.model.representation.Relation; -import tools.refinery.store.model.representation.TruthValue; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.representation.TruthValue; import tools.refinery.store.tuple.Tuple; import java.util.HashMap; @@ -20,7 +20,7 @@ public class ModelInitializer { private final MutableObjectIntMap nodeTrace = ObjectIntMaps.mutable.empty(); - private final Map> relationTrace = + private final Map> relationTrace = new HashMap<>(); private int nodeCount = 0; @@ -39,7 +39,7 @@ public class ModelInitializer { var isEqualsRelation = relation == builtinSymbols.equals(); var decisionTree = mergeAssertions(relationInfo, isEqualsRelation); var defaultValue = isEqualsRelation ? TruthValue.FALSE : TruthValue.UNKNOWN; - relationTrace.put(relation, new Relation<>(relationInfo.name(), relationInfo.arity(), TruthValue.class, defaultValue + relationTrace.put(relation, new Symbol<>(relationInfo.name(), relationInfo.arity(), TruthValue.class, defaultValue )); } } diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java index 3893f396..55edee6d 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java @@ -3,7 +3,7 @@ package tools.refinery.language.semantics.model.internal; import org.eclipse.collections.api.factory.primitive.IntObjectMaps; import tools.refinery.store.map.Cursor; import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.model.representation.TruthValue; +import tools.refinery.store.representation.TruthValue; public class DecisionTree { private final int levels; diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java index a1fdc73d..fdf8e452 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java @@ -1,7 +1,7 @@ package tools.refinery.language.semantics.model.internal; import tools.refinery.store.map.Cursor; -import tools.refinery.store.model.representation.TruthValue; +import tools.refinery.store.representation.TruthValue; import tools.refinery.store.tuple.Tuple; import java.util.ArrayDeque; diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeNode.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeNode.java index 8ca54969..b81ea3fe 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeNode.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeNode.java @@ -2,7 +2,7 @@ package tools.refinery.language.semantics.model.internal; import org.eclipse.collections.api.LazyIntIterable; import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.model.representation.TruthValue; +import tools.refinery.store.representation.TruthValue; abstract class DecisionTreeNode { public DecisionTreeValue getReducedValue() { diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeValue.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeValue.java index 993987f5..495a53dd 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeValue.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeValue.java @@ -1,6 +1,6 @@ package tools.refinery.language.semantics.model.internal; -import tools.refinery.store.model.representation.TruthValue; +import tools.refinery.store.representation.TruthValue; public enum DecisionTreeValue { UNSET(null), diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/IntermediateNode.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/IntermediateNode.java index a7486ecb..c4200509 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/IntermediateNode.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/IntermediateNode.java @@ -5,7 +5,7 @@ import org.eclipse.collections.api.factory.primitive.IntObjectMaps; import org.eclipse.collections.api.map.primitive.MutableIntObjectMap; import org.eclipse.collections.api.tuple.primitive.IntObjectPair; import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.model.representation.TruthValue; +import tools.refinery.store.representation.TruthValue; final class IntermediateNode extends DecisionTreeNode { private final MutableIntObjectMap children; diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/TerminalNode.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/TerminalNode.java index c0197e89..4af836ff 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/TerminalNode.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/TerminalNode.java @@ -5,7 +5,7 @@ import org.eclipse.collections.api.factory.primitive.IntObjectMaps; import org.eclipse.collections.api.map.primitive.MutableIntObjectMap; import org.eclipse.collections.api.tuple.primitive.IntObjectPair; import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.model.representation.TruthValue; +import tools.refinery.store.representation.TruthValue; class TerminalNode extends DecisionTreeNode { private MutableIntObjectMap children; diff --git a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java index f171e5c7..4630bf53 100644 --- a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java +++ b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java @@ -2,7 +2,7 @@ package tools.refinery.language.semantics.model.tests; import org.junit.jupiter.api.Test; import tools.refinery.language.semantics.model.internal.DecisionTree; -import tools.refinery.store.model.representation.TruthValue; +import tools.refinery.store.representation.TruthValue; import tools.refinery.store.tuple.Tuple; import java.util.LinkedHashMap; diff --git a/subprojects/store-query-viatra/build.gradle b/subprojects/store-query-viatra/build.gradle index 32a23fe7..c12b48fe 100644 --- a/subprojects/store-query-viatra/build.gradle +++ b/subprojects/store-query-viatra/build.gradle @@ -9,7 +9,7 @@ configurations.testRuntimeClasspath { dependencies { implementation libs.ecore - implementation libs.viatra + api libs.viatra api project(':refinery-store') testImplementation libs.slf4j.simple testImplementation libs.slf4j.log4j diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQuery.java new file mode 100644 index 00000000..ecac570b --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQuery.java @@ -0,0 +1,22 @@ +package tools.refinery.store.query.viatra; + +import tools.refinery.store.adapter.ModelAdapterBuilderFactory; +import tools.refinery.store.model.ModelStoreBuilder; +import tools.refinery.store.query.ModelQuery; +import tools.refinery.store.query.ModelQueryAdapter; +import tools.refinery.store.query.viatra.internal.ViatraModelQueryBuilderImpl; + +public final class ViatraModelQuery extends ModelAdapterBuilderFactory { + public static final ViatraModelQuery ADAPTER = new ViatraModelQuery(); + + private ViatraModelQuery() { + super(ModelQueryAdapter.class, ViatraModelQueryStoreAdapter.class, ViatraModelQueryBuilder.class); + extendsAdapter(ModelQuery.ADAPTER); + } + + @Override + public ViatraModelQueryBuilder createBuilder(ModelStoreBuilder storeBuilder) { + return new ViatraModelQueryBuilderImpl(storeBuilder); + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java new file mode 100644 index 00000000..ee445a79 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java @@ -0,0 +1,48 @@ +package tools.refinery.store.query.viatra; + +import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; +import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; +import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.query.DNF; +import tools.refinery.store.query.ModelQueryBuilder; + +import java.util.Collection; +import java.util.function.Function; + +@SuppressWarnings("UnusedReturnValue") +public interface ViatraModelQueryBuilder extends ModelQueryBuilder { + ViatraModelQueryBuilder engineOptions(ViatraQueryEngineOptions engineOptions); + + ViatraModelQueryBuilder defaultHint(QueryEvaluationHint queryEvaluationHint); + + ViatraModelQueryBuilder backend(IQueryBackendFactory queryBackendFactory); + + ViatraModelQueryBuilder cachingBackend(IQueryBackendFactory queryBackendFactory); + + ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory); + + @Override + default ViatraModelQueryBuilder queries(DNF... queries) { + ModelQueryBuilder.super.queries(queries); + return this; + } + + @Override + default ViatraModelQueryBuilder queries(Collection queries) { + ModelQueryBuilder.super.queries(queries); + return this; + } + + @Override + ViatraModelQueryBuilder query(DNF query); + + ViatraModelQueryBuilder query(DNF query, QueryEvaluationHint queryEvaluationHint); + + ViatraModelQueryBuilder computeHint(Function computeHint); + + ViatraModelQueryBuilder hint(DNF dnf, QueryEvaluationHint queryEvaluationHint); + + @Override + ViatraModelQueryStoreAdapter createStoreAdapter(ModelStore store); +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java new file mode 100644 index 00000000..d52575d2 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java @@ -0,0 +1,8 @@ +package tools.refinery.store.query.viatra; + +import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; +import tools.refinery.store.query.ModelQueryStoreAdapter; + +public interface ViatraModelQueryStoreAdapter extends ModelQueryStoreAdapter { + ViatraQueryEngineOptions getEngineOptions(); +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraQueryableModelStore.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraQueryableModelStore.java deleted file mode 100644 index 94d2db4f..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraQueryableModelStore.java +++ /dev/null @@ -1,140 +0,0 @@ -package tools.refinery.store.query.viatra; - -import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; -import tools.refinery.store.model.ModelDiffCursor; -import tools.refinery.store.model.ModelStore; -import tools.refinery.store.model.ModelStoreImpl; -import tools.refinery.store.model.RelationLike; -import tools.refinery.store.model.representation.AnyDataRepresentation; -import tools.refinery.store.model.representation.DataRepresentation; -import tools.refinery.store.query.DNF; -import tools.refinery.store.query.DNFAnd; -import tools.refinery.store.query.QueryableModel; -import tools.refinery.store.query.QueryableModelStore; -import tools.refinery.store.query.atom.*; -import tools.refinery.store.query.viatra.internal.RawPatternMatcher; -import tools.refinery.store.query.viatra.internal.ViatraQueryableModel; -import tools.refinery.store.query.viatra.internal.pquery.DNF2PQuery; -import tools.refinery.store.query.view.AnyRelationView; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -public class ViatraQueryableModelStore implements QueryableModelStore { - protected final ModelStore store; - - protected final Set relationViews; - - protected final Map> predicates; - - public ViatraQueryableModelStore(ModelStore store, Set relationViews, - Set predicates) { - this.store = store; - validateViews(store.getDataRepresentations(), relationViews); - this.relationViews = Collections.unmodifiableSet(relationViews); - validatePredicates(relationViews, predicates); - this.predicates = initPredicates(predicates); - } - - public ViatraQueryableModelStore(Set dataRepresentations, - Set relationViews, Set predicates) { - this(new ModelStoreImpl(dataRepresentations), relationViews, predicates); - } - - private void validateViews(Set dataRepresentations, Set relationViews) { - for (var relationView : relationViews) { - if (!dataRepresentations.contains(relationView.getRepresentation())) { - throw new IllegalArgumentException("%s %s added to %s without a referred representation.".formatted( - DataRepresentation.class.getSimpleName(), relationView.getName(), - QueryableModelStore.class.getSimpleName())); - } - } - } - - private void validatePredicates(Set relationViews, Set predicates) { - for (DNF dnfPredicate : predicates) { - for (DNFAnd clause : dnfPredicate.getClauses()) { - for (DNFAtom atom : clause.constraints()) { - if (atom instanceof RelationViewAtom relationViewAtom) { - validateRelationAtom(relationViews, dnfPredicate, relationViewAtom); - } else if (atom instanceof CallAtom queryCallAtom) { - validatePredicateAtom(predicates, dnfPredicate, queryCallAtom); - } else if (!(atom instanceof EquivalenceAtom || atom instanceof ConstantAtom)) { - throw new IllegalArgumentException("Unknown constraint: " + atom.toString()); - } - } - } - } - } - - private void validateRelationAtom(Set relationViews, DNF dnfPredicate, - RelationViewAtom relationViewAtom) { - if (!relationViews.contains(relationViewAtom.getTarget())) { - throw new IllegalArgumentException( - "%s %s contains reference to a view %s that is not in the model.".formatted( - DNF.class.getSimpleName(), dnfPredicate.getUniqueName(), - relationViewAtom.getTarget().getName())); - } - } - - private void validatePredicateReference(Set predicates, DNF dnfPredicate, RelationLike target) { - if (!(target instanceof DNF dnfTarget) || !predicates.contains(dnfTarget)) { - throw new IllegalArgumentException( - "%s %s contains reference to a predicate %s that is not in the model.".formatted( - DNF.class.getSimpleName(), dnfPredicate.getUniqueName(), target.getName())); - } - } - - private void validatePredicateAtom(Set predicates, DNF dnfPredicate, CallAtom queryCallAtom) { - validatePredicateReference(predicates, dnfPredicate, queryCallAtom.getTarget()); - } - - - private Map> initPredicates(Set predicates) { - Map> result = new HashMap<>(); - var dnf2PQuery = new DNF2PQuery(); - for (DNF dnfPredicate : predicates) { - GenericQuerySpecification query = dnf2PQuery.translate(dnfPredicate).build(); - result.put(dnfPredicate, query); - } - - return result; - } - - @Override - public Set getDataRepresentations() { - return store.getDataRepresentations(); - } - - @Override - public Set getViews() { - return this.relationViews; - } - - @Override - public Set getPredicates() { - return predicates.keySet(); - } - - @Override - public QueryableModel createModel() { - return new ViatraQueryableModel(this, this.store.createModel(), predicates); - } - - @Override - public QueryableModel createModel(long state) { - return new ViatraQueryableModel(this, this.store.createModel(state), predicates); - } - - @Override - public synchronized Set getStates() { - return this.store.getStates(); - } - - @Override - public synchronized ModelDiffCursor getDiffCursor(long from, long to) { - return this.store.getDiffCursor(from, to); - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RawPatternMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RawPatternMatcher.java deleted file mode 100644 index 2c488319..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RawPatternMatcher.java +++ /dev/null @@ -1,63 +0,0 @@ -package tools.refinery.store.query.viatra.internal; - -import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher; -import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; -import tools.refinery.store.query.viatra.ViatraTupleLike; -import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.tuple.TupleLike; - -import java.util.Optional; -import java.util.stream.Stream; - -public class RawPatternMatcher extends GenericPatternMatcher { - protected final Object[] empty; - - public RawPatternMatcher(GenericQuerySpecification specification) { - super(specification); - empty = new Object[specification.getParameterNames().size()]; - } - - public boolean hasResult() { - return backend.hasMatch(empty); - } - - public boolean hasResult(Tuple parameters) { - return backend.hasMatch(toParametersArray(parameters)); - } - - public Optional oneResult() { - return backend.getOneArbitraryMatch(empty).map(ViatraTupleLike::new); - } - - public Optional oneResult(Tuple parameters) { - return backend.getOneArbitraryMatch(toParametersArray(parameters)).map(ViatraTupleLike::new); - } - - public Stream allResults() { - return backend.getAllMatches(empty).map(ViatraTupleLike::new); - } - - public Stream allResults(Tuple parameters) { - return backend.getAllMatches(toParametersArray(parameters)).map(ViatraTupleLike::new); - } - - public int countResults() { - return backend.countMatches(empty); - } - - public int countResults(Tuple parameters) { - return backend.countMatches(toParametersArray(parameters)); - } - - private Object[] toParametersArray(Tuple tuple) { - int size = tuple.getSize(); - var array = new Object[tuple.getSize()]; - for (int i = 0; i < size; i++) { - var value = tuple.get(i); - if (value >= 0) { - array[i] = Tuple.of(value); - } - } - return array; - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java index 133c4c72..21dcaf15 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java @@ -6,34 +6,20 @@ import org.eclipse.viatra.query.runtime.api.scope.IEngineContext; import org.eclipse.viatra.query.runtime.api.scope.IIndexingErrorListener; import org.eclipse.viatra.query.runtime.api.scope.QueryScope; import tools.refinery.store.model.Model; -import tools.refinery.store.model.representation.Relation; import tools.refinery.store.query.viatra.internal.context.RelationalEngineContext; -import tools.refinery.store.query.viatra.internal.viewupdate.ModelUpdateListener; +import tools.refinery.store.query.viatra.internal.update.ModelUpdateListener; import tools.refinery.store.query.view.AnyRelationView; -import tools.refinery.store.tuple.Tuple; -import java.util.Set; +import java.util.Collection; public class RelationalScope extends QueryScope { private final Model model; private final ModelUpdateListener updateListener; - public RelationalScope(Model model, Set relationViews) { + public RelationalScope(Model model, Collection relationViews) { this.model = model; - this.updateListener = new ModelUpdateListener(relationViews); - } - - public void processUpdate(Relation relation, Tuple key, D oldValue, D newValue) { - updateListener.addUpdate(relation, key, oldValue, newValue); - } - - public boolean hasChanges() { - return updateListener.hasChanges(); - } - - public void flush() { - updateListener.flush(); + updateListener = new ModelUpdateListener(model, relationViews); } @Override diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java new file mode 100644 index 00000000..3c276935 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java @@ -0,0 +1,117 @@ +package tools.refinery.store.query.viatra.internal; + +import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine; +import org.eclipse.viatra.query.runtime.api.GenericQueryGroup; +import org.eclipse.viatra.query.runtime.api.IQuerySpecification; +import org.eclipse.viatra.query.runtime.internal.apiimpl.ViatraQueryEngineImpl; +import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend; +import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.store.model.Model; +import tools.refinery.store.query.DNF; +import tools.refinery.store.query.ModelQueryAdapter; +import tools.refinery.store.query.ModelQueryStoreAdapter; +import tools.refinery.store.query.ResultSet; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.util.*; + +public class ViatraModelQueryAdapterImpl implements ModelQueryAdapter { + private static final String DELAY_MESSAGE_DELIVERY_FIELD_NAME = "delayMessageDelivery"; + private static final String QUERY_BACKENDS_FIELD_NAME = "queryBackends"; + + private final Model model; + private final ViatraModelQueryStoreAdapterImpl storeAdapter; + private final ViatraQueryEngineImpl queryEngine; + private final MethodHandle setUpdatePropagationDelayedHandle; + private final MethodHandle getQueryBackendsHandle; + private final Map resultSets; + + ViatraModelQueryAdapterImpl(Model model, ViatraModelQueryStoreAdapterImpl storeAdapter) { + this.model = model; + this.storeAdapter = storeAdapter; + var scope = new RelationalScope(model, storeAdapter.getRelationViews()); + queryEngine = (ViatraQueryEngineImpl) AdvancedViatraQueryEngine.createUnmanagedEngine(scope); + + try { + var lookup = MethodHandles.privateLookupIn(ViatraQueryEngineImpl.class, MethodHandles.lookup()); + setUpdatePropagationDelayedHandle = lookup.findSetter(ViatraQueryEngineImpl.class, + DELAY_MESSAGE_DELIVERY_FIELD_NAME, Boolean.TYPE); + getQueryBackendsHandle = lookup.findGetter(ViatraQueryEngineImpl.class, QUERY_BACKENDS_FIELD_NAME, + Map.class); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new IllegalStateException("Cannot access private members of %s" + .formatted(ViatraQueryEngineImpl.class.getName()), e); + } + + var querySpecifications = storeAdapter.getQuerySpecifications(); + GenericQueryGroup.of( + Collections.>unmodifiableCollection(querySpecifications.values()).stream() + ).prepare(queryEngine); + resultSets = new HashMap<>(querySpecifications.size()); + for (var entry : querySpecifications.entrySet()) { + var matcher = queryEngine.getMatcher(entry.getValue()); + resultSets.put(entry.getKey(), matcher); + } + + setUpdatePropagationDelayed(true); + } + + private void setUpdatePropagationDelayed(boolean value) { + try { + setUpdatePropagationDelayedHandle.invokeExact(queryEngine, value); + } catch (Error e) { + // Fatal JVM errors should not be wrapped. + throw e; + } catch (Throwable e) { + throw new IllegalStateException("Cannot set %s".formatted(DELAY_MESSAGE_DELIVERY_FIELD_NAME), e); + } + } + + private Collection getQueryBackends() { + try { + @SuppressWarnings("unchecked") + var backendMap = (Map) getQueryBackendsHandle.invokeExact(queryEngine); + return backendMap.values(); + } catch (Error e) { + // Fatal JVM errors should not be wrapped. + throw e; + } catch (Throwable e) { + throw new IllegalStateException("Cannot get %s".formatted(QUERY_BACKENDS_FIELD_NAME), e); + } + } + + @Override + public Model getModel() { + return model; + } + + @Override + public ModelQueryStoreAdapter getStoreAdapter() { + return storeAdapter; + } + + @Override + public ResultSet getResultSet(DNF query) { + var resultSet = resultSets.get(query); + if (resultSet == null) { + throw new IllegalArgumentException("No matcher for query %s in model".formatted(query.name())); + } + return resultSet; + } + + @Override + public void flushChanges() { + if (!queryEngine.isUpdatePropagationDelayed()) { + throw new IllegalStateException("Trying to flush changes while changes are already being flushed"); + } + setUpdatePropagationDelayed(false); + try { + for (var queryBackend : getQueryBackends()) { + queryBackend.flushUpdates(); + } + } finally { + setUpdatePropagationDelayed(true); + } + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java new file mode 100644 index 00000000..5105c9a7 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java @@ -0,0 +1,116 @@ +package tools.refinery.store.query.viatra.internal; + +import org.eclipse.viatra.query.runtime.api.IQuerySpecification; +import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; +import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchGenericBackendFactory; +import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; +import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import org.eclipse.viatra.query.runtime.rete.matcher.ReteBackendFactory; +import tools.refinery.store.adapter.AbstractModelAdapterBuilder; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.model.ModelStoreBuilder; +import tools.refinery.store.query.DNF; +import tools.refinery.store.query.viatra.ViatraModelQueryBuilder; +import tools.refinery.store.query.viatra.internal.pquery.DNF2PQuery; +import tools.refinery.store.query.viatra.internal.pquery.RawPatternMatcher; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder implements ViatraModelQueryBuilder { + private ViatraQueryEngineOptions.Builder engineOptionsBuilder; + private final DNF2PQuery dnf2PQuery = new DNF2PQuery(); + private final Map> querySpecifications = new LinkedHashMap<>(); + + public ViatraModelQueryBuilderImpl(ModelStoreBuilder storeBuilder) { + super(storeBuilder); + engineOptionsBuilder = new ViatraQueryEngineOptions.Builder() + .withDefaultBackend(ReteBackendFactory.INSTANCE) + .withDefaultCachingBackend(ReteBackendFactory.INSTANCE) + .withDefaultSearchBackend(LocalSearchGenericBackendFactory.INSTANCE); + } + + @Override + public ViatraModelQueryBuilder engineOptions(ViatraQueryEngineOptions engineOptions) { + engineOptionsBuilder = new ViatraQueryEngineOptions.Builder(engineOptions); + return this; + } + + @Override + public ViatraModelQueryBuilder defaultHint(QueryEvaluationHint queryEvaluationHint) { + engineOptionsBuilder.withDefaultHint(queryEvaluationHint); + return this; + } + + @Override + public ViatraModelQueryBuilder backend(IQueryBackendFactory queryBackendFactory) { + engineOptionsBuilder.withDefaultBackend(queryBackendFactory); + return this; + } + + @Override + public ViatraModelQueryBuilder cachingBackend(IQueryBackendFactory queryBackendFactory) { + engineOptionsBuilder.withDefaultCachingBackend(queryBackendFactory); + return this; + } + + @Override + public ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory) { + engineOptionsBuilder.withDefaultSearchBackend(queryBackendFactory); + return this; + } + + @Override + public ViatraModelQueryBuilder query(DNF query) { + if (querySpecifications.containsKey(query)) { + throw new IllegalArgumentException("%s was already added to the query engine".formatted(query.name())); + } + var pQuery = dnf2PQuery.translate(query); + querySpecifications.put(query, pQuery.build()); + return this; + } + + @Override + public ViatraModelQueryBuilder query(DNF query, QueryEvaluationHint queryEvaluationHint) { + query(query); + hint(query, queryEvaluationHint); + return this; + } + + @Override + public ViatraModelQueryBuilder computeHint(Function computeHint) { + dnf2PQuery.setComputeHint(computeHint); + return this; + } + + @Override + public ViatraModelQueryBuilder hint(DNF dnf, QueryEvaluationHint queryEvaluationHint) { + var pQuery = dnf2PQuery.getAlreadyTranslated(dnf); + if (pQuery == null) { + throw new IllegalArgumentException( + "Cannot specify hint for %s, because it was not added to the query engine".formatted(dnf.name())); + } + pQuery.setEvaluationHints(queryEvaluationHint); + return this; + } + + @Override + public ViatraModelQueryStoreAdapterImpl createStoreAdapter(ModelStore store) { + validateSymbols(store); + return new ViatraModelQueryStoreAdapterImpl(store, engineOptionsBuilder.build(), dnf2PQuery.getRelationViews(), + Collections.unmodifiableMap(querySpecifications)); + } + + private void validateSymbols(ModelStore store) { + var symbols = store.getSymbols(); + for (var relationView : dnf2PQuery.getRelationViews()) { + var symbol = relationView.getSymbol(); + if (!symbols.contains(symbol)) { + throw new IllegalArgumentException("Cannot query relation view %s: symbol %s is not in the model" + .formatted(relationView, symbol)); + } + } + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java new file mode 100644 index 00000000..d77b7f4b --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java @@ -0,0 +1,58 @@ +package tools.refinery.store.query.viatra.internal; + +import org.eclipse.viatra.query.runtime.api.IQuerySpecification; +import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; +import tools.refinery.store.model.Model; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.query.DNF; +import tools.refinery.store.query.viatra.ViatraModelQueryStoreAdapter; +import tools.refinery.store.query.viatra.internal.pquery.RawPatternMatcher; +import tools.refinery.store.query.view.AnyRelationView; + +import java.util.Collection; +import java.util.Map; + +public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAdapter { + private final ModelStore store; + private final ViatraQueryEngineOptions engineOptions; + private final Collection relationViews; + private final Map> querySpecifications; + + ViatraModelQueryStoreAdapterImpl(ModelStore store, ViatraQueryEngineOptions engineOptions, + Collection relationViews, + Map> querySpecifications) { + this.store = store; + this.engineOptions = engineOptions; + this.relationViews = relationViews; + this.querySpecifications = querySpecifications; + } + + @Override + public ModelStore getStore() { + return store; + } + + @Override + public Collection getRelationViews() { + return relationViews; + } + + @Override + public Collection getQueries() { + return querySpecifications.keySet(); + } + + Map> getQuerySpecifications() { + return querySpecifications; + } + + @Override + public ViatraQueryEngineOptions getEngineOptions() { + return engineOptions; + } + + @Override + public ViatraModelQueryAdapterImpl createModelAdapter(Model model) { + return new ViatraModelQueryAdapterImpl(model, this); + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraQueryableModel.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraQueryableModel.java deleted file mode 100644 index 5b06e266..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraQueryableModel.java +++ /dev/null @@ -1,222 +0,0 @@ -package tools.refinery.store.query.viatra.internal; - -import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine; -import org.eclipse.viatra.query.runtime.api.GenericQueryGroup; -import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; -import org.eclipse.viatra.query.runtime.api.IQueryGroup; -import tools.refinery.store.map.Cursor; -import tools.refinery.store.map.DiffCursor; -import tools.refinery.store.model.Model; -import tools.refinery.store.model.ModelDiffCursor; -import tools.refinery.store.model.representation.AnyDataRepresentation; -import tools.refinery.store.model.representation.DataRepresentation; -import tools.refinery.store.model.representation.Relation; -import tools.refinery.store.query.QueryableModel; -import tools.refinery.store.query.QueryableModelStore; -import tools.refinery.store.query.DNF; -import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.tuple.TupleLike; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Stream; - -public class ViatraQueryableModel implements QueryableModel { - protected final QueryableModelStore store; - - protected final Model model; - - protected final Map> predicates2PQuery; - - protected RelationalScope scope; - - protected AdvancedViatraQueryEngine engine; - - protected Map predicate2Matcher; - - public ViatraQueryableModel(QueryableModelStore store, Model model, - Map> predicates2PQuery) { - this.store = store; - this.model = model; - this.predicates2PQuery = predicates2PQuery; - initEngine(); - } - - private void initEngine() { - this.scope = new RelationalScope(this.model, this.store.getViews()); - this.engine = AdvancedViatraQueryEngine.createUnmanagedEngine(this.scope); - this.predicate2Matcher = initMatchers(this.engine, this.predicates2PQuery); - } - - private Map initMatchers( - AdvancedViatraQueryEngine engine, - Map> predicates2pQuery) { - // 1. prepare group - IQueryGroup queryGroup = GenericQueryGroup.of(Set.copyOf(predicates2pQuery.values())); - engine.prepareGroup(queryGroup, null); - - // 2. then get all matchers - Map result = new HashMap<>(); - for (var entry : predicates2pQuery.entrySet()) { - var matcher = engine.getMatcher(entry.getValue()); - result.put(entry.getKey(), matcher); - } - return result; - } - - @Override - public Set getDataRepresentations() { - return model.getDataRepresentations(); - } - - @Override - public Set getPredicates() { - return store.getPredicates(); - } - - @Override - public V get(DataRepresentation representation, K key) { - return model.get(representation, key); - } - - @Override - public Cursor getAll(DataRepresentation representation) { - return model.getAll(representation); - } - - @SuppressWarnings("unchecked") - @Override - public V put(DataRepresentation representation, K key, V value) { - V oldValue = this.model.put(representation, key, value); - if (representation instanceof Relation relation) { - this.scope.processUpdate((Relation) relation, (Tuple) key, oldValue, value); - } - return oldValue; - } - - @Override - public void putAll(DataRepresentation representation, Cursor cursor) { - if (representation instanceof Relation) { - //noinspection RedundantSuppression - @SuppressWarnings("unchecked") - Relation relation = (Relation) representation; - while (cursor.move()) { - Tuple key = (Tuple) cursor.getKey(); - V newValue = cursor.getValue(); - V oldValue = this.model.put(relation, key, newValue); - this.scope.processUpdate(relation, key, oldValue, newValue); - } - } else { - this.model.putAll(representation, cursor); - } - } - - @Override - public long getSize(AnyDataRepresentation representation) { - return model.getSize(representation); - } - - protected RawPatternMatcher getMatcher(DNF predicate) { - var result = this.predicate2Matcher.get(predicate); - if (result == null) { - throw new IllegalArgumentException("Model does not contain predicate %s".formatted(predicate.getName())); - } else - return result; - } - - protected void validateParameters(DNF predicate, Tuple parameters) { - int predicateArity = predicate.getParameters().size(); - int parameterArity = parameters.getSize(); - if (parameterArity != predicateArity) { - throw new IllegalArgumentException( - "Predicate %s with %d arity called with different number of parameters (%d)" - .formatted(predicate.getName(), predicateArity, parameterArity)); - } - } - - @Override - public boolean hasResult(DNF predicate) { - return getMatcher(predicate).hasResult(); - } - - @Override - public boolean hasResult(DNF predicate, Tuple parameters) { - validateParameters(predicate, parameters); - return getMatcher(predicate).hasResult(parameters); - } - - @Override - public Optional oneResult(DNF predicate) { - return getMatcher(predicate).oneResult(); - } - - @Override - public Optional oneResult(DNF predicate, Tuple parameters) { - validateParameters(predicate, parameters); - return getMatcher(predicate).oneResult(parameters); - } - - @Override - public Stream allResults(DNF predicate) { - return getMatcher(predicate).allResults(); - } - - @Override - public Stream allResults(DNF predicate, Tuple parameters) { - validateParameters(predicate, parameters); - return getMatcher(predicate).allResults(parameters); - } - - @Override - public int countResults(DNF predicate) { - return getMatcher(predicate).countResults(); - } - - @Override - public int countResults(DNF predicate, Tuple parameters) { - validateParameters(predicate, parameters); - return getMatcher(predicate).countResults(parameters); - - } - - @Override - public boolean hasChanges() { - return scope.hasChanges(); - } - - @Override - public void flushChanges() { - this.scope.flush(); - } - - @Override - public ModelDiffCursor getDiffCursor(long to) { - return model.getDiffCursor(to); - } - - @Override - public long commit() { - return this.model.commit(); - } - - @Override - public void restore(long state) { - restoreWithDiffReplay(state); - } - - private void restoreWithDiffReplay(long state) { - var modelDiffCursor = getDiffCursor(state); - for (AnyDataRepresentation anyDataRepresentation : this.getDataRepresentations()) { - var dataRepresentation = (DataRepresentation) anyDataRepresentation; - restoreRepresentationWithDiffReplay(modelDiffCursor, dataRepresentation); - } - } - - private void restoreRepresentationWithDiffReplay(ModelDiffCursor modelDiffCursor, - DataRepresentation dataRepresentation) { - DiffCursor diffCursor = modelDiffCursor.getCursor(dataRepresentation); - this.putAll(dataRepresentation, diffCursor); - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java index ffd5f6de..e0bca9e0 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java @@ -2,10 +2,10 @@ package tools.refinery.store.query.viatra.internal.cardinality; import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.BoundAggregator; import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; -import tools.refinery.store.model.representation.cardinality.FiniteUpperCardinality; -import tools.refinery.store.model.representation.cardinality.UnboundedUpperCardinality; -import tools.refinery.store.model.representation.cardinality.UpperCardinalities; -import tools.refinery.store.model.representation.cardinality.UpperCardinality; +import tools.refinery.store.representation.cardinality.FiniteUpperCardinality; +import tools.refinery.store.representation.cardinality.UnboundedUpperCardinality; +import tools.refinery.store.representation.cardinality.UpperCardinalities; +import tools.refinery.store.representation.cardinality.UpperCardinality; import java.util.stream.Stream; diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java index d32d49ba..4eb8898b 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java @@ -5,7 +5,7 @@ import org.eclipse.viatra.query.runtime.api.scope.IEngineContext; import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext; import tools.refinery.store.model.Model; -import tools.refinery.store.query.viatra.internal.viewupdate.ModelUpdateListener; +import tools.refinery.store.query.viatra.internal.update.ModelUpdateListener; public class RelationalEngineContext implements IEngineContext { private final IBaseIndex baseIndex = new DummyBaseIndexer(); diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java index eb3c6fbd..47b83634 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java @@ -39,7 +39,7 @@ public class RelationalQueryMetaContext extends AbstractQueryMetaContext { public void ensureValidKey(IInputKey key) { if (!(key instanceof RelationViewWrapper)) { - throw new IllegalArgumentException("The input key %s is not a valid input key.".formatted(key)); + throw new IllegalArgumentException("The input key %s is not a valid input key".formatted(key)); } } } diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java index e01525e0..7375b240 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java @@ -8,7 +8,7 @@ import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; import org.eclipse.viatra.query.runtime.matchers.util.Accuracy; import tools.refinery.store.model.Model; import tools.refinery.store.query.viatra.internal.pquery.RelationViewWrapper; -import tools.refinery.store.query.viatra.internal.viewupdate.ModelUpdateListener; +import tools.refinery.store.query.viatra.internal.update.ModelUpdateListener; import tools.refinery.store.query.view.AnyRelationView; import tools.refinery.store.query.view.RelationView; @@ -48,12 +48,12 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { @Override public boolean isCoalescing() { - return true; + return false; } @Override public boolean isIndexed(IInputKey key, IndexingService service) { - if (key instanceof RelationView relationalKey) { + if (key instanceof AnyRelationView relationalKey) { return this.modelUpdateListener.containsRelationView(relationalKey); } else { return false; @@ -63,7 +63,7 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { @Override public void ensureIndexed(IInputKey key, IndexingService service) { if (!isIndexed(key, service)) { - throw new IllegalStateException("Engine tries to index a new key " + key); + throw new IllegalStateException("Engine tries to index a new key %s".formatted(key)); } } @@ -73,7 +73,7 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { if (modelUpdateListener.containsRelationView(relationViewKey)) { return relationViewKey; } else { - throw new IllegalStateException("Query is asking for non-indexed key"); + throw new IllegalStateException("Query is asking for non-indexed key %s".formatted(relationViewKey)); } } else { throw new IllegalStateException("Query is asking for non-relational key"); @@ -131,7 +131,7 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { @Override public void addUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener) { - var relationViewKey = (RelationView) checkKey(key); + var relationViewKey = checkKey(key); this.modelUpdateListener.addListener(key, relationViewKey, seed, listener); } @@ -168,7 +168,7 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { } @Override - public void executeAfterTraversal(Runnable runnable) throws InvocationTargetException { + public void executeAfterTraversal(Runnable runnable) { runnable.run(); } } diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java index aa3fba6e..2b5618d2 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java @@ -1,5 +1,6 @@ package tools.refinery.store.query.viatra.internal.pquery; +import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Equality; @@ -20,20 +21,28 @@ import tools.refinery.store.query.atom.*; import tools.refinery.store.query.view.AnyRelationView; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; public class DNF2PQuery { private final Set translating = new LinkedHashSet<>(); - private final Map dnf2PQueryMap = new HashMap<>(); + private final Map dnf2PQueryMap = new HashMap<>(); - private final Map view2WrapperMap = new HashMap<>(); + private final Map view2WrapperMap = new LinkedHashMap<>(); - public SimplePQuery translate(DNF dnfQuery) { + private Function computeHint = dnf -> new QueryEvaluationHint(null, + QueryEvaluationHint.BackendRequirement.UNSPECIFIED); + + public void setComputeHint(Function computeHint) { + this.computeHint = computeHint; + } + + public RawPQuery translate(DNF dnfQuery) { if (translating.contains(dnfQuery)) { - var path = translating.stream().map(DNF::getName).collect(Collectors.joining(" -> ")); + var path = translating.stream().map(DNF::name).collect(Collectors.joining(" -> ")); throw new IllegalStateException("Circular reference %s -> %s detected".formatted(path, - dnfQuery.getName())); + dnfQuery.name())); } // We can't use computeIfAbsent here, because translating referenced queries calls this method in a reentrant // way, which would cause a ConcurrentModificationException with computeIfAbsent. @@ -50,8 +59,17 @@ public class DNF2PQuery { return pQuery; } - private SimplePQuery doTranslate(DNF dnfQuery) { - var pQuery = new SimplePQuery(dnfQuery.getUniqueName()); + public Collection getRelationViews() { + return Collections.unmodifiableCollection(view2WrapperMap.keySet()); + } + + public RawPQuery getAlreadyTranslated(DNF dnfQuery) { + return dnf2PQueryMap.get(dnfQuery); + } + + private RawPQuery doTranslate(DNF dnfQuery) { + var pQuery = new RawPQuery(dnfQuery.getUniqueName()); + pQuery.setEvaluationHints(computeHint.apply(dnfQuery)); Map parameters = new HashMap<>(); for (Variable variable : dnfQuery.getParameters()) { @@ -86,7 +104,7 @@ public class DNF2PQuery { translateEquivalenceAtom(equivalenceAtom, body); } else if (constraint instanceof RelationViewAtom relationViewAtom) { translateRelationViewAtom(relationViewAtom, body); - } else if (constraint instanceof CallAtom callAtom) { + } else if (constraint instanceof DNFCallAtom callAtom) { translateCallAtom(callAtom, body); } else if (constraint instanceof ConstantAtom constantAtom) { translateConstantAtom(constantAtom, body); diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java new file mode 100644 index 00000000..5d0b9e82 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java @@ -0,0 +1,71 @@ +package tools.refinery.store.query.viatra.internal.pquery; + +import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; +import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; +import org.eclipse.viatra.query.runtime.api.scope.QueryScope; +import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; +import org.eclipse.viatra.query.runtime.matchers.psystem.queries.BasePQuery; +import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; +import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; +import tools.refinery.store.query.viatra.internal.RelationalScope; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public class RawPQuery extends BasePQuery { + private final String fullyQualifiedName; + private List parameters; + private final LinkedHashSet bodies = new LinkedHashSet<>(); + + public RawPQuery(String name) { + super(PVisibility.PUBLIC); + fullyQualifiedName = name; + } + + @Override + public String getFullyQualifiedName() { + return fullyQualifiedName; + } + + public void setParameters(List parameters) { + this.parameters = parameters; + } + + @Override + public List getParameters() { + return parameters; + } + + public void addBody(PBody body) { + bodies.add(body); + } + + @Override + protected Set doGetContainedBodies() { + return bodies; + } + + public GenericQuerySpecification build() { + return new GenericQuerySpecification<>(this) { + @Override + public Class getPreferredScopeClass() { + return RelationalScope.class; + } + + @Override + protected RawPatternMatcher instantiate(ViatraQueryEngine engine) { + RawPatternMatcher matcher = engine.getExistingMatcher(this); + if (matcher == null) { + matcher = engine.getMatcher(this); + } + return matcher; + } + + @Override + public RawPatternMatcher instantiate() { + return new RawPatternMatcher(this); + } + }; + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java new file mode 100644 index 00000000..e944e873 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java @@ -0,0 +1,72 @@ +package tools.refinery.store.query.viatra.internal.pquery; + +import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher; +import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; +import tools.refinery.store.query.ResultSet; +import tools.refinery.store.query.viatra.ViatraTupleLike; +import tools.refinery.store.tuple.Tuple; +import tools.refinery.store.tuple.TupleLike; + +import java.util.Optional; +import java.util.stream.Stream; + +public class RawPatternMatcher extends GenericPatternMatcher implements ResultSet { + protected final Object[] empty; + + public RawPatternMatcher(GenericQuerySpecification specification) { + super(specification); + empty = new Object[specification.getParameterNames().size()]; + } + + @Override + public boolean hasResult() { + return backend.hasMatch(empty); + } + + @Override + public boolean hasResult(Tuple parameters) { + return backend.hasMatch(toParametersArray(parameters)); + } + + @Override + public Optional oneResult() { + return backend.getOneArbitraryMatch(empty).map(ViatraTupleLike::new); + } + + @Override + public Optional oneResult(Tuple parameters) { + return backend.getOneArbitraryMatch(toParametersArray(parameters)).map(ViatraTupleLike::new); + } + + @Override + public Stream allResults() { + return backend.getAllMatches(empty).map(ViatraTupleLike::new); + } + + @Override + public Stream allResults(Tuple parameters) { + return backend.getAllMatches(toParametersArray(parameters)).map(ViatraTupleLike::new); + } + + @Override + public int countResults() { + return backend.countMatches(empty); + } + + @Override + public int countResults(Tuple parameters) { + return backend.countMatches(toParametersArray(parameters)); + } + + private Object[] toParametersArray(Tuple tuple) { + int size = tuple.getSize(); + var array = new Object[tuple.getSize()]; + for (int i = 0; i < size; i++) { + var value = tuple.get(i); + if (value >= 0) { + array[i] = Tuple.of(value); + } + } + return array; + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java index e48648bf..c442add8 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java @@ -10,7 +10,7 @@ public class RelationViewWrapper extends BaseInputKeyWrapper { @Override public String getPrettyPrintableName() { - return wrappedKey.getName(); + return wrappedKey.name(); } @Override @@ -20,7 +20,7 @@ public class RelationViewWrapper extends BaseInputKeyWrapper { @Override public int getArity() { - return wrappedKey.getArity(); + return wrappedKey.arity(); } @Override diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SimplePQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SimplePQuery.java deleted file mode 100644 index a367cbf2..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SimplePQuery.java +++ /dev/null @@ -1,74 +0,0 @@ -package tools.refinery.store.query.viatra.internal.pquery; - -import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; -import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; -import org.eclipse.viatra.query.runtime.api.scope.QueryScope; -import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; -import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.BasePQuery; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; -import tools.refinery.store.query.viatra.internal.RawPatternMatcher; -import tools.refinery.store.query.viatra.internal.RelationalScope; - -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -public class SimplePQuery extends BasePQuery { - private final String fullyQualifiedName; - private List parameters; - private final LinkedHashSet bodies = new LinkedHashSet<>(); - - public SimplePQuery(String name) { - super(PVisibility.PUBLIC); - fullyQualifiedName = name; - setEvaluationHints(new QueryEvaluationHint(null, QueryEvaluationHint.BackendRequirement.UNSPECIFIED)); - } - - @Override - public String getFullyQualifiedName() { - return fullyQualifiedName; - } - - public void setParameters(List parameters) { - this.parameters = parameters; - } - - @Override - public List getParameters() { - return parameters; - } - - public void addBody(PBody body) { - bodies.add(body); - } - - @Override - protected Set doGetContainedBodies() { - return bodies; - } - - public GenericQuerySpecification build() { - return new GenericQuerySpecification<>(this) { - @Override - public Class getPreferredScopeClass() { - return RelationalScope.class; - } - - @Override - protected RawPatternMatcher instantiate(ViatraQueryEngine engine) { - RawPatternMatcher matcher = engine.getExistingMatcher(this); - if (matcher == null) { - matcher = engine.getMatcher(this); - } - return matcher; - } - - @Override - public RawPatternMatcher instantiate() { - return new RawPatternMatcher(this); - } - }; - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java new file mode 100644 index 00000000..1ae3daa7 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java @@ -0,0 +1,46 @@ +package tools.refinery.store.query.viatra.internal.update; + +import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; +import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; +import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; +import tools.refinery.store.model.Model; +import tools.refinery.store.query.view.AnyRelationView; +import tools.refinery.store.query.view.RelationView; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class ModelUpdateListener { + private final Map> relationViewUpdateListeners; + + public ModelUpdateListener(Model model, Collection relationViews) { + relationViewUpdateListeners = new HashMap<>(relationViews.size()); + for (var relationView : relationViews) { + registerView(model, (RelationView) relationView); + } + } + + private void registerView(Model model, RelationView relationView) { + var listener = RelationViewUpdateListener.of(relationView); + var interpretation = model.getInterpretation(relationView.getSymbol()); + interpretation.addListener(listener, true); + relationViewUpdateListeners.put(relationView, listener); + } + + public boolean containsRelationView(AnyRelationView relationView) { + return relationViewUpdateListeners.containsKey(relationView); + } + + public void addListener(IInputKey key, AnyRelationView relationView, ITuple seed, + IQueryRuntimeContextListener listener) { + var relationViewUpdateListener = relationViewUpdateListeners.get(relationView); + relationViewUpdateListener.addFilter(key, seed, listener); + } + + public void removeListener(IInputKey key, AnyRelationView relationView, ITuple seed, + IQueryRuntimeContextListener listener) { + var relationViewUpdateListener = relationViewUpdateListeners.get(relationView); + relationViewUpdateListener.removeFilter(key, seed, listener); + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java new file mode 100644 index 00000000..221f1b4a --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java @@ -0,0 +1,66 @@ +package tools.refinery.store.query.viatra.internal.update; + +import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; +import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; +import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; +import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; + +import java.util.Arrays; +import java.util.Objects; + +public final class RelationViewFilter { + private final IInputKey inputKey; + private final Object[] seed; + private final IQueryRuntimeContextListener listener; + + public RelationViewFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) { + this.inputKey = inputKey; + this.seed = seedToArray(seed); + this.listener = listener; + } + + public void update(Tuple updateTuple, boolean isInsertion) { + if (isMatching(updateTuple)) { + listener.update(inputKey, updateTuple, isInsertion); + } + } + + private boolean isMatching(ITuple tuple) { + if (seed == null) { + return true; + } + int size = seed.length; + for (int i = 0; i < size; i++) { + var filterElement = seed[i]; + if (filterElement != null && !filterElement.equals(tuple.get(i))) { + return false; + } + } + return true; + } + + // Use null instead of an empty array to speed up comparisons. + @SuppressWarnings("squid:S1168") + private static Object[] seedToArray(ITuple seed) { + for (var element : seed.getElements()) { + if (element != null) { + return seed.getElements(); + } + } + return null; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (RelationViewFilter) obj; + return Objects.equals(this.inputKey, that.inputKey) && Arrays.equals(this.seed, that.seed) && + Objects.equals(this.listener, that.listener); + } + + @Override + public int hashCode() { + return Objects.hash(inputKey, Arrays.hashCode(seed), listener); + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java new file mode 100644 index 00000000..e0d44e34 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java @@ -0,0 +1,40 @@ +package tools.refinery.store.query.viatra.internal.update; + +import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; +import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; +import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; +import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; +import tools.refinery.store.model.InterpretationListener; +import tools.refinery.store.query.view.RelationView; +import tools.refinery.store.query.view.TuplePreservingRelationView; + +import java.util.ArrayList; +import java.util.List; + +public abstract class RelationViewUpdateListener implements InterpretationListener { + private final List filters = new ArrayList<>(); + + public void addFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) { + filters.add(new RelationViewFilter(inputKey, seed, listener)); + } + + public void removeFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) { + filters.remove(new RelationViewFilter(inputKey, seed, listener)); + } + + protected void processUpdate(Tuple tuple, boolean isInsertion) { + int size = filters.size(); + // Use a for loop instead of a for-each loop to avoid Iterator allocation overhead. + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < size; i++) { + filters.get(i).update(tuple, isInsertion); + } + } + + public static RelationViewUpdateListener of(RelationView relationView) { + if (relationView instanceof TuplePreservingRelationView tuplePreservingRelationView) { + return new TuplePreservingRelationViewUpdateListener<>(tuplePreservingRelationView); + } + return new TupleChangingRelationViewUpdateListener<>(relationView); + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java new file mode 100644 index 00000000..c17e826d --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java @@ -0,0 +1,35 @@ +package tools.refinery.store.query.viatra.internal.update; + +import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; +import tools.refinery.store.query.view.RelationView; +import tools.refinery.store.tuple.Tuple; + +import java.util.Arrays; + +public class TupleChangingRelationViewUpdateListener extends RelationViewUpdateListener { + private final RelationView relationView; + + TupleChangingRelationViewUpdateListener(RelationView relationView) { + this.relationView = relationView; + } + + @Override + public void put(Tuple key, T fromValue, T toValue, boolean restoring) { + boolean fromPresent = relationView.filter(key, fromValue); + boolean toPresent = relationView.filter(key, toValue); + if (fromPresent) { + if (toPresent) { // value change + var fromArray = relationView.forwardMap(key, fromValue); + var toArray = relationView.forwardMap(key, toValue); + if (!Arrays.equals(fromArray, toArray)) { + processUpdate(Tuples.flatTupleOf(fromArray), false); + processUpdate(Tuples.flatTupleOf(toArray), true); + } + } else { // fromValue disappears + processUpdate(Tuples.flatTupleOf(relationView.forwardMap(key, fromValue)), false); + } + } else if (toPresent) { // toValue disappears + processUpdate(Tuples.flatTupleOf(relationView.forwardMap(key, toValue)), true); + } + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java new file mode 100644 index 00000000..9c3ef61c --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java @@ -0,0 +1,24 @@ +package tools.refinery.store.query.viatra.internal.update; + +import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; +import tools.refinery.store.query.view.TuplePreservingRelationView; +import tools.refinery.store.tuple.Tuple; + +public class TuplePreservingRelationViewUpdateListener extends RelationViewUpdateListener { + private final TuplePreservingRelationView view; + + TuplePreservingRelationViewUpdateListener(TuplePreservingRelationView view) { + this.view = view; + } + + @Override + public void put(Tuple key, T fromValue, T toValue, boolean restoring) { + boolean fromPresent = view.filter(key, fromValue); + boolean toPresent = view.filter(key, toValue); + if (fromPresent == toPresent) { + return; + } + var translated = Tuples.flatTupleOf(view.forwardMap(key)); + processUpdate(translated, toPresent); + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ModelUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ModelUpdateListener.java deleted file mode 100644 index 6a1d06a9..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ModelUpdateListener.java +++ /dev/null @@ -1,112 +0,0 @@ -package tools.refinery.store.query.viatra.internal.viewupdate; - -import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; -import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; -import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; -import tools.refinery.store.model.representation.AnyRelation; -import tools.refinery.store.query.view.AnyRelationView; -import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.model.representation.Relation; -import tools.refinery.store.query.view.RelationView; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public class ModelUpdateListener { - /** - * Collections of Relations and their Views. - */ - private final Map> relation2View; - - /** - * Collection of Views and their buffers. - */ - private final Map>> view2Buffers; - - public ModelUpdateListener(Set relationViews) { - this.relation2View = new HashMap<>(); - this.view2Buffers = new HashMap<>(); - - for (var relationView : relationViews) { - registerView(relationView); - } - } - - private void registerView(AnyRelationView view) { - AnyRelation relation = view.getRepresentation(); - - // 1. register views to relations, if necessary - var views = relation2View.computeIfAbsent(relation, x -> new HashSet<>()); - views.add(view); - - // 2. register notifier map to views, if necessary - view2Buffers.computeIfAbsent(view, x -> new HashSet<>()); - } - - public boolean containsRelationView(AnyRelationView relationalKey) { - return view2Buffers.containsKey(relationalKey); - } - - public void addListener(IInputKey key, RelationView relationView, ITuple seed, - IQueryRuntimeContextListener listener) { - if (view2Buffers.containsKey(relationView)) { - ViewUpdateTranslator updateListener = new ViewUpdateTranslator<>(key, relationView, seed, listener); - ViewUpdateBuffer updateBuffer = new ViewUpdateBuffer<>(updateListener); - view2Buffers.get(relationView).add(updateBuffer); - } else { - throw new IllegalArgumentException(); - } - } - - public void removeListener(IInputKey key, AnyRelationView relationView, ITuple seed, - IQueryRuntimeContextListener listener) { - if (view2Buffers.containsKey(relationView)) { - Set> buffers = this.view2Buffers.get(relationView); - for (var buffer : buffers) { - if (buffer.getUpdateListener().equals(key, relationView, seed, listener)) { - // remove buffer and terminate immediately, or it will break iterator. - buffers.remove(buffer); - return; - } - } - } else { - throw new IllegalArgumentException("Relation view is not registered for updates"); - } - } - - public void addUpdate(Relation relation, Tuple key, D oldValue, D newValue) { - var views = this.relation2View.get(relation); - if (views == null) { - return; - } - for (var view : views) { - var buffers = this.view2Buffers.get(view); - for (var buffer : buffers) { - @SuppressWarnings("unchecked") - var typedBuffer = (ViewUpdateBuffer) buffer; - typedBuffer.addChange(key, oldValue, newValue); - } - } - } - - public boolean hasChanges() { - for (var bufferCollection : this.view2Buffers.values()) { - for (ViewUpdateBuffer buffer : bufferCollection) { - if (buffer.hasChanges()) { - return true; - } - } - } - return false; - } - - public void flush() { - for (var bufferCollection : this.view2Buffers.values()) { - for (ViewUpdateBuffer buffer : bufferCollection) { - buffer.flush(); - } - } - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdate.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdate.java deleted file mode 100644 index b9406018..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdate.java +++ /dev/null @@ -1,32 +0,0 @@ -package tools.refinery.store.query.viatra.internal.viewupdate; - -import java.util.Arrays; -import java.util.Objects; - -record ViewUpdate(Object[] tuple, boolean isInsertion) { - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.deepHashCode(tuple); - result = prime * result + Objects.hash(isInsertion); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - ViewUpdate other = (ViewUpdate) obj; - return isInsertion == other.isInsertion && Arrays.deepEquals(tuple, other.tuple); - } - - @Override - public String toString() { - return "ViewUpdate [" + Arrays.toString(tuple) + "insertion= " + this.isInsertion + "]"; - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateBuffer.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateBuffer.java deleted file mode 100644 index 49f4c501..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateBuffer.java +++ /dev/null @@ -1,47 +0,0 @@ -package tools.refinery.store.query.viatra.internal.viewupdate; - -import tools.refinery.store.tuple.Tuple; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class ViewUpdateBuffer { - protected final ViewUpdateTranslator updateListener; - - protected final List buffer = new ArrayList<>(); - - public ViewUpdateBuffer(ViewUpdateTranslator updateListener) { - this.updateListener = updateListener; - } - - public ViewUpdateTranslator getUpdateListener() { - return updateListener; - } - - public boolean hasChanges() { - return !buffer.isEmpty(); - } - - public void addChange(Tuple tuple, D oldValue, D newValue) { - if (oldValue != newValue) { - Object[] oldTuple = updateListener.isMatching(tuple, oldValue); - Object[] newTuple = updateListener.isMatching(tuple, newValue); - if (!Arrays.equals(oldTuple, newTuple)) { - if (oldTuple != null) { - buffer.add(new ViewUpdate(oldTuple, false)); - } - if (newTuple != null) { - buffer.add(new ViewUpdate(newTuple, true)); - } - } - } - } - - public void flush() { - for (ViewUpdate viewChange : buffer) { - updateListener.processChange(viewChange); - } - buffer.clear(); - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateTranslator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateTranslator.java deleted file mode 100644 index c324c84a..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateTranslator.java +++ /dev/null @@ -1,73 +0,0 @@ -package tools.refinery.store.query.viatra.internal.viewupdate; - -import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; -import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; -import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; -import tools.refinery.store.query.view.AnyRelationView; -import tools.refinery.store.query.view.RelationView; -import tools.refinery.store.tuple.Tuple; - -import java.util.Objects; - -public class ViewUpdateTranslator { - private final IInputKey wrappedKey; - - private final RelationView key; - - private final ITuple filter; - - private final IQueryRuntimeContextListener listener; - - public ViewUpdateTranslator(IInputKey wrappedKey, RelationView key, ITuple filter, - IQueryRuntimeContextListener listener) { - super(); - this.wrappedKey = wrappedKey; - this.key = key; - this.filter = filter; - this.listener = listener; - } - - public boolean equals(IInputKey wrappedKey, AnyRelationView relationView, ITuple seed, - IQueryRuntimeContextListener listener) { - return this.wrappedKey == wrappedKey && key == relationView && filter.equals(seed) && this.listener == listener; - } - - public void processChange(ViewUpdate change) { - listener.update(wrappedKey, Tuples.flatTupleOf(change.tuple()), change.isInsertion()); - } - - @SuppressWarnings("squid:S1168") - public Object[] isMatching(Tuple tuple, D value) { - if (!key.filter(tuple, value)) { - return null; - } - return isMatching(key.forwardMap(tuple, value), filter); - } - - @SuppressWarnings("squid:S1168") - private Object[] isMatching(Object[] tuple, ITuple filter) { - for (int i = 0; i < filter.getSize(); i++) { - final Object filterObject = filter.get(i); - if (filterObject != null && !filterObject.equals(tuple[i])) { - return null; - } - } - return tuple; - } - - @Override - public int hashCode() { - return Objects.hash(filter, key, listener); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (!(obj instanceof ViewUpdateTranslator other)) - return false; - return Objects.equals(filter, other.filter) && Objects.equals(key, other.key) - && Objects.equals(listener, other.listener); - } -} diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java index e82e006c..72e8d7e5 100644 --- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java @@ -1,17 +1,20 @@ package tools.refinery.store.query.viatra; import org.junit.jupiter.api.Test; -import tools.refinery.store.model.representation.Relation; -import tools.refinery.store.model.representation.TruthValue; -import tools.refinery.store.query.*; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.query.DNF; +import tools.refinery.store.query.ModelQuery; +import tools.refinery.store.query.Variable; import tools.refinery.store.query.atom.*; import tools.refinery.store.query.view.FilteredRelationView; import tools.refinery.store.query.view.KeyOnlyRelationView; -import tools.refinery.store.query.view.RelationView; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.representation.TruthValue; import tools.refinery.store.tuple.Tuple; import tools.refinery.store.tuple.TupleLike; -import java.util.*; +import java.util.HashSet; +import java.util.Set; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -19,41 +22,49 @@ import static org.junit.jupiter.api.Assertions.assertEquals; class QueryTest { @Test void typeConstraintTest() { - Relation person = new Relation<>("Person", 1, Boolean.class, false); - Relation asset = new Relation<>("Asset", 1, Boolean.class, false); - RelationView personView = new KeyOnlyRelationView(person); + var person = new Symbol<>("Person", 1, Boolean.class, false); + var asset = new Symbol<>("Asset", 1, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); var p1 = new Variable("p1"); - DNF predicate = DNF.builder("TypeConstraint") + var predicate = DNF.builder("TypeConstraint") .parameters(p1) .clause(new RelationViewAtom(personView, p1)) .build(); - QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, asset), Set.of(personView), - Set.of(predicate)); - QueryableModel model = store.createModel(); + var store = ModelStore.builder() + .symbols(person, asset) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createModel(); + var personInterpretation = model.getInterpretation(person); + var assetInterpretation = model.getInterpretation(asset); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var predicateResultSet = queryEngine.getResultSet(predicate); - model.put(person, Tuple.of(0), true); - model.put(person, Tuple.of(1), true); - model.put(asset, Tuple.of(1), true); - model.put(asset, Tuple.of(2), true); + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); - model.flushChanges(); - assertEquals(2, model.countResults(predicate)); - compareMatchSets(model.allResults(predicate), Set.of(Tuple.of(0), Tuple.of(1))); + assetInterpretation.put(Tuple.of(1), true); + assetInterpretation.put(Tuple.of(2), true); + + queryEngine.flushChanges(); + assertEquals(2, predicateResultSet.countResults()); + compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0), Tuple.of(1))); } @Test void relationConstraintTest() { - Relation person = new Relation<>("Person", 1, Boolean.class, false); - Relation friend = new Relation<>("friend", 2, TruthValue.class, TruthValue.FALSE); - RelationView personView = new KeyOnlyRelationView(person); - RelationView friendMustView = new FilteredRelationView<>(friend, "must", - TruthValue::must); - - Variable p1 = new Variable("p1"); - Variable p2 = new Variable("p2"); - DNF predicate = DNF.builder("RelationConstraint") + var person = new Symbol<>("Person", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); + + var p1 = new Variable("p1"); + var p2 = new Variable("p2"); + var predicate = DNF.builder("RelationConstraint") .parameters(p1, p2) .clause( new RelationViewAtom(personView, p1), @@ -62,37 +73,45 @@ class QueryTest { ) .build(); - QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, friend), - Set.of(personView, friendMustView), Set.of(predicate)); - QueryableModel model = store.createModel(); + var store = ModelStore.builder() + .symbols(person, friend) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var predicateResultSet = queryEngine.getResultSet(predicate); - assertEquals(0, model.countResults(predicate)); + assertEquals(0, predicateResultSet.countResults()); - model.put(person, Tuple.of(0), true); - model.put(person, Tuple.of(1), true); - model.put(person, Tuple.of(2), true); - model.put(friend, Tuple.of(0, 1), TruthValue.TRUE); - model.put(friend, Tuple.of(1, 0), TruthValue.TRUE); - model.put(friend, Tuple.of(1, 2), TruthValue.TRUE); + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); - assertEquals(0, model.countResults(predicate)); + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); - model.flushChanges(); - assertEquals(3, model.countResults(predicate)); - compareMatchSets(model.allResults(predicate), Set.of(Tuple.of(0, 1), Tuple.of(1, 0), Tuple.of(1, 2))); + assertEquals(0, predicateResultSet.countResults()); + + queryEngine.flushChanges(); + assertEquals(3, predicateResultSet.countResults()); + compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0), Tuple.of(1, 2))); } @Test void andTest() { - Relation person = new Relation<>("Person", 1, Boolean.class, false); - Relation friend = new Relation<>("friend", 2, TruthValue.class, TruthValue.FALSE); - RelationView personView = new KeyOnlyRelationView(person); - RelationView friendMustView = new FilteredRelationView<>(friend, "must", - TruthValue::must); - - Variable p1 = new Variable("p1"); - Variable p2 = new Variable("p2"); - DNF predicate = DNF.builder("RelationConstraint") + var person = new Symbol<>("Person", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); + + var p1 = new Variable("p1"); + var p2 = new Variable("p2"); + var predicate = DNF.builder("RelationConstraint") .parameters(p1, p2) .clause( new RelationViewAtom(personView, p1), @@ -102,45 +121,52 @@ class QueryTest { ) .build(); - QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, friend), - Set.of(personView, friendMustView), Set.of(predicate)); - QueryableModel model = store.createModel(); + var store = ModelStore.builder() + .symbols(person, friend) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var predicateResultSet = queryEngine.getResultSet(predicate); - assertEquals(0, model.countResults(predicate)); + assertEquals(0, predicateResultSet.countResults()); - model.put(person, Tuple.of(0), true); - model.put(person, Tuple.of(1), true); - model.put(person, Tuple.of(2), true); + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); - model.put(friend, Tuple.of(0, 1), TruthValue.TRUE); - model.put(friend, Tuple.of(0, 2), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); - model.flushChanges(); - assertEquals(0, model.countResults(predicate)); + queryEngine.flushChanges(); + assertEquals(0, predicateResultSet.countResults()); - model.put(friend, Tuple.of(1, 0), TruthValue.TRUE); - model.flushChanges(); - assertEquals(2, model.countResults(predicate)); - compareMatchSets(model.allResults(predicate), Set.of(Tuple.of(0, 1), Tuple.of(1, 0))); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + queryEngine.flushChanges(); + assertEquals(2, predicateResultSet.countResults()); + compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0))); - model.put(friend, Tuple.of(2, 0), TruthValue.TRUE); - model.flushChanges(); - assertEquals(4, model.countResults(predicate)); - compareMatchSets(model.allResults(predicate), Set.of(Tuple.of(0, 1), Tuple.of(1, 0), Tuple.of(0, 2), + friendInterpretation.put(Tuple.of(2, 0), TruthValue.TRUE); + queryEngine.flushChanges(); + assertEquals(4, predicateResultSet.countResults()); + compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0), Tuple.of(0, 2), Tuple.of(2, 0))); } @Test void existTest() { - Relation person = new Relation<>("Person", 1, Boolean.class, false); - Relation friend = new Relation<>("friend", 2, TruthValue.class, TruthValue.FALSE); - RelationView personView = new KeyOnlyRelationView(person); - RelationView friendMustView = new FilteredRelationView<>(friend, "must", - TruthValue::must); - - Variable p1 = new Variable("p1"); - Variable p2 = new Variable("p2"); - DNF predicate = DNF.builder("RelationConstraint") + var person = new Symbol<>("Person", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); + + var p1 = new Variable("p1"); + var p2 = new Variable("p2"); + var predicate = DNF.builder("RelationConstraint") .parameters(p1) .clause( new RelationViewAtom(personView, p1), @@ -149,39 +175,45 @@ class QueryTest { ) .build(); - QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, friend), - Set.of(personView, friendMustView), Set.of(predicate)); - QueryableModel model = store.createModel(); + var store = ModelStore.builder() + .symbols(person, friend) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var predicateResultSet = queryEngine.getResultSet(predicate); - assertEquals(0, model.countResults(predicate)); + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); - model.put(person, Tuple.of(0), true); - model.put(person, Tuple.of(1), true); - model.put(person, Tuple.of(2), true); - model.put(friend, Tuple.of(0, 1), TruthValue.TRUE); - model.put(friend, Tuple.of(1, 0), TruthValue.TRUE); - model.put(friend, Tuple.of(1, 2), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); - assertEquals(0, model.countResults(predicate)); + assertEquals(0, predicateResultSet.countResults()); - model.flushChanges(); - assertEquals(2, model.countResults(predicate)); - compareMatchSets(model.allResults(predicate), Set.of(Tuple.of(0), Tuple.of(1))); + queryEngine.flushChanges(); + assertEquals(2, predicateResultSet.countResults()); + compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0), Tuple.of(1))); } @Test void orTest() { - Relation person = new Relation<>("Person", 1, Boolean.class, false); - Relation animal = new Relation<>("Animal", 1, Boolean.class, false); - Relation friend = new Relation<>("friend", 2, TruthValue.class, TruthValue.FALSE); - RelationView personView = new KeyOnlyRelationView(person); - RelationView animalView = new KeyOnlyRelationView(animal); - RelationView friendMustView = new FilteredRelationView<>(friend, "must", - TruthValue::must); - - Variable p1 = new Variable("p1"); - Variable p2 = new Variable("p2"); - DNF predicate = DNF.builder("Or") + var person = new Symbol<>("Person", 1, Boolean.class, false); + var animal = new Symbol<>("Animal", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var animalView = new KeyOnlyRelationView<>(animal); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); + + var p1 = new Variable("p1"); + var p2 = new Variable("p2"); + var predicate = DNF.builder("Or") .parameters(p1, p2) .clause( new RelationViewAtom(personView, p1), @@ -195,32 +227,43 @@ class QueryTest { ) .build(); - QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, animal, friend), - Set.of(personView, animalView, friendMustView), Set.of(predicate)); - QueryableModel model = store.createModel(); - - model.put(person, Tuple.of(0), true); - model.put(person, Tuple.of(1), true); - model.put(animal, Tuple.of(2), true); - model.put(animal, Tuple.of(3), true); - model.put(friend, Tuple.of(0, 1), TruthValue.TRUE); - model.put(friend, Tuple.of(0, 2), TruthValue.TRUE); - model.put(friend, Tuple.of(2, 3), TruthValue.TRUE); - model.put(friend, Tuple.of(3, 0), TruthValue.TRUE); - - model.flushChanges(); - assertEquals(2, model.countResults(predicate)); - compareMatchSets(model.allResults(predicate), Set.of(Tuple.of(0, 1), Tuple.of(2, 3))); + var store = ModelStore.builder() + .symbols(person, animal, friend) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createModel(); + var personInterpretation = model.getInterpretation(person); + var animalInterpretation = model.getInterpretation(animal); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + animalInterpretation.put(Tuple.of(2), true); + animalInterpretation.put(Tuple.of(3), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(2, 3), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(3, 0), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertEquals(2, predicateResultSet.countResults()); + compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(2, 3))); } @Test void equalityTest() { - Relation person = new Relation<>("Person", 1, Boolean.class, false); - RelationView personView = new KeyOnlyRelationView(person); + var person = new Symbol<>("Person", 1, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); - Variable p1 = new Variable("p1"); - Variable p2 = new Variable("p2"); - DNF predicate = DNF.builder("Equality") + var p1 = new Variable("p1"); + var p2 = new Variable("p2"); + var predicate = DNF.builder("Equality") .parameters(p1, p2) .clause( new RelationViewAtom(personView, p1), @@ -229,30 +272,37 @@ class QueryTest { ) .build(); - QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person), Set.of(personView), Set.of(predicate)); - QueryableModel model = store.createModel(); + var store = ModelStore.builder() + .symbols(person) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createModel(); + var personInterpretation = model.getInterpretation(person); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var predicateResultSet = queryEngine.getResultSet(predicate); - model.put(person, Tuple.of(0), true); - model.put(person, Tuple.of(1), true); - model.put(person, Tuple.of(2), true); + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); - model.flushChanges(); - assertEquals(3, model.countResults(predicate)); - compareMatchSets(model.allResults(predicate), Set.of(Tuple.of(0, 0), Tuple.of(1, 1), Tuple.of(2, 2))); + queryEngine.flushChanges(); + assertEquals(3, predicateResultSet.countResults()); + compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 0), Tuple.of(1, 1), Tuple.of(2, 2))); } @Test void inequalityTest() { - Relation person = new Relation<>("Person", 1, Boolean.class, false); - Relation friend = new Relation<>("friend", 2, TruthValue.class, TruthValue.FALSE); - RelationView personView = new KeyOnlyRelationView(person); - RelationView friendMustView = new FilteredRelationView<>(friend, "must", - TruthValue::must); - - Variable p1 = new Variable("p1"); - Variable p2 = new Variable("p2"); - Variable p3 = new Variable("p3"); - DNF predicate = DNF.builder("Inequality") + var person = new Symbol<>("Person", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); + + var p1 = new Variable("p1"); + var p2 = new Variable("p2"); + var p3 = new Variable("p3"); + var predicate = DNF.builder("Inequality") .parameters(p1, p2, p3) .clause( new RelationViewAtom(personView, p1), @@ -263,32 +313,40 @@ class QueryTest { ) .build(); - QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, friend), - Set.of(personView, friendMustView), Set.of(predicate)); - QueryableModel model = store.createModel(); + var store = ModelStore.builder() + .symbols(person, friend) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); - model.put(person, Tuple.of(0), true); - model.put(person, Tuple.of(1), true); - model.put(person, Tuple.of(2), true); - model.put(friend, Tuple.of(0, 2), TruthValue.TRUE); - model.put(friend, Tuple.of(1, 2), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); - model.flushChanges(); - assertEquals(2, model.countResults(predicate)); - compareMatchSets(model.allResults(predicate), Set.of(Tuple.of(0, 1, 2), Tuple.of(1, 0, 2))); + queryEngine.flushChanges(); + assertEquals(2, predicateResultSet.countResults()); + compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1, 2), Tuple.of(1, 0, 2))); } @Test void patternCallTest() { - Relation person = new Relation<>("Person", 1, Boolean.class, false); - Relation friend = new Relation<>("friend", 2, TruthValue.class, TruthValue.FALSE); - RelationView personView = new KeyOnlyRelationView(person); - RelationView friendMustView = new FilteredRelationView<>(friend, "must", - TruthValue::must); - - Variable p1 = new Variable("p1"); - Variable p2 = new Variable("p2"); - DNF friendPredicate = DNF.builder("RelationConstraint") + var person = new Symbol<>("Person", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); + + var p1 = new Variable("p1"); + var p2 = new Variable("p2"); + var friendPredicate = DNF.builder("RelationConstraint") .parameters(p1, p2) .clause( new RelationViewAtom(personView, p1), @@ -297,44 +355,51 @@ class QueryTest { ) .build(); - Variable p3 = new Variable("p3"); - Variable p4 = new Variable("p4"); - DNF predicate = DNF.builder("PositivePatternCall") + var p3 = new Variable("p3"); + var p4 = new Variable("p4"); + var predicate = DNF.builder("PositivePatternCall") .parameters(p3, p4) .clause( new RelationViewAtom(personView, p3), new RelationViewAtom(personView, p4), - new CallAtom<>(friendPredicate, p3, p4) + new DNFCallAtom(friendPredicate, p3, p4) ) .build(); - QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, friend), - Set.of(personView, friendMustView), Set.of(friendPredicate, predicate)); - QueryableModel model = store.createModel(); + var store = ModelStore.builder() + .symbols(person, friend) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var predicateResultSet = queryEngine.getResultSet(predicate); - model.put(person, Tuple.of(0), true); - model.put(person, Tuple.of(1), true); - model.put(person, Tuple.of(2), true); - model.put(friend, Tuple.of(0, 1), TruthValue.TRUE); - model.put(friend, Tuple.of(1, 0), TruthValue.TRUE); - model.put(friend, Tuple.of(1, 2), TruthValue.TRUE); + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); - model.flushChanges(); + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); - assertEquals(3, model.countResults(friendPredicate)); + queryEngine.flushChanges(); + assertEquals(3, predicateResultSet.countResults()); } @Test void negativePatternCallTest() { - Relation person = new Relation<>("Person", 1, Boolean.class, false); - Relation friend = new Relation<>("friend", 2, TruthValue.class, TruthValue.FALSE); - RelationView personView = new KeyOnlyRelationView(person); - RelationView friendMustView = new FilteredRelationView<>(friend, "must", - TruthValue::must); - - Variable p1 = new Variable("p1"); - Variable p2 = new Variable("p2"); - DNF friendPredicate = DNF.builder("RelationConstraint") + var person = new Symbol<>("Person", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); + + var p1 = new Variable("p1"); + var p2 = new Variable("p2"); + var friendPredicate = DNF.builder("RelationConstraint") .parameters(p1, p2) .clause( new RelationViewAtom(personView, p1), @@ -343,44 +408,52 @@ class QueryTest { ) .build(); - Variable p3 = new Variable("p3"); - Variable p4 = new Variable("p4"); - DNF predicate = DNF.builder("NegativePatternCall") + var p3 = new Variable("p3"); + var p4 = new Variable("p4"); + var predicate = DNF.builder("NegativePatternCall") .parameters(p3, p4) .clause( new RelationViewAtom(personView, p3), new RelationViewAtom(personView, p4), - new CallAtom<>(false, friendPredicate, p3, p4) + new DNFCallAtom(false, friendPredicate, p3, p4) ) .build(); - QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, friend), - Set.of(personView, friendMustView), Set.of(friendPredicate, predicate)); - QueryableModel model = store.createModel(); + var store = ModelStore.builder() + .symbols(person, friend) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); - model.put(person, Tuple.of(0), true); - model.put(person, Tuple.of(1), true); - model.put(person, Tuple.of(2), true); - model.put(friend, Tuple.of(0, 1), TruthValue.TRUE); - model.put(friend, Tuple.of(1, 0), TruthValue.TRUE); - model.put(friend, Tuple.of(1, 2), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); - model.flushChanges(); - assertEquals(6, model.countResults(predicate)); + queryEngine.flushChanges(); + assertEquals(6, predicateResultSet.countResults()); } @Test void negativeWithQuantificationTest() { - Relation person = new Relation<>("Person", 1, Boolean.class, false); - Relation friend = new Relation<>("friend", 2, TruthValue.class, TruthValue.FALSE); - RelationView personView = new KeyOnlyRelationView(person); - RelationView friendMustView = new FilteredRelationView<>(friend, "must", - TruthValue::must); + var person = new Symbol<>("Person", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - Variable p1 = new Variable("p1"); - Variable p2 = new Variable("p2"); + var p1 = new Variable("p1"); + var p2 = new Variable("p2"); - DNF called = DNF.builder("Called") + var called = DNF.builder("Called") .parameters(p1, p2) .clause( new RelationViewAtom(personView, p1), @@ -389,39 +462,47 @@ class QueryTest { ) .build(); - DNF predicate = DNF.builder("Count") + var predicate = DNF.builder("Count") .parameters(p1) .clause( new RelationViewAtom(personView, p1), - new CallAtom<>(false, called, p1, p2) + new DNFCallAtom(false, called, p1, p2) ) .build(); - QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, friend), - Set.of(personView, friendMustView), Set.of(called, predicate)); - QueryableModel model = store.createModel(); + var store = ModelStore.builder() + .symbols(person, friend) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var predicateResultSet = queryEngine.getResultSet(predicate); - model.put(person, Tuple.of(0), true); - model.put(person, Tuple.of(1), true); - model.put(person, Tuple.of(2), true); - model.put(friend, Tuple.of(0, 1), TruthValue.TRUE); - model.put(friend, Tuple.of(0, 2), TruthValue.TRUE); + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); - model.flushChanges(); - assertEquals(2, model.countResults(predicate)); + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertEquals(2, predicateResultSet.countResults()); } @Test void transitivePatternCallTest() { - Relation person = new Relation<>("Person", 1, Boolean.class, false); - Relation friend = new Relation<>("friend", 2, TruthValue.class, TruthValue.FALSE); - RelationView personView = new KeyOnlyRelationView(person); - RelationView friendMustView = new FilteredRelationView<>(friend, "must", - TruthValue::must); - - Variable p1 = new Variable("p1"); - Variable p2 = new Variable("p2"); - DNF friendPredicate = DNF.builder("RelationConstraint") + var person = new Symbol<>("Person", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); + + var p1 = new Variable("p1"); + var p2 = new Variable("p2"); + var friendPredicate = DNF.builder("RelationConstraint") .parameters(p1, p2) .clause( new RelationViewAtom(personView, p1), @@ -430,29 +511,38 @@ class QueryTest { ) .build(); - Variable p3 = new Variable("p3"); - Variable p4 = new Variable("p4"); - DNF predicate = DNF.builder("TransitivePatternCall") + var p3 = new Variable("p3"); + var p4 = new Variable("p4"); + var predicate = DNF.builder("TransitivePatternCall") .parameters(p3, p4) .clause( new RelationViewAtom(personView, p3), new RelationViewAtom(personView, p4), - new CallAtom<>(CallPolarity.TRANSITIVE, friendPredicate, p3, p4) + new DNFCallAtom(CallPolarity.TRANSITIVE, friendPredicate, p3, p4) ) .build(); - QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, friend), - Set.of(personView, friendMustView), Set.of(friendPredicate, predicate)); - QueryableModel model = store.createModel(); + var store = ModelStore.builder() + .symbols(person, friend) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); - model.put(person, Tuple.of(0), true); - model.put(person, Tuple.of(1), true); - model.put(person, Tuple.of(2), true); - model.put(friend, Tuple.of(0, 1), TruthValue.TRUE); - model.put(friend, Tuple.of(1, 2), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); - model.flushChanges(); - assertEquals(3, model.countResults(predicate)); + queryEngine.flushChanges(); + assertEquals(3, predicateResultSet.countResults()); } static void compareMatchSets(Stream matchSet, Set expected) { diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java index e8fa6ed1..49087a8d 100644 --- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java @@ -1,54 +1,59 @@ package tools.refinery.store.query.viatra; import org.junit.jupiter.api.Test; -import tools.refinery.store.model.representation.Relation; +import tools.refinery.store.model.ModelStore; import tools.refinery.store.query.DNF; -import tools.refinery.store.query.QueryableModel; -import tools.refinery.store.query.QueryableModelStore; +import tools.refinery.store.query.ModelQuery; import tools.refinery.store.query.Variable; import tools.refinery.store.query.atom.RelationViewAtom; -import tools.refinery.store.query.viatra.ViatraQueryableModelStore; import tools.refinery.store.query.view.KeyOnlyRelationView; -import tools.refinery.store.query.view.RelationView; +import tools.refinery.store.representation.Symbol; import tools.refinery.store.tuple.Tuple; -import java.util.Set; - import static org.junit.jupiter.api.Assertions.assertEquals; class QueryTransactionTest { @Test void flushTest() { - Relation person = new Relation<>("Person", 1, Boolean.class, false); - Relation asset = new Relation<>("Asset", 1, Boolean.class, false); - RelationView personView = new KeyOnlyRelationView(person); + var person = new Symbol<>("Person", 1, Boolean.class, false); + var asset = new Symbol<>("Asset", 1, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); var p1 = new Variable("p1"); - DNF predicate = DNF.builder("TypeConstraint") + var predicate = DNF.builder("TypeConstraint") .parameters(p1) .clause(new RelationViewAtom(personView, p1)) .build(); - QueryableModelStore store = new ViatraQueryableModelStore(Set.of(person, asset), Set.of(personView), - Set.of(predicate)); - QueryableModel model = store.createModel(); + var store = ModelStore.builder() + .symbols(person, asset) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createModel(); + var personInterpretation = model.getInterpretation(person); + var assetInterpretation = model.getInterpretation(asset); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var predicateResultSet = queryEngine.getResultSet(predicate); + + assertEquals(0, predicateResultSet.countResults()); - assertEquals(0, model.countResults(predicate)); + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); - model.put(person, Tuple.of(0), true); - model.put(person, Tuple.of(1), true); - model.put(asset, Tuple.of(1), true); - model.put(asset, Tuple.of(2), true); + assetInterpretation.put(Tuple.of(1), true); + assetInterpretation.put(Tuple.of(2), true); - assertEquals(0, model.countResults(predicate)); + assertEquals(0, predicateResultSet.countResults()); - model.flushChanges(); - assertEquals(2, model.countResults(predicate)); + queryEngine.flushChanges(); + assertEquals(2, predicateResultSet.countResults()); - model.put(person, Tuple.of(4), true); - assertEquals(2, model.countResults(predicate)); + personInterpretation.put(Tuple.of(4), true); + assertEquals(2, predicateResultSet.countResults()); - model.flushChanges(); - assertEquals(3, model.countResults(predicate)); + queryEngine.flushChanges(); + assertEquals(3, predicateResultSet.countResults()); } } diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorStreamTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorStreamTest.java index 07869050..69491fda 100644 --- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorStreamTest.java +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorStreamTest.java @@ -3,8 +3,8 @@ package tools.refinery.store.query.viatra.internal.cardinality; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import tools.refinery.store.model.representation.cardinality.UpperCardinalities; -import tools.refinery.store.model.representation.cardinality.UpperCardinality; +import tools.refinery.store.representation.cardinality.UpperCardinalities; +import tools.refinery.store.representation.cardinality.UpperCardinality; import java.util.stream.Stream; diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorTest.java index afc4a2f3..20dad543 100644 --- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorTest.java +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorTest.java @@ -2,8 +2,8 @@ package tools.refinery.store.query.viatra.internal.cardinality; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import tools.refinery.store.model.representation.cardinality.UpperCardinalities; -import tools.refinery.store.model.representation.cardinality.UpperCardinality; +import tools.refinery.store.representation.cardinality.UpperCardinalities; +import tools.refinery.store.representation.cardinality.UpperCardinality; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/AbstractModelAdapterBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/AbstractModelAdapterBuilder.java new file mode 100644 index 00000000..4c142217 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/adapter/AbstractModelAdapterBuilder.java @@ -0,0 +1,27 @@ +package tools.refinery.store.adapter; + +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.model.ModelStoreBuilder; + +public abstract class AbstractModelAdapterBuilder implements ModelAdapterBuilder { + private final ModelStoreBuilder storeBuilder; + + protected AbstractModelAdapterBuilder(ModelStoreBuilder storeBuilder) { + this.storeBuilder = storeBuilder; + } + + @Override + public T with(ModelAdapterBuilderFactory adapterBuilderFactory) { + return storeBuilder.with(adapterBuilderFactory); + } + + @Override + public ModelStoreBuilder getStoreBuilder() { + return storeBuilder; + } + + @Override + public ModelStore build() { + return storeBuilder.build(); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/AdapterList.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/AdapterList.java new file mode 100644 index 00000000..2783675f --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/adapter/AdapterList.java @@ -0,0 +1,94 @@ +package tools.refinery.store.adapter; + +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.function.Consumer; + +public class AdapterList implements Iterable { + private final List adapterTypes; + private final List adapters; + + public AdapterList() { + adapterTypes = new ArrayList<>(); + adapters = new ArrayList<>(); + } + + public AdapterList(int adapterCount) { + adapterTypes = new ArrayList<>(adapterCount); + adapters = new ArrayList<>(adapterCount); + } + + public int size() { + return adapters.size(); + } + + public void add(AnyModelAdapterType adapterType, T adapter) { + adapterTypes.add(adapterType); + adapters.add(adapter); + } + + public Optional tryGet(AnyModelAdapterType adapterType, Class adapterClass) { + int size = size(); + for (int i = 0; i < size; i++) { + if (getType(i).supports(adapterType)) { + return Optional.of(adapterClass.cast(get(i))); + } + } + return Optional.empty(); + } + + public U get(AnyModelAdapterType adapterType, Class adapterClass) { + return tryGet(adapterType, adapterClass).orElseThrow(() -> new IllegalArgumentException( + "No %s was configured".formatted(adapterType))); + } + + public AnyModelAdapterType getType(int i) { + return adapterTypes.get(i); + } + + public T get(int i) { + return adapters.get(i); + } + + public Collection getAdapterTypes() { + return Collections.unmodifiableCollection(adapterTypes); + } + + public Iterable> withAdapterTypes() { + return () -> new Iterator<>() { + private int i = 0; + + @Override + public boolean hasNext() { + return i < size(); + } + + @Override + public Entry next() { + var entry = new Entry<>(getType(i), get(i)); + i++; + return entry; + } + }; + } + + @NotNull + @Override + public Iterator iterator() { + return adapters.iterator(); + } + + @Override + public void forEach(Consumer action) { + adapters.forEach(action); + } + + @Override + public Spliterator spliterator() { + return adapters.spliterator(); + } + + public record Entry(AnyModelAdapterType adapterType, T adapter) { + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/AnyModelAdapterType.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/AnyModelAdapterType.java new file mode 100644 index 00000000..37a247fe --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/adapter/AnyModelAdapterType.java @@ -0,0 +1,19 @@ +package tools.refinery.store.adapter; + +import java.util.Collection; + +public sealed interface AnyModelAdapterType permits ModelAdapterType { + Class getModelAdapterClass(); + + Class getModelStoreAdapterClass(); + + Class getModelAdapterBuilderClass(); + + Collection getSupportedAdapterTypes(); + + default boolean supports(AnyModelAdapterType targetAdapter) { + return getSupportedAdapterTypes().contains(targetAdapter); + } + + String getName(); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapter.java new file mode 100644 index 00000000..aa079e01 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapter.java @@ -0,0 +1,9 @@ +package tools.refinery.store.adapter; + +import tools.refinery.store.model.Model; + +public interface ModelAdapter { + Model getModel(); + + ModelStoreAdapter getStoreAdapter(); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilder.java new file mode 100644 index 00000000..64b3e59f --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilder.java @@ -0,0 +1,17 @@ +package tools.refinery.store.adapter; + +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.model.ModelStoreBuilder; + +public interface ModelAdapterBuilder { + ModelStoreAdapter createStoreAdapter(ModelStore store); + + T with(ModelAdapterBuilderFactory adapterBuilderFactory); + + ModelStoreBuilder getStoreBuilder(); + + default void configure() { + } + + ModelStore build(); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilderFactory.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilderFactory.java new file mode 100644 index 00000000..7c9b01bc --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilderFactory.java @@ -0,0 +1,14 @@ +package tools.refinery.store.adapter; + +import tools.refinery.store.model.ModelStoreBuilder; + +public abstract class ModelAdapterBuilderFactory extends ModelAdapterType { + + protected ModelAdapterBuilderFactory(Class modelAdapterClass, Class modelStoreAdapterClass, + Class modelAdapterBuilderClass) { + super(modelAdapterClass, modelStoreAdapterClass, modelAdapterBuilderClass); + } + + public abstract T3 createBuilder(ModelStoreBuilder storeBuilder); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterType.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterType.java new file mode 100644 index 00000000..82ddeb12 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterType.java @@ -0,0 +1,79 @@ +package tools.refinery.store.adapter; + +import tools.refinery.store.model.Model; +import tools.refinery.store.model.ModelStore; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public abstract non-sealed class ModelAdapterType implements AnyModelAdapterType { + private final Class modelAdapterClass; + private final Class modelStoreAdapterClass; + private final Class modelAdapterBuilderClass; + private final Set supportedAdapters = new HashSet<>(); + + protected ModelAdapterType(Class modelAdapterClass, Class modelStoreAdapterClass, + Class modelAdapterBuilderClass) { + checkReturnType(modelAdapterClass, modelStoreAdapterClass, "createModelAdapter", Model.class); + checkReturnType(modelStoreAdapterClass, modelAdapterBuilderClass, "createStoreAdapter", ModelStore.class); + this.modelAdapterClass = modelAdapterClass; + this.modelStoreAdapterClass = modelStoreAdapterClass; + this.modelAdapterBuilderClass = modelAdapterBuilderClass; + supportedAdapters.add(this); + } + + private void checkReturnType(Class expectedReturnType, Class ownerClass, String methodName, + Class... argumentTypes) { + Method method; + try { + method = ownerClass.getMethod(methodName, argumentTypes); + } catch (NoSuchMethodException e) { + throw new IllegalStateException("Invalid %s: %s#%s method is required" + .formatted(this, ownerClass.getName(), methodName), e); + } + var returnType = method.getReturnType(); + if (!expectedReturnType.isAssignableFrom(returnType)) { + throw new IllegalStateException("Invalid %s: %s is not assignable from the return type %s of %s#%s" + .formatted(this, expectedReturnType.getName(), returnType.getCanonicalName(), + ownerClass.getName(), methodName)); + } + } + + protected void extendsAdapter(ModelAdapterType superAdapter) { + supportedAdapters.addAll(superAdapter.supportedAdapters); + } + + @Override + public final Class getModelAdapterClass() { + return modelAdapterClass; + } + + @Override + public final Class getModelStoreAdapterClass() { + return modelStoreAdapterClass; + } + + @Override + public final Class getModelAdapterBuilderClass() { + return modelAdapterBuilderClass; + } + + @Override + public Collection getSupportedAdapterTypes() { + return Collections.unmodifiableCollection(supportedAdapters); + } + + @Override + public String getName() { + return "%s.ADAPTER".formatted(this.getClass().getName()); + } + + @Override + public String toString() { + return getName(); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelStoreAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelStoreAdapter.java new file mode 100644 index 00000000..1eb40ada --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelStoreAdapter.java @@ -0,0 +1,10 @@ +package tools.refinery.store.adapter; + +import tools.refinery.store.model.Model; +import tools.refinery.store.model.ModelStore; + +public interface ModelStoreAdapter { + ModelStore getStore(); + + ModelAdapter createModelAdapter(Model model); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/AnyInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/AnyInterpretation.java new file mode 100644 index 00000000..d18ba71d --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/model/AnyInterpretation.java @@ -0,0 +1,11 @@ +package tools.refinery.store.model; + +import tools.refinery.store.representation.AnySymbol; + +public sealed interface AnyInterpretation permits Interpretation { + Model getModel(); + + AnySymbol getSymbol(); + + long getSize(); +} 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 new file mode 100644 index 00000000..55949d0c --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java @@ -0,0 +1,25 @@ +package tools.refinery.store.model; + +import tools.refinery.store.map.Cursor; +import tools.refinery.store.map.DiffCursor; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.tuple.Tuple; + +public non-sealed interface Interpretation extends AnyInterpretation { + @Override + Symbol getSymbol(); + + T get(Tuple key); + + Cursor getAll(); + + T put(Tuple key, T value); + + void putAll(Cursor cursor); + + DiffCursor getDiffCursor(long to); + + void addListener(InterpretationListener listener, boolean alsoWhenRestoring); + + void removeListener(InterpretationListener listener); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/InterpretationListener.java b/subprojects/store/src/main/java/tools/refinery/store/model/InterpretationListener.java new file mode 100644 index 00000000..73950779 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/model/InterpretationListener.java @@ -0,0 +1,7 @@ +package tools.refinery.store.model; + +import tools.refinery.store.tuple.Tuple; + +public interface InterpretationListener { + void put(Tuple key, T fromValue, T toValue, boolean restoring); +} 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 69f57389..8d2a4614 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 @@ -1,24 +1,31 @@ package tools.refinery.store.model; -import tools.refinery.store.map.Cursor; +import tools.refinery.store.adapter.ModelAdapter; +import tools.refinery.store.adapter.ModelAdapterType; import tools.refinery.store.map.Versioned; -import tools.refinery.store.model.representation.AnyDataRepresentation; -import tools.refinery.store.model.representation.DataRepresentation; +import tools.refinery.store.representation.AnySymbol; +import tools.refinery.store.representation.Symbol; -import java.util.Set; +import java.util.Optional; public interface Model extends Versioned { - Set getDataRepresentations(); + ModelStore getStore(); - V get(DataRepresentation representation, K key); + long getState(); - Cursor getAll(DataRepresentation representation); + default AnyInterpretation getInterpretation(AnySymbol symbol) { + return getInterpretation((Symbol) symbol); + } - V put(DataRepresentation representation, K key, V value); + Interpretation getInterpretation(Symbol symbol); - void putAll(DataRepresentation representation, Cursor cursor); + ModelDiffCursor getDiffCursor(long to); - long getSize(AnyDataRepresentation representation); + Optional tryGetAdapter(ModelAdapterType adapterType); - ModelDiffCursor getDiffCursor(long to); + T getAdapter(ModelAdapterType adapterType); + + void addListener(ModelListener listener); + + void removeListener(ModelListener listener); } diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/ModelDiffCursor.java b/subprojects/store/src/main/java/tools/refinery/store/model/ModelDiffCursor.java index d5bb541b..97bf2039 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/ModelDiffCursor.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/ModelDiffCursor.java @@ -1,27 +1,27 @@ package tools.refinery.store.model; -import tools.refinery.store.map.Cursor; import tools.refinery.store.map.DiffCursor; -import tools.refinery.store.model.representation.AnyDataRepresentation; -import tools.refinery.store.model.representation.DataRepresentation; +import tools.refinery.store.representation.AnySymbol; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.tuple.Tuple; import java.util.Map; public class ModelDiffCursor { - final Map> diffCursors; + private final Map> diffCursors; - public ModelDiffCursor(Map> diffCursors) { + public ModelDiffCursor(Map> diffCursors) { super(); this.diffCursors = diffCursors; } - @SuppressWarnings("unchecked") - public DiffCursor getCursor(DataRepresentation representation) { - Cursor cursor = diffCursors.get(representation); - if (cursor != null) { - return (DiffCursor) cursor; - } else { - throw new IllegalArgumentException("ModelCursor does not contain cursor for representation " + representation); + public DiffCursor getCursor(Symbol symbol) { + var cursor = diffCursors.get(symbol); + if (cursor == null) { + throw new IllegalArgumentException("No cursor for symbol %s".formatted(symbol)); } + @SuppressWarnings("unchecked") + var typedCursor = (DiffCursor) cursor; + return typedCursor; } } 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 new file mode 100644 index 00000000..f67540bb --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java @@ -0,0 +1,15 @@ +package tools.refinery.store.model; + +public interface ModelListener { + default void beforeCommit() { + } + + default void afterCommit() { + } + + default void beforeRestore(long 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 84b67765..bc863d4b 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 @@ -1,11 +1,16 @@ package tools.refinery.store.model; -import tools.refinery.store.model.representation.AnyDataRepresentation; +import tools.refinery.store.adapter.ModelAdapterType; +import tools.refinery.store.adapter.ModelStoreAdapter; +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 { - Set getDataRepresentations(); + Collection getSymbols(); Model createModel(); @@ -14,4 +19,12 @@ public interface ModelStore { Set getStates(); ModelDiffCursor getDiffCursor(long from, long to); + + Optional tryGetAdapter(ModelAdapterType adapterType); + + T getAdapter(ModelAdapterType adapterType); + + static ModelStoreBuilder builder() { + return new ModelStoreBuilderImpl(); + } } diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreBuilder.java new file mode 100644 index 00000000..289099da --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreBuilder.java @@ -0,0 +1,36 @@ +package tools.refinery.store.model; + +import tools.refinery.store.adapter.ModelAdapterBuilder; +import tools.refinery.store.adapter.ModelAdapterBuilderFactory; +import tools.refinery.store.adapter.ModelAdapterType; +import tools.refinery.store.representation.AnySymbol; +import tools.refinery.store.representation.Symbol; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +public interface ModelStoreBuilder { + default ModelStoreBuilder symbols(AnySymbol... symbols) { + return symbols(List.of(symbols)); + } + + default ModelStoreBuilder symbols(Collection symbols) { + symbols.forEach(this::symbol); + return this; + } + + default ModelStoreBuilder symbol(AnySymbol symbol) { + return symbol((Symbol) symbol); + } + + ModelStoreBuilder symbol(Symbol symbol); + + T with(ModelAdapterBuilderFactory adapterBuilderFactory); + + Optional tryGetAdapter(ModelAdapterType adapterType); + + T getAdapter(ModelAdapterType adapterType); + + ModelStore build(); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreImpl.java b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreImpl.java deleted file mode 100644 index 2f73a0e5..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreImpl.java +++ /dev/null @@ -1,115 +0,0 @@ -package tools.refinery.store.model; - -import tools.refinery.store.map.*; -import tools.refinery.store.model.internal.ModelImpl; -import tools.refinery.store.model.internal.SimilarRelationEquivalenceClass; -import tools.refinery.store.model.representation.AnyDataRepresentation; -import tools.refinery.store.model.representation.AuxiliaryData; -import tools.refinery.store.model.representation.DataRepresentation; -import tools.refinery.store.model.representation.Relation; -import tools.refinery.store.tuple.Tuple; - -import java.util.*; -import java.util.Map.Entry; - -public class ModelStoreImpl implements ModelStore { - - private final Map> stores; - - public ModelStoreImpl(Set dataRepresentations) { - stores = initStores(dataRepresentations); - } - - private Map> initStores( - Set dataRepresentations) { - Map> result = new HashMap<>(); - - Map>> symbolRepresentationsPerHashPerArity = new HashMap<>(); - - for (AnyDataRepresentation dataRepresentation : dataRepresentations) { - if (dataRepresentation instanceof Relation symbolRepresentation) { - addOrCreate(symbolRepresentationsPerHashPerArity, - new SimilarRelationEquivalenceClass(symbolRepresentation), symbolRepresentation); - } else if (dataRepresentation instanceof AuxiliaryData auxiliaryData) { - VersionedMapStoreImpl store = new VersionedMapStoreImpl<>(auxiliaryData.getHashProvider(), - auxiliaryData.getDefaultValue()); - result.put(auxiliaryData, store); - } else { - throw new UnsupportedOperationException( - "Model store does not have strategy to use " + dataRepresentation.getClass() + "!"); - } - } - for (List> symbolGroup : symbolRepresentationsPerHashPerArity.values()) { - initRepresentationGroup(result, symbolGroup); - } - - return result; - } - - private void initRepresentationGroup(Map> result, - List> symbolGroup) { - final ContinousHashProvider hashProvider = symbolGroup.get(0).getHashProvider(); - final Object defaultValue = symbolGroup.get(0).getDefaultValue(); - - List> maps = VersionedMapStoreImpl - .createSharedVersionedMapStores(symbolGroup.size(), hashProvider, defaultValue); - - for (int i = 0; i < symbolGroup.size(); i++) { - result.put(symbolGroup.get(i), maps.get(i)); - } - } - - private static void addOrCreate(Map> map, K key, V value) { - List list; - if (map.containsKey(key)) { - list = map.get(key); - } else { - list = new LinkedList<>(); - map.put(key, list); - } - list.add(value); - } - - @Override - public Set getDataRepresentations() { - return this.stores.keySet(); - } - - @Override - public ModelImpl createModel() { - Map> maps = new HashMap<>(); - for (var entry : this.stores.entrySet()) { - maps.put(entry.getKey(), entry.getValue().createMap()); - } - return new ModelImpl(this, maps); - } - - @Override - public synchronized ModelImpl createModel(long state) { - Map> maps = new HashMap<>(); - for (var entry : this.stores.entrySet()) { - maps.put(entry.getKey(), entry.getValue().createMap(state)); - } - return new ModelImpl(this, maps); - } - - @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) { - Map> diffcursors = new HashMap<>(); - for (var entry : stores.entrySet()) { - var representation = entry.getKey(); - var diffCursor = entry.getValue().getDiffCursor(from, to); - diffcursors.put(representation, diffCursor); - } - return new ModelDiffCursor(diffcursors); - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/RelationLike.java b/subprojects/store/src/main/java/tools/refinery/store/model/RelationLike.java deleted file mode 100644 index 6a5fdcd5..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/RelationLike.java +++ /dev/null @@ -1,7 +0,0 @@ -package tools.refinery.store.model; - -public interface RelationLike { - String getName(); - - int getArity(); -} 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 24ec3b59..4bcf9ff4 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 @@ -4,15 +4,6 @@ import tools.refinery.store.map.ContinousHashProvider; import tools.refinery.store.tuple.Tuple; public class TupleHashProvider implements ContinousHashProvider { - protected static TupleHashProvider instance; - - public static TupleHashProvider singleton() { - if (instance == null) { - instance = new TupleHashProvider(); - } - return instance; - } - protected static final int[] primes = new int[] { 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, 293, 307, 311, 313, 317, 331, 337, @@ -45,6 +36,8 @@ public class TupleHashProvider implements ContinousHashProvider { public static final int MAX_MODEL_SIZE = (int) LARGEST_PRIME_30_BITS; + public static final TupleHashProvider INSTANCE = new TupleHashProvider(); + public TupleHashProvider() { if (primes.length < MAX_PRACTICAL_DEPTH) { throw new UnsupportedOperationException( diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelAction.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelAction.java new file mode 100644 index 00000000..f68859db --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelAction.java @@ -0,0 +1,7 @@ +package tools.refinery.store.model.internal; + +public enum ModelAction { + NONE, + COMMIT, + RESTORE +} 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 795a891b..10331e28 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 @@ -1,131 +1,166 @@ package tools.refinery.store.model.internal; -import tools.refinery.store.map.ContinousHashProvider; -import tools.refinery.store.map.Cursor; +import tools.refinery.store.adapter.AdapterList; +import tools.refinery.store.adapter.AnyModelAdapterType; +import tools.refinery.store.adapter.ModelAdapter; +import tools.refinery.store.adapter.ModelAdapterType; import tools.refinery.store.map.DiffCursor; -import tools.refinery.store.map.VersionedMap; -import tools.refinery.store.map.internal.MapDiffCursor; -import tools.refinery.store.model.Model; -import tools.refinery.store.model.ModelDiffCursor; -import tools.refinery.store.model.ModelStore; -import tools.refinery.store.model.representation.AnyDataRepresentation; -import tools.refinery.store.model.representation.DataRepresentation; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import tools.refinery.store.model.*; +import tools.refinery.store.representation.AnySymbol; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.tuple.Tuple; + +import java.util.*; public class ModelImpl implements Model { private final ModelStore store; - - private final Map> maps; - - public ModelImpl(ModelStore store, Map> maps) { + private long state; + private Map> interpretations; + private final AdapterList adapters; + private final List listeners = new ArrayList<>(); + private ModelAction pendingAction = ModelAction.NONE; + private long restoringToState = -1; + + ModelImpl(ModelStore store, long state, int adapterCount) { this.store = store; - this.maps = maps; + this.state = state; + adapters = new AdapterList<>(adapterCount); } - @Override - public Set getDataRepresentations() { - return maps.keySet(); + void setInterpretations(Map> interpretations) { + this.interpretations = interpretations; } - private VersionedMap getMap(AnyDataRepresentation representation) { - if (maps.containsKey(representation)) { - return maps.get(representation); - } else { - throw new IllegalArgumentException("Model does have representation " + representation); - } + @Override + public ModelStore getStore() { + return store; } - @SuppressWarnings("unchecked") - private VersionedMap getMap(DataRepresentation representation) { - return (VersionedMap) maps.get(representation); + @Override + public long getState() { + return state; } - private VersionedMap getMapValidateKey(DataRepresentation representation, K key) { - if (representation.isValidKey(key)) { - return getMap(representation); - } else { - throw new IllegalArgumentException( - "Key is not valid for representation! (representation=" + representation + ", key=" + key + ");"); + @Override + public Interpretation getInterpretation(Symbol symbol) { + var interpretation = interpretations.get(symbol); + if (interpretation == null) { + throw new IllegalArgumentException("No interpretation for symbol %s in model".formatted(symbol)); } + @SuppressWarnings("unchecked") + var typedInterpretation = (Interpretation) interpretation; + return typedInterpretation; } @Override - public V get(DataRepresentation representation, K key) { - return getMapValidateKey(representation, key).get(key); + public ModelDiffCursor getDiffCursor(long to) { + var diffCursors = new HashMap>(interpretations.size()); + for (var entry : interpretations.entrySet()) { + diffCursors.put(entry.getKey(), entry.getValue().getDiffCursor(to)); + } + return new ModelDiffCursor(diffCursors); } @Override - public Cursor getAll(DataRepresentation representation) { - return getMap(representation).getAll(); + public long commit() { + if (pendingAction != ModelAction.NONE) { + throw pendingActionError("commit"); + } + pendingAction = ModelAction.COMMIT; + try { + int listenerCount = listeners.size(); + int i = listenerCount; + long version = 0; + 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; + } + } + state = version; + while (i < listenerCount) { + listeners.get(i).afterCommit(); + i++; + } + return version; + } finally { + pendingAction = ModelAction.NONE; + } } @Override - public V put(DataRepresentation representation, K key, V value) { - return getMapValidateKey(representation, key).put(key, value); + public void restore(long version) { + if (pendingAction != ModelAction.NONE) { + throw pendingActionError("restore to %d".formatted(version)); + } + if (!store.getStates().contains(version)) { + throw new IllegalArgumentException("Store does not contain state %d".formatted(version)); + } + pendingAction = ModelAction.RESTORE; + restoringToState = version; + try { + int listenerCount = listeners.size(); + int i = listenerCount; + while (i > 0) { + i--; + listeners.get(i).beforeRestore(version); + } + for (var interpretation : interpretations.values()) { + interpretation.restore(version); + } + state = version; + while (i < listenerCount) { + listeners.get(i).afterRestore(); + i++; + } + } finally { + pendingAction = ModelAction.NONE; + restoringToState = -1; + } } - @Override - public void putAll(DataRepresentation representation, Cursor cursor) { - getMap(representation).putAll(cursor); + public RuntimeException pendingActionError(String currentActionName) { + var pendingActionName = switch (pendingAction) { + 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); + }; + return new IllegalStateException("Cannot %s due to pending %s".formatted(currentActionName, pendingActionName)); } @Override - public long getSize(AnyDataRepresentation representation) { - return getMap(representation).getSize(); + public Optional tryGetAdapter(ModelAdapterType adapterType) { + return adapters.tryGet(adapterType, adapterType.getModelAdapterClass()); } @Override - public ModelDiffCursor getDiffCursor(long to) { - Model toModel = store.createModel(to); - Map> diffCursors = new HashMap<>(); - for (AnyDataRepresentation anyDataRepresentation : this.maps.keySet()) { - var dataRepresentation = (DataRepresentation) anyDataRepresentation; - MapDiffCursor diffCursor = constructDiffCursor(toModel, dataRepresentation); - diffCursors.put(dataRepresentation, diffCursor); - } - return new ModelDiffCursor(diffCursors); + public T getAdapter(ModelAdapterType adapterType) { + return adapters.get(adapterType, adapterType.getModelAdapterClass()); } - private MapDiffCursor constructDiffCursor(Model toModel, DataRepresentation representation) { - @SuppressWarnings("unchecked") - Cursor fromCursor = (Cursor) this.maps.get(representation).getAll(); - Cursor toCursor = toModel.getAll(representation); - - ContinousHashProvider hashProvider = representation.getHashProvider(); - V defaultValue = representation.getDefaultValue(); - return new MapDiffCursor<>(hashProvider, defaultValue, fromCursor, toCursor); + void addAdapter(AnyModelAdapterType adapterType, ModelAdapter adapter) { + adapters.add(adapterType, adapter); } @Override - public long commit() { - long version = 0; - boolean versionSet = false; - for (VersionedMap map : maps.values()) { - long newVersion = map.commit(); - if (versionSet) { - if (version != newVersion) { - throw new IllegalStateException( - "Maps in model have different versions! (" + version + " and" + newVersion + ")"); - } - } else { - version = newVersion; - versionSet = true; - } - } - return version; + public void addListener(ModelListener listener) { + listeners.add(listener); } @Override - public void restore(long state) { - if (store.getStates().contains(state)) { - for (VersionedMap map : maps.values()) { - map.restore(state); - } - } else { - throw new IllegalArgumentException("Map does not contain state " + state + "!"); - } + public void removeListener(ModelListener listener) { + listeners.remove(listener); } } 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 new file mode 100644 index 00000000..79f7195d --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java @@ -0,0 +1,104 @@ +package tools.refinery.store.model.internal; + +import tools.refinery.store.adapter.AdapterList; +import tools.refinery.store.adapter.ModelAdapterBuilder; +import tools.refinery.store.adapter.ModelAdapterBuilderFactory; +import tools.refinery.store.adapter.ModelAdapterType; +import tools.refinery.store.map.VersionedMapStore; +import tools.refinery.store.map.VersionedMapStoreImpl; +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; + +import java.util.*; + +public class ModelStoreBuilderImpl implements ModelStoreBuilder { + private final Set allSymbols = new HashSet<>(); + private final Map, List> equivalenceClasses = new HashMap<>(); + private final AdapterList adapters = new AdapterList<>(); + + @Override + public ModelStoreBuilder symbol(Symbol symbol) { + if (!allSymbols.add(symbol)) { + throw new IllegalArgumentException("Symbol %s already added".formatted(symbol)); + } + var equivalenceClass = new SymbolEquivalenceClass<>(symbol); + var symbolsInEquivalenceClass = equivalenceClasses.computeIfAbsent(equivalenceClass, + ignored -> new ArrayList<>()); + symbolsInEquivalenceClass.add(symbol); + return this; + } + + @Override + public T with(ModelAdapterBuilderFactory adapterBuilderFactory) { + return adapters.tryGet(adapterBuilderFactory, adapterBuilderFactory.getModelAdapterBuilderClass()) + .orElseGet(() -> addAdapter(adapterBuilderFactory)); + } + + private T addAdapter(ModelAdapterBuilderFactory adapterBuilderFactory) { + for (var configuredAdapterType : adapters.getAdapterTypes()) { + var intersection = new HashSet<>(adapterBuilderFactory.getSupportedAdapterTypes()); + intersection.retainAll(configuredAdapterType.getSupportedAdapterTypes()); + if (!intersection.isEmpty()) { + if (configuredAdapterType.supports(adapterBuilderFactory)) { + // Impossible to end up here from #with, because we should have returned + // the existing adapter there instead of adding a new one. + throw new IllegalArgumentException( + "Cannot add %s, because it is already provided by configured adapter %s" + .formatted(adapterBuilderFactory, configuredAdapterType)); + } else if (adapterBuilderFactory.supports(configuredAdapterType)) { + throw new IllegalArgumentException( + "Cannot add %s, because it provides already configured adapter %s" + .formatted(adapterBuilderFactory, configuredAdapterType)); + } else { + throw new IllegalArgumentException( + "Cannot add %s, because configured adapter %s already provides %s" + .formatted(adapterBuilderFactory, configuredAdapterType, intersection)); + } + } + } + var newAdapter = adapterBuilderFactory.createBuilder(this); + adapters.add(adapterBuilderFactory, newAdapter); + return newAdapter; + } + + @Override + public Optional tryGetAdapter(ModelAdapterType adapterType) { + return adapters.tryGet(adapterType, adapterType.getModelAdapterBuilderClass()); + } + + @Override + public T getAdapter(ModelAdapterType adapterType) { + return adapters.get(adapterType, adapterType.getModelAdapterBuilderClass()); + } + + @Override + public ModelStore build() { + var stores = new HashMap>(allSymbols.size()); + for (var entry : equivalenceClasses.entrySet()) { + createStores(stores, entry.getKey(), entry.getValue()); + } + var modelStore = new ModelStoreImpl(stores, adapters.size()); + for (int i = adapters.size() - 1; i >= 0; i--) { + adapters.get(i).configure(); + } + for (var entry : adapters.withAdapterTypes()) { + var adapter = entry.adapter().createStoreAdapter(modelStore); + modelStore.addAdapter(entry.adapterType(), adapter); + } + return modelStore; + } + + private void createStores(Map> stores, + SymbolEquivalenceClass equivalenceClass, List symbols) { + int size = symbols.size(); + var storeGroup = VersionedMapStoreImpl.createSharedVersionedMapStores(size, TupleHashProvider.INSTANCE, + equivalenceClass.defaultValue()); + for (int i = 0; i < size; i++) { + stores.put(symbols.get(i), storeGroup.get(i)); + } + } +} 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 new file mode 100644 index 00000000..8aab57af --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java @@ -0,0 +1,100 @@ +package tools.refinery.store.model.internal; + +import tools.refinery.store.adapter.AdapterList; +import tools.refinery.store.adapter.AnyModelAdapterType; +import tools.refinery.store.adapter.ModelAdapterType; +import tools.refinery.store.adapter.ModelStoreAdapter; +import tools.refinery.store.map.DiffCursor; +import tools.refinery.store.map.VersionedMapStore; +import tools.refinery.store.model.ModelDiffCursor; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.representation.AnySymbol; +import tools.refinery.store.tuple.Tuple; + +import java.util.*; + +public class ModelStoreImpl implements ModelStore { + private final Map> stores; + private final AdapterList adapters; + + ModelStoreImpl(Map> stores, int adapterCount) { + this.stores = stores; + adapters = new AdapterList<>(adapterCount); + } + + @Override + public Collection getSymbols() { + return Collections.unmodifiableCollection(stores.keySet()); + } + + private ModelImpl createEmptyModel(long state) { + return new ModelImpl(this, state, adapters.size()); + } + + @Override + public ModelImpl createModel() { + var model = createEmptyModel(-1); + var interpretations = new HashMap>(stores.size()); + for (var entry : this.stores.entrySet()) { + var symbol = entry.getKey(); + interpretations.put(symbol, VersionedInterpretation.of(model, symbol, entry.getValue())); + } + model.setInterpretations(interpretations); + adaptModel(model); + return model; + } + + @Override + public synchronized ModelImpl createModel(long state) { + var model = createEmptyModel(state); + var interpretations = new HashMap>(stores.size()); + for (var entry : this.stores.entrySet()) { + var symbol = entry.getKey(); + interpretations.put(symbol, VersionedInterpretation.of(model, symbol, entry.getValue(), state)); + } + model.setInterpretations(interpretations); + adaptModel(model); + return model; + } + + private void adaptModel(ModelImpl model) { + for (var entry : adapters.withAdapterTypes()) { + var adapter = entry.adapter().createModelAdapter(model); + model.addAdapter(entry.adapterType(), adapter); + } + } + + @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) { + var diffCursors = new HashMap>(); + for (var entry : stores.entrySet()) { + var representation = entry.getKey(); + var diffCursor = entry.getValue().getDiffCursor(from, to); + diffCursors.put(representation, diffCursor); + } + return new ModelDiffCursor(diffCursors); + } + + @Override + public Optional tryGetAdapter(ModelAdapterType adapterType) { + return adapters.tryGet(adapterType, adapterType.getModelStoreAdapterClass()); + } + + @Override + public T getAdapter(ModelAdapterType adapterType) { + return adapters.get(adapterType, adapterType.getModelStoreAdapterClass()); + } + + void addAdapter(AnyModelAdapterType adapterType, ModelStoreAdapter adapter) { + adapters.add(adapterType, adapter); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/SimilarRelationEquivalenceClass.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/SimilarRelationEquivalenceClass.java deleted file mode 100644 index 79e4c9f9..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/SimilarRelationEquivalenceClass.java +++ /dev/null @@ -1,35 +0,0 @@ -package tools.refinery.store.model.internal; - -import tools.refinery.store.map.ContinousHashProvider; -import tools.refinery.store.model.representation.Relation; -import tools.refinery.store.tuple.Tuple; - -import java.util.Objects; - -public class SimilarRelationEquivalenceClass { - final ContinousHashProvider hashProvider; - final Object defaultValue; - final int arity; - - public SimilarRelationEquivalenceClass(Relation representation) { - this.hashProvider = representation.getHashProvider(); - this.defaultValue = representation.getDefaultValue(); - this.arity = representation.getArity(); - } - - @Override - public int hashCode() { - return Objects.hash(arity, defaultValue, hashProvider); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (!(obj instanceof SimilarRelationEquivalenceClass other)) - return false; - return arity == other.arity && Objects.equals(defaultValue, other.defaultValue) - && Objects.equals(hashProvider, other.hashProvider); - } - -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/SymbolEquivalenceClass.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/SymbolEquivalenceClass.java new file mode 100644 index 00000000..5bf1b90d --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/SymbolEquivalenceClass.java @@ -0,0 +1,9 @@ +package tools.refinery.store.model.internal; + +import tools.refinery.store.representation.Symbol; + +public record SymbolEquivalenceClass(int arity, Class valueType, T defaultValue) { + public SymbolEquivalenceClass(Symbol symbol) { + this(symbol.arity(), symbol.valueType(), symbol.defaultValue()); + } +} 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 new file mode 100644 index 00000000..1bdb1cdf --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java @@ -0,0 +1,159 @@ +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.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; + +import java.util.ArrayList; +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) { + this.model = model; + this.symbol = symbol; + this.store = store; + this.map = map; + } + + @Override + public Model getModel() { + return model; + } + + @Override + public Symbol getSymbol() { + return symbol; + } + + @Override + public long getSize() { + return map.getSize(); + } + + private void checkKey(Tuple key) { + if (key == null || key.getSize() != symbol.arity()) { + throw new IllegalArgumentException("Key for %s must be a tuple with arity %s" + .formatted(symbol, symbol.arity())); + } + } + @Override + public T get(Tuple key) { + checkKey(key); + return map.get(key); + } + + @Override + public Cursor getAll() { + return map.getAll(); + } + + private void notifyListeners(Tuple key, T fromValue, T toValue, boolean restoring) { + var listenerList = restoring ? restoreListeners : listeners; + int listenerCount = listenerList.size(); + // Use a for loop instead of a for-each loop to avoid Iterator allocation overhead. + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < listenerCount; i++) { + listenerList.get(i).put(key, fromValue, toValue, restoring); + } + } + + @Override + public T put(Tuple key, T value) { + checkKey(key); + var oldValue = map.put(key, value); + notifyListeners(key, oldValue, value, false); + return oldValue; + } + + @Override + public void putAll(Cursor cursor) { + if (listeners.isEmpty()) { + map.putAll(cursor); + return; + } + if (cursor.getDependingMaps().contains(map)) { + List keys = new ArrayList<>(); + List values = new ArrayList<>(); + while (cursor.move()) { + keys.add(cursor.getKey()); + values.add(cursor.getValue()); + } + var keyIterator = keys.iterator(); + var valueIterator = values.iterator(); + while (keyIterator.hasNext()) { + put(keyIterator.next(), valueIterator.next()); + } + } else { + while (cursor.move()) { + put(cursor.getKey(), cursor.getValue()); + } + } + } + + @Override + public DiffCursor getDiffCursor(long to) { + var fromCursor = getAll(); + var toCursor = store.createMap(to).getAll(); + return new MapDiffCursor<>(TupleHashProvider.INSTANCE, symbol.defaultValue(), fromCursor, toCursor); + } + + public long commit() { + return map.commit(); + } + + public void restore(long state) { + if (!restoreListeners.isEmpty()) { + var diffCursor = getDiffCursor(state); + while (diffCursor.move()) { + notifyListeners(diffCursor.getKey(), diffCursor.getFromValue(), diffCursor.getToValue(), true); + } + } + map.restore(state); + } + + @Override + public void addListener(InterpretationListener listener, boolean alsoWhenRestoring) { + listeners.add(listener); + if (alsoWhenRestoring) { + restoreListeners.add(listener); + } + } + + @Override + public void removeListener(InterpretationListener listener) { + listeners.remove(listener); + restoreListeners.remove(listener); + } + + static VersionedInterpretation of(ModelImpl model, AnySymbol symbol, VersionedMapStore store) { + @SuppressWarnings("unchecked") + var typedSymbol = (Symbol) symbol; + var map = store.createMap(); + return new VersionedInterpretation<>(model, typedSymbol, store, map); + } + + static VersionedInterpretation of(ModelImpl model, AnySymbol symbol, VersionedMapStore store, + long state) { + @SuppressWarnings("unchecked") + var typedSymbol = (Symbol) symbol; + var map = store.createMap(state); + return new VersionedInterpretation<>(model, typedSymbol, store, map); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/AnyAuxiliaryData.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/AnyAuxiliaryData.java deleted file mode 100644 index 951952e5..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/representation/AnyAuxiliaryData.java +++ /dev/null @@ -1,4 +0,0 @@ -package tools.refinery.store.model.representation; - -public sealed interface AnyAuxiliaryData extends AnyDataRepresentation permits AuxiliaryData { -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/AnyDataRepresentation.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/AnyDataRepresentation.java deleted file mode 100644 index ea74a625..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/representation/AnyDataRepresentation.java +++ /dev/null @@ -1,9 +0,0 @@ -package tools.refinery.store.model.representation; - -public sealed interface AnyDataRepresentation permits DataRepresentation, AnyRelation, AnyAuxiliaryData { - String getName(); - - Class getKeyType(); - - Class getValueType(); -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/AnyRelation.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/AnyRelation.java deleted file mode 100644 index 1d698c28..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/representation/AnyRelation.java +++ /dev/null @@ -1,9 +0,0 @@ -package tools.refinery.store.model.representation; - -import tools.refinery.store.model.RelationLike; -import tools.refinery.store.tuple.Tuple; - -public sealed interface AnyRelation extends AnyDataRepresentation, RelationLike permits Relation { - @Override - Class getKeyType(); -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/AuxiliaryData.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/AuxiliaryData.java deleted file mode 100644 index dad1ccf4..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/representation/AuxiliaryData.java +++ /dev/null @@ -1,23 +0,0 @@ -package tools.refinery.store.model.representation; - -import tools.refinery.store.map.ContinousHashProvider; - -public final class AuxiliaryData extends DataRepresentation implements AnyAuxiliaryData { - private final ContinousHashProvider hashProvider; - - public AuxiliaryData(String name, Class keyType, ContinousHashProvider hashProvider, Class valueType, - V defaultValue) { - super(name, keyType, valueType, defaultValue); - this.hashProvider = hashProvider; - } - - @Override - public ContinousHashProvider getHashProvider() { - return hashProvider; - } - - @Override - public boolean isValidKey(K key) { - return true; - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/DataRepresentation.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/DataRepresentation.java deleted file mode 100644 index 2bf498b9..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/representation/DataRepresentation.java +++ /dev/null @@ -1,43 +0,0 @@ -package tools.refinery.store.model.representation; - -import tools.refinery.store.map.ContinousHashProvider; - -public abstract sealed class DataRepresentation implements AnyDataRepresentation permits Relation, AuxiliaryData { - private final String name; - - private final V defaultValue; - - private final Class keyType; - - private final Class valueType; - - protected DataRepresentation(String name, Class keyType, Class valueType, V defaultValue) { - this.name = name; - this.defaultValue = defaultValue; - this.keyType = keyType; - this.valueType = valueType; - } - - @Override - public String getName() { - return name; - } - - public abstract ContinousHashProvider getHashProvider(); - - public abstract boolean isValidKey(K key); - - public V getDefaultValue() { - return defaultValue; - } - - @Override - public Class getKeyType() { - return keyType; - } - - @Override - public Class getValueType() { - return valueType; - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/Relation.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/Relation.java deleted file mode 100644 index 47a07536..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/representation/Relation.java +++ /dev/null @@ -1,33 +0,0 @@ -package tools.refinery.store.model.representation; - -import tools.refinery.store.map.ContinousHashProvider; -import tools.refinery.store.model.TupleHashProvider; -import tools.refinery.store.tuple.Tuple; - -public final class Relation extends DataRepresentation implements AnyRelation { - private final int arity; - - public Relation(String name, int arity, Class valueType, D defaultValue) { - super(name, Tuple.class, valueType, defaultValue); - this.arity = arity; - } - - @Override - public int getArity() { - return arity; - } - - @Override - public ContinousHashProvider getHashProvider() { - return TupleHashProvider.singleton(); - } - - @Override - public boolean isValidKey(Tuple key) { - if (key == null) { - return false; - } else { - return key.getSize() == getArity(); - } - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/TruthValue.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/TruthValue.java deleted file mode 100644 index a5ba825b..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/representation/TruthValue.java +++ /dev/null @@ -1,58 +0,0 @@ -package tools.refinery.store.model.representation; - -public enum TruthValue { - TRUE("true"), - - FALSE("false"), - - UNKNOWN("unknown"), - - ERROR("error"); - - private final String name; - - TruthValue(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public static TruthValue toTruthValue(boolean value) { - return value ? TRUE : FALSE; - } - - public boolean isConsistent() { - return this != ERROR; - } - - public boolean isComplete() { - return this != UNKNOWN; - } - - public boolean must() { - return this == TRUE || this == ERROR; - } - - public boolean may() { - return this == TRUE || this == UNKNOWN; - } - - public TruthValue not() { - return switch (this) { - case TRUE -> FALSE; - case FALSE -> TRUE; - default -> this; - }; - } - - public TruthValue merge(TruthValue other) { - return switch (this) { - case TRUE -> other == UNKNOWN || other == TRUE ? TRUE : ERROR; - case FALSE -> other == TruthValue.UNKNOWN || other == TruthValue.FALSE ? FALSE : ERROR; - case UNKNOWN -> other; - default -> ERROR; - }; - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/CardinalityInterval.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/CardinalityInterval.java deleted file mode 100644 index 1f252b4c..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/CardinalityInterval.java +++ /dev/null @@ -1,21 +0,0 @@ -package tools.refinery.store.model.representation.cardinality; - -public sealed interface CardinalityInterval permits NonEmptyCardinalityInterval, EmptyCardinalityInterval { - int lowerBound(); - - UpperCardinality upperBound(); - - boolean isEmpty(); - - CardinalityInterval min(CardinalityInterval other); - - CardinalityInterval max(CardinalityInterval other); - - CardinalityInterval add(CardinalityInterval other); - - CardinalityInterval multiply(CardinalityInterval other); - - CardinalityInterval meet(CardinalityInterval other); - - CardinalityInterval join(CardinalityInterval other); -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervals.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervals.java deleted file mode 100644 index b7c52bd1..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervals.java +++ /dev/null @@ -1,46 +0,0 @@ -package tools.refinery.store.model.representation.cardinality; - -public final class CardinalityIntervals { - public static final CardinalityInterval NONE = exactly(0); - - public static final CardinalityInterval ONE = exactly(1); - - public static final CardinalityInterval LONE = atMost(1); - - public static final CardinalityInterval SET = atLeast(0); - - public static final CardinalityInterval SOME = atLeast(1); - - public static final CardinalityInterval ERROR = EmptyCardinalityInterval.INSTANCE; - - private CardinalityIntervals() { - throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); - } - - public static CardinalityInterval between(int lowerBound, UpperCardinality upperBound) { - if (upperBound.compareToInt(lowerBound) < 0) { - return ERROR; - } - return new NonEmptyCardinalityInterval(lowerBound, upperBound); - } - - public static CardinalityInterval between(int lowerBound, int upperBound) { - return between(lowerBound, UpperCardinalities.valueOf(upperBound)); - } - - public static CardinalityInterval atMost(UpperCardinality upperBound) { - return new NonEmptyCardinalityInterval(0, upperBound); - } - - public static CardinalityInterval atMost(int upperBound) { - return atMost(UpperCardinalities.valueOf(upperBound)); - } - - public static CardinalityInterval atLeast(int lowerBound) { - return new NonEmptyCardinalityInterval(lowerBound, UpperCardinalities.UNBOUNDED); - } - - public static CardinalityInterval exactly(int lowerBound) { - return new NonEmptyCardinalityInterval(lowerBound, UpperCardinalities.valueOf(lowerBound)); - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/EmptyCardinalityInterval.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/EmptyCardinalityInterval.java deleted file mode 100644 index 127651df..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/EmptyCardinalityInterval.java +++ /dev/null @@ -1,59 +0,0 @@ -package tools.refinery.store.model.representation.cardinality; - -public final class EmptyCardinalityInterval implements CardinalityInterval { - static final EmptyCardinalityInterval INSTANCE = new EmptyCardinalityInterval(); - - private EmptyCardinalityInterval() { - // Singleton constructor. - } - - @Override - public int lowerBound() { - return 1; - } - - @Override - public boolean isEmpty() { - return true; - } - - @Override - public UpperCardinality upperBound() { - return UpperCardinalities.ZERO; - } - - @Override - public CardinalityInterval min(CardinalityInterval other) { - return this; - } - - @Override - public CardinalityInterval max(CardinalityInterval other) { - return this; - } - - @Override - public CardinalityInterval add(CardinalityInterval other) { - return this; - } - - @Override - public CardinalityInterval multiply(CardinalityInterval other) { - return this; - } - - @Override - public CardinalityInterval meet(CardinalityInterval other) { - return this; - } - - @Override - public CardinalityInterval join(CardinalityInterval other) { - return other; - } - - @Override - public String toString() { - return "error"; - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/FiniteUpperCardinality.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/FiniteUpperCardinality.java deleted file mode 100644 index ec643a97..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/FiniteUpperCardinality.java +++ /dev/null @@ -1,55 +0,0 @@ -package tools.refinery.store.model.representation.cardinality; - -import org.jetbrains.annotations.NotNull; - -import java.util.function.IntBinaryOperator; - -public record FiniteUpperCardinality(int finiteUpperBound) implements UpperCardinality { - public FiniteUpperCardinality { - if (finiteUpperBound < 0) { - throw new IllegalArgumentException("finiteUpperBound must not be negative"); - } - } - - @Override - public UpperCardinality add(UpperCardinality other) { - return lift(other, Integer::sum); - } - - @Override - public UpperCardinality multiply(UpperCardinality other) { - return lift(other, (a, b) -> a * b); - } - - @Override - public int compareTo(@NotNull UpperCardinality upperCardinality) { - if (upperCardinality instanceof FiniteUpperCardinality finiteUpperCardinality) { - return compareToInt(finiteUpperCardinality.finiteUpperBound); - } - if (upperCardinality instanceof UnboundedUpperCardinality) { - return -1; - } - throw new IllegalArgumentException("Unknown UpperCardinality: " + upperCardinality); - } - - @Override - public int compareToInt(int value) { - return Integer.compare(finiteUpperBound, value); - } - - @Override - public String toString() { - return Integer.toString(finiteUpperBound); - } - - private UpperCardinality lift(@NotNull UpperCardinality other, IntBinaryOperator operator) { - if (other instanceof FiniteUpperCardinality finiteUpperCardinality) { - return UpperCardinalities.valueOf(operator.applyAsInt(finiteUpperBound, - finiteUpperCardinality.finiteUpperBound)); - } - if (other instanceof UnboundedUpperCardinality) { - return UpperCardinalities.UNBOUNDED; - } - throw new IllegalArgumentException("Unknown UpperCardinality: " + other); - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/NonEmptyCardinalityInterval.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/NonEmptyCardinalityInterval.java deleted file mode 100644 index b534f2e3..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/NonEmptyCardinalityInterval.java +++ /dev/null @@ -1,74 +0,0 @@ -package tools.refinery.store.model.representation.cardinality; - -import java.util.function.BinaryOperator; -import java.util.function.IntBinaryOperator; - -public record NonEmptyCardinalityInterval(int lowerBound, UpperCardinality upperBound) implements CardinalityInterval { - public NonEmptyCardinalityInterval { - if (lowerBound < 0) { - throw new IllegalArgumentException("lowerBound must not be negative"); - } - if (upperBound.compareToInt(lowerBound) < 0) { - throw new IllegalArgumentException("lowerBound must not be larger than upperBound"); - } - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public CardinalityInterval min(CardinalityInterval other) { - return lift(other, Math::min, UpperCardinality::min); - } - - @Override - public CardinalityInterval max(CardinalityInterval other) { - return lift(other, Math::max, UpperCardinality::max); - } - - @Override - public CardinalityInterval add(CardinalityInterval other) { - return lift(other, Integer::sum, UpperCardinality::add); - } - - @Override - public CardinalityInterval multiply(CardinalityInterval other) { - return lift(other, (a, b) -> a * b, UpperCardinality::multiply); - } - - @Override - public CardinalityInterval meet(CardinalityInterval other) { - return lift(other, Math::max, UpperCardinality::min); - } - - @Override - public CardinalityInterval join(CardinalityInterval other) { - return lift(other, Math::min, UpperCardinality::max, this); - } - - private CardinalityInterval lift(CardinalityInterval other, IntBinaryOperator lowerOperator, - BinaryOperator upperOperator, - CardinalityInterval whenEmpty) { - if (other instanceof NonEmptyCardinalityInterval nonEmptyOther) { - return CardinalityIntervals.between(lowerOperator.applyAsInt(lowerBound, nonEmptyOther.lowerBound), - upperOperator.apply(upperBound, nonEmptyOther.upperBound)); - } - if (other instanceof EmptyCardinalityInterval) { - return whenEmpty; - } - throw new IllegalArgumentException("Unknown CardinalityInterval: " + other); - } - - private CardinalityInterval lift(CardinalityInterval other, IntBinaryOperator lowerOperator, - BinaryOperator upperOperator) { - return lift(other, lowerOperator, upperOperator, CardinalityIntervals.ERROR); - } - - @Override - public String toString() { - var closeBracket = upperBound instanceof UnboundedUpperCardinality ? ")" : "]"; - return "[%d..%s%s".formatted(lowerBound, upperBound, closeBracket); - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UnboundedUpperCardinality.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UnboundedUpperCardinality.java deleted file mode 100644 index 4199d44f..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UnboundedUpperCardinality.java +++ /dev/null @@ -1,42 +0,0 @@ -package tools.refinery.store.model.representation.cardinality; - -import org.jetbrains.annotations.NotNull; - -public final class UnboundedUpperCardinality implements UpperCardinality { - static final UnboundedUpperCardinality INSTANCE = new UnboundedUpperCardinality(); - - private UnboundedUpperCardinality() { - // Singleton constructor. - } - - @Override - public UpperCardinality add(UpperCardinality other) { - return this; - } - - @Override - public UpperCardinality multiply(UpperCardinality other) { - return this; - } - - @Override - public int compareTo(@NotNull UpperCardinality upperCardinality) { - if (upperCardinality instanceof FiniteUpperCardinality) { - return 1; - } - if (upperCardinality instanceof UnboundedUpperCardinality) { - return 0; - } - throw new IllegalArgumentException("Unknown UpperCardinality: " + upperCardinality); - } - - @Override - public int compareToInt(int value) { - return 1; - } - - @Override - public String toString() { - return "*"; - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UpperCardinalities.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UpperCardinalities.java deleted file mode 100644 index b433cda2..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UpperCardinalities.java +++ /dev/null @@ -1,33 +0,0 @@ -package tools.refinery.store.model.representation.cardinality; - -public final class UpperCardinalities { - public static final UpperCardinality UNBOUNDED = UnboundedUpperCardinality.INSTANCE; - - public static final UpperCardinality ZERO; - - public static final UpperCardinality ONE; - - private static final FiniteUpperCardinality[] cache = new FiniteUpperCardinality[256]; - - static { - for (int i = 0; i < cache.length; i++) { - cache[i] = new FiniteUpperCardinality(i); - } - ZERO = cache[0]; - ONE = cache[1]; - } - - private UpperCardinalities() { - throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); - } - - public static UpperCardinality valueOf(int upperBound) { - if (upperBound < 0) { - return UNBOUNDED; - } - if (upperBound < cache.length) { - return cache[upperBound]; - } - return new FiniteUpperCardinality(upperBound); - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UpperCardinality.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UpperCardinality.java deleted file mode 100644 index 91dd40b0..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UpperCardinality.java +++ /dev/null @@ -1,22 +0,0 @@ -package tools.refinery.store.model.representation.cardinality; - -public sealed interface UpperCardinality extends Comparable permits FiniteUpperCardinality, - UnboundedUpperCardinality { - default UpperCardinality min(UpperCardinality other) { - return this.compareTo(other) <= 0 ? this : other; - } - - default UpperCardinality max(UpperCardinality other) { - return this.compareTo(other) >= 0 ? this : other; - } - - UpperCardinality add(UpperCardinality other); - - UpperCardinality multiply(UpperCardinality other); - - int compareToInt(int value); - - static UpperCardinality of(int upperBound) { - return UpperCardinalities.valueOf(upperBound); - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/DNF.java b/subprojects/store/src/main/java/tools/refinery/store/query/DNF.java index 7d4b3091..336c25a1 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/query/DNF.java +++ b/subprojects/store/src/main/java/tools/refinery/store/query/DNF.java @@ -1,11 +1,11 @@ package tools.refinery.store.query; -import tools.refinery.store.model.RelationLike; +import tools.refinery.store.representation.SymbolLike; import tools.refinery.store.query.atom.DNFAtom; import java.util.*; -public class DNF implements RelationLike { +public class DNF implements SymbolLike { private final String name; private final String uniqueName; @@ -22,7 +22,7 @@ public class DNF implements RelationLike { } @Override - public String getName() { + public String name() { return name; } @@ -35,7 +35,7 @@ public class DNF implements RelationLike { } @Override - public int getArity() { + public int arity() { return parameters.size(); } diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQuery.java b/subprojects/store/src/main/java/tools/refinery/store/query/ModelQuery.java new file mode 100644 index 00000000..6a1aeabb --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/query/ModelQuery.java @@ -0,0 +1,11 @@ +package tools.refinery.store.query; + +import tools.refinery.store.adapter.ModelAdapterType; + +public final class ModelQuery extends ModelAdapterType { + public static final ModelQuery ADAPTER = new ModelQuery(); + + private ModelQuery() { + super(ModelQueryAdapter.class, ModelQueryStoreAdapter.class, ModelQueryBuilder.class); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java new file mode 100644 index 00000000..d0cdf02d --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java @@ -0,0 +1,11 @@ +package tools.refinery.store.query; + +import tools.refinery.store.adapter.ModelAdapter; + +public interface ModelQueryAdapter extends ModelAdapter { + ModelQueryStoreAdapter getStoreAdapter(); + + ResultSet getResultSet(DNF query); + + void flushChanges(); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java new file mode 100644 index 00000000..853a1796 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java @@ -0,0 +1,23 @@ +package tools.refinery.store.query; + +import tools.refinery.store.adapter.ModelAdapterBuilder; +import tools.refinery.store.model.ModelStore; + +import java.util.Collection; +import java.util.List; + +public interface ModelQueryBuilder extends ModelAdapterBuilder { + default ModelQueryBuilder queries(DNF... queries) { + return queries(List.of(queries)); + } + + default ModelQueryBuilder queries(Collection queries) { + queries.forEach(this::query); + return this; + } + + ModelQueryBuilder query(DNF query); + + @Override + ModelQueryStoreAdapter createStoreAdapter(ModelStore store); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java new file mode 100644 index 00000000..ef5a4587 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java @@ -0,0 +1,16 @@ +package tools.refinery.store.query; + +import tools.refinery.store.adapter.ModelStoreAdapter; +import tools.refinery.store.model.Model; +import tools.refinery.store.query.view.AnyRelationView; + +import java.util.Collection; + +public interface ModelQueryStoreAdapter extends ModelStoreAdapter { + Collection getRelationViews(); + + Collection getQueries(); + + @Override + ModelQueryAdapter createModelAdapter(Model model); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/QueryableModel.java b/subprojects/store/src/main/java/tools/refinery/store/query/QueryableModel.java deleted file mode 100644 index 9655d1a1..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/QueryableModel.java +++ /dev/null @@ -1,33 +0,0 @@ -package tools.refinery.store.query; - -import tools.refinery.store.model.Model; -import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.tuple.TupleLike; - -import java.util.Optional; -import java.util.Set; -import java.util.stream.Stream; - -public interface QueryableModel extends Model { - Set getPredicates(); - - boolean hasChanges(); - - void flushChanges(); - - boolean hasResult(DNF predicate); - - boolean hasResult(DNF predicate, Tuple parameters); - - Optional oneResult(DNF predicate); - - Optional oneResult(DNF predicate, Tuple parameters); - - Stream allResults(DNF predicate); - - Stream allResults(DNF predicate, Tuple parameters); - - int countResults(DNF predicate); - - int countResults(DNF predicate, Tuple parameters); -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/QueryableModelStore.java b/subprojects/store/src/main/java/tools/refinery/store/query/QueryableModelStore.java deleted file mode 100644 index 3a69f3a4..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/QueryableModelStore.java +++ /dev/null @@ -1,19 +0,0 @@ -package tools.refinery.store.query; - -import tools.refinery.store.model.ModelStore; -import tools.refinery.store.model.representation.AnyDataRepresentation; -import tools.refinery.store.query.view.AnyRelationView; - -import java.util.Set; - -public interface QueryableModelStore extends ModelStore { - Set getDataRepresentations(); - - Set getViews(); - - Set getPredicates(); - - QueryableModel createModel(); - - QueryableModel createModel(long state); -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/ResultSet.java b/subprojects/store/src/main/java/tools/refinery/store/query/ResultSet.java new file mode 100644 index 00000000..3542e252 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/query/ResultSet.java @@ -0,0 +1,25 @@ +package tools.refinery.store.query; + +import tools.refinery.store.tuple.Tuple; +import tools.refinery.store.tuple.TupleLike; + +import java.util.Optional; +import java.util.stream.Stream; + +public interface ResultSet { + boolean hasResult(); + + boolean hasResult(Tuple parameters); + + Optional oneResult(); + + Optional oneResult(Tuple parameters); + + Stream allResults(); + + Stream allResults(Tuple parameters); + + int countResults(); + + int countResults(Tuple parameters); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/AbstractSubstitutionAtom.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/AbstractSubstitutionAtom.java index e8a8b5e1..af6a8f5a 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/AbstractSubstitutionAtom.java +++ b/subprojects/store/src/main/java/tools/refinery/store/query/atom/AbstractSubstitutionAtom.java @@ -1,20 +1,20 @@ package tools.refinery.store.query.atom; -import tools.refinery.store.model.RelationLike; +import tools.refinery.store.representation.SymbolLike; import tools.refinery.store.query.Variable; import java.util.List; import java.util.Set; -public abstract class AbstractSubstitutionAtom implements DNFAtom { +public abstract class AbstractSubstitutionAtom implements DNFAtom { private final T target; private final List substitution; protected AbstractSubstitutionAtom(T target, List substitution) { - if (substitution.size() != target.getArity()) { - throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.getName(), - target.getArity(), substitution.size())); + if (substitution.size() != target.arity()) { + throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.name(), + target.arity(), substitution.size())); } this.target = target; this.substitution = substitution; diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/CallAtom.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/CallAtom.java index 9e289a8a..2e855246 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/CallAtom.java +++ b/subprojects/store/src/main/java/tools/refinery/store/query/atom/CallAtom.java @@ -1,40 +1,40 @@ package tools.refinery.store.query.atom; -import tools.refinery.store.model.RelationLike; +import tools.refinery.store.representation.SymbolLike; import tools.refinery.store.query.Variable; import java.util.List; import java.util.Objects; import java.util.Set; -public final class CallAtom extends AbstractSubstitutionAtom { +public abstract class CallAtom extends AbstractSubstitutionAtom { private final CallPolarity polarity; - public CallAtom(CallPolarity polarity, T target, List substitution) { + protected CallAtom(CallPolarity polarity, T target, List substitution) { super(target, substitution); - if (polarity.isTransitive() && target.getArity() != 2) { + if (polarity.isTransitive() && target.arity() != 2) { throw new IllegalArgumentException("Transitive closures can only take binary relations"); } this.polarity = polarity; } - public CallAtom(CallPolarity polarity, T target, Variable... substitution) { + protected CallAtom(CallPolarity polarity, T target, Variable... substitution) { this(polarity, target, List.of(substitution)); } - public CallAtom(boolean positive, T target, List substitution) { + protected CallAtom(boolean positive, T target, List substitution) { this(CallPolarity.fromBoolean(positive), target, substitution); } - public CallAtom(boolean positive, T target, Variable... substitution) { + protected CallAtom(boolean positive, T target, Variable... substitution) { this(positive, target, List.of(substitution)); } - public CallAtom(T target, List substitution) { + protected CallAtom(T target, List substitution) { this(true, target, substitution); } - public CallAtom(T target, Variable... substitution) { + protected CallAtom(T target, Variable... substitution) { this(target, List.of(substitution)); } diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFCallAtom.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFCallAtom.java new file mode 100644 index 00000000..3b4f5cd1 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFCallAtom.java @@ -0,0 +1,32 @@ +package tools.refinery.store.query.atom; + +import tools.refinery.store.query.DNF; +import tools.refinery.store.query.Variable; + +import java.util.List; + +public class DNFCallAtom extends CallAtom { + public DNFCallAtom(CallPolarity polarity, DNF target, List substitution) { + super(polarity, target, substitution); + } + + public DNFCallAtom(CallPolarity polarity, DNF target, Variable... substitution) { + super(polarity, target, substitution); + } + + public DNFCallAtom(boolean positive, DNF target, List substitution) { + super(positive, target, substitution); + } + + public DNFCallAtom(boolean positive, DNF target, Variable... substitution) { + super(positive, target, substitution); + } + + public DNFCallAtom(DNF target, List substitution) { + super(target, substitution); + } + + public DNFCallAtom(DNF target, Variable... substitution) { + super(target, substitution); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/ModalRelation.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/ModalRelation.java index 1e4f8f55..c2ca1fdb 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/ModalRelation.java +++ b/subprojects/store/src/main/java/tools/refinery/store/query/atom/ModalRelation.java @@ -1,17 +1,17 @@ package tools.refinery.store.query.atom; -import tools.refinery.store.model.RelationLike; -import tools.refinery.store.model.representation.Relation; -import tools.refinery.store.model.representation.TruthValue; +import tools.refinery.store.representation.SymbolLike; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.representation.TruthValue; -public record ModalRelation(Modality modality, Relation relation) implements RelationLike { +public record ModalRelation(Modality modality, Symbol relation) implements SymbolLike { @Override - public String getName() { + public String name() { return "%s %s".formatted(modality, relation); } @Override - public int getArity() { - return relation.getArity(); + public int arity() { + return relation.arity(); } } diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/AbstractFilteredRelationView.java b/subprojects/store/src/main/java/tools/refinery/store/query/view/AbstractFilteredRelationView.java deleted file mode 100644 index 79de1c4d..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/view/AbstractFilteredRelationView.java +++ /dev/null @@ -1,44 +0,0 @@ -package tools.refinery.store.query.view; - -import tools.refinery.store.model.Model; -import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.tuple.Tuple1; -import tools.refinery.store.model.representation.Relation; - -public abstract class AbstractFilteredRelationView extends RelationView { - protected AbstractFilteredRelationView(Relation representation, String name) { - super(representation, name); - } - - protected AbstractFilteredRelationView(Relation representation) { - super(representation); - } - - @Override - public Object[] forwardMap(Tuple key, D value) { - return toTuple1Array(key); - } - - @Override - public boolean get(Model model, Object[] tuple) { - int[] content = new int[tuple.length]; - for (int i = 0; i < tuple.length; i++) { - content[i] = ((Tuple1) tuple[i]).value0(); - } - Tuple key = Tuple.of(content); - D value = model.get(getRepresentation(), key); - return filter(key, value); - } - - public int getArity() { - return this.getRepresentation().getArity(); - } - - private static Object[] toTuple1Array(Tuple t) { - Object[] result = new Object[t.getSize()]; - for (int i = 0; i < t.getSize(); i++) { - result[i] = Tuple.of(t.get(i)); - } - return result; - } -} diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/AnyRelationView.java b/subprojects/store/src/main/java/tools/refinery/store/query/view/AnyRelationView.java index df5e0d72..9df1038b 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/query/view/AnyRelationView.java +++ b/subprojects/store/src/main/java/tools/refinery/store/query/view/AnyRelationView.java @@ -1,11 +1,11 @@ package tools.refinery.store.query.view; import tools.refinery.store.model.Model; -import tools.refinery.store.model.RelationLike; -import tools.refinery.store.model.representation.AnyRelation; +import tools.refinery.store.representation.SymbolLike; +import tools.refinery.store.representation.AnySymbol; -public sealed interface AnyRelationView extends RelationLike permits RelationView { - AnyRelation getRepresentation(); +public sealed interface AnyRelationView extends SymbolLike permits RelationView { + AnySymbol getSymbol(); boolean get(Model model, Object[] tuple); diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java b/subprojects/store/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java index 5b892cc6..64c601bb 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java +++ b/subprojects/store/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java @@ -1,35 +1,35 @@ package tools.refinery.store.query.view; import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.model.representation.Relation; +import tools.refinery.store.representation.Symbol; import java.util.Objects; import java.util.function.BiPredicate; import java.util.function.Predicate; -public class FilteredRelationView extends AbstractFilteredRelationView { - private final BiPredicate predicate; +public class FilteredRelationView extends TuplePreservingRelationView { + private final BiPredicate predicate; - public FilteredRelationView(Relation representation, String name, BiPredicate predicate) { - super(representation, name); + public FilteredRelationView(Symbol symbol, String name, BiPredicate predicate) { + super(symbol, name); this.predicate = predicate; } - public FilteredRelationView(Relation representation, BiPredicate predicate) { - super(representation); + public FilteredRelationView(Symbol symbol, BiPredicate predicate) { + super(symbol); this.predicate = predicate; } - public FilteredRelationView(Relation representation, String name, Predicate predicate) { - this(representation, name, (k, v) -> predicate.test(v)); + public FilteredRelationView(Symbol symbol, String name, Predicate predicate) { + this(symbol, name, (k, v) -> predicate.test(v)); } - public FilteredRelationView(Relation representation, Predicate predicate) { - this(representation, (k, v) -> predicate.test(v)); + public FilteredRelationView(Symbol symbol, Predicate predicate) { + this(symbol, (k, v) -> predicate.test(v)); } @Override - public boolean filter(Tuple key, D value) { + public boolean filter(Tuple key, T value) { return this.predicate.test(key, value); } diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java b/subprojects/store/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java index ef3954a4..d263679a 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java +++ b/subprojects/store/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java @@ -3,24 +3,24 @@ package tools.refinery.store.query.view; import tools.refinery.store.model.Model; import tools.refinery.store.tuple.Tuple; import tools.refinery.store.tuple.Tuple1; -import tools.refinery.store.model.representation.Relation; +import tools.refinery.store.representation.Symbol; -public class FunctionalRelationView extends RelationView { - public FunctionalRelationView(Relation representation, String name) { - super(representation, name); +public class FunctionalRelationView extends RelationView { + public FunctionalRelationView(Symbol symbol, String name) { + super(symbol, name); } - public FunctionalRelationView(Relation representation) { - super(representation); + public FunctionalRelationView(Symbol symbol) { + super(symbol); } @Override - public boolean filter(Tuple key, D value) { + public boolean filter(Tuple key, T value) { return true; } @Override - public Object[] forwardMap(Tuple key, D value) { + public Object[] forwardMap(Tuple key, T value) { return toTuple1ArrayPlusValue(key, value); } @@ -32,14 +32,14 @@ public class FunctionalRelationView extends RelationView { } Tuple key = Tuple.of(content); @SuppressWarnings("unchecked") - D valueInTuple = (D) tuple[tuple.length - 1]; - D valueInMap = model.get(getRepresentation(), key); + T valueInTuple = (T) tuple[tuple.length - 1]; + T valueInMap = model.getInterpretation(getSymbol()).get(key); return valueInTuple.equals(valueInMap); } @Override - public int getArity() { - return getRepresentation().getArity() + 1; + public int arity() { + return getSymbol().arity() + 1; } private static Object[] toTuple1ArrayPlusValue(Tuple t, D value) { diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java b/subprojects/store/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java index f7e7d28a..fdfcdf28 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java +++ b/subprojects/store/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java @@ -1,23 +1,23 @@ package tools.refinery.store.query.view; +import tools.refinery.store.representation.Symbol; import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.model.representation.Relation; import java.util.Objects; -public class KeyOnlyRelationView extends AbstractFilteredRelationView { +public class KeyOnlyRelationView extends TuplePreservingRelationView { public static final String VIEW_NAME = "key"; - private final Boolean defaultValue; + private final T defaultValue; - public KeyOnlyRelationView(Relation representation) { - super(representation, VIEW_NAME); - defaultValue = representation.getDefaultValue(); + public KeyOnlyRelationView(Symbol symbol) { + super(symbol, VIEW_NAME); + defaultValue = symbol.defaultValue(); } @Override - public boolean filter(Tuple key, Boolean value) { - return !value.equals(defaultValue); + public boolean filter(Tuple key, T value) { + return !Objects.equals(value, defaultValue); } @Override @@ -25,7 +25,7 @@ public class KeyOnlyRelationView extends AbstractFilteredRelationView { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; - KeyOnlyRelationView that = (KeyOnlyRelationView) o; + KeyOnlyRelationView that = (KeyOnlyRelationView) o; return Objects.equals(defaultValue, that.defaultValue); } diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/RelationView.java b/subprojects/store/src/main/java/tools/refinery/store/query/view/RelationView.java index 1224076c..bbec1e73 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/query/view/RelationView.java +++ b/subprojects/store/src/main/java/tools/refinery/store/query/view/RelationView.java @@ -2,49 +2,49 @@ package tools.refinery.store.query.view; import tools.refinery.store.map.CursorAsIterator; import tools.refinery.store.model.Model; -import tools.refinery.store.model.representation.Relation; +import tools.refinery.store.representation.Symbol; import tools.refinery.store.tuple.Tuple; import java.util.Objects; import java.util.UUID; /** - * Represents a view of a {@link Relation} that can be queried. + * Represents a view of a {@link Symbol} that can be queried. * - * @param + * @param * @author Oszkar Semerath */ -public abstract non-sealed class RelationView implements AnyRelationView { - private final Relation representation; +public abstract non-sealed class RelationView implements AnyRelationView { + private final Symbol symbol; private final String name; - protected RelationView(Relation representation, String name) { - this.representation = representation; + protected RelationView(Symbol symbol, String name) { + this.symbol = symbol; this.name = name; } - protected RelationView(Relation representation) { + protected RelationView(Symbol representation) { this(representation, UUID.randomUUID().toString()); } @Override - public Relation getRepresentation() { - return representation; + public Symbol getSymbol() { + return symbol; } @Override - public String getName() { - return representation.getName() + "#" + name; + public String name() { + return symbol.name() + "#" + name; } - public abstract boolean filter(Tuple key, D value); + public abstract boolean filter(Tuple key, T value); - public abstract Object[] forwardMap(Tuple key, D value); + public abstract Object[] forwardMap(Tuple key, T value); @Override public Iterable getAll(Model model) { - return (() -> new CursorAsIterator<>(model.getAll(representation), this::forwardMap, this::filter)); + return (() -> new CursorAsIterator<>(model.getInterpretation(symbol).getAll(), this::forwardMap, this::filter)); } @Override @@ -52,11 +52,11 @@ public abstract non-sealed class RelationView implements AnyRelationView { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; RelationView that = (RelationView) o; - return Objects.equals(representation, that.representation) && Objects.equals(name, that.name); + return Objects.equals(symbol, that.symbol) && Objects.equals(name, that.name); } @Override public int hashCode() { - return Objects.hash(representation, name); + return Objects.hash(symbol, name); } } diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java b/subprojects/store/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java new file mode 100644 index 00000000..8cc4986e --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java @@ -0,0 +1,44 @@ +package tools.refinery.store.query.view; + +import tools.refinery.store.model.Model; +import tools.refinery.store.tuple.Tuple; +import tools.refinery.store.tuple.Tuple1; +import tools.refinery.store.representation.Symbol; + +public abstract class TuplePreservingRelationView extends RelationView { + protected TuplePreservingRelationView(Symbol symbol, String name) { + super(symbol, name); + } + + protected TuplePreservingRelationView(Symbol symbol) { + super(symbol); + } + + public Object[] forwardMap(Tuple key) { + Object[] result = new Object[key.getSize()]; + for (int i = 0; i < key.getSize(); i++) { + result[i] = Tuple.of(key.get(i)); + } + return result; + } + + @Override + public Object[] forwardMap(Tuple key, T value) { + return forwardMap(key); + } + + @Override + public boolean get(Model model, Object[] tuple) { + int[] content = new int[tuple.length]; + for (int i = 0; i < tuple.length; i++) { + content[i] = ((Tuple1) tuple[i]).value0(); + } + Tuple key = Tuple.of(content); + T value = model.getInterpretation(getSymbol()).get(key); + return filter(key, value); + } + + public int arity() { + return this.getSymbol().arity(); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/AnySymbol.java b/subprojects/store/src/main/java/tools/refinery/store/representation/AnySymbol.java new file mode 100644 index 00000000..12a45bed --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/AnySymbol.java @@ -0,0 +1,5 @@ +package tools.refinery.store.representation; + +public sealed interface AnySymbol extends SymbolLike permits Symbol { + Class valueType(); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/Symbol.java b/subprojects/store/src/main/java/tools/refinery/store/representation/Symbol.java new file mode 100644 index 00000000..404b5ddb --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/Symbol.java @@ -0,0 +1,47 @@ +package tools.refinery.store.representation; + +import java.util.Objects; + +// Deliberately not a record, because we want equality by reference. +@SuppressWarnings({"squid:S6206", "ClassCanBeRecord"}) +public final class Symbol implements AnySymbol { + private final String name; + private final int arity; + private final Class valueType; + private final T defaultValue; + + public Symbol(String name, int arity, Class valueType, T defaultValue) { + this.name = name; + this.arity = arity; + this.valueType = valueType; + this.defaultValue = defaultValue; + } + + @Override + public String name() { + return name; + } + + @Override + public int arity() { + return arity; + } + + @Override + public Class valueType() { + return valueType; + } + + public T defaultValue() { + return defaultValue; + } + + public boolean isDefaultValue(T value) { + return Objects.equals(defaultValue, value); + } + + @Override + public String toString() { + return "%s/%d".formatted(name, arity); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/SymbolLike.java b/subprojects/store/src/main/java/tools/refinery/store/representation/SymbolLike.java new file mode 100644 index 00000000..8c84500c --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/SymbolLike.java @@ -0,0 +1,7 @@ +package tools.refinery.store.representation; + +public interface SymbolLike { + String name(); + + int arity(); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValue.java b/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValue.java new file mode 100644 index 00000000..b7893fd3 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValue.java @@ -0,0 +1,58 @@ +package tools.refinery.store.representation; + +public enum TruthValue { + TRUE("true"), + + FALSE("false"), + + UNKNOWN("unknown"), + + ERROR("error"); + + private final String name; + + TruthValue(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static TruthValue toTruthValue(boolean value) { + return value ? TRUE : FALSE; + } + + public boolean isConsistent() { + return this != ERROR; + } + + public boolean isComplete() { + return this != UNKNOWN; + } + + public boolean must() { + return this == TRUE || this == ERROR; + } + + public boolean may() { + return this == TRUE || this == UNKNOWN; + } + + public TruthValue not() { + return switch (this) { + case TRUE -> FALSE; + case FALSE -> TRUE; + default -> this; + }; + } + + public TruthValue merge(TruthValue other) { + return switch (this) { + case TRUE -> other == UNKNOWN || other == TRUE ? TRUE : ERROR; + case FALSE -> other == TruthValue.UNKNOWN || other == TruthValue.FALSE ? FALSE : ERROR; + case UNKNOWN -> other; + default -> ERROR; + }; + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityInterval.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityInterval.java new file mode 100644 index 00000000..273d0de7 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityInterval.java @@ -0,0 +1,21 @@ +package tools.refinery.store.representation.cardinality; + +public sealed interface CardinalityInterval permits NonEmptyCardinalityInterval, EmptyCardinalityInterval { + int lowerBound(); + + UpperCardinality upperBound(); + + boolean isEmpty(); + + CardinalityInterval min(CardinalityInterval other); + + CardinalityInterval max(CardinalityInterval other); + + CardinalityInterval add(CardinalityInterval other); + + CardinalityInterval multiply(CardinalityInterval other); + + CardinalityInterval meet(CardinalityInterval other); + + CardinalityInterval join(CardinalityInterval other); +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityIntervals.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityIntervals.java new file mode 100644 index 00000000..e1a08bf9 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityIntervals.java @@ -0,0 +1,46 @@ +package tools.refinery.store.representation.cardinality; + +public final class CardinalityIntervals { + public static final CardinalityInterval NONE = exactly(0); + + public static final CardinalityInterval ONE = exactly(1); + + public static final CardinalityInterval LONE = atMost(1); + + public static final CardinalityInterval SET = atLeast(0); + + public static final CardinalityInterval SOME = atLeast(1); + + public static final CardinalityInterval ERROR = EmptyCardinalityInterval.INSTANCE; + + private CardinalityIntervals() { + throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); + } + + public static CardinalityInterval between(int lowerBound, UpperCardinality upperBound) { + if (upperBound.compareToInt(lowerBound) < 0) { + return ERROR; + } + return new NonEmptyCardinalityInterval(lowerBound, upperBound); + } + + public static CardinalityInterval between(int lowerBound, int upperBound) { + return between(lowerBound, UpperCardinalities.valueOf(upperBound)); + } + + public static CardinalityInterval atMost(UpperCardinality upperBound) { + return new NonEmptyCardinalityInterval(0, upperBound); + } + + public static CardinalityInterval atMost(int upperBound) { + return atMost(UpperCardinalities.valueOf(upperBound)); + } + + public static CardinalityInterval atLeast(int lowerBound) { + return new NonEmptyCardinalityInterval(lowerBound, UpperCardinalities.UNBOUNDED); + } + + public static CardinalityInterval exactly(int lowerBound) { + return new NonEmptyCardinalityInterval(lowerBound, UpperCardinalities.valueOf(lowerBound)); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/EmptyCardinalityInterval.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/EmptyCardinalityInterval.java new file mode 100644 index 00000000..ab3ad9d1 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/EmptyCardinalityInterval.java @@ -0,0 +1,59 @@ +package tools.refinery.store.representation.cardinality; + +public final class EmptyCardinalityInterval implements CardinalityInterval { + static final EmptyCardinalityInterval INSTANCE = new EmptyCardinalityInterval(); + + private EmptyCardinalityInterval() { + // Singleton constructor. + } + + @Override + public int lowerBound() { + return 1; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public UpperCardinality upperBound() { + return UpperCardinalities.ZERO; + } + + @Override + public CardinalityInterval min(CardinalityInterval other) { + return this; + } + + @Override + public CardinalityInterval max(CardinalityInterval other) { + return this; + } + + @Override + public CardinalityInterval add(CardinalityInterval other) { + return this; + } + + @Override + public CardinalityInterval multiply(CardinalityInterval other) { + return this; + } + + @Override + public CardinalityInterval meet(CardinalityInterval other) { + return this; + } + + @Override + public CardinalityInterval join(CardinalityInterval other) { + return other; + } + + @Override + public String toString() { + return "error"; + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinality.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinality.java new file mode 100644 index 00000000..381c8a57 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinality.java @@ -0,0 +1,55 @@ +package tools.refinery.store.representation.cardinality; + +import org.jetbrains.annotations.NotNull; + +import java.util.function.IntBinaryOperator; + +public record FiniteUpperCardinality(int finiteUpperBound) implements UpperCardinality { + public FiniteUpperCardinality { + if (finiteUpperBound < 0) { + throw new IllegalArgumentException("finiteUpperBound must not be negative"); + } + } + + @Override + public UpperCardinality add(UpperCardinality other) { + return lift(other, Integer::sum); + } + + @Override + public UpperCardinality multiply(UpperCardinality other) { + return lift(other, (a, b) -> a * b); + } + + @Override + public int compareTo(@NotNull UpperCardinality upperCardinality) { + if (upperCardinality instanceof FiniteUpperCardinality finiteUpperCardinality) { + return compareToInt(finiteUpperCardinality.finiteUpperBound); + } + if (upperCardinality instanceof UnboundedUpperCardinality) { + return -1; + } + throw new IllegalArgumentException("Unknown UpperCardinality: " + upperCardinality); + } + + @Override + public int compareToInt(int value) { + return Integer.compare(finiteUpperBound, value); + } + + @Override + public String toString() { + return Integer.toString(finiteUpperBound); + } + + private UpperCardinality lift(@NotNull UpperCardinality other, IntBinaryOperator operator) { + if (other instanceof FiniteUpperCardinality finiteUpperCardinality) { + return UpperCardinalities.valueOf(operator.applyAsInt(finiteUpperBound, + finiteUpperCardinality.finiteUpperBound)); + } + if (other instanceof UnboundedUpperCardinality) { + return UpperCardinalities.UNBOUNDED; + } + throw new IllegalArgumentException("Unknown UpperCardinality: " + other); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java new file mode 100644 index 00000000..32b3786f --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java @@ -0,0 +1,74 @@ +package tools.refinery.store.representation.cardinality; + +import java.util.function.BinaryOperator; +import java.util.function.IntBinaryOperator; + +public record NonEmptyCardinalityInterval(int lowerBound, UpperCardinality upperBound) implements CardinalityInterval { + public NonEmptyCardinalityInterval { + if (lowerBound < 0) { + throw new IllegalArgumentException("lowerBound must not be negative"); + } + if (upperBound.compareToInt(lowerBound) < 0) { + throw new IllegalArgumentException("lowerBound must not be larger than upperBound"); + } + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public CardinalityInterval min(CardinalityInterval other) { + return lift(other, Math::min, UpperCardinality::min); + } + + @Override + public CardinalityInterval max(CardinalityInterval other) { + return lift(other, Math::max, UpperCardinality::max); + } + + @Override + public CardinalityInterval add(CardinalityInterval other) { + return lift(other, Integer::sum, UpperCardinality::add); + } + + @Override + public CardinalityInterval multiply(CardinalityInterval other) { + return lift(other, (a, b) -> a * b, UpperCardinality::multiply); + } + + @Override + public CardinalityInterval meet(CardinalityInterval other) { + return lift(other, Math::max, UpperCardinality::min); + } + + @Override + public CardinalityInterval join(CardinalityInterval other) { + return lift(other, Math::min, UpperCardinality::max, this); + } + + private CardinalityInterval lift(CardinalityInterval other, IntBinaryOperator lowerOperator, + BinaryOperator upperOperator, + CardinalityInterval whenEmpty) { + if (other instanceof NonEmptyCardinalityInterval nonEmptyOther) { + return CardinalityIntervals.between(lowerOperator.applyAsInt(lowerBound, nonEmptyOther.lowerBound), + upperOperator.apply(upperBound, nonEmptyOther.upperBound)); + } + if (other instanceof EmptyCardinalityInterval) { + return whenEmpty; + } + throw new IllegalArgumentException("Unknown CardinalityInterval: " + other); + } + + private CardinalityInterval lift(CardinalityInterval other, IntBinaryOperator lowerOperator, + BinaryOperator upperOperator) { + return lift(other, lowerOperator, upperOperator, CardinalityIntervals.ERROR); + } + + @Override + public String toString() { + var closeBracket = upperBound instanceof UnboundedUpperCardinality ? ")" : "]"; + return "[%d..%s%s".formatted(lowerBound, upperBound, closeBracket); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UnboundedUpperCardinality.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UnboundedUpperCardinality.java new file mode 100644 index 00000000..593bc322 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UnboundedUpperCardinality.java @@ -0,0 +1,42 @@ +package tools.refinery.store.representation.cardinality; + +import org.jetbrains.annotations.NotNull; + +public final class UnboundedUpperCardinality implements UpperCardinality { + static final UnboundedUpperCardinality INSTANCE = new UnboundedUpperCardinality(); + + private UnboundedUpperCardinality() { + // Singleton constructor. + } + + @Override + public UpperCardinality add(UpperCardinality other) { + return this; + } + + @Override + public UpperCardinality multiply(UpperCardinality other) { + return this; + } + + @Override + public int compareTo(@NotNull UpperCardinality upperCardinality) { + if (upperCardinality instanceof FiniteUpperCardinality) { + return 1; + } + if (upperCardinality instanceof UnboundedUpperCardinality) { + return 0; + } + throw new IllegalArgumentException("Unknown UpperCardinality: " + upperCardinality); + } + + @Override + public int compareToInt(int value) { + return 1; + } + + @Override + public String toString() { + return "*"; + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinalities.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinalities.java new file mode 100644 index 00000000..d850fdc9 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinalities.java @@ -0,0 +1,33 @@ +package tools.refinery.store.representation.cardinality; + +public final class UpperCardinalities { + public static final UpperCardinality UNBOUNDED = UnboundedUpperCardinality.INSTANCE; + + public static final UpperCardinality ZERO; + + public static final UpperCardinality ONE; + + private static final FiniteUpperCardinality[] cache = new FiniteUpperCardinality[256]; + + static { + for (int i = 0; i < cache.length; i++) { + cache[i] = new FiniteUpperCardinality(i); + } + ZERO = cache[0]; + ONE = cache[1]; + } + + private UpperCardinalities() { + throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); + } + + public static UpperCardinality valueOf(int upperBound) { + if (upperBound < 0) { + return UNBOUNDED; + } + if (upperBound < cache.length) { + return cache[upperBound]; + } + return new FiniteUpperCardinality(upperBound); + } +} diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinality.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinality.java new file mode 100644 index 00000000..c6e31cb7 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinality.java @@ -0,0 +1,22 @@ +package tools.refinery.store.representation.cardinality; + +public sealed interface UpperCardinality extends Comparable permits FiniteUpperCardinality, + UnboundedUpperCardinality { + default UpperCardinality min(UpperCardinality other) { + return this.compareTo(other) <= 0 ? this : other; + } + + default UpperCardinality max(UpperCardinality other) { + return this.compareTo(other) >= 0 ? this : other; + } + + UpperCardinality add(UpperCardinality other); + + UpperCardinality multiply(UpperCardinality other); + + int compareToInt(int value); + + static UpperCardinality of(int upperBound) { + return UpperCardinalities.valueOf(upperBound); + } +} 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 081112cc..77c62305 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 @@ -1,18 +1,17 @@ package tools.refinery.store.map.tests; -import static org.junit.jupiter.api.Assertions.assertEquals; - import org.junit.jupiter.api.Test; - import tools.refinery.store.map.VersionedMapStore; import tools.refinery.store.map.VersionedMapStoreImpl; -import tools.refinery.store.tuple.Tuple; import tools.refinery.store.model.TupleHashProvider; +import tools.refinery.store.tuple.Tuple; + +import static org.junit.jupiter.api.Assertions.assertEquals; class MapUnitTests { @Test void defaultTest() { - VersionedMapStore store = new VersionedMapStoreImpl(TupleHashProvider.singleton(), false); + VersionedMapStore store = new VersionedMapStoreImpl<>(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/model/hashTests/HashEfficiencyTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/hashTests/HashEfficiencyTest.java deleted file mode 100644 index ceec40f5..00000000 --- a/subprojects/store/src/test/java/tools/refinery/store/model/hashTests/HashEfficiencyTest.java +++ /dev/null @@ -1,161 +0,0 @@ -package tools.refinery.store.model.hashTests; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Random; - -import org.junit.jupiter.api.Test; - -import tools.refinery.store.map.ContinousHashProvider; -import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.model.TupleHashProvider; -import tools.refinery.store.model.TupleHashProviderBitMagic; - -class HashEfficiencyTest { - - private static List permutations(int range, int arity) { - if(arity == 1) { - List result = new ArrayList<>(range); - for(int i=0; i 1) { - List smallers = permutations(range, arity-1); - List result = new ArrayList<>(range*smallers.size()); - for(Tuple smaller : smallers) { - for(int i=0; i nPermutations(int arity, int n) { - int range = amountToRange(arity, n); - List permutations = permutations(range, arity); - return permutations.subList(0, n); - } - - public static List nRandoms(int arity, int n, int seed) { - int range = amountToRange(arity, n); - List permutations = new ArrayList<>(n); - Random r = new Random(seed); - for(int i = 0; i p = permutations(10, 2); - assertEquals(p.size(),10*10); - } -// private void printTuples(List p) { -// for(Tuple element : p) { -// System.out.println(element); -// } -// } - @Test - void nPermutationTest() { - final int amount = 500; - List p = nPermutations(2, amount); - assertEquals(amount,p.size()); - } - @Test - void nRandomTest() { - final int amount = 500; - List p = nRandoms(2, amount, 1);; - assertEquals(amount,p.size()); - } - private static double calculateHashClashes(List tuples, ContinousHashProvider chp) { - int sumClashes = 0; - - for(int i = 0; i chp, Tuple a, Tuple b) { - if(a.equals(b)) return 0; - final int bits = 5; - final int segments = Integer.SIZE/bits; - final int mask = (1<>(depth*5))&mask; - int bHash = (chp.getHash(b, index)>>(depth*5))&mask; - if(aHash != bHash) { - return i+1; - } - if(i>400) { - throw new IllegalStateException(a+" vs "+b); - } - } - } - private static double caclulateOptimalHashClash(int size) { - return (Math.log(size)/Math.log(32)); - } - public static void main(String[] args) { - List hashNames = 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}; - - System.out.println("Size,Arity,DataSource,Hash,Chashes,Optimal,Badness"); - for(int size : sizes) { - double optimalClashes = caclulateOptimalHashClash(size); - for(int arity : arities) { - List dataSourceNames = new LinkedList<>(); - List> dataSources = new LinkedList<>(); - -// dataSourceNames.add("Permutation"); -// dataSources.add(nPermutations(arity, size)); - dataSourceNames.add("Random"); - dataSources.add(nRandoms(arity, size, 0)); - - for(int dataSourceIndex = 0; dataSourceIndex permutations(int range, int arity) { + if(arity == 1) { + List result = new ArrayList<>(range); + for(int i=0; i 1) { + List smallers = permutations(range, arity-1); + List result = new ArrayList<>(range*smallers.size()); + for(Tuple smaller : smallers) { + for(int i=0; i nPermutations(int arity, int n) { + int range = amountToRange(arity, n); + List permutations = permutations(range, arity); + return permutations.subList(0, n); + } + + public static List nRandoms(int arity, int n, int seed) { + int range = amountToRange(arity, n); + List permutations = new ArrayList<>(n); + Random r = new Random(seed); + for(int i = 0; i p = permutations(10, 2); + assertEquals(p.size(),10*10); + } +// private void printTuples(List p) { +// for(Tuple element : p) { +// System.out.println(element); +// } +// } + @Test + void nPermutationTest() { + final int amount = 500; + List p = nPermutations(2, amount); + assertEquals(amount,p.size()); + } + @Test + void nRandomTest() { + final int amount = 500; + List p = nRandoms(2, amount, 1);; + assertEquals(amount,p.size()); + } + private static double calculateHashClashes(List tuples, ContinousHashProvider chp) { + int sumClashes = 0; + + for(int i = 0; i chp, Tuple a, Tuple b) { + if(a.equals(b)) return 0; + final int bits = 5; + final int segments = Integer.SIZE/bits; + final int mask = (1<>(depth*5))&mask; + int bHash = (chp.getHash(b, index)>>(depth*5))&mask; + if(aHash != bHash) { + return i+1; + } + if(i>400) { + throw new IllegalStateException(a+" vs "+b); + } + } + } + private static double caclulateOptimalHashClash(int size) { + return (Math.log(size)/Math.log(32)); + } + public static void main(String[] args) { + List hashNames = 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}; + + System.out.println("Size,Arity,DataSource,Hash,Chashes,Optimal,Badness"); + for(int size : sizes) { + double optimalClashes = caclulateOptimalHashClash(size); + for(int arity : arities) { + List dataSourceNames = new LinkedList<>(); + List> dataSources = new LinkedList<>(); + +// dataSourceNames.add("Permutation"); +// dataSources.add(nPermutations(arity, size)); + dataSourceNames.add("Random"); + dataSources.add(nRandoms(arity, size, 0)); + + for(int dataSourceIndex = 0; dataSourceIndex minTest() { - return Stream.of( - Arguments.of(atMost(1), atMost(1), atMost(1)), - Arguments.of(atMost(1), between(2, 3), atMost(1)), - Arguments.of(atMost(1), atLeast(2), atMost(1)), - Arguments.of(atMost(1), ERROR, ERROR), - Arguments.of(atLeast(1), atLeast(2), atLeast(1)), - Arguments.of(atLeast(1), ERROR, ERROR), - Arguments.of(ERROR, atLeast(2), ERROR), - Arguments.of(ERROR, ERROR, ERROR) - ); - } - - @ParameterizedTest(name = "max({0}, {1}) == {2}") - @MethodSource - void maxTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { - assertThat(a.max(b), equalTo(expected)); - } - - static Stream maxTest() { - return Stream.of( - Arguments.of(atMost(1), atMost(1), atMost(1)), - Arguments.of(atMost(1), between(2, 3), between(2, 3)), - Arguments.of(atMost(1), atLeast(2), atLeast(2)), - Arguments.of(atMost(1), ERROR, ERROR), - Arguments.of(atLeast(1), atLeast(2), atLeast(2)), - Arguments.of(atLeast(1), ERROR, ERROR), - Arguments.of(ERROR, atLeast(2), ERROR), - Arguments.of(ERROR, ERROR, ERROR) - ); - } - - @ParameterizedTest(name = "{0} + {1} == {2}") - @MethodSource - void addTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { - assertThat(a.add(b), equalTo(expected)); - } - - static Stream addTest() { - return Stream.of( - Arguments.of(atMost(1), atMost(1), atMost(2)), - Arguments.of(atMost(1), between(2, 3), between(2, 4)), - Arguments.of(atMost(1), atLeast(2), atLeast(2)), - Arguments.of(atMost(1), ERROR, ERROR), - Arguments.of(atLeast(1), atLeast(2), atLeast(3)), - Arguments.of(atLeast(1), ERROR, ERROR), - Arguments.of(ERROR, atLeast(2), ERROR), - Arguments.of(ERROR, ERROR, ERROR) - ); - } - - @ParameterizedTest(name = "{0} * {1} == {2}") - @MethodSource - void multiplyTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { - assertThat(a.multiply(b), equalTo(expected)); - } - - static Stream multiplyTest() { - return Stream.of( - Arguments.of(between(2, 3), between(4, 5), between(8, 15)), - Arguments.of(atLeast(2), between(4, 5), atLeast(8)), - Arguments.of(between(2, 3), atLeast(4), atLeast(8)), - Arguments.of(between(2, 3), ERROR, ERROR), - Arguments.of(ERROR, between(4, 5), ERROR), - Arguments.of(ERROR, ERROR, ERROR) - ); - } - - @ParameterizedTest(name = "{0} /\\ {1} == {2}") - @MethodSource - void meetTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { - assertThat(a.meet(b), equalTo(expected)); - } - - static Stream meetTest() { - return Stream.of( - Arguments.of(atMost(1), atMost(2), atMost(1)), - Arguments.of(atMost(2), between(1, 3), between(1, 2)), - Arguments.of(atMost(1), between(1, 3), exactly(1)), - Arguments.of(atMost(1), between(2, 3), ERROR), - Arguments.of(atMost(1), ERROR, ERROR), - Arguments.of(ERROR, atMost(1), ERROR), - Arguments.of(ERROR, ERROR, ERROR) - ); - } - - @ParameterizedTest(name = "{0} \\/ {1} == {2}") - @MethodSource - void joinTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { - assertThat(a.join(b), equalTo(expected)); - } - - static Stream joinTest() { - return Stream.of( - Arguments.of(atMost(1), atMost(2), atMost(2)), - Arguments.of(atMost(2), between(1, 3), atMost(3)), - Arguments.of(atMost(1), between(2, 3), atMost(3)), - Arguments.of(atMost(1), ERROR, atMost(1)), - Arguments.of(ERROR, atMost(1), atMost(1)), - Arguments.of(ERROR, ERROR, ERROR) - ); - } -} diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervalsTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervalsTest.java deleted file mode 100644 index ef68a607..00000000 --- a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervalsTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package tools.refinery.store.model.representation.cardinality; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; - -class CardinalityIntervalsTest { - @Test - void betweenEmptyTest() { - var interval = CardinalityIntervals.between(2, 1); - assertThat(interval.isEmpty(), equalTo(true)); - } - - @Test - void betweenNegativeUpperBoundTest() { - var interval = CardinalityIntervals.between(0, -1); - assertThat(interval.upperBound(), equalTo(UpperCardinalities.UNBOUNDED)); - assertThat(interval.isEmpty(), equalTo(false)); - } -} diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/EmptyCardinalityIntervalTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/EmptyCardinalityIntervalTest.java deleted file mode 100644 index 41c884ec..00000000 --- a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/EmptyCardinalityIntervalTest.java +++ /dev/null @@ -1,14 +0,0 @@ -package tools.refinery.store.model.representation.cardinality; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.lessThan; - -class EmptyCardinalityIntervalTest { - @Test - void inconsistentBoundsTest() { - assertThat(CardinalityIntervals.ERROR.upperBound().compareToInt(CardinalityIntervals.ERROR.lowerBound()), - lessThan(0)); - } -} diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/FiniteCardinalityIntervalTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/FiniteCardinalityIntervalTest.java deleted file mode 100644 index dfb37786..00000000 --- a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/FiniteCardinalityIntervalTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package tools.refinery.store.model.representation.cardinality; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -class FiniteCardinalityIntervalTest { - @Test - void invalidLowerBoundConstructorTest() { - assertThrows(IllegalArgumentException.class, () -> new NonEmptyCardinalityInterval(-1, - UpperCardinalities.UNBOUNDED)); - } - - @Test - void invalidUpperBoundConstructorTest() { - var upperCardinality = UpperCardinality.of(1); - assertThrows(IllegalArgumentException.class, () -> new NonEmptyCardinalityInterval(2, - upperCardinality)); - } -} diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/FiniteUpperCardinalityTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/FiniteUpperCardinalityTest.java deleted file mode 100644 index 3f0f7a4a..00000000 --- a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/FiniteUpperCardinalityTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package tools.refinery.store.model.representation.cardinality; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -class FiniteUpperCardinalityTest { - @Test - void invalidConstructorTest() { - assertThrows(IllegalArgumentException.class, () -> new FiniteUpperCardinality(-1)); - } -} diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/UpperCardinalitiesTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/UpperCardinalitiesTest.java deleted file mode 100644 index 13171ae5..00000000 --- a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/UpperCardinalitiesTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package tools.refinery.store.model.representation.cardinality; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.instanceOf; - -class UpperCardinalitiesTest { - @ParameterizedTest - @ValueSource(ints = {0, 1, 255, 256, 1000, Integer.MAX_VALUE}) - void valueOfBoundedTest(int value) { - var upperCardinality = UpperCardinalities.valueOf(value); - assertThat(upperCardinality, instanceOf(FiniteUpperCardinality.class)); - assertThat(((FiniteUpperCardinality) upperCardinality).finiteUpperBound(), equalTo(value)); - } - - @Test - void valueOfUnboundedTest() { - var upperCardinality = UpperCardinalities.valueOf(-1); - assertThat(upperCardinality, instanceOf(UnboundedUpperCardinality.class)); - } -} diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/UpperCardinalityTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/UpperCardinalityTest.java deleted file mode 100644 index f5763c2d..00000000 --- a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/UpperCardinalityTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package tools.refinery.store.model.representation.cardinality; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.stream.Stream; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; - -class UpperCardinalityTest { - @ParameterizedTest(name = "min({0}, {1}) == {2}") - @MethodSource - void minTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { - assertThat(a.min(b), equalTo(expected)); - } - - static Stream minTest() { - return Stream.of( - Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)), - Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(0)), - Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(0)), - Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinality.of(0)), - Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinality.of(0)), - Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED) - ); - } - - @ParameterizedTest(name = "max({0}, {1}) == {2}") - @MethodSource - void maxTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { - assertThat(a.max(b), equalTo(expected)); - } - - static Stream maxTest() { - return Stream.of( - Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)), - Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(1)), - Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(1)), - Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), - Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinalities.UNBOUNDED), - Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED) - ); - } - - @ParameterizedTest(name = "{0} + {1} == {2}") - @MethodSource - void addTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { - assertThat(a.add(b), equalTo(expected)); - } - - static Stream addTest() { - return Stream.of( - Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(5)), - Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), - Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED), - Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED) - ); - } - - @ParameterizedTest(name = "{0} * {1} == {2}") - @MethodSource - void multiplyTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { - assertThat(a.multiply(b), equalTo(expected)); - } - - static Stream multiplyTest() { - return Stream.of( - Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(6)), - Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), - Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED), - Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED) - ); - } - - @ParameterizedTest(name = "{0}.compareTo({1}) == {2}") - @MethodSource - void compareToTest(UpperCardinality a, UpperCardinality b, int expected) { - assertThat(a.compareTo(b), equalTo(expected)); - } - - static Stream compareToTest() { - return Stream.of( - Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), 0), - Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), -1), - Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), 1), - Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, -1), - Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), 1), - Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, 0) - ); - } - - @ParameterizedTest(name = "{0}.compareToInt({1}) == {2}") - @MethodSource - void compareToIntTest(UpperCardinality a, int b, int expected) { - assertThat(a.compareToInt(b), equalTo(expected)); - } - - static Stream compareToIntTest() { - return Stream.of( - Arguments.of(UpperCardinality.of(3), -1, 1), - Arguments.of(UpperCardinality.of(3), 2, 1), - Arguments.of(UpperCardinality.of(3), 3, 0), - Arguments.of(UpperCardinality.of(3), 4, -1), - Arguments.of(UpperCardinalities.UNBOUNDED, -1, 1), - Arguments.of(UpperCardinalities.UNBOUNDED, 3, 1) - ); - } -} 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 7a89daeb..e7bff1f9 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 @@ -1,145 +1,149 @@ package tools.refinery.store.model.tests; import org.junit.jupiter.api.Test; -import tools.refinery.store.model.Model; import tools.refinery.store.model.ModelStore; -import tools.refinery.store.model.ModelStoreImpl; -import tools.refinery.store.model.representation.Relation; +import tools.refinery.store.representation.Symbol; import tools.refinery.store.tuple.Tuple; -import java.util.Set; - import static org.junit.jupiter.api.Assertions.*; class ModelTest { - @Test void modelConstructionTest() { - Relation person = new Relation<>("Person", 1, Boolean.class, false); - Relation friend = new Relation<>("friend", 2, Boolean.class, false); + var person = new Symbol<>("Person", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, Boolean.class, false); - ModelStore store = new ModelStoreImpl(Set.of(person, friend)); - Model model = store.createModel(); + var store = ModelStore.builder().symbols(person, friend).build(); + var symbols = store.getSymbols(); - assertTrue(store.getDataRepresentations().contains(person)); - assertTrue(store.getDataRepresentations().contains(friend)); - assertTrue(model.getDataRepresentations().contains(person)); - assertTrue(model.getDataRepresentations().contains(friend)); + assertTrue(symbols.contains(person)); + assertTrue(symbols.contains(friend)); - Relation other = new Relation("other", 2, Integer.class, null); - assertFalse(model.getDataRepresentations().contains(other)); + var other = new Symbol<>("other", 2, Integer.class, null); + assertFalse(symbols.contains(other)); } @Test void modelBuildingTest() { - Relation person = new Relation<>("Person", 1, Boolean.class, false); - Relation age = new Relation("age", 1, Integer.class, null); - Relation friend = new Relation<>("friend", 2, Boolean.class, false); - - ModelStore store = new ModelStoreImpl(Set.of(person, age, friend)); - Model model = store.createModel(); - - model.put(person, Tuple.of(0), true); - model.put(person, Tuple.of(1), true); - model.put(age, Tuple.of(0), 3); - model.put(age, Tuple.of(1), 1); - model.put(friend, Tuple.of(0, 1), true); - model.put(friend, Tuple.of(1, 0), true); - - assertTrue(model.get(person, Tuple.of(0))); - assertTrue(model.get(person, Tuple.of(1))); - assertFalse(model.get(person, Tuple.of(2))); - - assertEquals(3, model.get(age, Tuple.of(0))); - assertEquals(1, model.get(age, Tuple.of(1))); - assertNull(model.get(age, Tuple.of(2))); - - assertTrue(model.get(friend, Tuple.of(0, 1))); - assertFalse(model.get(friend, Tuple.of(0, 5))); + var person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var friend = new Symbol<>("friend", 2, Boolean.class, false); + + var store = ModelStore.builder().symbols(person, age, friend).build(); + var model = store.createModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var friendInterpretation = model.getInterpretation(friend); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + ageInterpretation.put(Tuple.of(0), 3); + ageInterpretation.put(Tuple.of(1), 1); + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 0), true); + + assertTrue(personInterpretation.get(Tuple.of(0))); + assertTrue(personInterpretation.get(Tuple.of(1))); + assertFalse(personInterpretation.get(Tuple.of(2))); + + assertEquals(3, ageInterpretation.get(Tuple.of(0))); + assertEquals(1, ageInterpretation.get(Tuple.of(1))); + assertNull(ageInterpretation.get( Tuple.of(2))); + + assertTrue(friendInterpretation.get(Tuple.of(0, 1))); + assertFalse(friendInterpretation.get(Tuple.of(0, 5))); } @Test void modelBuildingArityFailTest() { - Relation person = new Relation<>("Person", 1, Boolean.class, false); + var person = new Symbol<>("Person", 1, Boolean.class, false); - ModelStore store = new ModelStoreImpl(Set.of(person)); - Model model = store.createModel(); + var store = ModelStore.builder().symbols(person).build(); + var model = store.createModel(); + var personInterpretation = model.getInterpretation(person); final Tuple tuple3 = Tuple.of(1, 1, 1); - assertThrows(IllegalArgumentException.class, () -> model.put(person, tuple3, true)); - assertThrows(IllegalArgumentException.class, () -> model.get(person, tuple3)); + assertThrows(IllegalArgumentException.class, () -> personInterpretation.put(tuple3, true)); + assertThrows(IllegalArgumentException.class, () -> personInterpretation.get(tuple3)); } @Test void modelBuildingNullFailTest() { - Relation age = new Relation("age", 1, Integer.class, null); - ModelStore store = new ModelStoreImpl(Set.of(age)); - Model model = store.createModel(); + var age = new Symbol<>("age", 1, Integer.class, null); + + var store = ModelStore.builder().symbols(age).build(); + var model = store.createModel(); + var ageInterpretation = model.getInterpretation(age); - model.put(age, Tuple.of(1), null); // valid - assertThrows(IllegalArgumentException.class, () -> model.put(age, null, 1)); - assertThrows(IllegalArgumentException.class, () -> model.get(age, null)); + ageInterpretation.put(Tuple.of(1), null); // valid + assertThrows(IllegalArgumentException.class, () -> ageInterpretation.put(null, 1)); + assertThrows(IllegalArgumentException.class, () -> ageInterpretation.get(null)); } @Test void modelUpdateTest() { - Relation person = new Relation<>("Person", 1, Boolean.class, false); - Relation age = new Relation("age", 1, Integer.class, null); - Relation friend = new Relation<>("friend", 2, Boolean.class, false); - - ModelStore store = new ModelStoreImpl(Set.of(person, age, friend)); - Model model = store.createModel(); - - model.put(person, Tuple.of(0), true); - model.put(person, Tuple.of(1), true); - model.put(age, Tuple.of(0), 3); - model.put(age, Tuple.of(1), 1); - model.put(friend, Tuple.of(0, 1), true); - model.put(friend, Tuple.of(1, 0), true); - - assertEquals(3, model.get(age, Tuple.of(0))); - assertTrue(model.get(friend, Tuple.of(0, 1))); - - model.put(age, Tuple.of(0), 4); - model.put(friend, Tuple.of(0, 1), false); - - assertEquals(4, model.get(age, Tuple.of(0))); - assertFalse(model.get(friend, Tuple.of(0, 1))); + var person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var friend = new Symbol<>("friend", 2, Boolean.class, false); + + var store = ModelStore.builder().symbols(person, age, friend).build(); + var model = store.createModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var friendInterpretation = model.getInterpretation(friend); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + ageInterpretation.put(Tuple.of(0), 3); + ageInterpretation.put(Tuple.of(1), 1); + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 0), true); + + assertEquals(3, ageInterpretation.get(Tuple.of(0))); + assertTrue(friendInterpretation.get(Tuple.of(0, 1))); + + ageInterpretation.put(Tuple.of(0), 4); + friendInterpretation.put(Tuple.of(0, 1), false); + + assertEquals(4, ageInterpretation.get(Tuple.of(0))); + assertFalse(friendInterpretation.get(Tuple.of(0, 1))); } @Test void restoreTest() { - Relation person = new Relation("Person", 1, Boolean.class, false); - Relation friend = new Relation("friend", 2, Boolean.class, false); - - ModelStore store = new ModelStoreImpl(Set.of(person, friend)); - Model model = store.createModel(); - - model.put(person, Tuple.of(0), true); - model.put(person, Tuple.of(1), true); - model.put(friend, Tuple.of(0, 1), true); - model.put(friend, Tuple.of(1, 0), true); + var person = new Symbol<>("Person", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, Boolean.class, false); + + var store = ModelStore.builder().symbols(person, friend).build(); + var model = store.createModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + friendInterpretation.put(Tuple.of(0, 1), true); + friendInterpretation.put(Tuple.of(1, 0), true); long state1 = model.commit(); - assertFalse(model.get(person, Tuple.of(2))); - assertFalse(model.get(friend, Tuple.of(0, 2))); + assertFalse(personInterpretation.get(Tuple.of(2))); + assertFalse(friendInterpretation.get(Tuple.of(0, 2))); - model.put(person, Tuple.of(2), true); - model.put(friend, Tuple.of(0, 2), true); + personInterpretation.put(Tuple.of(2), true); + friendInterpretation.put(Tuple.of(0, 2), true); long state2 = model.commit(); - assertTrue(model.get(person, Tuple.of(2))); - assertTrue(model.get(friend, Tuple.of(0, 2))); + assertTrue(personInterpretation.get(Tuple.of(2))); + assertTrue(friendInterpretation.get(Tuple.of(0, 2))); model.restore(state1); - assertFalse(model.get(person, Tuple.of(2))); - assertFalse(model.get(friend, Tuple.of(0, 2))); + assertFalse(personInterpretation.get(Tuple.of(2))); + assertFalse(friendInterpretation.get(Tuple.of(0, 2))); model.restore(state2); - assertTrue(model.get(person, Tuple.of(2))); - assertTrue(model.get(friend, Tuple.of(0, 2))); + assertTrue(personInterpretation.get(Tuple.of(2))); + assertTrue(friendInterpretation.get(Tuple.of(0, 2))); } } diff --git a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalTest.java b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalTest.java new file mode 100644 index 00000000..96fdc49e --- /dev/null +++ b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalTest.java @@ -0,0 +1,123 @@ +package tools.refinery.store.representation.cardinality; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import tools.refinery.store.representation.cardinality.CardinalityInterval; + +import java.util.stream.Stream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static tools.refinery.store.representation.cardinality.CardinalityIntervals.*; + +class CardinalityIntervalTest { + @ParameterizedTest(name = "min({0}, {1}) == {2}") + @MethodSource + void minTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { + assertThat(a.min(b), equalTo(expected)); + } + + static Stream minTest() { + return Stream.of( + Arguments.of(atMost(1), atMost(1), atMost(1)), + Arguments.of(atMost(1), between(2, 3), atMost(1)), + Arguments.of(atMost(1), atLeast(2), atMost(1)), + Arguments.of(atMost(1), ERROR, ERROR), + Arguments.of(atLeast(1), atLeast(2), atLeast(1)), + Arguments.of(atLeast(1), ERROR, ERROR), + Arguments.of(ERROR, atLeast(2), ERROR), + Arguments.of(ERROR, ERROR, ERROR) + ); + } + + @ParameterizedTest(name = "max({0}, {1}) == {2}") + @MethodSource + void maxTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { + assertThat(a.max(b), equalTo(expected)); + } + + static Stream maxTest() { + return Stream.of( + Arguments.of(atMost(1), atMost(1), atMost(1)), + Arguments.of(atMost(1), between(2, 3), between(2, 3)), + Arguments.of(atMost(1), atLeast(2), atLeast(2)), + Arguments.of(atMost(1), ERROR, ERROR), + Arguments.of(atLeast(1), atLeast(2), atLeast(2)), + Arguments.of(atLeast(1), ERROR, ERROR), + Arguments.of(ERROR, atLeast(2), ERROR), + Arguments.of(ERROR, ERROR, ERROR) + ); + } + + @ParameterizedTest(name = "{0} + {1} == {2}") + @MethodSource + void addTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { + assertThat(a.add(b), equalTo(expected)); + } + + static Stream addTest() { + return Stream.of( + Arguments.of(atMost(1), atMost(1), atMost(2)), + Arguments.of(atMost(1), between(2, 3), between(2, 4)), + Arguments.of(atMost(1), atLeast(2), atLeast(2)), + Arguments.of(atMost(1), ERROR, ERROR), + Arguments.of(atLeast(1), atLeast(2), atLeast(3)), + Arguments.of(atLeast(1), ERROR, ERROR), + Arguments.of(ERROR, atLeast(2), ERROR), + Arguments.of(ERROR, ERROR, ERROR) + ); + } + + @ParameterizedTest(name = "{0} * {1} == {2}") + @MethodSource + void multiplyTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { + assertThat(a.multiply(b), equalTo(expected)); + } + + static Stream multiplyTest() { + return Stream.of( + Arguments.of(between(2, 3), between(4, 5), between(8, 15)), + Arguments.of(atLeast(2), between(4, 5), atLeast(8)), + Arguments.of(between(2, 3), atLeast(4), atLeast(8)), + Arguments.of(between(2, 3), ERROR, ERROR), + Arguments.of(ERROR, between(4, 5), ERROR), + Arguments.of(ERROR, ERROR, ERROR) + ); + } + + @ParameterizedTest(name = "{0} /\\ {1} == {2}") + @MethodSource + void meetTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { + assertThat(a.meet(b), equalTo(expected)); + } + + static Stream meetTest() { + return Stream.of( + Arguments.of(atMost(1), atMost(2), atMost(1)), + Arguments.of(atMost(2), between(1, 3), between(1, 2)), + Arguments.of(atMost(1), between(1, 3), exactly(1)), + Arguments.of(atMost(1), between(2, 3), ERROR), + Arguments.of(atMost(1), ERROR, ERROR), + Arguments.of(ERROR, atMost(1), ERROR), + Arguments.of(ERROR, ERROR, ERROR) + ); + } + + @ParameterizedTest(name = "{0} \\/ {1} == {2}") + @MethodSource + void joinTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) { + assertThat(a.join(b), equalTo(expected)); + } + + static Stream joinTest() { + return Stream.of( + Arguments.of(atMost(1), atMost(2), atMost(2)), + Arguments.of(atMost(2), between(1, 3), atMost(3)), + Arguments.of(atMost(1), between(2, 3), atMost(3)), + Arguments.of(atMost(1), ERROR, atMost(1)), + Arguments.of(ERROR, atMost(1), atMost(1)), + Arguments.of(ERROR, ERROR, ERROR) + ); + } +} diff --git a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalsTest.java b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalsTest.java new file mode 100644 index 00000000..4a9ef8da --- /dev/null +++ b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalsTest.java @@ -0,0 +1,23 @@ +package tools.refinery.store.representation.cardinality; + +import org.junit.jupiter.api.Test; +import tools.refinery.store.representation.cardinality.CardinalityIntervals; +import tools.refinery.store.representation.cardinality.UpperCardinalities; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +class CardinalityIntervalsTest { + @Test + void betweenEmptyTest() { + var interval = CardinalityIntervals.between(2, 1); + assertThat(interval.isEmpty(), equalTo(true)); + } + + @Test + void betweenNegativeUpperBoundTest() { + var interval = CardinalityIntervals.between(0, -1); + assertThat(interval.upperBound(), equalTo(UpperCardinalities.UNBOUNDED)); + assertThat(interval.isEmpty(), equalTo(false)); + } +} diff --git a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/EmptyCardinalityIntervalTest.java b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/EmptyCardinalityIntervalTest.java new file mode 100644 index 00000000..e8b77b9f --- /dev/null +++ b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/EmptyCardinalityIntervalTest.java @@ -0,0 +1,15 @@ +package tools.refinery.store.representation.cardinality; + +import org.junit.jupiter.api.Test; +import tools.refinery.store.representation.cardinality.CardinalityIntervals; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.lessThan; + +class EmptyCardinalityIntervalTest { + @Test + void inconsistentBoundsTest() { + assertThat(CardinalityIntervals.ERROR.upperBound().compareToInt(CardinalityIntervals.ERROR.lowerBound()), + lessThan(0)); + } +} diff --git a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteCardinalityIntervalTest.java b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteCardinalityIntervalTest.java new file mode 100644 index 00000000..9a190818 --- /dev/null +++ b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteCardinalityIntervalTest.java @@ -0,0 +1,23 @@ +package tools.refinery.store.representation.cardinality; + +import org.junit.jupiter.api.Test; +import tools.refinery.store.representation.cardinality.NonEmptyCardinalityInterval; +import tools.refinery.store.representation.cardinality.UpperCardinalities; +import tools.refinery.store.representation.cardinality.UpperCardinality; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +class FiniteCardinalityIntervalTest { + @Test + void invalidLowerBoundConstructorTest() { + assertThrows(IllegalArgumentException.class, () -> new NonEmptyCardinalityInterval(-1, + UpperCardinalities.UNBOUNDED)); + } + + @Test + void invalidUpperBoundConstructorTest() { + var upperCardinality = UpperCardinality.of(1); + assertThrows(IllegalArgumentException.class, () -> new NonEmptyCardinalityInterval(2, + upperCardinality)); + } +} diff --git a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinalityTest.java b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinalityTest.java new file mode 100644 index 00000000..90c21759 --- /dev/null +++ b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinalityTest.java @@ -0,0 +1,13 @@ +package tools.refinery.store.representation.cardinality; + +import org.junit.jupiter.api.Test; +import tools.refinery.store.representation.cardinality.FiniteUpperCardinality; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +class FiniteUpperCardinalityTest { + @Test + void invalidConstructorTest() { + assertThrows(IllegalArgumentException.class, () -> new FiniteUpperCardinality(-1)); + } +} diff --git a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalitiesTest.java b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalitiesTest.java new file mode 100644 index 00000000..3c7c0320 --- /dev/null +++ b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalitiesTest.java @@ -0,0 +1,28 @@ +package tools.refinery.store.representation.cardinality; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import tools.refinery.store.representation.cardinality.FiniteUpperCardinality; +import tools.refinery.store.representation.cardinality.UnboundedUpperCardinality; +import tools.refinery.store.representation.cardinality.UpperCardinalities; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; + +class UpperCardinalitiesTest { + @ParameterizedTest + @ValueSource(ints = {0, 1, 255, 256, 1000, Integer.MAX_VALUE}) + void valueOfBoundedTest(int value) { + var upperCardinality = UpperCardinalities.valueOf(value); + assertThat(upperCardinality, instanceOf(FiniteUpperCardinality.class)); + assertThat(((FiniteUpperCardinality) upperCardinality).finiteUpperBound(), equalTo(value)); + } + + @Test + void valueOfUnboundedTest() { + var upperCardinality = UpperCardinalities.valueOf(-1); + assertThat(upperCardinality, instanceOf(UnboundedUpperCardinality.class)); + } +} diff --git a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalityTest.java b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalityTest.java new file mode 100644 index 00000000..e87ce29b --- /dev/null +++ b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalityTest.java @@ -0,0 +1,112 @@ +package tools.refinery.store.representation.cardinality; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import tools.refinery.store.representation.cardinality.UpperCardinalities; +import tools.refinery.store.representation.cardinality.UpperCardinality; + +import java.util.stream.Stream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +class UpperCardinalityTest { + @ParameterizedTest(name = "min({0}, {1}) == {2}") + @MethodSource + void minTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { + assertThat(a.min(b), equalTo(expected)); + } + + static Stream minTest() { + return Stream.of( + Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)), + Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(0)), + Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(0)), + Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinality.of(0)), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinality.of(0)), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED) + ); + } + + @ParameterizedTest(name = "max({0}, {1}) == {2}") + @MethodSource + void maxTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { + assertThat(a.max(b), equalTo(expected)); + } + + static Stream maxTest() { + return Stream.of( + Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)), + Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(1)), + Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(1)), + Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED) + ); + } + + @ParameterizedTest(name = "{0} + {1} == {2}") + @MethodSource + void addTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { + assertThat(a.add(b), equalTo(expected)); + } + + static Stream addTest() { + return Stream.of( + Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(5)), + Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED) + ); + } + + @ParameterizedTest(name = "{0} * {1} == {2}") + @MethodSource + void multiplyTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { + assertThat(a.multiply(b), equalTo(expected)); + } + + static Stream multiplyTest() { + return Stream.of( + Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(6)), + Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED) + ); + } + + @ParameterizedTest(name = "{0}.compareTo({1}) == {2}") + @MethodSource + void compareToTest(UpperCardinality a, UpperCardinality b, int expected) { + assertThat(a.compareTo(b), equalTo(expected)); + } + + static Stream compareToTest() { + return Stream.of( + Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), 0), + Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), -1), + Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), 1), + Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, -1), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), 1), + Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, 0) + ); + } + + @ParameterizedTest(name = "{0}.compareToInt({1}) == {2}") + @MethodSource + void compareToIntTest(UpperCardinality a, int b, int expected) { + assertThat(a.compareToInt(b), equalTo(expected)); + } + + static Stream compareToIntTest() { + return Stream.of( + Arguments.of(UpperCardinality.of(3), -1, 1), + Arguments.of(UpperCardinality.of(3), 2, 1), + Arguments.of(UpperCardinality.of(3), 3, 0), + Arguments.of(UpperCardinality.of(3), 4, -1), + Arguments.of(UpperCardinalities.UNBOUNDED, -1, 1), + Arguments.of(UpperCardinalities.UNBOUNDED, 3, 1) + ); + } +} -- cgit v1.2.3-70-g09d2