aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store-query/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/store-query/src/main/java')
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java72
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java26
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AbstractQueryBuilder.java175
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java324
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java213
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java262
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java28
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java24
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java20
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java99
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java179
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java27
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java66
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java52
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback0.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data0.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data1.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data0.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data1.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data2.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data0.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data1.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data2.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data3.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data0.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data1.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data2.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data3.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data4.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback0.java14
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback1.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback2.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback3.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback4.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback0.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback1.java14
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback2.java14
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback3.java14
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback4.java14
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java78
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java54
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java143
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java138
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java79
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java83
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java63
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java130
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java37
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java10
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java65
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java101
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java81
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Reduction.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java63
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java49
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java80
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSetListener.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java18
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java20
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java23
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/SubstitutionBuilder.java79
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java41
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java18
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java49
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java113
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java71
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java82
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java108
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java60
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java60
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java28
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java25
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java26
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java79
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java84
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolAndTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolBinaryTerm.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolOrTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java35
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolXorTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/ComparisonTerm.java19
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/EqTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterEqTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessEqTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/NotEqTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntAddTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntBinaryTerm.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntDivTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMaxTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinusTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMulTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPlusTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPowTerm.java43
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSubTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java40
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java94
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntUnaryTerm.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealAddTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealBinaryTerm.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealDivTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMaxTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinusTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMulTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPlusTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPowTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSubTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java90
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java94
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealUnaryTerm.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityAddTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityBinaryTerm.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMaxTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMinTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMulTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java86
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTerms.java73
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java754
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/MapBasedValuation.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java37
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/ValuationBuilder.java40
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java110
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java73
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenView.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java36
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyView.java41
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayView.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustView.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/NodeFunctionView.java20
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/SymbolView.java85
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java82
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/ViewImplication.java23
155 files changed, 7679 insertions, 0 deletions
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java
new file mode 100644
index 00000000..916fb35c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java
@@ -0,0 +1,72 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.literal.*;
10import tools.refinery.store.query.term.*;
11
12import java.util.List;
13
14public interface Constraint {
15 String name();
16
17 List<Parameter> getParameters();
18
19 default int arity() {
20 return getParameters().size();
21 }
22
23 default boolean invalidIndex(int i) {
24 return i < 0 || i >= arity();
25 }
26
27 default Reduction getReduction() {
28 return Reduction.NOT_REDUCIBLE;
29 }
30
31 default boolean equals(LiteralEqualityHelper helper, Constraint other) {
32 return equals(other);
33 }
34
35 default String toReferenceString() {
36 return name();
37 }
38
39 default CallLiteral call(CallPolarity polarity, List<Variable> arguments) {
40 return new CallLiteral(polarity, this, arguments);
41 }
42
43 default CallLiteral call(CallPolarity polarity, Variable... arguments) {
44 return call(polarity, List.of(arguments));
45 }
46
47 default CallLiteral call(Variable... arguments) {
48 return call(CallPolarity.POSITIVE, arguments);
49 }
50
51 default CallLiteral callTransitive(NodeVariable left, NodeVariable right) {
52 return call(CallPolarity.TRANSITIVE, List.of(left, right));
53 }
54
55 default AssignedValue<Integer> count(List<Variable> arguments) {
56 return targetVariable -> new CountLiteral(targetVariable, this, arguments);
57 }
58
59 default AssignedValue<Integer> count(Variable... arguments) {
60 return count(List.of(arguments));
61 }
62
63 default <R, T> AssignedValue<R> aggregateBy(DataVariable<T> inputVariable, Aggregator<R, T> aggregator,
64 List<Variable> arguments) {
65 return targetVariable -> new AggregationLiteral<>(targetVariable, aggregator, inputVariable, this, arguments);
66 }
67
68 default <R, T> AssignedValue<R> aggregateBy(DataVariable<T> inputVariable, Aggregator<R, T> aggregator,
69 Variable... arguments) {
70 return aggregateBy(inputVariable, aggregator, List.of(arguments));
71 }
72}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java
new file mode 100644
index 00000000..1fa96a07
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java
@@ -0,0 +1,26 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query;
7
8import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.query.dnf.AnyQuery;
10import tools.refinery.store.query.dnf.Query;
11import tools.refinery.store.query.resultset.AnyResultSet;
12import tools.refinery.store.query.resultset.ResultSet;
13
14public interface ModelQueryAdapter extends ModelAdapter {
15 ModelQueryStoreAdapter getStoreAdapter();
16
17 default AnyResultSet getResultSet(AnyQuery query) {
18 return getResultSet((Query<?>) query);
19 }
20
21 <T> ResultSet<T> getResultSet(Query<T> query);
22
23 boolean hasPendingChanges();
24
25 void flushChanges();
26}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java
new file mode 100644
index 00000000..c62a95b5
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query;
7
8import tools.refinery.store.adapter.ModelAdapterBuilder;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.dnf.AnyQuery;
11
12import java.util.Collection;
13import java.util.List;
14
15@SuppressWarnings("UnusedReturnValue")
16public interface ModelQueryBuilder extends ModelAdapterBuilder {
17 default ModelQueryBuilder queries(AnyQuery... queries) {
18 return queries(List.of(queries));
19 }
20
21 default ModelQueryBuilder queries(Collection<? extends AnyQuery> queries) {
22 queries.forEach(this::query);
23 return this;
24 }
25
26 ModelQueryBuilder query(AnyQuery query);
27
28 @Override
29 ModelQueryStoreAdapter build(ModelStore store);
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java
new file mode 100644
index 00000000..f0a950a6
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query;
7
8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.query.dnf.AnyQuery;
11import tools.refinery.store.query.view.AnySymbolView;
12
13import java.util.Collection;
14
15public interface ModelQueryStoreAdapter extends ModelStoreAdapter {
16 Collection<AnySymbolView> getSymbolViews();
17
18 Collection<AnyQuery> getQueries();
19
20 @Override
21 ModelQueryAdapter createModelAdapter(Model model);
22}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AbstractQueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AbstractQueryBuilder.java
new file mode 100644
index 00000000..2a3e3ce0
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AbstractQueryBuilder.java
@@ -0,0 +1,175 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.dnf.callback.*;
9import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.Collection;
15import java.util.List;
16import java.util.Set;
17
18public abstract class AbstractQueryBuilder<T extends AbstractQueryBuilder<T>> {
19 protected final DnfBuilder dnfBuilder;
20
21 protected AbstractQueryBuilder(DnfBuilder dnfBuilder) {
22 this.dnfBuilder = dnfBuilder;
23 }
24
25 protected abstract T self();
26
27 public NodeVariable parameter() {
28 return dnfBuilder.parameter();
29 }
30
31 public NodeVariable parameter(String name) {
32 return dnfBuilder.parameter(name);
33 }
34
35 public NodeVariable parameter(ParameterDirection direction) {
36 return dnfBuilder.parameter(direction);
37 }
38
39 public NodeVariable parameter(String name, ParameterDirection direction) {
40 return dnfBuilder.parameter(name, direction);
41 }
42
43 public T parameter(NodeVariable variable) {
44 dnfBuilder.parameter(variable);
45 return self();
46 }
47
48 public T parameter(NodeVariable variable, ParameterDirection direction) {
49 dnfBuilder.parameter(variable, direction);
50 return self();
51 }
52
53 public T parameters(NodeVariable... variables) {
54 dnfBuilder.parameters(variables);
55 return self();
56 }
57
58 public T parameters(List<NodeVariable> variables) {
59 dnfBuilder.parameters(variables);
60 return self();
61 }
62
63 public T parameters(List<NodeVariable> variables, ParameterDirection direction) {
64 dnfBuilder.parameters(variables, direction);
65 return self();
66 }
67
68 public T symbolicParameters(List<SymbolicParameter> parameters) {
69 dnfBuilder.symbolicParameters(parameters);
70 return self();
71 }
72
73 public T functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) {
74 dnfBuilder.functionalDependencies(functionalDependencies);
75 return self();
76 }
77
78 public T functionalDependency(FunctionalDependency<Variable> functionalDependency) {
79 dnfBuilder.functionalDependency(functionalDependency);
80 return self();
81 }
82
83 public T functionalDependency(Set<? extends Variable> forEach, Set<? extends Variable> unique) {
84 dnfBuilder.functionalDependency(forEach, unique);
85 return self();
86 }
87
88 public T clause(ClauseCallback0 callback) {
89 dnfBuilder.clause(callback);
90 return self();
91 }
92
93 public T clause(ClauseCallback1Data0 callback) {
94 dnfBuilder.clause(callback);
95 return self();
96 }
97
98 public <U1> T clause(Class<U1> type1, ClauseCallback1Data1<U1> callback) {
99 dnfBuilder.clause(type1, callback);
100 return self();
101 }
102
103 public T clause(ClauseCallback2Data0 callback) {
104 dnfBuilder.clause(callback);
105 return self();
106 }
107
108 public <U1> T clause(Class<U1> type1, ClauseCallback2Data1<U1> callback) {
109 dnfBuilder.clause(type1, callback);
110 return self();
111 }
112
113 public <U1, U2> T clause(Class<U1> type1, Class<U2> type2, ClauseCallback2Data2<U1, U2> callback) {
114 dnfBuilder.clause(type1, type2, callback);
115 return self();
116 }
117
118 public T clause(ClauseCallback3Data0 callback) {
119 dnfBuilder.clause(callback);
120 return self();
121 }
122
123 public <U1> T clause(Class<U1> type1, ClauseCallback3Data1<U1> callback) {
124 dnfBuilder.clause(type1, callback);
125 return self();
126 }
127
128 public <U1, U2> T clause(Class<U1> type1, Class<U2> type2, ClauseCallback3Data2<U1, U2> callback) {
129 dnfBuilder.clause(type1, type2, callback);
130 return self();
131 }
132
133 public <U1, U2, U3> T clause(Class<U1> type1, Class<U2> type2, Class<U3> type3,
134 ClauseCallback3Data3<U1, U2, U3> callback) {
135 dnfBuilder.clause(type1, type2, type3, callback);
136 return self();
137 }
138
139 public T clause(ClauseCallback4Data0 callback) {
140 dnfBuilder.clause(callback);
141 return self();
142 }
143
144 public <U1> T clause(Class<U1> type1, ClauseCallback4Data1<U1> callback) {
145 dnfBuilder.clause(type1, callback);
146 return self();
147 }
148
149 public <U1, U2> T clause(Class<U1> type1, Class<U2> type2, ClauseCallback4Data2<U1, U2> callback) {
150 dnfBuilder.clause(type1, type2, callback);
151 return self();
152 }
153
154 public <U1, U2, U3> T clause(Class<U1> type1, Class<U2> type2, Class<U3> type3,
155 ClauseCallback4Data3<U1, U2, U3> callback) {
156 dnfBuilder.clause(type1, type2, type3, callback);
157 return self();
158 }
159
160 public <U1, U2, U3, U4> T clause(Class<U1> type1, Class<U2> type2, Class<U3> type3, Class<U4> type4,
161 ClauseCallback4Data4<U1, U2, U3, U4> callback) {
162 dnfBuilder.clause(type1, type2, type3, type4, callback);
163 return self();
164 }
165
166 public T clause(Literal... literals) {
167 dnfBuilder.clause(literals);
168 return self();
169 }
170
171 public T clause(Collection<? extends Literal> literals) {
172 dnfBuilder.clause(literals);
173 return self();
174 }
175}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java
new file mode 100644
index 00000000..5e28af68
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8public sealed interface AnyQuery permits Query {
9 String name();
10
11 int arity();
12
13 Class<?> valueType();
14
15 Dnf getDnf();
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java
new file mode 100644
index 00000000..b5e7092b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java
@@ -0,0 +1,324 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import org.jetbrains.annotations.NotNull;
9import tools.refinery.store.query.literal.BooleanLiteral;
10import tools.refinery.store.query.literal.EquivalenceLiteral;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.query.substitution.MapBasedSubstitution;
13import tools.refinery.store.query.substitution.StatelessSubstitution;
14import tools.refinery.store.query.substitution.Substitution;
15import tools.refinery.store.query.term.NodeVariable;
16import tools.refinery.store.query.term.ParameterDirection;
17import tools.refinery.store.query.term.Variable;
18
19import java.util.*;
20import java.util.function.Function;
21
22class ClausePostProcessor {
23 private final Map<Variable, ParameterInfo> parameters;
24 private final List<Literal> literals;
25 private final Map<NodeVariable, NodeVariable> representatives = new LinkedHashMap<>();
26 private final Map<NodeVariable, Set<NodeVariable>> equivalencePartition = new HashMap<>();
27 private List<Literal> substitutedLiterals;
28 private final Set<Variable> existentiallyQuantifiedVariables = new LinkedHashSet<>();
29 private Set<Variable> positiveVariables;
30 private Map<Variable, Set<SortableLiteral>> variableToLiteralInputMap;
31 private PriorityQueue<SortableLiteral> literalsWithAllInputsBound;
32 private LinkedHashSet<Literal> topologicallySortedLiterals;
33
34 public ClausePostProcessor(Map<Variable, ParameterInfo> parameters, List<Literal> literals) {
35 this.parameters = parameters;
36 this.literals = literals;
37 }
38
39 public Result postProcessClause() {
40 mergeEquivalentNodeVariables();
41 substitutedLiterals = new ArrayList<>(literals.size());
42 keepParameterEquivalences();
43 substituteLiterals();
44 computeExistentiallyQuantifiedVariables();
45 computePositiveVariables();
46 validatePositiveRepresentatives();
47 validatePrivateVariables();
48 topologicallySortLiterals();
49 var filteredLiterals = new ArrayList<Literal>(topologicallySortedLiterals.size());
50 for (var literal : topologicallySortedLiterals) {
51 var reducedLiteral = literal.reduce();
52 if (BooleanLiteral.FALSE.equals(reducedLiteral)) {
53 return ConstantResult.ALWAYS_FALSE;
54 } else if (!BooleanLiteral.TRUE.equals(reducedLiteral)) {
55 filteredLiterals.add(reducedLiteral);
56 }
57 }
58 if (filteredLiterals.isEmpty()) {
59 return ConstantResult.ALWAYS_TRUE;
60 }
61 var clause = new DnfClause(Collections.unmodifiableSet(positiveVariables),
62 Collections.unmodifiableList(filteredLiterals));
63 return new ClauseResult(clause);
64 }
65
66 private void mergeEquivalentNodeVariables() {
67 for (var literal : literals) {
68 if (isPositiveEquivalence(literal)) {
69 var equivalenceLiteral = (EquivalenceLiteral) literal;
70 mergeVariables(equivalenceLiteral.left(), equivalenceLiteral.right());
71 }
72 }
73 }
74
75 private static boolean isPositiveEquivalence(Literal literal) {
76 return literal instanceof EquivalenceLiteral equivalenceLiteral && equivalenceLiteral.positive();
77 }
78
79 private void mergeVariables(NodeVariable left, NodeVariable right) {
80 var leftRepresentative = getRepresentative(left);
81 var rightRepresentative = getRepresentative(right);
82 var leftInfo = parameters.get(leftRepresentative);
83 var rightInfo = parameters.get(rightRepresentative);
84 if (leftInfo != null && (rightInfo == null || leftInfo.index() <= rightInfo.index())) {
85 // Prefer the variable occurring earlier in the parameter list as a representative.
86 doMergeVariables(leftRepresentative, rightRepresentative);
87 } else {
88 doMergeVariables(rightRepresentative, leftRepresentative);
89 }
90 }
91
92 private void doMergeVariables(NodeVariable parentRepresentative, NodeVariable newChildRepresentative) {
93 var parentSet = getEquivalentVariables(parentRepresentative);
94 var childSet = getEquivalentVariables(newChildRepresentative);
95 parentSet.addAll(childSet);
96 equivalencePartition.remove(newChildRepresentative);
97 for (var childEquivalentNodeVariable : childSet) {
98 representatives.put(childEquivalentNodeVariable, parentRepresentative);
99 }
100 }
101
102 private NodeVariable getRepresentative(NodeVariable variable) {
103 return representatives.computeIfAbsent(variable, Function.identity());
104 }
105
106 private Set<NodeVariable> getEquivalentVariables(NodeVariable variable) {
107 var representative = getRepresentative(variable);
108 if (!representative.equals(variable)) {
109 throw new AssertionError("NodeVariable %s already has a representative %s"
110 .formatted(variable, representative));
111 }
112 return equivalencePartition.computeIfAbsent(variable, key -> {
113 var set = new HashSet<NodeVariable>(1);
114 set.add(key);
115 return set;
116 });
117 }
118
119 private void keepParameterEquivalences() {
120 for (var pair : representatives.entrySet()) {
121 var left = pair.getKey();
122 var right = pair.getValue();
123 if (!left.equals(right) && parameters.containsKey(left) && parameters.containsKey(right)) {
124 substitutedLiterals.add(left.isEquivalent(right));
125 }
126 }
127 }
128
129 private void substituteLiterals() {
130 Substitution substitution;
131 if (representatives.isEmpty()) {
132 substitution = null;
133 } else {
134 substitution = new MapBasedSubstitution(Collections.unmodifiableMap(representatives),
135 StatelessSubstitution.IDENTITY);
136 }
137 for (var literal : literals) {
138 if (isPositiveEquivalence(literal)) {
139 // We already retained all equivalences that cannot be replaced with substitutions in
140 // {@link#keepParameterEquivalences()}.
141 continue;
142 }
143 var substitutedLiteral = substitution == null ? literal : literal.substitute(substitution);
144 substitutedLiterals.add(substitutedLiteral);
145 }
146 }
147
148 private void computeExistentiallyQuantifiedVariables() {
149 for (var literal : substitutedLiterals) {
150 for (var variable : literal.getOutputVariables()) {
151 boolean added = existentiallyQuantifiedVariables.add(variable);
152 if (!variable.isUnifiable()) {
153 var parameterInfo = parameters.get(variable);
154 if (parameterInfo != null && parameterInfo.direction() == ParameterDirection.IN) {
155 throw new IllegalArgumentException("Trying to bind %s parameter %s"
156 .formatted(ParameterDirection.IN, variable));
157 }
158 if (!added) {
159 throw new IllegalArgumentException("Variable %s has multiple assigned values"
160 .formatted(variable));
161 }
162 }
163 }
164 }
165 }
166
167 private void computePositiveVariables() {
168 positiveVariables = new LinkedHashSet<>();
169 for (var pair : parameters.entrySet()) {
170 var variable = pair.getKey();
171 if (pair.getValue().direction() == ParameterDirection.IN) {
172 // Inputs count as positive, because they are already bound when we evaluate literals.
173 positiveVariables.add(variable);
174 } else if (!existentiallyQuantifiedVariables.contains(variable)) {
175 throw new IllegalArgumentException("Unbound %s parameter %s"
176 .formatted(ParameterDirection.OUT, variable));
177 }
178 }
179 positiveVariables.addAll(existentiallyQuantifiedVariables);
180 }
181
182 private void validatePositiveRepresentatives() {
183 for (var pair : equivalencePartition.entrySet()) {
184 var representative = pair.getKey();
185 if (!positiveVariables.contains(representative)) {
186 var variableSet = pair.getValue();
187 throw new IllegalArgumentException("Variables %s were merged by equivalence but are not bound"
188 .formatted(variableSet));
189 }
190 }
191 }
192
193 private void validatePrivateVariables() {
194 var negativeVariablesMap = new HashMap<Variable, Literal>();
195 for (var literal : substitutedLiterals) {
196 for (var variable : literal.getPrivateVariables(positiveVariables)) {
197 var oldLiteral = negativeVariablesMap.put(variable, literal);
198 if (oldLiteral != null) {
199 throw new IllegalArgumentException("Unbound variable %s appears in multiple literals %s and %s"
200 .formatted(variable, oldLiteral, literal));
201 }
202 }
203 }
204 }
205
206 private void topologicallySortLiterals() {
207 topologicallySortedLiterals = new LinkedHashSet<>(substitutedLiterals.size());
208 variableToLiteralInputMap = new HashMap<>();
209 literalsWithAllInputsBound = new PriorityQueue<>();
210 int size = substitutedLiterals.size();
211 for (int i = 0; i < size; i++) {
212 var literal = substitutedLiterals.get(i);
213 var sortableLiteral = new SortableLiteral(i, literal);
214 sortableLiteral.enqueue();
215 }
216 while (!literalsWithAllInputsBound.isEmpty()) {
217 var variable = literalsWithAllInputsBound.remove();
218 variable.addToSortedLiterals();
219 }
220 if (!variableToLiteralInputMap.isEmpty()) {
221 throw new IllegalArgumentException("Unbound input variables %s"
222 .formatted(variableToLiteralInputMap.keySet()));
223 }
224 }
225
226 private class SortableLiteral implements Comparable<SortableLiteral> {
227 private final int index;
228 private final Literal literal;
229 private final Set<Variable> remainingInputs;
230
231 private SortableLiteral(int index, Literal literal) {
232 this.index = index;
233 this.literal = literal;
234 remainingInputs = new HashSet<>(literal.getInputVariables(positiveVariables));
235 for (var pair : parameters.entrySet()) {
236 if (pair.getValue().direction() == ParameterDirection.IN) {
237 remainingInputs.remove(pair.getKey());
238 }
239 }
240 }
241
242 public void enqueue() {
243 if (allInputsBound()) {
244 addToAllInputsBoundQueue();
245 } else {
246 addToVariableToLiteralInputMap();
247 }
248 }
249
250 private void bindVariable(Variable input) {
251 if (!remainingInputs.remove(input)) {
252 throw new AssertionError("Already processed input %s of literal %s".formatted(input, literal));
253 }
254 if (allInputsBound()) {
255 addToAllInputsBoundQueue();
256 }
257 }
258
259 private boolean allInputsBound() {
260 return remainingInputs.isEmpty();
261 }
262
263 private void addToVariableToLiteralInputMap() {
264 for (var inputVariable : remainingInputs) {
265 var literalSetForInput = variableToLiteralInputMap.computeIfAbsent(
266 inputVariable, key -> new HashSet<>());
267 literalSetForInput.add(this);
268 }
269 }
270
271 private void addToAllInputsBoundQueue() {
272 literalsWithAllInputsBound.add(this);
273 }
274
275 public void addToSortedLiterals() {
276 if (!allInputsBound()) {
277 throw new AssertionError("Inputs %s of %s are not yet bound".formatted(remainingInputs, literal));
278 }
279 // Add literal if we haven't yet added a duplicate of this literal.
280 topologicallySortedLiterals.add(literal);
281 for (var variable : literal.getOutputVariables()) {
282 var literalSetForInput = variableToLiteralInputMap.remove(variable);
283 if (literalSetForInput == null) {
284 continue;
285 }
286 for (var targetSortableLiteral : literalSetForInput) {
287 targetSortableLiteral.bindVariable(variable);
288 }
289 }
290 }
291
292 @Override
293 public int compareTo(@NotNull ClausePostProcessor.SortableLiteral other) {
294 return Integer.compare(index, other.index);
295 }
296
297 @Override
298 public boolean equals(Object o) {
299 if (this == o) return true;
300 if (o == null || getClass() != o.getClass()) return false;
301 SortableLiteral that = (SortableLiteral) o;
302 return index == that.index && Objects.equals(literal, that.literal);
303 }
304
305 @Override
306 public int hashCode() {
307 return Objects.hash(index, literal);
308 }
309 }
310
311 public sealed interface Result permits ClauseResult, ConstantResult {
312 }
313
314 public record ClauseResult(DnfClause clause) implements Result {
315 }
316
317 public enum ConstantResult implements Result {
318 ALWAYS_TRUE,
319 ALWAYS_FALSE
320 }
321
322 public record ParameterInfo(ParameterDirection direction, int index) {
323 }
324}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java
new file mode 100644
index 00000000..50b245f7
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java
@@ -0,0 +1,213 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.literal.Reduction;
10import tools.refinery.store.query.equality.DnfEqualityChecker;
11import tools.refinery.store.query.equality.LiteralEqualityHelper;
12import tools.refinery.store.query.term.Parameter;
13import tools.refinery.store.query.term.Variable;
14
15import java.util.Collection;
16import java.util.Collections;
17import java.util.List;
18import java.util.Set;
19import java.util.function.Consumer;
20import java.util.stream.Collectors;
21
22public final class Dnf implements Constraint {
23 private static final String INDENTATION = " ";
24
25 private final String name;
26 private final String uniqueName;
27 private final List<SymbolicParameter> symbolicParameters;
28 private final List<FunctionalDependency<Variable>> functionalDependencies;
29 private final List<DnfClause> clauses;
30
31 Dnf(String name, List<SymbolicParameter> symbolicParameters,
32 List<FunctionalDependency<Variable>> functionalDependencies, List<DnfClause> clauses) {
33 validateFunctionalDependencies(symbolicParameters, functionalDependencies);
34 this.name = name;
35 this.uniqueName = DnfUtils.generateUniqueName(name);
36 this.symbolicParameters = symbolicParameters;
37 this.functionalDependencies = functionalDependencies;
38 this.clauses = clauses;
39 }
40
41 private static void validateFunctionalDependencies(
42 Collection<SymbolicParameter> symbolicParameters,
43 Collection<FunctionalDependency<Variable>> functionalDependencies) {
44 var parameterSet = symbolicParameters.stream().map(SymbolicParameter::getVariable).collect(Collectors.toSet());
45 for (var functionalDependency : functionalDependencies) {
46 validateParameters(symbolicParameters, parameterSet, functionalDependency.forEach(), functionalDependency);
47 validateParameters(symbolicParameters, parameterSet, functionalDependency.unique(), functionalDependency);
48 }
49 }
50
51 private static void validateParameters(Collection<SymbolicParameter> symbolicParameters,
52 Set<Variable> parameterSet, Collection<Variable> toValidate,
53 FunctionalDependency<Variable> functionalDependency) {
54 for (var variable : toValidate) {
55 if (!parameterSet.contains(variable)) {
56 throw new IllegalArgumentException(
57 "Variable %s of functional dependency %s does not appear in the parameter list %s"
58 .formatted(variable, functionalDependency, symbolicParameters));
59 }
60 }
61 }
62
63 @Override
64 public String name() {
65 return name == null ? uniqueName : name;
66 }
67
68 public boolean isExplicitlyNamed() {
69 return name == null;
70 }
71
72 public String getUniqueName() {
73 return uniqueName;
74 }
75
76 public List<SymbolicParameter> getSymbolicParameters() {
77 return symbolicParameters;
78 }
79
80 public List<Parameter> getParameters() {
81 return Collections.unmodifiableList(symbolicParameters);
82 }
83
84 public List<FunctionalDependency<Variable>> getFunctionalDependencies() {
85 return functionalDependencies;
86 }
87
88 @Override
89 public int arity() {
90 return symbolicParameters.size();
91 }
92
93 public List<DnfClause> getClauses() {
94 return clauses;
95 }
96
97 public RelationalQuery asRelation() {
98 return new RelationalQuery(this);
99 }
100
101 public <T> FunctionalQuery<T> asFunction(Class<T> type) {
102 return new FunctionalQuery<>(this, type);
103 }
104
105 @Override
106 public Reduction getReduction() {
107 if (clauses.isEmpty()) {
108 return Reduction.ALWAYS_FALSE;
109 }
110 for (var clause : clauses) {
111 if (clause.literals().isEmpty()) {
112 return Reduction.ALWAYS_TRUE;
113 }
114 }
115 return Reduction.NOT_REDUCIBLE;
116 }
117
118 public boolean equalsWithSubstitution(DnfEqualityChecker callEqualityChecker, Dnf other) {
119 if (arity() != other.arity()) {
120 return false;
121 }
122 for (int i = 0; i < arity(); i++) {
123 if (!symbolicParameters.get(i).getDirection().equals(other.getSymbolicParameters().get(i).getDirection())) {
124 return false;
125 }
126 }
127 int numClauses = clauses.size();
128 if (numClauses != other.clauses.size()) {
129 return false;
130 }
131 for (int i = 0; i < numClauses; i++) {
132 var literalEqualityHelper = new LiteralEqualityHelper(callEqualityChecker, symbolicParameters,
133 other.symbolicParameters);
134 if (!clauses.get(i).equalsWithSubstitution(literalEqualityHelper, other.clauses.get(i))) {
135 return false;
136 }
137 }
138 return true;
139 }
140
141 @Override
142 public boolean equals(LiteralEqualityHelper helper, Constraint other) {
143 if (other instanceof Dnf otherDnf) {
144 return helper.dnfEqual(this, otherDnf);
145 }
146 return false;
147 }
148
149 @Override
150 public String toString() {
151 return "%s/%d".formatted(name(), arity());
152 }
153
154 @Override
155 public String toReferenceString() {
156 return "@Dnf " + name();
157 }
158
159 public String toDefinitionString() {
160 var builder = new StringBuilder();
161 builder.append("pred ").append(name()).append("(");
162 var parameterIterator = symbolicParameters.iterator();
163 if (parameterIterator.hasNext()) {
164 builder.append(parameterIterator.next());
165 while (parameterIterator.hasNext()) {
166 builder.append(", ").append(parameterIterator.next());
167 }
168 }
169 builder.append(") <->");
170 var clauseIterator = clauses.iterator();
171 if (clauseIterator.hasNext()) {
172 appendClause(clauseIterator.next(), builder);
173 while (clauseIterator.hasNext()) {
174 builder.append("\n;");
175 appendClause(clauseIterator.next(), builder);
176 }
177 } else {
178 builder.append("\n").append(INDENTATION).append("<no clauses>");
179 }
180 builder.append(".\n");
181 return builder.toString();
182 }
183
184 private static void appendClause(DnfClause clause, StringBuilder builder) {
185 var iterator = clause.literals().iterator();
186 if (!iterator.hasNext()) {
187 builder.append("\n").append(INDENTATION).append("<empty>");
188 return;
189 }
190 builder.append("\n").append(INDENTATION).append(iterator.next());
191 while (iterator.hasNext()) {
192 builder.append(",\n").append(INDENTATION).append(iterator.next());
193 }
194 }
195
196 public static DnfBuilder builder() {
197 return builder(null);
198 }
199
200 public static DnfBuilder builder(String name) {
201 return new DnfBuilder(name);
202 }
203
204 public static Dnf of(Consumer<DnfBuilder> callback) {
205 return of(null, callback);
206 }
207
208 public static Dnf of(String name, Consumer<DnfBuilder> callback) {
209 var builder = builder(name);
210 callback.accept(builder);
211 return builder.build();
212 }
213}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java
new file mode 100644
index 00000000..8e38ca6b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java
@@ -0,0 +1,262 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.dnf.callback.*;
9import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.term.DataVariable;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.query.term.ParameterDirection;
13import tools.refinery.store.query.term.Variable;
14
15import java.util.*;
16
17@SuppressWarnings("UnusedReturnValue")
18public final class DnfBuilder {
19 private final String name;
20 private final Set<Variable> parameterVariables = new LinkedHashSet<>();
21 private final List<SymbolicParameter> parameters = new ArrayList<>();
22 private final List<FunctionalDependency<Variable>> functionalDependencies = new ArrayList<>();
23 private final List<List<Literal>> clauses = new ArrayList<>();
24
25 DnfBuilder(String name) {
26 this.name = name;
27 }
28
29 public NodeVariable parameter() {
30 return parameter((String) null);
31 }
32
33 public NodeVariable parameter(String name) {
34 return parameter(name, ParameterDirection.OUT);
35 }
36
37 public NodeVariable parameter(ParameterDirection direction) {
38 return parameter((String) null, direction);
39 }
40
41 public NodeVariable parameter(String name, ParameterDirection direction) {
42 var variable = Variable.of(name);
43 parameter(variable, direction);
44 return variable;
45 }
46
47 public <T> DataVariable<T> parameter(Class<T> type) {
48 return parameter(null, type);
49 }
50
51 public <T> DataVariable<T> parameter(String name, Class<T> type) {
52 return parameter(name, type, ParameterDirection.OUT);
53 }
54
55 public <T> DataVariable<T> parameter(Class<T> type, ParameterDirection direction) {
56 return parameter(null, type, direction);
57 }
58
59 public <T> DataVariable<T> parameter(String name, Class<T> type, ParameterDirection direction) {
60 var variable = Variable.of(name, type);
61 parameter(variable, direction);
62 return variable;
63 }
64
65 public DnfBuilder parameter(Variable variable) {
66 return parameter(variable, ParameterDirection.OUT);
67 }
68
69 public DnfBuilder parameter(Variable variable, ParameterDirection direction) {
70 return symbolicParameter(new SymbolicParameter(variable, direction));
71 }
72
73 public DnfBuilder parameters(Variable... variables) {
74 return parameters(List.of(variables));
75 }
76
77 public DnfBuilder parameters(Collection<? extends Variable> variables) {
78 return parameters(variables, ParameterDirection.OUT);
79 }
80
81 public DnfBuilder parameters(Collection<? extends Variable> variables, ParameterDirection direction) {
82 for (var variable : variables) {
83 parameter(variable, direction);
84 }
85 return this;
86 }
87
88 public DnfBuilder symbolicParameter(SymbolicParameter symbolicParameter) {
89 var variable = symbolicParameter.getVariable();
90 if (!parameterVariables.add(variable)) {
91 throw new IllegalArgumentException("Variable %s is already on the parameter list %s"
92 .formatted(variable, parameters));
93 }
94 parameters.add(symbolicParameter);
95 return this;
96 }
97
98 public DnfBuilder symbolicParameters(SymbolicParameter... symbolicParameters) {
99 return symbolicParameters(List.of(symbolicParameters));
100 }
101
102 public DnfBuilder symbolicParameters(Collection<SymbolicParameter> symbolicParameters) {
103 for (var symbolicParameter : symbolicParameters) {
104 symbolicParameter(symbolicParameter);
105 }
106 return this;
107 }
108
109 public DnfBuilder functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) {
110 this.functionalDependencies.addAll(functionalDependencies);
111 return this;
112 }
113
114 public DnfBuilder functionalDependency(FunctionalDependency<Variable> functionalDependency) {
115 functionalDependencies.add(functionalDependency);
116 return this;
117 }
118
119 public DnfBuilder functionalDependency(Set<? extends Variable> forEach, Set<? extends Variable> unique) {
120 return functionalDependency(new FunctionalDependency<>(Set.copyOf(forEach), Set.copyOf(unique)));
121 }
122
123 public DnfBuilder clause(ClauseCallback0 callback) {
124 return clause(callback.toLiterals());
125 }
126
127 public DnfBuilder clause(ClauseCallback1Data0 callback) {
128 return clause(callback.toLiterals(Variable.of("v1")));
129 }
130
131 public <T> DnfBuilder clause(Class<T> type1, ClauseCallback1Data1<T> callback) {
132 return clause(callback.toLiterals(Variable.of("v1", type1)));
133 }
134
135 public DnfBuilder clause(ClauseCallback2Data0 callback) {
136 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2")));
137 }
138
139 public <T> DnfBuilder clause(Class<T> type1, ClauseCallback2Data1<T> callback) {
140 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("d1", type1)));
141 }
142
143 public <T1, T2> DnfBuilder clause(Class<T1> type1, Class<T2> type2, ClauseCallback2Data2<T1, T2> callback) {
144 return clause(callback.toLiterals(Variable.of("d1", type1), Variable.of("d2", type2)));
145 }
146
147 public DnfBuilder clause(ClauseCallback3Data0 callback) {
148 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3")));
149 }
150
151 public <T> DnfBuilder clause(Class<T> type1, ClauseCallback3Data1<T> callback) {
152 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("d1", type1)));
153 }
154
155 public <T1, T2> DnfBuilder clause(Class<T1> type1, Class<T2> type2, ClauseCallback3Data2<T1, T2> callback) {
156 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("d1", type1), Variable.of("d2", type2)));
157 }
158
159 public <T1, T2, T3> DnfBuilder clause(Class<T1> type1, Class<T2> type2, Class<T3> type3,
160 ClauseCallback3Data3<T1, T2, T3> callback) {
161 return clause(callback.toLiterals(Variable.of("d1", type1), Variable.of("d2", type2),
162 Variable.of("d3", type3)));
163 }
164
165 public DnfBuilder clause(ClauseCallback4Data0 callback) {
166 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3"), Variable.of("v4")));
167 }
168
169 public <T> DnfBuilder clause(Class<T> type1, ClauseCallback4Data1<T> callback) {
170 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3"), Variable.of("d1",
171 type1)));
172 }
173
174 public <T1, T2> DnfBuilder clause(Class<T1> type1, Class<T2> type2, ClauseCallback4Data2<T1, T2> callback) {
175 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("d1", type1),
176 Variable.of("d2", type2)));
177 }
178
179 public <T1, T2, T3> DnfBuilder clause(Class<T1> type1, Class<T2> type2, Class<T3> type3,
180 ClauseCallback4Data3<T1, T2, T3> callback) {
181 return clause(callback.toLiterals(Variable.of("v1"), Variable.of("d1", type1), Variable.of("d2", type2),
182 Variable.of("d3", type3)));
183 }
184
185 public <T1, T2, T3, T4> DnfBuilder clause(Class<T1> type1, Class<T2> type2, Class<T3> type3, Class<T4> type4,
186 ClauseCallback4Data4<T1, T2, T3, T4> callback) {
187 return clause(callback.toLiterals(Variable.of("d1", type1), Variable.of("d2", type2),
188 Variable.of("d3", type3), Variable.of("d4", type4)));
189 }
190
191 public DnfBuilder clause(Literal... literals) {
192 clause(List.of(literals));
193 return this;
194 }
195
196 public DnfBuilder clause(Collection<? extends Literal> literals) {
197 clauses.add(List.copyOf(literals));
198 return this;
199 }
200
201 <T> void output(DataVariable<T> outputVariable) {
202 // Copy parameter variables to exclude the newly added {@code outputVariable}.
203 var fromParameters = Set.copyOf(parameterVariables);
204 parameter(outputVariable, ParameterDirection.OUT);
205 functionalDependency(fromParameters, Set.of(outputVariable));
206 }
207
208 public Dnf build() {
209 var postProcessedClauses = postProcessClauses();
210 return new Dnf(name, Collections.unmodifiableList(parameters),
211 Collections.unmodifiableList(functionalDependencies),
212 Collections.unmodifiableList(postProcessedClauses));
213 }
214
215 private List<DnfClause> postProcessClauses() {
216 var parameterInfoMap = getParameterInfoMap();
217 var postProcessedClauses = new ArrayList<DnfClause>(clauses.size());
218 for (var literals : clauses) {
219 var postProcessor = new ClausePostProcessor(parameterInfoMap, literals);
220 var result = postProcessor.postProcessClause();
221 if (result instanceof ClausePostProcessor.ClauseResult clauseResult) {
222 postProcessedClauses.add(clauseResult.clause());
223 } else if (result instanceof ClausePostProcessor.ConstantResult constantResult) {
224 switch (constantResult) {
225 case ALWAYS_TRUE -> {
226 var inputVariables = getInputVariables();
227 return List.of(new DnfClause(inputVariables, List.of()));
228 }
229 case ALWAYS_FALSE -> {
230 // Skip this clause because it can never match.
231 }
232 default -> throw new IllegalStateException("Unexpected ClausePostProcessor.ConstantResult: " +
233 constantResult);
234 }
235 } else {
236 throw new IllegalStateException("Unexpected ClausePostProcessor.Result: " + result);
237 }
238 }
239 return postProcessedClauses;
240 }
241
242 private Map<Variable, ClausePostProcessor.ParameterInfo> getParameterInfoMap() {
243 var mutableParameterInfoMap = new LinkedHashMap<Variable, ClausePostProcessor.ParameterInfo>();
244 int arity = parameters.size();
245 for (int i = 0; i < arity; i++) {
246 var parameter = parameters.get(i);
247 mutableParameterInfoMap.put(parameter.getVariable(),
248 new ClausePostProcessor.ParameterInfo(parameter.getDirection(), i));
249 }
250 return Collections.unmodifiableMap(mutableParameterInfoMap);
251 }
252
253 private Set<Variable> getInputVariables() {
254 var inputParameters = new LinkedHashSet<Variable>();
255 for (var parameter : parameters) {
256 if (parameter.getDirection() == ParameterDirection.IN) {
257 inputParameters.add(parameter.getVariable());
258 }
259 }
260 return Collections.unmodifiableSet(inputParameters);
261 }
262}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java
new file mode 100644
index 00000000..fdd0d47c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java
@@ -0,0 +1,28 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.List;
13import java.util.Set;
14
15public record DnfClause(Set<Variable> positiveVariables, List<Literal> literals) {
16 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, DnfClause other) {
17 int size = literals.size();
18 if (size != other.literals.size()) {
19 return false;
20 }
21 for (int i = 0; i < size; i++) {
22 if (!literals.get(i).equalsWithSubstitution(helper, other.literals.get(i))) {
23 return false;
24 }
25 }
26 return true;
27 }
28}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java
new file mode 100644
index 00000000..65ab3634
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java
@@ -0,0 +1,24 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import java.util.UUID;
9
10public final class DnfUtils {
11 private DnfUtils() {
12 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
13 }
14
15 public static String generateUniqueName(String originalName) {
16 UUID uuid = UUID.randomUUID();
17 String uniqueString = "_" + uuid.toString().replace('-', '_');
18 if (originalName == null) {
19 return uniqueString;
20 } else {
21 return originalName + uniqueString;
22 }
23 }
24}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java
new file mode 100644
index 00000000..b00b2cb7
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java
@@ -0,0 +1,20 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import java.util.HashSet;
9import java.util.Set;
10
11public record FunctionalDependency<T>(Set<T> forEach, Set<T> unique) {
12 public FunctionalDependency {
13 var uniqueForEach = new HashSet<>(unique);
14 uniqueForEach.retainAll(forEach);
15 if (!uniqueForEach.isEmpty()) {
16 throw new IllegalArgumentException("Variables %s appear on both sides of the functional dependency"
17 .formatted(uniqueForEach));
18 }
19 }
20}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java
new file mode 100644
index 00000000..5a32b1ba
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java
@@ -0,0 +1,99 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.literal.CallPolarity;
9import tools.refinery.store.query.term.Aggregator;
10import tools.refinery.store.query.term.AssignedValue;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.ArrayList;
15import java.util.List;
16import java.util.Objects;
17
18public final class FunctionalQuery<T> extends Query<T> {
19 private final Class<T> type;
20
21 FunctionalQuery(Dnf dnf, Class<T> type) {
22 super(dnf);
23 var parameters = dnf.getSymbolicParameters();
24 int outputIndex = dnf.arity() - 1;
25 for (int i = 0; i < outputIndex; i++) {
26 var parameter = parameters.get(i);
27 var parameterType = parameter.tryGetType();
28 if (parameterType.isPresent()) {
29 throw new IllegalArgumentException("Expected parameter %s of %s to be a node variable, got %s instead"
30 .formatted(parameter, dnf, parameterType.get().getName()));
31 }
32 }
33 var outputParameter = parameters.get(outputIndex);
34 var outputParameterType = outputParameter.tryGetType();
35 if (outputParameterType.isEmpty() || !outputParameterType.get().equals(type)) {
36 throw new IllegalArgumentException("Expected parameter %s of %s to be %s, but got %s instead".formatted(
37 outputParameter, dnf, type, outputParameterType.map(Class::getName).orElse("node")));
38 }
39 this.type = type;
40 }
41
42 @Override
43 public int arity() {
44 return getDnf().arity() - 1;
45 }
46
47 @Override
48 public Class<T> valueType() {
49 return type;
50 }
51
52 @Override
53 public T defaultValue() {
54 return null;
55 }
56
57 public AssignedValue<T> call(List<NodeVariable> arguments) {
58 return targetVariable -> {
59 var argumentsWithTarget = new ArrayList<Variable>(arguments.size() + 1);
60 argumentsWithTarget.addAll(arguments);
61 argumentsWithTarget.add(targetVariable);
62 return getDnf().call(CallPolarity.POSITIVE, argumentsWithTarget);
63 };
64 }
65
66 public AssignedValue<T> call(NodeVariable... arguments) {
67 return call(List.of(arguments));
68 }
69
70 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, List<NodeVariable> arguments) {
71 return targetVariable -> {
72 var placeholderVariable = Variable.of(type);
73 var argumentsWithPlaceholder = new ArrayList<Variable>(arguments.size() + 1);
74 argumentsWithPlaceholder.addAll(arguments);
75 argumentsWithPlaceholder.add(placeholderVariable);
76 return getDnf()
77 .aggregateBy(placeholderVariable, aggregator, argumentsWithPlaceholder)
78 .toLiteral(targetVariable);
79 };
80 }
81
82 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, NodeVariable... arguments) {
83 return aggregate(aggregator, List.of(arguments));
84 }
85
86 @Override
87 public boolean equals(Object o) {
88 if (this == o) return true;
89 if (o == null || getClass() != o.getClass()) return false;
90 if (!super.equals(o)) return false;
91 FunctionalQuery<?> that = (FunctionalQuery<?>) o;
92 return Objects.equals(type, that.type);
93 }
94
95 @Override
96 public int hashCode() {
97 return Objects.hash(super.hashCode(), type);
98 }
99}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java
new file mode 100644
index 00000000..d1cd7ba8
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.term.DataVariable;
9
10public final class FunctionalQueryBuilder<T> extends AbstractQueryBuilder<FunctionalQueryBuilder<T>> {
11 private final DataVariable<T> outputVariable;
12 private final Class<T> type;
13
14 FunctionalQueryBuilder(DataVariable<T> outputVariable, DnfBuilder dnfBuilder, Class<T> type) {
15 super(dnfBuilder);
16 this.outputVariable = outputVariable;
17 this.type = type;
18 }
19
20 @Override
21 protected FunctionalQueryBuilder<T> self() {
22 return this;
23 }
24
25 public FunctionalQuery<T> build() {
26 dnfBuilder.output(outputVariable);
27 return dnfBuilder.build().asFunction(type);
28 }
29}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java
new file mode 100644
index 00000000..aaa52ce6
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java
@@ -0,0 +1,179 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.dnf.callback.*;
9import tools.refinery.store.query.term.ParameterDirection;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Objects;
13
14public abstract sealed class Query<T> implements AnyQuery permits FunctionalQuery, RelationalQuery {
15 private static final String OUTPUT_VARIABLE_NAME = "output";
16
17 private final Dnf dnf;
18
19 protected Query(Dnf dnf) {
20 for (var parameter : dnf.getSymbolicParameters()) {
21 if (parameter.getDirection() != ParameterDirection.OUT) {
22 throw new IllegalArgumentException("Query parameter %s with direction %s is not allowed"
23 .formatted(parameter.getVariable(), parameter.getDirection()));
24 }
25 }
26 this.dnf = dnf;
27 }
28
29 @Override
30 public String name() {
31 return dnf.name();
32 }
33
34 @Override
35 public Dnf getDnf() {
36 return dnf;
37 }
38
39 // Allow redeclaration of the method with refined return type.
40 @SuppressWarnings("squid:S3038")
41 @Override
42 public abstract Class<T> valueType();
43
44 public abstract T defaultValue();
45
46 @Override
47 public boolean equals(Object o) {
48 if (this == o) return true;
49 if (o == null || getClass() != o.getClass()) return false;
50 Query<?> that = (Query<?>) o;
51 return Objects.equals(dnf, that.dnf);
52 }
53
54 @Override
55 public int hashCode() {
56 return Objects.hash(dnf);
57 }
58
59 @Override
60 public String toString() {
61 return dnf.toString();
62 }
63
64 public static QueryBuilder builder() {
65 return builder(null);
66 }
67
68 public static QueryBuilder builder(String name) {
69 return new QueryBuilder(name);
70 }
71
72 public static RelationalQuery of(QueryCallback0 callback) {
73 return of(null, callback);
74 }
75
76 public static RelationalQuery of(String name, QueryCallback0 callback) {
77 var builder = builder(name);
78 callback.accept(builder);
79 return builder.build();
80 }
81
82 public static RelationalQuery of(QueryCallback1 callback) {
83 return of(null, callback);
84 }
85
86 public static RelationalQuery of(String name, QueryCallback1 callback) {
87 var builder = builder(name);
88 callback.accept(builder, builder.parameter("p1"));
89 return builder.build();
90 }
91
92 public static RelationalQuery of(QueryCallback2 callback) {
93 return of(null, callback);
94 }
95
96 public static RelationalQuery of(String name, QueryCallback2 callback) {
97 var builder = builder(name);
98 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"));
99 return builder.build();
100 }
101
102 public static RelationalQuery of(QueryCallback3 callback) {
103 return of(null, callback);
104 }
105
106 public static RelationalQuery of(String name, QueryCallback3 callback) {
107 var builder = builder(name);
108 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"));
109 return builder.build();
110 }
111
112 public static RelationalQuery of(QueryCallback4 callback) {
113 return of(null, callback);
114 }
115
116 public static RelationalQuery of(String name, QueryCallback4 callback) {
117 var builder = builder(name);
118 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"),
119 builder.parameter("p4"));
120 return builder.build();
121 }
122
123 public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback0<T> callback) {
124 return of(null, type, callback);
125 }
126
127 public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback0<T> callback) {
128 var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type);
129 var builder = builder(name).output(outputVariable);
130 callback.accept(builder, outputVariable);
131 return builder.build();
132 }
133
134 public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback1<T> callback) {
135 return of(null, type, callback);
136 }
137
138 public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback1<T> callback) {
139 var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type);
140 var builder = builder(name).output(outputVariable);
141 callback.accept(builder, builder.parameter("p1"), outputVariable);
142 return builder.build();
143 }
144
145 public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback2<T> callback) {
146 return of(null, type, callback);
147 }
148
149 public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback2<T> callback) {
150 var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type);
151 var builder = builder(name).output(outputVariable);
152 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), outputVariable);
153 return builder.build();
154 }
155
156 public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback3<T> callback) {
157 return of(null, type, callback);
158 }
159
160 public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback3<T> callback) {
161 var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type);
162 var builder = builder(name).output(outputVariable);
163 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"),
164 outputVariable);
165 return builder.build();
166 }
167
168 public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback4<T> callback) {
169 return of(null, type, callback);
170 }
171
172 public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback4<T> callback) {
173 var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type);
174 var builder = builder(name).output(outputVariable);
175 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"),
176 builder.parameter("p4"), outputVariable);
177 return builder.build();
178 }
179}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java
new file mode 100644
index 00000000..138911bc
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java
@@ -0,0 +1,27 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.term.DataVariable;
9
10public final class QueryBuilder extends AbstractQueryBuilder<QueryBuilder> {
11 QueryBuilder(String name) {
12 super(Dnf.builder(name));
13 }
14
15 @Override
16 protected QueryBuilder self() {
17 return this;
18 }
19
20 public <T> FunctionalQueryBuilder<T> output(DataVariable<T> outputVariable) {
21 return new FunctionalQueryBuilder<>(outputVariable, dnfBuilder, outputVariable.getType());
22 }
23
24 public RelationalQuery build() {
25 return dnfBuilder.build().asRelation();
26 }
27}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java
new file mode 100644
index 00000000..d34a7ace
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java
@@ -0,0 +1,66 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.literal.CallLiteral;
9import tools.refinery.store.query.literal.CallPolarity;
10import tools.refinery.store.query.term.AssignedValue;
11import tools.refinery.store.query.term.NodeVariable;
12
13import java.util.Collections;
14import java.util.List;
15
16public final class RelationalQuery extends Query<Boolean> {
17 RelationalQuery(Dnf dnf) {
18 super(dnf);
19 for (var parameter : dnf.getSymbolicParameters()) {
20 var parameterType = parameter.tryGetType();
21 if (parameterType.isPresent()) {
22 throw new IllegalArgumentException("Expected parameter %s of %s to be a node variable, got %s instead"
23 .formatted(parameter, dnf, parameterType.get().getName()));
24 }
25 }
26 }
27
28 @Override
29 public int arity() {
30 return getDnf().arity();
31 }
32
33 @Override
34 public Class<Boolean> valueType() {
35 return Boolean.class;
36 }
37
38 @Override
39 public Boolean defaultValue() {
40 return false;
41 }
42
43 public CallLiteral call(CallPolarity polarity, List<NodeVariable> arguments) {
44 return getDnf().call(polarity, Collections.unmodifiableList(arguments));
45 }
46
47 public CallLiteral call(CallPolarity polarity, NodeVariable... arguments) {
48 return getDnf().call(polarity, arguments);
49 }
50
51 public CallLiteral call(NodeVariable... arguments) {
52 return getDnf().call(arguments);
53 }
54
55 public CallLiteral callTransitive(NodeVariable left, NodeVariable right) {
56 return getDnf().callTransitive(left, right);
57 }
58
59 public AssignedValue<Integer> count(List<NodeVariable> arguments) {
60 return getDnf().count(Collections.unmodifiableList(arguments));
61 }
62
63 public AssignedValue<Integer> count(NodeVariable... arguments) {
64 return getDnf().count(arguments);
65 }
66}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java
new file mode 100644
index 00000000..e0d3ba1f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java
@@ -0,0 +1,52 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.term.Parameter;
9import tools.refinery.store.query.term.ParameterDirection;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Objects;
13
14public final class SymbolicParameter extends Parameter {
15 private final Variable variable;
16
17 public SymbolicParameter(Variable variable, ParameterDirection direction) {
18 super(variable.tryGetType().orElse(null), direction);
19 this.variable = variable;
20 }
21
22 public Variable getVariable() {
23 return variable;
24 }
25
26 public boolean isUnifiable() {
27 return variable.isUnifiable();
28 }
29
30 @Override
31 public String toString() {
32 var direction = getDirection();
33 if (direction == ParameterDirection.OUT) {
34 return variable.toString();
35 }
36 return "%s %s".formatted(getDirection(), variable);
37 }
38
39 @Override
40 public boolean equals(Object o) {
41 if (this == o) return true;
42 if (o == null || getClass() != o.getClass()) return false;
43 if (!super.equals(o)) return false;
44 SymbolicParameter that = (SymbolicParameter) o;
45 return Objects.equals(variable, that.variable);
46 }
47
48 @Override
49 public int hashCode() {
50 return Objects.hash(super.hashCode(), variable);
51 }
52}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback0.java
new file mode 100644
index 00000000..d98dda2e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback0.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9
10import java.util.Collection;
11
12@FunctionalInterface
13public interface ClauseCallback0 {
14 Collection<Literal> toLiterals();
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data0.java
new file mode 100644
index 00000000..4c01a527
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data0.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback1Data0 {
15 Collection<Literal> toLiterals(NodeVariable v1);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data1.java
new file mode 100644
index 00000000..2c0cb6eb
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data1.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback1Data1<T> {
15 Collection<Literal> toLiterals(DataVariable<T> d1);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data0.java
new file mode 100644
index 00000000..d764bdba
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data0.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback2Data0 {
15 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data1.java
new file mode 100644
index 00000000..140af03a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data1.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback2Data1<T> {
16 Collection<Literal> toLiterals(NodeVariable v1, DataVariable<T> x1);
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data2.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data2.java
new file mode 100644
index 00000000..bfc8637c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data2.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback2Data2<T1, T2> {
15 Collection<Literal> toLiterals(DataVariable<T1> x1, DataVariable<T2> x2);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data0.java
new file mode 100644
index 00000000..074df65b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data0.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback3Data0 {
15 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data1.java
new file mode 100644
index 00000000..24ba5187
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data1.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback3Data1<T> {
16 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, DataVariable<T> d1);
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data2.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data2.java
new file mode 100644
index 00000000..2a2e837a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data2.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback3Data2<T1, T2> {
16 Collection<Literal> toLiterals(NodeVariable v1, DataVariable<T1> d1, DataVariable<T2> d2);
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data3.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data3.java
new file mode 100644
index 00000000..8f4bdd01
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data3.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback3Data3<T1, T2, T3> {
15 Collection<Literal> toLiterals(DataVariable<T1> d1, DataVariable<T2> d2, DataVariable<T3> d3);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data0.java
new file mode 100644
index 00000000..ed0f87b2
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data0.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback4Data0 {
15 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3, NodeVariable v4);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data1.java
new file mode 100644
index 00000000..9b27e2e1
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data1.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback4Data1<T> {
16 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3, DataVariable<T> d1);
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data2.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data2.java
new file mode 100644
index 00000000..cbc4808e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data2.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback4Data2<T1, T2> {
16 Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, DataVariable<T1> d1, DataVariable<T2> d2);
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data3.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data3.java
new file mode 100644
index 00000000..a6258f36
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data3.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public interface ClauseCallback4Data3<T1, T2, T3> {
16 Collection<Literal> toLiterals(NodeVariable v1, DataVariable<T1> d1, DataVariable<T2> d2, DataVariable<T3> d3);
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data4.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data4.java
new file mode 100644
index 00000000..b52a911a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data4.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public interface ClauseCallback4Data4<T1, T2, T3, T4> {
15 Collection<Literal> toLiterals(DataVariable<T1> d1, DataVariable<T2> d2, DataVariable<T3> d3, DataVariable<T4> d4);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback0.java
new file mode 100644
index 00000000..63b3eee6
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback0.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.FunctionalQueryBuilder;
9import tools.refinery.store.query.term.DataVariable;
10
11@FunctionalInterface
12public interface FunctionalQueryCallback0<T> {
13 void accept(FunctionalQueryBuilder<T> builder, DataVariable<T> output);
14}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback1.java
new file mode 100644
index 00000000..1295a118
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback1.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.FunctionalQueryBuilder;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12@FunctionalInterface
13public interface FunctionalQueryCallback1<T> {
14 void accept(FunctionalQueryBuilder<T> builder, NodeVariable p1, DataVariable<T> output);
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback2.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback2.java
new file mode 100644
index 00000000..d5b7f9ff
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback2.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.FunctionalQueryBuilder;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12@FunctionalInterface
13public interface FunctionalQueryCallback2<T> {
14 void accept(FunctionalQueryBuilder<T> builder, NodeVariable p1, NodeVariable p2, DataVariable<T> output);
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback3.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback3.java
new file mode 100644
index 00000000..dc8404a0
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback3.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.FunctionalQueryBuilder;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12@FunctionalInterface
13public interface FunctionalQueryCallback3<T> {
14 void accept(FunctionalQueryBuilder<T> builder, NodeVariable p1, NodeVariable p2, NodeVariable p3,
15 DataVariable<T> output);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback4.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback4.java
new file mode 100644
index 00000000..b6d3ddb0
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback4.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.FunctionalQueryBuilder;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12@FunctionalInterface
13public interface FunctionalQueryCallback4<T> {
14 void accept(FunctionalQueryBuilder<T> builder, NodeVariable p1, NodeVariable p2, NodeVariable p3, NodeVariable p4,
15 DataVariable<T> output);
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback0.java
new file mode 100644
index 00000000..3cf1de48
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback0.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.QueryBuilder;
9
10@FunctionalInterface
11public interface QueryCallback0 {
12 void accept(QueryBuilder builder);
13}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback1.java
new file mode 100644
index 00000000..0a150955
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback1.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.QueryBuilder;
9import tools.refinery.store.query.term.NodeVariable;
10
11@FunctionalInterface
12public interface QueryCallback1 {
13 void accept(QueryBuilder builder, NodeVariable p1);
14}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback2.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback2.java
new file mode 100644
index 00000000..9493a7b4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback2.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.QueryBuilder;
9import tools.refinery.store.query.term.NodeVariable;
10
11@FunctionalInterface
12public interface QueryCallback2 {
13 void accept(QueryBuilder builder, NodeVariable p1, NodeVariable p2);
14}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback3.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback3.java
new file mode 100644
index 00000000..358c7da7
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback3.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.QueryBuilder;
9import tools.refinery.store.query.term.NodeVariable;
10
11@FunctionalInterface
12public interface QueryCallback3 {
13 void accept(QueryBuilder builder, NodeVariable p1, NodeVariable p2, NodeVariable p3);
14}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback4.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback4.java
new file mode 100644
index 00000000..890dda16
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback4.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.QueryBuilder;
9import tools.refinery.store.query.term.NodeVariable;
10
11@FunctionalInterface
12public interface QueryCallback4 {
13 void accept(QueryBuilder builder, NodeVariable p1, NodeVariable p2, NodeVariable p3, NodeVariable p4);
14}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java
new file mode 100644
index 00000000..1eeb5723
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java
@@ -0,0 +1,78 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.equality;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.DnfClause;
10import tools.refinery.store.query.dnf.SymbolicParameter;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.util.CycleDetectingMapper;
13
14import java.util.List;
15
16public class DeepDnfEqualityChecker implements DnfEqualityChecker {
17 private final CycleDetectingMapper<Pair, Boolean> mapper = new CycleDetectingMapper<>(Pair::toString,
18 this::doCheckEqual);
19
20 @Override
21 public boolean dnfEqual(Dnf left, Dnf right) {
22 return mapper.map(new Pair(left, right));
23 }
24
25 public boolean dnfEqualRaw(List<SymbolicParameter> symbolicParameters,
26 List<? extends List<? extends Literal>> clauses, Dnf other) {
27 int arity = symbolicParameters.size();
28 if (arity != other.arity()) {
29 return false;
30 }
31 for (int i = 0; i < arity; i++) {
32 if (!symbolicParameters.get(i).getDirection().equals(other.getSymbolicParameters().get(i).getDirection())) {
33 return false;
34 }
35 }
36 int numClauses = clauses.size();
37 if (numClauses != other.getClauses().size()) {
38 return false;
39 }
40 for (int i = 0; i < numClauses; i++) {
41 var literalEqualityHelper = new LiteralEqualityHelper(this, symbolicParameters,
42 other.getSymbolicParameters());
43 if (!equalsWithSubstitutionRaw(literalEqualityHelper, clauses.get(i), other.getClauses().get(i))) {
44 return false;
45 }
46 }
47 return true;
48 }
49
50 private boolean equalsWithSubstitutionRaw(LiteralEqualityHelper helper, List<? extends Literal> literals,
51 DnfClause other) {
52 int size = literals.size();
53 if (size != other.literals().size()) {
54 return false;
55 }
56 for (int i = 0; i < size; i++) {
57 if (!literals.get(i).equalsWithSubstitution(helper, other.literals().get(i))) {
58 return false;
59 }
60 }
61 return true;
62 }
63
64 protected boolean doCheckEqual(Pair pair) {
65 return pair.left.equalsWithSubstitution(this, pair.right);
66 }
67
68 protected List<Pair> getInProgress() {
69 return mapper.getInProgress();
70 }
71
72 protected record Pair(Dnf left, Dnf right) {
73 @Override
74 public String toString() {
75 return "(%s, %s)".formatted(left.name(), right.name());
76 }
77 }
78}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java
new file mode 100644
index 00000000..4a8bee3b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.equality;
7
8import tools.refinery.store.query.dnf.Dnf;
9
10@FunctionalInterface
11public interface DnfEqualityChecker {
12 boolean dnfEqual(Dnf left, Dnf right);
13}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java
new file mode 100644
index 00000000..9315fb30
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java
@@ -0,0 +1,54 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.equality;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.SymbolicParameter;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.HashMap;
13import java.util.List;
14import java.util.Map;
15
16public class LiteralEqualityHelper {
17 private final DnfEqualityChecker dnfEqualityChecker;
18 private final Map<Variable, Variable> leftToRight;
19 private final Map<Variable, Variable> rightToLeft;
20
21 public LiteralEqualityHelper(DnfEqualityChecker dnfEqualityChecker, List<SymbolicParameter> leftParameters,
22 List<SymbolicParameter> rightParameters) {
23 this.dnfEqualityChecker = dnfEqualityChecker;
24 var arity = leftParameters.size();
25 if (arity != rightParameters.size()) {
26 throw new IllegalArgumentException("Parameter lists have unequal length");
27 }
28 leftToRight = new HashMap<>(arity);
29 rightToLeft = new HashMap<>(arity);
30 for (int i = 0; i < arity; i++) {
31 if (!variableEqual(leftParameters.get(i).getVariable(), rightParameters.get(i).getVariable())) {
32 throw new IllegalArgumentException("Parameter lists cannot be unified: duplicate parameter " + i);
33 }
34 }
35 }
36
37 public boolean dnfEqual(Dnf left, Dnf right) {
38 return dnfEqualityChecker.dnfEqual(left, right);
39 }
40
41 public boolean variableEqual(Variable left, Variable right) {
42 if (checkMapping(leftToRight, left, right) && checkMapping(rightToLeft, right, left)) {
43 leftToRight.put(left, right);
44 rightToLeft.put(right, left);
45 return true;
46 }
47 return false;
48 }
49
50 private static boolean checkMapping(Map<Variable, Variable> map, Variable key, Variable expectedValue) {
51 var currentValue = map.get(key);
52 return currentValue == null || currentValue.equals(expectedValue);
53 }
54}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java
new file mode 100644
index 00000000..8ef8e8b4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java
@@ -0,0 +1,143 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.*;
15
16public abstract class AbstractCallLiteral implements Literal {
17 private final Constraint target;
18 private final List<Variable> arguments;
19 private final Set<Variable> inArguments;
20 private final Set<Variable> outArguments;
21
22 // Use exhaustive switch over enums.
23 @SuppressWarnings("squid:S1301")
24 protected AbstractCallLiteral(Constraint target, List<Variable> arguments) {
25 int arity = target.arity();
26 if (arguments.size() != arity) {
27 throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.name(),
28 target.arity(), arguments.size()));
29 }
30 this.target = target;
31 this.arguments = arguments;
32 var mutableInArguments = new LinkedHashSet<Variable>();
33 var mutableOutArguments = new LinkedHashSet<Variable>();
34 var parameters = target.getParameters();
35 for (int i = 0; i < arity; i++) {
36 var argument = arguments.get(i);
37 var parameter = parameters.get(i);
38 if (!parameter.isAssignable(argument)) {
39 throw new IllegalArgumentException("Argument %d of %s is not assignable to parameter %s"
40 .formatted(i, target, parameter));
41 }
42 switch (parameter.getDirection()) {
43 case IN -> {
44 if (mutableOutArguments.remove(argument)) {
45 checkInOutUnifiable(argument);
46 }
47 mutableInArguments.add(argument);
48 }
49 case OUT -> {
50 if (mutableInArguments.contains(argument)) {
51 checkInOutUnifiable(argument);
52 } else if (!mutableOutArguments.add(argument)) {
53 checkDuplicateOutUnifiable(argument);
54 }
55 }
56 }
57 }
58 inArguments = Collections.unmodifiableSet(mutableInArguments);
59 outArguments = Collections.unmodifiableSet(mutableOutArguments);
60 }
61
62 private static void checkInOutUnifiable(Variable argument) {
63 if (!argument.isUnifiable()) {
64 throw new IllegalArgumentException("Argument %s cannot appear with both %s and %s direction"
65 .formatted(argument, ParameterDirection.IN, ParameterDirection.OUT));
66 }
67 }
68
69 private static void checkDuplicateOutUnifiable(Variable argument) {
70 if (!argument.isUnifiable()) {
71 throw new IllegalArgumentException("Argument %s cannot be bound multiple times".formatted(argument));
72 }
73 }
74
75 public Constraint getTarget() {
76 return target;
77 }
78
79 public List<Variable> getArguments() {
80 return arguments;
81 }
82
83 protected Set<Variable> getArgumentsOfDirection(ParameterDirection direction) {
84 return switch (direction) {
85 case IN -> inArguments;
86 case OUT -> outArguments;
87 };
88 }
89
90 @Override
91 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
92 var inputVariables = new LinkedHashSet<>(getArgumentsOfDirection(ParameterDirection.OUT));
93 inputVariables.retainAll(positiveVariablesInClause);
94 inputVariables.addAll(getArgumentsOfDirection(ParameterDirection.IN));
95 return Collections.unmodifiableSet(inputVariables);
96 }
97
98 @Override
99 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
100 var privateVariables = new LinkedHashSet<>(getArgumentsOfDirection(ParameterDirection.OUT));
101 privateVariables.removeAll(positiveVariablesInClause);
102 return Collections.unmodifiableSet(privateVariables);
103 }
104
105 @Override
106 public Literal substitute(Substitution substitution) {
107 var substitutedArguments = arguments.stream().map(substitution::getSubstitute).toList();
108 return doSubstitute(substitution, substitutedArguments);
109 }
110
111 protected abstract Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments);
112
113 @Override
114 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
115 if (other == null || getClass() != other.getClass()) {
116 return false;
117 }
118 var otherCallLiteral = (AbstractCallLiteral) other;
119 var arity = arguments.size();
120 if (arity != otherCallLiteral.arguments.size()) {
121 return false;
122 }
123 for (int i = 0; i < arity; i++) {
124 if (!helper.variableEqual(arguments.get(i), otherCallLiteral.arguments.get(i))) {
125 return false;
126 }
127 }
128 return target.equals(helper, otherCallLiteral.target);
129 }
130
131 @Override
132 public boolean equals(Object o) {
133 if (this == o) return true;
134 if (o == null || getClass() != o.getClass()) return false;
135 AbstractCallLiteral that = (AbstractCallLiteral) o;
136 return target.equals(that.target) && arguments.equals(that.arguments);
137 }
138
139 @Override
140 public int hashCode() {
141 return Objects.hash(getClass(), target, arguments);
142 }
143}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java
new file mode 100644
index 00000000..3a5eb5c7
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java
@@ -0,0 +1,138 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.*;
12
13import java.util.List;
14import java.util.Objects;
15import java.util.Set;
16
17public class AggregationLiteral<R, T> extends AbstractCallLiteral {
18 private final DataVariable<R> resultVariable;
19 private final DataVariable<T> inputVariable;
20 private final Aggregator<R, T> aggregator;
21
22 public AggregationLiteral(DataVariable<R> resultVariable, Aggregator<R, T> aggregator,
23 DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) {
24 super(target, arguments);
25 if (!inputVariable.getType().equals(aggregator.getInputType())) {
26 throw new IllegalArgumentException("Input variable %s must of type %s, got %s instead".formatted(
27 inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName()));
28 }
29 if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(inputVariable)) {
30 throw new IllegalArgumentException("Input variable %s must be bound with direction %s in the argument list"
31 .formatted(inputVariable, ParameterDirection.OUT));
32 }
33 if (!resultVariable.getType().equals(aggregator.getResultType())) {
34 throw new IllegalArgumentException("Result variable %s must of type %s, got %s instead".formatted(
35 resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName()));
36 }
37 if (arguments.contains(resultVariable)) {
38 throw new IllegalArgumentException("Result variable %s must not appear in the argument list".formatted(
39 resultVariable));
40 }
41 this.resultVariable = resultVariable;
42 this.inputVariable = inputVariable;
43 this.aggregator = aggregator;
44 }
45
46 public DataVariable<R> getResultVariable() {
47 return resultVariable;
48 }
49
50 public DataVariable<T> getInputVariable() {
51 return inputVariable;
52 }
53
54 public Aggregator<R, T> getAggregator() {
55 return aggregator;
56 }
57
58 @Override
59 public Set<Variable> getOutputVariables() {
60 return Set.of(resultVariable);
61 }
62
63 @Override
64 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
65 if (positiveVariablesInClause.contains(inputVariable)) {
66 throw new IllegalArgumentException("Aggregation variable %s must not be bound".formatted(inputVariable));
67 }
68 return super.getInputVariables(positiveVariablesInClause);
69 }
70
71 @Override
72 public Literal reduce() {
73 var reduction = getTarget().getReduction();
74 return switch (reduction) {
75 case ALWAYS_FALSE -> {
76 var emptyValue = aggregator.getEmptyResult();
77 yield emptyValue == null ? BooleanLiteral.FALSE :
78 resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), emptyValue));
79 }
80 case ALWAYS_TRUE -> throw new IllegalArgumentException("Trying to aggregate over an infinite set");
81 case NOT_REDUCIBLE -> this;
82 };
83 }
84
85 @Override
86 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
87 return new AggregationLiteral<>(substitution.getTypeSafeSubstitute(resultVariable), aggregator,
88 substitution.getTypeSafeSubstitute(inputVariable), getTarget(), substitutedArguments);
89 }
90
91 @Override
92 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
93 if (!super.equalsWithSubstitution(helper, other)) {
94 return false;
95 }
96 var otherAggregationLiteral = (AggregationLiteral<?, ?>) other;
97 return helper.variableEqual(resultVariable, otherAggregationLiteral.resultVariable) &&
98 aggregator.equals(otherAggregationLiteral.aggregator) &&
99 helper.variableEqual(inputVariable, otherAggregationLiteral.inputVariable);
100 }
101
102 @Override
103 public boolean equals(Object o) {
104 if (this == o) return true;
105 if (o == null || getClass() != o.getClass()) return false;
106 if (!super.equals(o)) return false;
107 AggregationLiteral<?, ?> that = (AggregationLiteral<?, ?>) o;
108 return resultVariable.equals(that.resultVariable) && inputVariable.equals(that.inputVariable) &&
109 aggregator.equals(that.aggregator);
110 }
111
112 @Override
113 public int hashCode() {
114 return Objects.hash(super.hashCode(), resultVariable, inputVariable, aggregator);
115 }
116
117 @Override
118 public String toString() {
119 var builder = new StringBuilder();
120 builder.append(resultVariable);
121 builder.append(" is ");
122 builder.append(getTarget().toReferenceString());
123 builder.append("(");
124 var argumentIterator = getArguments().iterator();
125 if (argumentIterator.hasNext()) {
126 var argument = argumentIterator.next();
127 if (inputVariable.equals(argument)) {
128 builder.append("@Aggregate(\"").append(aggregator).append("\") ");
129 }
130 builder.append(argument);
131 while (argumentIterator.hasNext()) {
132 builder.append(", ").append(argumentIterator.next());
133 }
134 }
135 builder.append(")");
136 return builder.toString();
137 }
138}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java
new file mode 100644
index 00000000..dbf999a2
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java
@@ -0,0 +1,79 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.DataVariable;
11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.Collections;
15import java.util.Objects;
16import java.util.Set;
17
18public record AssignLiteral<T>(DataVariable<T> variable, Term<T> term) implements Literal {
19 public AssignLiteral {
20 if (!term.getType().equals(variable.getType())) {
21 throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted(
22 term, variable.getType().getName(), term.getType().getName()));
23 }
24 var inputVariables = term.getInputVariables();
25 if (inputVariables.contains(variable)) {
26 throw new IllegalArgumentException("Result variable %s must not appear in the term %s".formatted(
27 variable, term));
28 }
29 }
30
31 @Override
32 public Set<Variable> getOutputVariables() {
33 return Set.of(variable);
34 }
35
36 @Override
37 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
38 return Collections.unmodifiableSet(term.getInputVariables());
39 }
40
41 @Override
42 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
43 return Set.of();
44 }
45
46 @Override
47 public Literal substitute(Substitution substitution) {
48 return new AssignLiteral<>(substitution.getTypeSafeSubstitute(variable), term.substitute(substitution));
49 }
50
51 @Override
52 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
53 if (other == null || getClass() != other.getClass()) {
54 return false;
55 }
56 var otherLetLiteral = (AssignLiteral<?>) other;
57 return helper.variableEqual(variable, otherLetLiteral.variable) && term.equalsWithSubstitution(helper,
58 otherLetLiteral.term);
59 }
60
61 @Override
62 public String toString() {
63 return "%s is (%s)".formatted(variable, term);
64 }
65
66 @Override
67 public boolean equals(Object obj) {
68 if (obj == this) return true;
69 if (obj == null || obj.getClass() != this.getClass()) return false;
70 var that = (AssignLiteral<?>) obj;
71 return Objects.equals(this.variable, that.variable) &&
72 Objects.equals(this.term, that.term);
73 }
74
75 @Override
76 public int hashCode() {
77 return Objects.hash(getClass(), variable, term);
78 }
79}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java
new file mode 100644
index 00000000..1ca04c77
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java
@@ -0,0 +1,83 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.ConstantTerm;
11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.Collections;
15import java.util.Objects;
16import java.util.Set;
17
18public record AssumeLiteral(Term<Boolean> term) implements Literal {
19 public AssumeLiteral {
20 if (!term.getType().equals(Boolean.class)) {
21 throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted(
22 term, Boolean.class.getName(), term.getType().getName()));
23 }
24 }
25
26 @Override
27 public Set<Variable> getOutputVariables() {
28 return Set.of();
29 }
30
31 @Override
32 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
33 return Collections.unmodifiableSet(term.getInputVariables());
34 }
35
36 @Override
37 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
38 return Set.of();
39 }
40
41
42 @Override
43 public Literal substitute(Substitution substitution) {
44 return new AssumeLiteral(term.substitute(substitution));
45 }
46
47 @Override
48 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
49 if (other == null || getClass() != other.getClass()) {
50 return false;
51 }
52 var otherAssumeLiteral = (AssumeLiteral) other;
53 return term.equalsWithSubstitution(helper, otherAssumeLiteral.term);
54 }
55
56 @Override
57 public Literal reduce() {
58 if (term instanceof ConstantTerm<Boolean> constantTerm) {
59 // Return {@link BooleanLiteral#FALSE} for {@code false} or {@code null} literals.
60 return Boolean.TRUE.equals(constantTerm.getValue()) ? BooleanLiteral.TRUE :
61 BooleanLiteral.FALSE;
62 }
63 return this;
64 }
65
66 @Override
67 public String toString() {
68 return "(%s)".formatted(term);
69 }
70
71 @Override
72 public boolean equals(Object obj) {
73 if (obj == this) return true;
74 if (obj == null || obj.getClass() != this.getClass()) return false;
75 var that = (AssumeLiteral) obj;
76 return Objects.equals(this.term, that.term);
77 }
78
79 @Override
80 public int hashCode() {
81 return Objects.hash(getClass(), term);
82 }
83}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java
new file mode 100644
index 00000000..f312d202
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java
@@ -0,0 +1,63 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Set;
13
14public enum BooleanLiteral implements CanNegate<BooleanLiteral> {
15 TRUE(true),
16 FALSE(false);
17
18 private final boolean value;
19
20 BooleanLiteral(boolean value) {
21 this.value = value;
22 }
23
24 @Override
25 public Set<Variable> getOutputVariables() {
26 return Set.of();
27 }
28
29 @Override
30 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
31 return Set.of();
32 }
33
34 @Override
35 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
36 return Set.of();
37 }
38
39 @Override
40 public Literal substitute(Substitution substitution) {
41 // No variables to substitute.
42 return this;
43 }
44
45 @Override
46 public BooleanLiteral negate() {
47 return fromBoolean(!value);
48 }
49
50 @Override
51 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
52 return equals(other);
53 }
54
55 @Override
56 public String toString() {
57 return Boolean.toString(value);
58 }
59
60 public static BooleanLiteral fromBoolean(boolean value) {
61 return value ? TRUE : FALSE;
62 }
63}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java
new file mode 100644
index 00000000..29772aee
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java
@@ -0,0 +1,130 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.*;
15
16public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> {
17 private final CallPolarity polarity;
18
19 public CallLiteral(CallPolarity polarity, Constraint target, List<Variable> arguments) {
20 super(target, arguments);
21 var parameters = target.getParameters();
22 int arity = target.arity();
23 if (polarity.isTransitive()) {
24 if (arity != 2) {
25 throw new IllegalArgumentException("Transitive closures can only take binary relations");
26 }
27 if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) {
28 throw new IllegalArgumentException("Transitive closures can only be computed over nodes");
29 }
30 }
31 this.polarity = polarity;
32 }
33
34 public CallPolarity getPolarity() {
35 return polarity;
36 }
37
38 @Override
39 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
40 return new CallLiteral(polarity, getTarget(), substitutedArguments);
41 }
42
43 @Override
44 public Set<Variable> getOutputVariables() {
45 if (polarity.isPositive()) {
46 return getArgumentsOfDirection(ParameterDirection.OUT);
47 }
48 return Set.of();
49 }
50
51 @Override
52 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
53 if (polarity.isPositive()) {
54 return getArgumentsOfDirection(ParameterDirection.IN);
55 }
56 return super.getInputVariables(positiveVariablesInClause);
57 }
58
59 @Override
60 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
61 if (polarity.isPositive()) {
62 return Set.of();
63 }
64 return super.getPrivateVariables(positiveVariablesInClause);
65 }
66
67 @Override
68 public Literal reduce() {
69 var reduction = getTarget().getReduction();
70 var negatedReduction = polarity.isPositive() ? reduction : reduction.negate();
71 return switch (negatedReduction) {
72 case ALWAYS_TRUE -> BooleanLiteral.TRUE;
73 case ALWAYS_FALSE -> BooleanLiteral.FALSE;
74 default -> this;
75 };
76 }
77
78 @Override
79 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
80 if (!super.equalsWithSubstitution(helper, other)) {
81 return false;
82 }
83 var otherCallLiteral = (CallLiteral) other;
84 return polarity.equals(otherCallLiteral.polarity);
85 }
86
87 @Override
88 public CallLiteral negate() {
89 return new CallLiteral(polarity.negate(), getTarget(), getArguments());
90 }
91
92 @Override
93 public boolean equals(Object o) {
94 if (this == o) return true;
95 if (o == null || getClass() != o.getClass()) return false;
96 if (!super.equals(o)) return false;
97 CallLiteral that = (CallLiteral) o;
98 return polarity == that.polarity;
99 }
100
101 @Override
102 public int hashCode() {
103 return Objects.hash(super.hashCode(), polarity);
104 }
105
106 @Override
107 public String toString() {
108 var builder = new StringBuilder();
109 if (!polarity.isPositive()) {
110 builder.append("!(");
111 }
112 builder.append(getTarget().toReferenceString());
113 if (polarity.isTransitive()) {
114 builder.append("+");
115 }
116 builder.append("(");
117 var argumentIterator = getArguments().iterator();
118 if (argumentIterator.hasNext()) {
119 builder.append(argumentIterator.next());
120 while (argumentIterator.hasNext()) {
121 builder.append(", ").append(argumentIterator.next());
122 }
123 }
124 builder.append(")");
125 if (!polarity.isPositive()) {
126 builder.append(")");
127 }
128 return builder.toString();
129 }
130}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java
new file mode 100644
index 00000000..ca70b0fd
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java
@@ -0,0 +1,37 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8public enum CallPolarity {
9 POSITIVE(true, false),
10 NEGATIVE(false, false),
11 TRANSITIVE(true, true);
12
13 private final boolean positive;
14
15 private final boolean transitive;
16
17 CallPolarity(boolean positive, boolean transitive) {
18 this.positive = positive;
19 this.transitive = transitive;
20 }
21
22 public boolean isPositive() {
23 return positive;
24 }
25
26 public boolean isTransitive() {
27 return transitive;
28 }
29
30 public CallPolarity negate() {
31 return switch (this) {
32 case POSITIVE -> NEGATIVE;
33 case NEGATIVE -> POSITIVE;
34 case TRANSITIVE -> throw new IllegalArgumentException("Transitive polarity cannot be negated");
35 };
36 }
37}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java
new file mode 100644
index 00000000..35dcb3fb
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java
@@ -0,0 +1,10 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8public interface CanNegate<T extends CanNegate<T>> extends Literal {
9 T negate();
10}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java
new file mode 100644
index 00000000..73545620
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java
@@ -0,0 +1,65 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.Variable;
12
13import java.util.Objects;
14import java.util.Set;
15
16public record ConstantLiteral(NodeVariable variable, int nodeId) implements Literal {
17 @Override
18 public Set<Variable> getOutputVariables() {
19 return Set.of(variable);
20 }
21
22 @Override
23 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
24 return Set.of();
25 }
26
27 @Override
28 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
29 return Set.of();
30 }
31
32 @Override
33 public ConstantLiteral substitute(Substitution substitution) {
34 return new ConstantLiteral(substitution.getTypeSafeSubstitute(variable), nodeId);
35 }
36
37 @Override
38 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
39 if (other.getClass() != getClass()) {
40 return false;
41 }
42 var otherConstantLiteral = (ConstantLiteral) other;
43 return helper.variableEqual(variable, otherConstantLiteral.variable) && nodeId == otherConstantLiteral.nodeId;
44 }
45
46
47 @Override
48 public String toString() {
49 return "%s === @Constant %d".formatted(variable, nodeId);
50 }
51
52 @Override
53 public boolean equals(Object obj) {
54 if (obj == this) return true;
55 if (obj == null || obj.getClass() != this.getClass()) return false;
56 var that = (ConstantLiteral) obj;
57 return Objects.equals(this.variable, that.variable) &&
58 this.nodeId == that.nodeId;
59 }
60
61 @Override
62 public int hashCode() {
63 return Objects.hash(getClass(), variable, nodeId);
64 }
65}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java
new file mode 100644
index 00000000..4d4749c8
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java
@@ -0,0 +1,101 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.DataVariable;
12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.query.term.int_.IntTerms;
14
15import java.util.List;
16import java.util.Objects;
17import java.util.Set;
18
19public class CountLiteral extends AbstractCallLiteral {
20 private final DataVariable<Integer> resultVariable;
21
22 public CountLiteral(DataVariable<Integer> resultVariable, Constraint target, List<Variable> arguments) {
23 super(target, arguments);
24 if (!resultVariable.getType().equals(Integer.class)) {
25 throw new IllegalArgumentException("Count result variable %s must be of type %s, got %s instead".formatted(
26 resultVariable, Integer.class.getName(), resultVariable.getType().getName()));
27 }
28 if (arguments.contains(resultVariable)) {
29 throw new IllegalArgumentException("Count result variable %s must not appear in the argument list"
30 .formatted(resultVariable));
31 }
32 this.resultVariable = resultVariable;
33 }
34
35 public DataVariable<Integer> getResultVariable() {
36 return resultVariable;
37 }
38
39 @Override
40 public Set<Variable> getOutputVariables() {
41 return Set.of(resultVariable);
42 }
43
44 @Override
45 public Literal reduce() {
46 var reduction = getTarget().getReduction();
47 return switch (reduction) {
48 case ALWAYS_FALSE -> getResultVariable().assign(IntTerms.constant(0));
49 // The only way a constant {@code true} predicate can be called in a negative position is to have all of
50 // its arguments bound as input variables. Thus, there will only be a single match.
51 case ALWAYS_TRUE -> getResultVariable().assign(IntTerms.constant(1));
52 case NOT_REDUCIBLE -> this;
53 };
54 }
55
56 @Override
57 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
58 return new CountLiteral(substitution.getTypeSafeSubstitute(resultVariable), getTarget(), substitutedArguments);
59 }
60
61 @Override
62 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
63 if (!super.equalsWithSubstitution(helper, other)) {
64 return false;
65 }
66 var otherCountLiteral = (CountLiteral) other;
67 return helper.variableEqual(resultVariable, otherCountLiteral.resultVariable);
68 }
69
70 @Override
71 public boolean equals(Object o) {
72 if (this == o) return true;
73 if (o == null || getClass() != o.getClass()) return false;
74 if (!super.equals(o)) return false;
75 CountLiteral that = (CountLiteral) o;
76 return resultVariable.equals(that.resultVariable);
77 }
78
79 @Override
80 public int hashCode() {
81 return Objects.hash(super.hashCode(), resultVariable);
82 }
83
84 @Override
85 public String toString() {
86 var builder = new StringBuilder();
87 builder.append(resultVariable);
88 builder.append(" is count ");
89 builder.append(getTarget().toReferenceString());
90 builder.append("(");
91 var argumentIterator = getArguments().iterator();
92 if (argumentIterator.hasNext()) {
93 builder.append(argumentIterator.next());
94 while (argumentIterator.hasNext()) {
95 builder.append(", ").append(argumentIterator.next());
96 }
97 }
98 builder.append(")");
99 return builder.toString();
100 }
101}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java
new file mode 100644
index 00000000..28ba7625
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java
@@ -0,0 +1,81 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.Variable;
12
13import java.util.Objects;
14import java.util.Set;
15
16public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariable right)
17 implements CanNegate<EquivalenceLiteral> {
18 @Override
19 public Set<Variable> getOutputVariables() {
20 return Set.of(left);
21 }
22
23 @Override
24 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
25 return Set.of(right);
26 }
27
28 @Override
29 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
30 return Set.of();
31 }
32
33 @Override
34 public EquivalenceLiteral negate() {
35 return new EquivalenceLiteral(!positive, left, right);
36 }
37
38 @Override
39 public EquivalenceLiteral substitute(Substitution substitution) {
40 return new EquivalenceLiteral(positive, substitution.getTypeSafeSubstitute(left),
41 substitution.getTypeSafeSubstitute(right));
42 }
43
44 @Override
45 public Literal reduce() {
46 if (left.equals(right)) {
47 return positive ? BooleanLiteral.TRUE : BooleanLiteral.FALSE;
48 }
49 return this;
50 }
51
52 @Override
53 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
54 if (other.getClass() != getClass()) {
55 return false;
56 }
57 var otherEquivalenceLiteral = (EquivalenceLiteral) other;
58 return helper.variableEqual(left, otherEquivalenceLiteral.left) && helper.variableEqual(right,
59 otherEquivalenceLiteral.right);
60 }
61
62 @Override
63 public String toString() {
64 return "%s %s %s".formatted(left, positive ? "===" : "!==", right);
65 }
66
67 @Override
68 public boolean equals(Object obj) {
69 if (obj == this) return true;
70 if (obj == null || obj.getClass() != this.getClass()) return false;
71 var that = (EquivalenceLiteral) obj;
72 return this.positive == that.positive &&
73 Objects.equals(this.left, that.left) &&
74 Objects.equals(this.right, that.right);
75 }
76
77 @Override
78 public int hashCode() {
79 return Objects.hash(getClass(), positive, left, right);
80 }
81}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java
new file mode 100644
index 00000000..ce6c11fe
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Set;
13
14public interface Literal {
15 Set<Variable> getOutputVariables();
16
17 Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause);
18
19 Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause);
20
21 Literal substitute(Substitution substitution);
22
23 default Literal reduce() {
24 return this;
25 }
26
27 @SuppressWarnings("BooleanMethodIsAlwaysInverted")
28 boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other);
29}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java
new file mode 100644
index 00000000..b3a87811
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.term.Term;
9
10public final class Literals {
11 private Literals() {
12 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
13 }
14
15 public static <T extends CanNegate<T>> T not(CanNegate<T> literal) {
16 return literal.negate();
17 }
18
19 public static AssumeLiteral assume(Term<Boolean> term) {
20 return new AssumeLiteral(term);
21 }
22}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Reduction.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Reduction.java
new file mode 100644
index 00000000..ee155a9a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Reduction.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8public enum Reduction {
9 /**
10 * Signifies that a literal should be preserved in the clause.
11 */
12 NOT_REDUCIBLE,
13
14 /**
15 * Signifies that the literal may be omitted from the cause (if the model being queried is nonempty).
16 */
17 ALWAYS_TRUE,
18
19 /**
20 * Signifies that the clause with the literal may be omitted entirely.
21 */
22 ALWAYS_FALSE;
23
24 public Reduction negate() {
25 return switch (this) {
26 case NOT_REDUCIBLE -> NOT_REDUCIBLE;
27 case ALWAYS_TRUE -> ALWAYS_FALSE;
28 case ALWAYS_FALSE -> ALWAYS_TRUE;
29 };
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java
new file mode 100644
index 00000000..a710c64d
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java
@@ -0,0 +1,63 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.query.ModelQueryAdapter;
9import tools.refinery.store.query.dnf.Query;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.ArrayList;
13import java.util.List;
14
15public abstract class AbstractResultSet<T> implements ResultSet<T> {
16 private final ModelQueryAdapter adapter;
17 private final Query<T> query;
18 private final List<ResultSetListener<T>> listeners = new ArrayList<>();
19
20 protected AbstractResultSet(ModelQueryAdapter adapter, Query<T> query) {
21 this.adapter = adapter;
22 this.query = query;
23 }
24
25 @Override
26 public ModelQueryAdapter getAdapter() {
27 return adapter;
28 }
29
30 @Override
31 public Query<T> getQuery() {
32 return query;
33 }
34
35 @Override
36 public void addListener(ResultSetListener<T> listener) {
37 if (listeners.isEmpty()) {
38 startListeningForChanges();
39 }
40 listeners.add(listener);
41 }
42
43 @Override
44 public void removeListener(ResultSetListener<T> listener) {
45 listeners.remove(listener);
46 if (listeners.isEmpty()) {
47 stopListeningForChanges();
48 }
49 }
50
51 protected abstract void startListeningForChanges();
52
53 protected abstract void stopListeningForChanges();
54
55 protected void notifyChange(Tuple key, T oldValue, T newValue) {
56 int listenerCount = listeners.size();
57 // Use a for loop instead of a for-each loop to avoid {@code Iterator} allocation overhead.
58 //noinspection ForLoopReplaceableByForEach
59 for (int i = 0; i < listenerCount; i++) {
60 listeners.get(i).put(key, oldValue, newValue);
61 }
62 }
63}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java
new file mode 100644
index 00000000..02809477
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.query.ModelQueryAdapter;
9import tools.refinery.store.query.dnf.AnyQuery;
10
11public sealed interface AnyResultSet permits ResultSet {
12 ModelQueryAdapter getAdapter();
13
14 AnyQuery getQuery();
15
16 int size();
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java
new file mode 100644
index 00000000..2795a44b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java
@@ -0,0 +1,49 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.Cursors;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.tuple.Tuple;
13
14public record EmptyResultSet<T>(ModelQueryAdapter adapter, Query<T> query) implements ResultSet<T> {
15 @Override
16 public ModelQueryAdapter getAdapter() {
17 return adapter;
18 }
19
20 @Override
21 public Query<T> getQuery() {
22 return query;
23 }
24
25 @Override
26 public T get(Tuple parameters) {
27 return query.defaultValue();
28 }
29
30 @Override
31 public Cursor<Tuple, T> getAll() {
32 return Cursors.empty();
33 }
34
35 @Override
36 public int size() {
37 return 0;
38 }
39
40 @Override
41 public void addListener(ResultSetListener<T> listener) {
42 // No need to store the listener, because the empty result set will never change.
43 }
44
45 @Override
46 public void removeListener(ResultSetListener<T> listener) {
47 // No need to remove the listener, because we never stored it.
48 }
49}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java
new file mode 100644
index 00000000..39006d65
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java
@@ -0,0 +1,80 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.query.ModelQueryAdapter;
10import tools.refinery.store.query.dnf.Query;
11import tools.refinery.store.query.utils.OrderStatisticTree;
12import tools.refinery.store.tuple.Tuple;
13
14import java.util.Objects;
15
16public class OrderedResultSet<T> implements AutoCloseable, ResultSet<T> {
17 private final ResultSet<T> resultSet;
18 private final OrderStatisticTree<Tuple> tree = new OrderStatisticTree<>();
19 private final ResultSetListener<T> listener = (key, fromValue, toValue) -> {
20 var defaultValue = getQuery().defaultValue();
21 if (Objects.equals(defaultValue, toValue)) {
22 tree.remove(key);
23 } else {
24 tree.add(key);
25 }
26 };
27
28 public OrderedResultSet(ResultSet<T> resultSet) {
29 this.resultSet = resultSet;
30 resultSet.addListener(listener);
31 var cursor = resultSet.getAll();
32 while (cursor.move()) {
33 tree.add(cursor.getKey());
34 }
35 }
36
37 @Override
38 public ModelQueryAdapter getAdapter() {
39 return resultSet.getAdapter();
40 }
41
42 @Override
43 public int size() {
44 return resultSet.size();
45 }
46
47 @Override
48 public Query<T> getQuery() {
49 return resultSet.getQuery();
50 }
51
52 @Override
53 public T get(Tuple parameters) {
54 return resultSet.get(parameters);
55 }
56
57 public Tuple getKey(int index) {
58 return tree.get(index);
59 }
60
61 @Override
62 public Cursor<Tuple, T> getAll() {
63 return resultSet.getAll();
64 }
65
66 @Override
67 public void addListener(ResultSetListener<T> listener) {
68 resultSet.addListener(listener);
69 }
70
71 @Override
72 public void removeListener(ResultSetListener<T> listener) {
73 resultSet.removeListener(listener);
74 }
75
76 @Override
77 public void close() {
78 resultSet.removeListener(listener);
79 }
80}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java
new file mode 100644
index 00000000..33d1ea95
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.query.dnf.Query;
10import tools.refinery.store.tuple.Tuple;
11
12public non-sealed interface ResultSet<T> extends AnyResultSet {
13 Query<T> getQuery();
14
15 T get(Tuple parameters);
16
17 Cursor<Tuple, T> getAll();
18
19 void addListener(ResultSetListener<T> listener);
20
21 void removeListener(ResultSetListener<T> listener);
22}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSetListener.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSetListener.java
new file mode 100644
index 00000000..fd8a503e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSetListener.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.tuple.Tuple;
9
10@FunctionalInterface
11public interface ResultSetListener<T> {
12 void put(Tuple key, T fromValue, T toValue);
13}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java
new file mode 100644
index 00000000..a8201eef
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java
@@ -0,0 +1,18 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.substitution;
7
8import tools.refinery.store.query.term.Variable;
9
10import java.util.Map;
11
12public record MapBasedSubstitution(Map<Variable, Variable> map, Substitution fallback) implements Substitution {
13 @Override
14 public Variable getSubstitute(Variable variable) {
15 var value = map.get(variable);
16 return value == null ? fallback.getSubstitute(variable) : value;
17 }
18}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java
new file mode 100644
index 00000000..9b737ceb
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java
@@ -0,0 +1,20 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.substitution;
7
8import tools.refinery.store.query.term.Variable;
9
10import java.util.HashMap;
11import java.util.Map;
12
13public class RenewingSubstitution implements Substitution {
14 private final Map<Variable, Variable> alreadyRenewed = new HashMap<>();
15
16 @Override
17 public Variable getSubstitute(Variable variable) {
18 return alreadyRenewed.computeIfAbsent(variable, Variable::renew);
19 }
20}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java
new file mode 100644
index 00000000..bb3803d3
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java
@@ -0,0 +1,23 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.substitution;
7
8import tools.refinery.store.query.term.Variable;
9
10public enum StatelessSubstitution implements Substitution {
11 FAILING {
12 @Override
13 public Variable getSubstitute(Variable variable) {
14 throw new IllegalArgumentException("No substitute for " + variable);
15 }
16 },
17 IDENTITY {
18 @Override
19 public Variable getSubstitute(Variable variable) {
20 return variable;
21 }
22 }
23}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java
new file mode 100644
index 00000000..834fce12
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.substitution;
7
8import tools.refinery.store.query.term.DataVariable;
9import tools.refinery.store.query.term.NodeVariable;
10import tools.refinery.store.query.term.Variable;
11
12@FunctionalInterface
13public interface Substitution {
14 Variable getSubstitute(Variable variable);
15
16 default NodeVariable getTypeSafeSubstitute(NodeVariable variable) {
17 var substitute = getSubstitute(variable);
18 return substitute.asNodeVariable();
19 }
20
21 default <T> DataVariable<T> getTypeSafeSubstitute(DataVariable<T> variable) {
22 var substitute = getSubstitute(variable);
23 return substitute.asDataVariable(variable.getType());
24 }
25
26 static SubstitutionBuilder builder() {
27 return new SubstitutionBuilder();
28 }
29}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/SubstitutionBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/SubstitutionBuilder.java
new file mode 100644
index 00000000..37fb6908
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/SubstitutionBuilder.java
@@ -0,0 +1,79 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.substitution;
7
8import tools.refinery.store.query.term.DataVariable;
9import tools.refinery.store.query.term.NodeVariable;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Collections;
13import java.util.HashMap;
14import java.util.List;
15import java.util.Map;
16
17@SuppressWarnings("UnusedReturnValue")
18public class SubstitutionBuilder {
19 private final Map<Variable, Variable> map = new HashMap<>();
20 private Substitution fallback;
21
22 SubstitutionBuilder() {
23 total();
24 }
25
26 public SubstitutionBuilder put(NodeVariable original, NodeVariable substitute) {
27 return putChecked(original, substitute);
28 }
29
30 public <T> SubstitutionBuilder put(DataVariable<T> original, DataVariable<T> substitute) {
31 return putChecked(original, substitute);
32 }
33
34 public SubstitutionBuilder putChecked(Variable original, Variable substitute) {
35 if (!original.tryGetType().equals(substitute.tryGetType())) {
36 throw new IllegalArgumentException("Cannot substitute variable %s of sort %s with variable %s of sort %s"
37 .formatted(original, original.tryGetType().map(Class::getName).orElse("node"), substitute,
38 substitute.tryGetType().map(Class::getName).orElse("node")));
39 }
40 if (map.containsKey(original)) {
41 throw new IllegalArgumentException("Already has substitution for variable %s".formatted(original));
42 }
43 map.put(original, substitute);
44 return this;
45 }
46
47 public SubstitutionBuilder putManyChecked(List<Variable> originals, List<Variable> substitutes) {
48 int size = originals.size();
49 if (size != substitutes.size()) {
50 throw new IllegalArgumentException("Cannot substitute %d variables %s with %d variables %s"
51 .formatted(size, originals, substitutes.size(), substitutes));
52 }
53 for (int i = 0; i < size; i++) {
54 putChecked(originals.get(i), substitutes.get(i));
55 }
56 return this;
57 }
58
59 public SubstitutionBuilder fallback(Substitution newFallback) {
60 fallback = newFallback;
61 return this;
62 }
63
64 public SubstitutionBuilder total() {
65 return fallback(StatelessSubstitution.FAILING);
66 }
67
68 public SubstitutionBuilder partial() {
69 return fallback(StatelessSubstitution.IDENTITY);
70 }
71
72 public SubstitutionBuilder renewing() {
73 return fallback(new RenewingSubstitution());
74 }
75
76 public Substitution build() {
77 return map.isEmpty() ? fallback : new MapBasedSubstitution(Collections.unmodifiableMap(map), fallback);
78 }
79}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java
new file mode 100644
index 00000000..d0ae3c12
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java
@@ -0,0 +1,41 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9
10import java.util.Objects;
11
12public abstract class AbstractTerm<T> implements Term<T> {
13 private final Class<T> type;
14
15 protected AbstractTerm(Class<T> type) {
16 this.type = type;
17 }
18
19 @Override
20 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
21 return getClass().equals(other.getClass()) && type.equals(other.getType());
22 }
23
24 @Override
25 public Class<T> getType() {
26 return type;
27 }
28
29 @Override
30 public boolean equals(Object o) {
31 if (this == o) return true;
32 if (o == null || getClass() != o.getClass()) return false;
33 AbstractTerm<?> that = (AbstractTerm<?>) o;
34 return type.equals(that.type);
35 }
36
37 @Override
38 public int hashCode() {
39 return Objects.hash(getClass(), type);
40 }
41}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java
new file mode 100644
index 00000000..0684a9d9
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java
@@ -0,0 +1,18 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import java.util.stream.Stream;
9
10public interface Aggregator<R, T> {
11 Class<R> getResultType();
12
13 Class<T> getInputType();
14
15 R aggregateStream(Stream<T> stream);
16
17 R getEmptyResult();
18}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java
new file mode 100644
index 00000000..192c39c5
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java
@@ -0,0 +1,49 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10
11import java.util.Optional;
12import java.util.Set;
13
14public abstract sealed class AnyDataVariable extends Variable implements AnyTerm permits DataVariable {
15 protected AnyDataVariable(String name) {
16 super(name);
17 }
18
19 @Override
20 public Optional<Class<?>> tryGetType() {
21 return Optional.of(getType());
22 }
23
24 @Override
25 public NodeVariable asNodeVariable() {
26 throw new IllegalStateException("%s is a data variable".formatted(this));
27 }
28
29 @Override
30 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
31 return other instanceof AnyDataVariable dataVariable && helper.variableEqual(this, dataVariable);
32 }
33
34 @Override
35 public Set<AnyDataVariable> getInputVariables() {
36 return Set.of(this);
37 }
38
39 @Override
40 public boolean isUnifiable() {
41 return false;
42 }
43
44 @Override
45 public abstract AnyDataVariable renew(@Nullable String name);
46
47 @Override
48 public abstract AnyDataVariable renew();
49}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java
new file mode 100644
index 00000000..c12c0166
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10
11import java.util.Set;
12
13public sealed interface AnyTerm permits AnyDataVariable, Term {
14 Class<?> getType();
15
16 AnyTerm substitute(Substitution substitution);
17
18 boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other);
19
20 Set<AnyDataVariable> getInputVariables();
21}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java
new file mode 100644
index 00000000..0cf30aa6
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.literal.Literal;
9
10@FunctionalInterface
11public interface AssignedValue<T> {
12 Literal toLiteral(DataVariable<T> targetVariable);
13}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java
new file mode 100644
index 00000000..8ad17839
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java
@@ -0,0 +1,113 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.valuation.Valuation;
11
12import java.util.Collections;
13import java.util.HashSet;
14import java.util.Objects;
15import java.util.Set;
16
17public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> {
18 private final Class<T1> leftType;
19 private final Class<T2> rightType;
20 private final Term<T1> left;
21 private final Term<T2> right;
22
23 protected BinaryTerm(Class<R> type, Class<T1> leftType, Class<T2> rightType, Term<T1> left, Term<T2> right) {
24 super(type);
25 if (!left.getType().equals(leftType)) {
26 throw new IllegalArgumentException("Expected left %s to be of type %s, got %s instead".formatted(
27 left, leftType.getName(), left.getType().getName()));
28 }
29 if (!right.getType().equals(rightType)) {
30 throw new IllegalArgumentException("Expected right %s to be of type %s, got %s instead".formatted(
31 right, rightType.getName(), right.getType().getName()));
32 }
33 this.leftType = leftType;
34 this.rightType = rightType;
35 this.left = left;
36 this.right = right;
37 }
38
39 public Class<T1> getLeftType() {
40 return leftType;
41 }
42
43 public Class<T2> getRightType() {
44 return rightType;
45 }
46
47 public Term<T1> getLeft() {
48 return left;
49 }
50
51 public Term<T2> getRight() {
52 return right;
53 }
54
55 @Override
56 public R evaluate(Valuation valuation) {
57 var leftValue = left.evaluate(valuation);
58 if (leftValue == null) {
59 return null;
60 }
61 var rightValue = right.evaluate(valuation);
62 if (rightValue == null) {
63 return null;
64 }
65 return doEvaluate(leftValue, rightValue);
66 }
67
68 protected abstract R doEvaluate(T1 leftValue, T2 rightValue);
69
70 @Override
71 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
72 if (!super.equalsWithSubstitution(helper, other)) {
73 return false;
74 }
75 var otherBinaryTerm = (BinaryTerm<?, ?, ?>) other;
76 return leftType.equals(otherBinaryTerm.leftType) &&
77 rightType.equals(otherBinaryTerm.rightType) &&
78 left.equalsWithSubstitution(helper, otherBinaryTerm.left) &&
79 right.equalsWithSubstitution(helper, otherBinaryTerm.right);
80 }
81
82 @Override
83 public Term<R> substitute(Substitution substitution) {
84 return doSubstitute(substitution, left.substitute(substitution), right.substitute(substitution));
85 }
86
87 public abstract Term<R> doSubstitute(Substitution substitution, Term<T1> substitutedLeft,
88 Term<T2> substitutedRight);
89
90 @Override
91 public Set<AnyDataVariable> getInputVariables() {
92 var inputVariables = new HashSet<>(left.getInputVariables());
93 inputVariables.addAll(right.getInputVariables());
94 return Collections.unmodifiableSet(inputVariables);
95 }
96
97 @Override
98 public boolean equals(Object o) {
99 if (this == o) return true;
100 if (o == null || getClass() != o.getClass()) return false;
101 if (!super.equals(o)) return false;
102 BinaryTerm<?, ?, ?> that = (BinaryTerm<?, ?, ?>) o;
103 return Objects.equals(leftType, that.leftType) &&
104 Objects.equals(rightType, that.rightType) &&
105 Objects.equals(left, that.left) &&
106 Objects.equals(right, that.right);
107 }
108
109 @Override
110 public int hashCode() {
111 return Objects.hash(super.hashCode(), leftType, rightType, left, right);
112 }
113}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java
new file mode 100644
index 00000000..2f6c56d1
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java
@@ -0,0 +1,71 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.valuation.Valuation;
11
12import java.util.Objects;
13import java.util.Set;
14
15public final class ConstantTerm<T> extends AbstractTerm<T> {
16 private final T value;
17
18 public ConstantTerm(Class<T> type, T value) {
19 super(type);
20 if (value != null && !type.isInstance(value)) {
21 throw new IllegalArgumentException("Value %s is not an instance of %s".formatted(value, type.getName()));
22 }
23 this.value = value;
24 }
25
26 public T getValue() {
27 return value;
28 }
29
30 @Override
31 public T evaluate(Valuation valuation) {
32 return getValue();
33 }
34
35 @Override
36 public Term<T> substitute(Substitution substitution) {
37 return this;
38 }
39
40 @Override
41 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
42 if (!super.equalsWithSubstitution(helper, other)) {
43 return false;
44 }
45 var otherConstantTerm = (ConstantTerm<?>) other;
46 return Objects.equals(value, otherConstantTerm.value);
47 }
48
49 @Override
50 public Set<AnyDataVariable> getInputVariables() {
51 return Set.of();
52 }
53
54 @Override
55 public String toString() {
56 return value.toString();
57 }
58
59 @Override
60 public boolean equals(Object o) {
61 if (this == o) return true;
62 if (o == null || getClass() != o.getClass()) return false;
63 ConstantTerm<?> that = (ConstantTerm<?>) o;
64 return Objects.equals(value, that.value);
65 }
66
67 @Override
68 public int hashCode() {
69 return Objects.hash(value);
70 }
71}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java
new file mode 100644
index 00000000..00950360
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java
@@ -0,0 +1,82 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.literal.Literal;
11import tools.refinery.store.query.substitution.Substitution;
12import tools.refinery.store.query.valuation.Valuation;
13
14import java.util.Objects;
15
16public final class DataVariable<T> extends AnyDataVariable implements Term<T> {
17 private final Class<T> type;
18
19 DataVariable(String name, Class<T> type) {
20 super(name);
21 this.type = type;
22 }
23
24 @Override
25 public Class<T> getType() {
26 return type;
27 }
28
29 @Override
30 public DataVariable<T> renew(@Nullable String name) {
31 return new DataVariable<>(name, type);
32 }
33
34 @Override
35 public DataVariable<T> renew() {
36 return renew(getExplicitName());
37 }
38
39 @Override
40 public <U> DataVariable<U> asDataVariable(Class<U> newType) {
41 if (!getType().equals(newType)) {
42 throw new IllegalStateException("%s is not of type %s but of type %s".formatted(this, newType.getName(),
43 getType().getName()));
44 }
45 @SuppressWarnings("unchecked")
46 var result = (DataVariable<U>) this;
47 return result;
48 }
49
50 @Override
51 public T evaluate(Valuation valuation) {
52 return valuation.getValue(this);
53 }
54
55 @Override
56 public Term<T> substitute(Substitution substitution) {
57 return substitution.getTypeSafeSubstitute(this);
58 }
59
60 @Override
61 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
62 return other instanceof DataVariable<?> dataVariable && helper.variableEqual(this, dataVariable);
63 }
64
65 public Literal assign(AssignedValue<T> value) {
66 return value.toLiteral(this);
67 }
68
69 @Override
70 public boolean equals(Object o) {
71 if (this == o) return true;
72 if (o == null || getClass() != o.getClass()) return false;
73 if (!super.equals(o)) return false;
74 DataVariable<?> that = (DataVariable<?>) o;
75 return type.equals(that.type);
76 }
77
78 @Override
79 public int hashCode() {
80 return Objects.hash(super.hashCode(), type);
81 }
82}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java
new file mode 100644
index 00000000..657cb631
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java
@@ -0,0 +1,108 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import java.util.Comparator;
9import java.util.Objects;
10import java.util.SortedMap;
11import java.util.TreeMap;
12
13public class ExtremeValueAggregator<T> implements StatefulAggregator<T, T> {
14 private final Class<T> type;
15 private final T emptyResult;
16 private final Comparator<T> comparator;
17
18 public ExtremeValueAggregator(Class<T> type, T emptyResult) {
19 this(type, emptyResult, null);
20 }
21
22 public ExtremeValueAggregator(Class<T> type, T emptyResult, Comparator<T> comparator) {
23 this.type = type;
24 this.emptyResult = emptyResult;
25 this.comparator = comparator;
26 }
27
28 @Override
29 public Class<T> getResultType() {
30 return getInputType();
31 }
32
33 @Override
34 public Class<T> getInputType() {
35 return type;
36 }
37
38 @Override
39 public StatefulAggregate<T, T> createEmptyAggregate() {
40 return new Aggregate();
41 }
42
43 @Override
44 public T getEmptyResult() {
45 return emptyResult;
46 }
47
48 @Override
49 public boolean equals(Object o) {
50 if (this == o) return true;
51 if (o == null || getClass() != o.getClass()) return false;
52 ExtremeValueAggregator<?> that = (ExtremeValueAggregator<?>) o;
53 return type.equals(that.type) && Objects.equals(emptyResult, that.emptyResult) && Objects.equals(comparator,
54 that.comparator);
55 }
56
57 @Override
58 public int hashCode() {
59 return Objects.hash(type, emptyResult, comparator);
60 }
61
62 private class Aggregate implements StatefulAggregate<T, T> {
63 private final SortedMap<T, Integer> values;
64
65 private Aggregate() {
66 values = new TreeMap<>(comparator);
67 }
68
69 private Aggregate(Aggregate other) {
70 values = new TreeMap<>(other.values);
71 }
72
73 @Override
74 public void add(T value) {
75 values.compute(value, (ignoredValue, currentCount) -> currentCount == null ? 1 : currentCount + 1);
76 }
77
78 @Override
79 public void remove(T value) {
80 values.compute(value, (theValue, currentCount) -> {
81 if (currentCount == null || currentCount <= 0) {
82 throw new IllegalStateException("Invalid count %d for value %s".formatted(currentCount, theValue));
83 }
84 return currentCount.equals(1) ? null : currentCount - 1;
85 });
86 }
87
88 @Override
89 public T getResult() {
90 return isEmpty() ? emptyResult : values.firstKey();
91 }
92
93 @Override
94 public boolean isEmpty() {
95 return values.isEmpty();
96 }
97
98 @Override
99 public StatefulAggregate<T, T> deepCopy() {
100 return new Aggregate(this);
101 }
102
103 @Override
104 public boolean contains(T value) {
105 return StatefulAggregate.super.contains(value);
106 }
107 }
108}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java
new file mode 100644
index 00000000..a2f3261f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java
@@ -0,0 +1,60 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.literal.ConstantLiteral;
10import tools.refinery.store.query.literal.EquivalenceLiteral;
11
12import java.util.Optional;
13
14public final class NodeVariable extends Variable {
15 NodeVariable(@Nullable String name) {
16 super(name);
17 }
18
19 @Override
20 public Optional<Class<?>> tryGetType() {
21 return Optional.empty();
22 }
23
24 @Override
25 public boolean isUnifiable() {
26 return true;
27 }
28
29 @Override
30 public NodeVariable renew(@Nullable String name) {
31 return Variable.of(name);
32 }
33
34 @Override
35 public NodeVariable renew() {
36 return renew(getExplicitName());
37 }
38
39 @Override
40 public NodeVariable asNodeVariable() {
41 return this;
42 }
43
44 @Override
45 public <T> DataVariable<T> asDataVariable(Class<T> type) {
46 throw new IllegalStateException("%s is a node variable".formatted(this));
47 }
48
49 public ConstantLiteral isConstant(int value) {
50 return new ConstantLiteral(this, value);
51 }
52
53 public EquivalenceLiteral isEquivalent(NodeVariable other) {
54 return new EquivalenceLiteral(true, this, other);
55 }
56
57 public EquivalenceLiteral notEquivalent(NodeVariable other) {
58 return new EquivalenceLiteral(false, this, other);
59 }
60}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java
new file mode 100644
index 00000000..e5a0cdf1
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java
@@ -0,0 +1,60 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import java.util.Objects;
9import java.util.Optional;
10
11public class Parameter {
12 public static final Parameter NODE_OUT = new Parameter(null, ParameterDirection.OUT);
13
14 private final Class<?> dataType;
15 private final ParameterDirection direction;
16
17 public Parameter(Class<?> dataType, ParameterDirection direction) {
18 this.dataType = dataType;
19 this.direction = direction;
20 }
21
22 public boolean isNodeVariable() {
23 return dataType == null;
24 }
25
26 public boolean isDataVariable() {
27 return !isNodeVariable();
28 }
29
30 public Optional<Class<?>> tryGetType() {
31 return Optional.ofNullable(dataType);
32 }
33
34 public ParameterDirection getDirection() {
35 return direction;
36 }
37
38 public boolean isAssignable(Variable variable) {
39 if (variable instanceof AnyDataVariable dataVariable) {
40 return dataVariable.getType().equals(dataType);
41 } else if (variable instanceof NodeVariable) {
42 return !isDataVariable();
43 } else {
44 throw new IllegalArgumentException("Unknown variable " + variable);
45 }
46 }
47
48 @Override
49 public boolean equals(Object o) {
50 if (this == o) return true;
51 if (o == null || getClass() != o.getClass()) return false;
52 Parameter parameter = (Parameter) o;
53 return Objects.equals(dataType, parameter.dataType) && direction == parameter.direction;
54 }
55
56 @Override
57 public int hashCode() {
58 return Objects.hash(dataType, direction);
59 }
60}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java
new file mode 100644
index 00000000..cd0739be
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8public enum ParameterDirection {
9 OUT("@Out"),
10 IN("@In");
11
12 private final String name;
13
14 ParameterDirection(String name) {
15 this.name = name;
16 }
17
18 @Override
19 public String toString() {
20 return name;
21 }
22}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java
new file mode 100644
index 00000000..ab310556
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8public interface StatefulAggregate<R, T> {
9 void add(T value);
10
11 void remove(T value);
12
13 R getResult();
14
15 boolean isEmpty();
16
17 StatefulAggregate<R, T> deepCopy();
18
19 default boolean contains(T value) {
20 throw new UnsupportedOperationException();
21 }
22}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java
new file mode 100644
index 00000000..df746a90
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java
@@ -0,0 +1,28 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import java.util.stream.Stream;
9
10public interface StatefulAggregator<R, T> extends Aggregator<R, T> {
11 StatefulAggregate<R, T> createEmptyAggregate();
12
13 @Override
14 default R aggregateStream(Stream<T> stream) {
15 var accumulator = createEmptyAggregate();
16 var iterator = stream.iterator();
17 while (iterator.hasNext()) {
18 var value = iterator.next();
19 accumulator.add(value);
20 }
21 return accumulator.getResult();
22 }
23
24 @Override
25 default R getEmptyResult() {
26 return createEmptyAggregate().getResult();
27 }
28}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java
new file mode 100644
index 00000000..a094919e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java
@@ -0,0 +1,25 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import java.util.stream.Stream;
9
10public interface StatelessAggregator<R, T> extends Aggregator<R, T> {
11 R add(R current, T value);
12
13 R remove(R current, T value);
14
15 @Override
16 default R aggregateStream(Stream<T> stream) {
17 var accumulator = getEmptyResult();
18 var iterator = stream.iterator();
19 while (iterator.hasNext()) {
20 var value = iterator.next();
21 accumulator = add(accumulator, value);
22 }
23 return accumulator;
24 }
25}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java
new file mode 100644
index 00000000..e6818b88
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java
@@ -0,0 +1,26 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.literal.AssignLiteral;
9import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.valuation.Valuation;
12
13public non-sealed interface Term<T> extends AnyTerm, AssignedValue<T> {
14 @Override
15 Class<T> getType();
16
17 T evaluate(Valuation valuation);
18
19 @Override
20 Term<T> substitute(Substitution substitution);
21
22 @Override
23 default Literal toLiteral(DataVariable<T> targetVariable) {
24 return new AssignLiteral<>(targetVariable, this);
25 }
26}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java
new file mode 100644
index 00000000..a46ebe31
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java
@@ -0,0 +1,79 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.valuation.Valuation;
11
12import java.util.Objects;
13import java.util.Set;
14
15public abstract class UnaryTerm<R, T> extends AbstractTerm<R> {
16 private final Class<T> bodyType;
17 private final Term<T> body;
18
19 protected UnaryTerm(Class<R> type, Class<T> bodyType, Term<T> body) {
20 super(type);
21 if (!body.getType().equals(bodyType)) {
22 throw new IllegalArgumentException("Expected body %s to be of type %s, got %s instead".formatted(body,
23 bodyType.getName(), body.getType().getName()));
24 }
25 this.bodyType = bodyType;
26 this.body = body;
27 }
28
29 public Class<T> getBodyType() {
30 return bodyType;
31 }
32
33 public Term<T> getBody() {
34 return body;
35 }
36
37 @Override
38 public R evaluate(Valuation valuation) {
39 var bodyValue = body.evaluate(valuation);
40 return bodyValue == null ? null : doEvaluate(bodyValue);
41 }
42
43 protected abstract R doEvaluate(T bodyValue);
44
45 @Override
46 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
47 if (!super.equalsWithSubstitution(helper, other)) {
48 return false;
49 }
50 var otherUnaryTerm = (UnaryTerm<?, ?>) other;
51 return bodyType.equals(otherUnaryTerm.bodyType) && body.equalsWithSubstitution(helper, otherUnaryTerm.body);
52 }
53
54 @Override
55 public Term<R> substitute(Substitution substitution) {
56 return doSubstitute(substitution, body.substitute(substitution));
57 }
58
59 protected abstract Term<R> doSubstitute(Substitution substitution, Term<T> substitutedBody);
60
61 @Override
62 public Set<AnyDataVariable> getInputVariables() {
63 return body.getInputVariables();
64 }
65
66 @Override
67 public boolean equals(Object o) {
68 if (this == o) return true;
69 if (o == null || getClass() != o.getClass()) return false;
70 if (!super.equals(o)) return false;
71 UnaryTerm<?, ?> unaryTerm = (UnaryTerm<?, ?>) o;
72 return Objects.equals(bodyType, unaryTerm.bodyType) && Objects.equals(body, unaryTerm.body);
73 }
74
75 @Override
76 public int hashCode() {
77 return Objects.hash(super.hashCode(), bodyType, body);
78 }
79}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java
new file mode 100644
index 00000000..a0268c8e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java
@@ -0,0 +1,84 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.dnf.DnfUtils;
10
11import java.util.Objects;
12import java.util.Optional;
13
14public abstract sealed class Variable permits AnyDataVariable, NodeVariable {
15 private final String explicitName;
16 private final String uniqueName;
17
18 protected Variable(String name) {
19 this.explicitName = name;
20 uniqueName = DnfUtils.generateUniqueName(name);
21 }
22
23 public abstract Optional<Class<?>> tryGetType();
24
25 public String getName() {
26 return explicitName == null ? uniqueName : explicitName;
27 }
28
29 protected String getExplicitName() {
30 return explicitName;
31 }
32
33 public boolean isExplicitlyNamed() {
34 return explicitName != null;
35 }
36
37 public String getUniqueName() {
38 return uniqueName;
39 }
40
41 public abstract boolean isUnifiable();
42
43 public abstract Variable renew(@Nullable String name);
44
45 public abstract Variable renew();
46
47 public abstract NodeVariable asNodeVariable();
48
49 public abstract <T> DataVariable<T> asDataVariable(Class<T> type);
50
51 @Override
52 public String toString() {
53 return getName();
54 }
55
56 @Override
57 public boolean equals(Object o) {
58 if (this == o) return true;
59 if (o == null || getClass() != o.getClass()) return false;
60 Variable variable = (Variable) o;
61 return Objects.equals(uniqueName, variable.uniqueName);
62 }
63
64 @Override
65 public int hashCode() {
66 return Objects.hash(uniqueName);
67 }
68
69 public static NodeVariable of(@Nullable String name) {
70 return new NodeVariable(name);
71 }
72
73 public static NodeVariable of() {
74 return of((String) null);
75 }
76
77 public static <T> DataVariable<T> of(@Nullable String name, Class<T> type) {
78 return new DataVariable<>(name, type);
79 }
80
81 public static <T> DataVariable<T> of(Class<T> type) {
82 return of(null, type);
83 }
84}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolAndTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolAndTerm.java
new file mode 100644
index 00000000..f9e1c06f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolAndTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class BoolAndTerm extends BoolBinaryTerm {
12 public BoolAndTerm(Term<Boolean> left, Term<Boolean> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedLeft,
18 Term<Boolean> substitutedRight) {
19 return new BoolAndTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) {
24 return leftValue && rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s && %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolBinaryTerm.java
new file mode 100644
index 00000000..a85aa63a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolBinaryTerm.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.term.BinaryTerm;
9import tools.refinery.store.query.term.Term;
10
11public abstract class BoolBinaryTerm extends BinaryTerm<Boolean, Boolean, Boolean> {
12 protected BoolBinaryTerm(Term<Boolean> left, Term<Boolean> right) {
13 super(Boolean.class, Boolean.class, Boolean.class, left, right);
14 }
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java
new file mode 100644
index 00000000..8d3382b3
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.query.term.UnaryTerm;
11
12public class BoolNotTerm extends UnaryTerm<Boolean, Boolean> {
13 protected BoolNotTerm(Term<Boolean> body) {
14 super(Boolean.class, Boolean.class, body);
15 }
16
17 @Override
18 protected Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedBody) {
19 return new BoolNotTerm(substitutedBody);
20 }
21
22 @Override
23 protected Boolean doEvaluate(Boolean bodyValue) {
24 return !bodyValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(!%s)".formatted(getBody());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolOrTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolOrTerm.java
new file mode 100644
index 00000000..b5195d52
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolOrTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class BoolOrTerm extends BoolBinaryTerm {
12 public BoolOrTerm(Term<Boolean> left, Term<Boolean> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedLeft,
18 Term<Boolean> substitutedRight) {
19 return new BoolOrTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) {
24 return leftValue || rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s || %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java
new file mode 100644
index 00000000..fa54f686
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java
@@ -0,0 +1,35 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.term.ConstantTerm;
9import tools.refinery.store.query.term.Term;
10
11public final class BoolTerms {
12 private BoolTerms() {
13 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
14 }
15
16 public static Term<Boolean> constant(Boolean value) {
17 return new ConstantTerm<>(Boolean.class, value);
18 }
19
20 public static Term<Boolean> not(Term<Boolean> body) {
21 return new BoolNotTerm(body);
22 }
23
24 public static Term<Boolean> and(Term<Boolean> left, Term<Boolean> right) {
25 return new BoolAndTerm(left, right);
26 }
27
28 public static Term<Boolean> or(Term<Boolean> left, Term<Boolean> right) {
29 return new BoolOrTerm(left, right);
30 }
31
32 public static Term<Boolean> xor(Term<Boolean> left, Term<Boolean> right) {
33 return new BoolXorTerm(left, right);
34 }
35}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolXorTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolXorTerm.java
new file mode 100644
index 00000000..7478b8a5
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolXorTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class BoolXorTerm extends BoolBinaryTerm {
12 public BoolXorTerm(Term<Boolean> left, Term<Boolean> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedLeft,
18 Term<Boolean> substitutedRight) {
19 return new BoolXorTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) {
24 return leftValue ^ rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s ^^ %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/ComparisonTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/ComparisonTerm.java
new file mode 100644
index 00000000..5ca5a0a1
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/ComparisonTerm.java
@@ -0,0 +1,19 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.term.BinaryTerm;
9import tools.refinery.store.query.term.Term;
10
11public abstract class ComparisonTerm<T> extends BinaryTerm<Boolean, T, T> {
12 protected ComparisonTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(Boolean.class, argumentType, argumentType, left, right);
14 }
15
16 public Class<T> getArgumentType() {
17 return getLeftType();
18 }
19}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/EqTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/EqTerm.java
new file mode 100644
index 00000000..b8cf36f8
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/EqTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class EqTerm<T> extends ComparisonTerm<T> {
12 public EqTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return leftValue.equals(rightValue);
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new EqTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s == %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterEqTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterEqTerm.java
new file mode 100644
index 00000000..b109eb1a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterEqTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class GreaterEqTerm<T extends Comparable<T>> extends ComparisonTerm<T> {
12 public GreaterEqTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return leftValue.compareTo(rightValue) >= 0;
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new GreaterEqTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s >= %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterTerm.java
new file mode 100644
index 00000000..1b67f8b5
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class GreaterTerm<T extends Comparable<T>> extends ComparisonTerm<T> {
12 public GreaterTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return leftValue.compareTo(rightValue) > 0;
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new GreaterTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s > %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessEqTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessEqTerm.java
new file mode 100644
index 00000000..1b34535f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessEqTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class LessEqTerm<T extends Comparable<T>> extends ComparisonTerm<T> {
12 public LessEqTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return leftValue.compareTo(rightValue) <= 0;
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new LessEqTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s <= %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessTerm.java
new file mode 100644
index 00000000..44e70902
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class LessTerm<T extends Comparable<T>> extends ComparisonTerm<T> {
12 public LessTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return leftValue.compareTo(rightValue) < 0;
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new LessTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s < %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/NotEqTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/NotEqTerm.java
new file mode 100644
index 00000000..1f9734c4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/NotEqTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class NotEqTerm<T> extends ComparisonTerm<T> {
12 public NotEqTerm(Class<T> argumentType, Term<T> left, Term<T> right) {
13 super(argumentType, left, right);
14 }
15
16 @Override
17 protected Boolean doEvaluate(T leftValue, T rightValue) {
18 return !leftValue.equals(rightValue);
19 }
20
21 @Override
22 public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) {
23 return new NotEqTerm<>(getArgumentType(), substitutedLeft, substitutedRight);
24 }
25
26 @Override
27 public String toString() {
28 return "(%s != %s)".formatted(getLeft(), getRight());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntAddTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntAddTerm.java
new file mode 100644
index 00000000..dbea3efc
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntAddTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntAddTerm extends IntBinaryTerm {
12 public IntAddTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntAddTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return leftValue + rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s + %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntBinaryTerm.java
new file mode 100644
index 00000000..27ced4e4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntBinaryTerm.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.term.BinaryTerm;
9import tools.refinery.store.query.term.Term;
10
11public abstract class IntBinaryTerm extends BinaryTerm<Integer, Integer, Integer> {
12 protected IntBinaryTerm(Term<Integer> left, Term<Integer> right) {
13 super(Integer.class, Integer.class, Integer.class, left, right);
14 }
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntDivTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntDivTerm.java
new file mode 100644
index 00000000..2a35058c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntDivTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntDivTerm extends IntBinaryTerm {
12 public IntDivTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntDivTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return rightValue == 0 ? null : leftValue / rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s / %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMaxTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMaxTerm.java
new file mode 100644
index 00000000..f81fc509
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMaxTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntMaxTerm extends IntBinaryTerm {
12 public IntMaxTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntMaxTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return Math.max(rightValue, leftValue);
25 }
26
27 @Override
28 public String toString() {
29 return "max(%s, %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinTerm.java
new file mode 100644
index 00000000..89182e26
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntMinTerm extends IntBinaryTerm {
12 public IntMinTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntMinTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return Math.min(rightValue, leftValue);
25 }
26
27 @Override
28 public String toString() {
29 return "min(%s, %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinusTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinusTerm.java
new file mode 100644
index 00000000..709aa5ba
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinusTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntMinusTerm extends IntUnaryTerm {
12 public IntMinusTerm(Term<Integer> body) {
13 super(body);
14 }
15
16 @Override
17 protected Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedBody) {
18 return new IntMinusTerm(substitutedBody);
19 }
20
21 @Override
22 protected Integer doEvaluate(Integer bodyValue) {
23 return -bodyValue;
24 }
25
26 @Override
27 public String toString() {
28 return "(-%s)".formatted(getBody());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMulTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMulTerm.java
new file mode 100644
index 00000000..89d4c5f4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMulTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntMulTerm extends IntBinaryTerm {
12 public IntMulTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntMulTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return leftValue * rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s * %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPlusTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPlusTerm.java
new file mode 100644
index 00000000..aef83bb4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPlusTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntPlusTerm extends IntUnaryTerm {
12 public IntPlusTerm(Term<Integer> body) {
13 super(body);
14 }
15
16 @Override
17 protected Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedBody) {
18 return new IntPlusTerm(substitutedBody);
19 }
20
21 @Override
22 protected Integer doEvaluate(Integer bodyValue) {
23 return bodyValue;
24 }
25
26 @Override
27 public String toString() {
28 return "(+%s)".formatted(getBody());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPowTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPowTerm.java
new file mode 100644
index 00000000..d5af97a1
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPowTerm.java
@@ -0,0 +1,43 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntPowTerm extends IntBinaryTerm {
12 public IntPowTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntPowTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return rightValue < 0 ? null : power(leftValue, rightValue);
25 }
26
27 private static int power(int base, int exponent) {
28 int accum = 1;
29 while (exponent > 0) {
30 if (exponent % 2 == 1) {
31 accum = accum * base;
32 }
33 base = base * base;
34 exponent = exponent / 2;
35 }
36 return accum;
37 }
38
39 @Override
40 public String toString() {
41 return "(%s ** %s)".formatted(getLeft(), getRight());
42 }
43}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSubTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSubTerm.java
new file mode 100644
index 00000000..2c27afb1
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSubTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class IntSubTerm extends IntBinaryTerm {
12 public IntSubTerm(Term<Integer> left, Term<Integer> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
18 Term<Integer> substitutedRight) {
19 return new IntSubTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
24 return leftValue - rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s - %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java
new file mode 100644
index 00000000..cd718c53
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java
@@ -0,0 +1,40 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.term.StatelessAggregator;
9
10public final class IntSumAggregator implements StatelessAggregator<Integer, Integer> {
11 public static final IntSumAggregator INSTANCE = new IntSumAggregator();
12
13 private IntSumAggregator() {
14 }
15
16 @Override
17 public Class<Integer> getResultType() {
18 return Integer.class;
19 }
20
21 @Override
22 public Class<Integer> getInputType() {
23 return Integer.class;
24 }
25
26 @Override
27 public Integer getEmptyResult() {
28 return 0;
29 }
30
31 @Override
32 public Integer add(Integer current, Integer value) {
33 return current + value;
34 }
35
36 @Override
37 public Integer remove(Integer current, Integer value) {
38 return current - value;
39 }
40}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java
new file mode 100644
index 00000000..acb98b94
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java
@@ -0,0 +1,94 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.term.Aggregator;
9import tools.refinery.store.query.term.ConstantTerm;
10import tools.refinery.store.query.term.ExtremeValueAggregator;
11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.comparable.*;
13
14import java.util.Comparator;
15
16public final class IntTerms {
17 public static final Aggregator<Integer, Integer> INT_SUM = IntSumAggregator.INSTANCE;
18 public static final Aggregator<Integer, Integer> INT_MIN = new ExtremeValueAggregator<>(Integer.class,
19 Integer.MAX_VALUE);
20 public static final Aggregator<Integer, Integer> INT_MAX = new ExtremeValueAggregator<>(Integer.class,
21 Integer.MIN_VALUE, Comparator.reverseOrder());
22
23 private IntTerms() {
24 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
25 }
26
27 public static Term<Integer> constant(Integer value) {
28 return new ConstantTerm<>(Integer.class, value);
29 }
30
31 public static Term<Integer> plus(Term<Integer> body) {
32 return new IntPlusTerm(body);
33 }
34
35 public static Term<Integer> minus(Term<Integer> body) {
36 return new IntMinusTerm(body);
37 }
38
39 public static Term<Integer> add(Term<Integer> left, Term<Integer> right) {
40 return new IntAddTerm(left, right);
41 }
42
43 public static Term<Integer> sub(Term<Integer> left, Term<Integer> right) {
44 return new IntSubTerm(left, right);
45 }
46
47 public static Term<Integer> mul(Term<Integer> left, Term<Integer> right) {
48 return new IntMulTerm(left, right);
49 }
50
51 public static Term<Integer> div(Term<Integer> left, Term<Integer> right) {
52 return new IntDivTerm(left, right);
53 }
54
55 public static Term<Integer> pow(Term<Integer> left, Term<Integer> right) {
56 return new IntPowTerm(left, right);
57 }
58
59 public static Term<Integer> min(Term<Integer> left, Term<Integer> right) {
60 return new IntMinTerm(left, right);
61 }
62
63 public static Term<Integer> max(Term<Integer> left, Term<Integer> right) {
64 return new IntMaxTerm(left, right);
65 }
66
67 public static Term<Boolean> eq(Term<Integer> left, Term<Integer> right) {
68 return new EqTerm<>(Integer.class, left, right);
69 }
70
71 public static Term<Boolean> notEq(Term<Integer> left, Term<Integer> right) {
72 return new NotEqTerm<>(Integer.class, left, right);
73 }
74
75 public static Term<Boolean> less(Term<Integer> left, Term<Integer> right) {
76 return new LessTerm<>(Integer.class, left, right);
77 }
78
79 public static Term<Boolean> lessEq(Term<Integer> left, Term<Integer> right) {
80 return new LessEqTerm<>(Integer.class, left, right);
81 }
82
83 public static Term<Boolean> greater(Term<Integer> left, Term<Integer> right) {
84 return new GreaterTerm<>(Integer.class, left, right);
85 }
86
87 public static Term<Boolean> greaterEq(Term<Integer> left, Term<Integer> right) {
88 return new GreaterEqTerm<>(Integer.class, left, right);
89 }
90
91 public static Term<Integer> asInt(Term<Double> body) {
92 return new RealToIntTerm(body);
93 }
94}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntUnaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntUnaryTerm.java
new file mode 100644
index 00000000..49b4c647
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntUnaryTerm.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.term.Term;
9import tools.refinery.store.query.term.UnaryTerm;
10
11public abstract class IntUnaryTerm extends UnaryTerm<Integer, Integer> {
12 protected IntUnaryTerm(Term<Integer> body) {
13 super(Integer.class, Integer.class, body);
14 }
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java
new file mode 100644
index 00000000..7d383562
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.query.term.UnaryTerm;
11
12public class RealToIntTerm extends UnaryTerm<Integer, Double> {
13 protected RealToIntTerm(Term<Double> body) {
14 super(Integer.class, Double.class, body);
15 }
16
17 @Override
18 protected Integer doEvaluate(Double bodyValue) {
19 return bodyValue.isNaN() ? null : bodyValue.intValue();
20 }
21
22 @Override
23 protected Term<Integer> doSubstitute(Substitution substitution, Term<Double> substitutedBody) {
24 return new RealToIntTerm(substitutedBody);
25 }
26
27 @Override
28 public String toString() {
29 return "(%s as int)".formatted(getBody());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java
new file mode 100644
index 00000000..2f53117a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.query.term.UnaryTerm;
11
12public class IntToRealTerm extends UnaryTerm<Double, Integer> {
13 protected IntToRealTerm(Term<Integer> body) {
14 super(Double.class, Integer.class, body);
15 }
16
17 @Override
18 protected Term<Double> doSubstitute(Substitution substitution, Term<Integer> substitutedBody) {
19 return new IntToRealTerm(substitutedBody);
20 }
21
22 @Override
23 protected Double doEvaluate(Integer bodyValue) {
24 return bodyValue.doubleValue();
25 }
26
27 @Override
28 public String toString() {
29 return "(%s as real)".formatted(getBody());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealAddTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealAddTerm.java
new file mode 100644
index 00000000..33fc9e41
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealAddTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealAddTerm extends RealBinaryTerm {
12 public RealAddTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealAddTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return leftValue + rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s + %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealBinaryTerm.java
new file mode 100644
index 00000000..000f3623
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealBinaryTerm.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.term.BinaryTerm;
9import tools.refinery.store.query.term.Term;
10
11public abstract class RealBinaryTerm extends BinaryTerm<Double, Double, Double> {
12 protected RealBinaryTerm(Term<Double> left, Term<Double> right) {
13 super(Double.class, Double.class, Double.class, left, right);
14 }
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealDivTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealDivTerm.java
new file mode 100644
index 00000000..1e55bf42
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealDivTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealDivTerm extends RealBinaryTerm {
12 public RealDivTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealDivTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return leftValue / rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s / %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMaxTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMaxTerm.java
new file mode 100644
index 00000000..2a249496
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMaxTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealMaxTerm extends RealBinaryTerm {
12 public RealMaxTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealMaxTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return Math.max(leftValue, rightValue);
25 }
26
27 @Override
28 public String toString() {
29 return "max(%s, %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinTerm.java
new file mode 100644
index 00000000..2eb4cc1e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealMinTerm extends RealBinaryTerm {
12 public RealMinTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealMinTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return Math.min(leftValue, rightValue);
25 }
26
27 @Override
28 public String toString() {
29 return "min(%s, %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinusTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinusTerm.java
new file mode 100644
index 00000000..4afec7a1
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinusTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealMinusTerm extends RealUnaryTerm {
12 public RealMinusTerm(Term<Double> body) {
13 super(body);
14 }
15
16 @Override
17 protected Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedBody) {
18 return new RealMinusTerm(substitutedBody);
19 }
20
21 @Override
22 protected Double doEvaluate(Double bodyValue) {
23 return -bodyValue;
24 }
25
26 @Override
27 public String toString() {
28 return "(-%s)".formatted(getBody());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMulTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMulTerm.java
new file mode 100644
index 00000000..ec95ac6f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMulTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealMulTerm extends RealBinaryTerm {
12 public RealMulTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealMulTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return leftValue * rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s * %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPlusTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPlusTerm.java
new file mode 100644
index 00000000..64dd2e70
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPlusTerm.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealPlusTerm extends RealUnaryTerm {
12 public RealPlusTerm(Term<Double> body) {
13 super(body);
14 }
15
16 @Override
17 protected Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedBody) {
18 return new RealPlusTerm(substitutedBody);
19 }
20
21 @Override
22 protected Double doEvaluate(Double bodyValue) {
23 return bodyValue;
24 }
25
26 @Override
27 public String toString() {
28 return "(+%s)".formatted(getBody());
29 }
30}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPowTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPowTerm.java
new file mode 100644
index 00000000..11c952ea
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPowTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealPowTerm extends RealBinaryTerm {
12 public RealPowTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealPowTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return Math.pow(leftValue, rightValue);
25 }
26
27 @Override
28 public String toString() {
29 return "(%s ** %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSubTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSubTerm.java
new file mode 100644
index 00000000..8cc701ed
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSubTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public class RealSubTerm extends RealBinaryTerm {
12 public RealSubTerm(Term<Double> left, Term<Double> right) {
13 super(left, right);
14 }
15
16 @Override
17 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
18 Term<Double> substitutedRight) {
19 return new RealSubTerm(substitutedLeft, substitutedRight);
20 }
21
22 @Override
23 protected Double doEvaluate(Double leftValue, Double rightValue) {
24 return leftValue - rightValue;
25 }
26
27 @Override
28 public String toString() {
29 return "(%s - %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java
new file mode 100644
index 00000000..d21048e9
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java
@@ -0,0 +1,90 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.term.StatefulAggregate;
9import tools.refinery.store.query.term.StatefulAggregator;
10
11import java.util.Map;
12import java.util.TreeMap;
13
14public final class RealSumAggregator implements StatefulAggregator<Double, Double> {
15 public static final RealSumAggregator INSTANCE = new RealSumAggregator();
16
17 private RealSumAggregator() {
18 }
19
20 @Override
21 public Class<Double> getResultType() {
22 return Double.class;
23 }
24
25 @Override
26 public Class<Double> getInputType() {
27 return Double.class;
28 }
29
30 @Override
31 public StatefulAggregate<Double, Double> createEmptyAggregate() {
32 return new Aggregate();
33 }
34
35 @Override
36 public Double getEmptyResult() {
37 return 0d;
38 }
39
40 private static class Aggregate implements StatefulAggregate<Double, Double> {
41 private final Map<Double, Integer> values;
42
43 public Aggregate() {
44 values = new TreeMap<>();
45 }
46
47 private Aggregate(Aggregate other) {
48 values = new TreeMap<>(other.values);
49 }
50
51 @Override
52 public void add(Double value) {
53 values.compute(value, (ignoredValue, currentCount) -> currentCount == null ? 1 : currentCount + 1);
54 }
55
56 @Override
57 public void remove(Double value) {
58 values.compute(value, (theValue, currentCount) -> {
59 if (currentCount == null || currentCount <= 0) {
60 throw new IllegalStateException("Invalid count %d for value %f".formatted(currentCount, theValue));
61 }
62 return currentCount.equals(1) ? null : currentCount - 1;
63 });
64 }
65
66 @Override
67 public Double getResult() {
68 return values.entrySet()
69 .stream()
70 .mapToDouble(entry -> entry.getKey() * entry.getValue())
71 .reduce(Double::sum)
72 .orElse(0d);
73 }
74
75 @Override
76 public boolean isEmpty() {
77 return values.isEmpty();
78 }
79
80 @Override
81 public StatefulAggregate<Double, Double> deepCopy() {
82 return new Aggregate(this);
83 }
84
85 @Override
86 public boolean contains(Double value) {
87 return values.containsKey(value);
88 }
89 }
90}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java
new file mode 100644
index 00000000..79220358
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java
@@ -0,0 +1,94 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.term.Aggregator;
9import tools.refinery.store.query.term.ConstantTerm;
10import tools.refinery.store.query.term.ExtremeValueAggregator;
11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.comparable.*;
13
14import java.util.Comparator;
15
16public final class RealTerms {
17 public static final Aggregator<Double, Double> REAL_SUM = RealSumAggregator.INSTANCE;
18 public static final Aggregator<Double, Double> REAL_MIN = new ExtremeValueAggregator<>(Double.class,
19 Double.POSITIVE_INFINITY);
20 public static final Aggregator<Double, Double> REAL_MAX = new ExtremeValueAggregator<>(Double.class,
21 Double.NEGATIVE_INFINITY, Comparator.reverseOrder());
22
23 private RealTerms() {
24 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
25 }
26
27 public static Term<Double> constant(Double value) {
28 return new ConstantTerm<>(Double.class, value);
29 }
30
31 public static Term<Double> plus(Term<Double> body) {
32 return new RealPlusTerm(body);
33 }
34
35 public static Term<Double> minus(Term<Double> body) {
36 return new RealMinusTerm(body);
37 }
38
39 public static Term<Double> add(Term<Double> left, Term<Double> right) {
40 return new RealAddTerm(left, right);
41 }
42
43 public static Term<Double> sub(Term<Double> left, Term<Double> right) {
44 return new RealSubTerm(left, right);
45 }
46
47 public static Term<Double> mul(Term<Double> left, Term<Double> right) {
48 return new RealMulTerm(left, right);
49 }
50
51 public static Term<Double> div(Term<Double> left, Term<Double> right) {
52 return new RealDivTerm(left, right);
53 }
54
55 public static Term<Double> pow(Term<Double> left, Term<Double> right) {
56 return new RealPowTerm(left, right);
57 }
58
59 public static Term<Double> min(Term<Double> left, Term<Double> right) {
60 return new RealMinTerm(left, right);
61 }
62
63 public static Term<Double> max(Term<Double> left, Term<Double> right) {
64 return new RealMaxTerm(left, right);
65 }
66
67 public static Term<Boolean> eq(Term<Double> left, Term<Double> right) {
68 return new EqTerm<>(Double.class, left, right);
69 }
70
71 public static Term<Boolean> notEq(Term<Double> left, Term<Double> right) {
72 return new NotEqTerm<>(Double.class, left, right);
73 }
74
75 public static Term<Boolean> less(Term<Double> left, Term<Double> right) {
76 return new LessTerm<>(Double.class, left, right);
77 }
78
79 public static Term<Boolean> lessEq(Term<Double> left, Term<Double> right) {
80 return new LessEqTerm<>(Double.class, left, right);
81 }
82
83 public static Term<Boolean> greater(Term<Double> left, Term<Double> right) {
84 return new GreaterTerm<>(Double.class, left, right);
85 }
86
87 public static Term<Boolean> greaterEq(Term<Double> left, Term<Double> right) {
88 return new GreaterEqTerm<>(Double.class, left, right);
89 }
90
91 public static Term<Double> asReal(Term<Integer> body) {
92 return new IntToRealTerm(body);
93 }
94}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealUnaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealUnaryTerm.java
new file mode 100644
index 00000000..d41c4ed9
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealUnaryTerm.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.term.Term;
9import tools.refinery.store.query.term.UnaryTerm;
10
11public abstract class RealUnaryTerm extends UnaryTerm<Double, Double> {
12 protected RealUnaryTerm(Term<Double> body) {
13 super(Double.class, Double.class, body);
14 }
15}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityAddTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityAddTerm.java
new file mode 100644
index 00000000..68905f51
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityAddTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.representation.cardinality.UpperCardinality;
11
12public class UpperCardinalityAddTerm extends UpperCardinalityBinaryTerm {
13 protected UpperCardinalityAddTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) {
14 super(left, right);
15 }
16
17 @Override
18 protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) {
19 return leftValue.add(rightValue);
20 }
21
22 @Override
23 public Term<UpperCardinality> doSubstitute(Substitution substitution, Term<UpperCardinality> substitutedLeft, Term<UpperCardinality> substitutedRight) {
24 return new UpperCardinalityAddTerm(substitutedLeft, substitutedRight);
25 }
26
27 @Override
28 public String toString() {
29 return "(%s + %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityBinaryTerm.java
new file mode 100644
index 00000000..0cf8fe44
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityBinaryTerm.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.term.BinaryTerm;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.representation.cardinality.UpperCardinality;
11
12public abstract class UpperCardinalityBinaryTerm extends BinaryTerm<UpperCardinality, UpperCardinality,
13 UpperCardinality> {
14 protected UpperCardinalityBinaryTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) {
15 super(UpperCardinality.class, UpperCardinality.class, UpperCardinality.class, left, right);
16 }
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMaxTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMaxTerm.java
new file mode 100644
index 00000000..ff75f64e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMaxTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.representation.cardinality.UpperCardinality;
11
12public class UpperCardinalityMaxTerm extends UpperCardinalityBinaryTerm {
13 protected UpperCardinalityMaxTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) {
14 super(left, right);
15 }
16
17 @Override
18 protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) {
19 return leftValue.max(rightValue);
20 }
21
22 @Override
23 public Term<UpperCardinality> doSubstitute(Substitution substitution, Term<UpperCardinality> substitutedLeft, Term<UpperCardinality> substitutedRight) {
24 return new UpperCardinalityMaxTerm(substitutedLeft, substitutedRight);
25 }
26
27 @Override
28 public String toString() {
29 return "max(%s, %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMinTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMinTerm.java
new file mode 100644
index 00000000..1e89e9f4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMinTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.representation.cardinality.UpperCardinality;
11
12public class UpperCardinalityMinTerm extends UpperCardinalityBinaryTerm {
13 protected UpperCardinalityMinTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) {
14 super(left, right);
15 }
16
17 @Override
18 protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) {
19 return leftValue.min(rightValue);
20 }
21
22 @Override
23 public Term<UpperCardinality> doSubstitute(Substitution substitution, Term<UpperCardinality> substitutedLeft, Term<UpperCardinality> substitutedRight) {
24 return new UpperCardinalityMinTerm(substitutedLeft, substitutedRight);
25 }
26
27 @Override
28 public String toString() {
29 return "min(%s, %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMulTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMulTerm.java
new file mode 100644
index 00000000..3b4970f4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMulTerm.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.representation.cardinality.UpperCardinality;
11
12public class UpperCardinalityMulTerm extends UpperCardinalityBinaryTerm {
13 protected UpperCardinalityMulTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) {
14 super(left, right);
15 }
16
17 @Override
18 protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) {
19 return leftValue.multiply(rightValue);
20 }
21
22 @Override
23 public Term<UpperCardinality> doSubstitute(Substitution substitution, Term<UpperCardinality> substitutedLeft, Term<UpperCardinality> substitutedRight) {
24 return new UpperCardinalityMulTerm(substitutedLeft, substitutedRight);
25 }
26
27 @Override
28 public String toString() {
29 return "(%s * %s)".formatted(getLeft(), getRight());
30 }
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java
new file mode 100644
index 00000000..5bbd3081
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java
@@ -0,0 +1,86 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.term.StatefulAggregate;
9import tools.refinery.store.query.term.StatefulAggregator;
10import tools.refinery.store.representation.cardinality.FiniteUpperCardinality;
11import tools.refinery.store.representation.cardinality.UnboundedUpperCardinality;
12import tools.refinery.store.representation.cardinality.UpperCardinalities;
13import tools.refinery.store.representation.cardinality.UpperCardinality;
14
15public class UpperCardinalitySumAggregator implements StatefulAggregator<UpperCardinality, UpperCardinality> {
16 public static final UpperCardinalitySumAggregator INSTANCE = new UpperCardinalitySumAggregator();
17
18 private UpperCardinalitySumAggregator() {
19 }
20
21 @Override
22 public Class<UpperCardinality> getResultType() {
23 return UpperCardinality.class;
24 }
25
26 @Override
27 public Class<UpperCardinality> getInputType() {
28 return UpperCardinality.class;
29 }
30
31 @Override
32 public StatefulAggregate<UpperCardinality, UpperCardinality> createEmptyAggregate() {
33 return new Aggregate();
34 }
35
36 private static class Aggregate implements StatefulAggregate<UpperCardinality, UpperCardinality> {
37 private int sumFiniteUpperBounds;
38 private int countUnbounded;
39
40 public Aggregate() {
41 this(0, 0);
42 }
43
44 private Aggregate(int sumFiniteUpperBounds, int countUnbounded) {
45 this.sumFiniteUpperBounds = sumFiniteUpperBounds;
46 this.countUnbounded = countUnbounded;
47 }
48
49 @Override
50 public void add(UpperCardinality value) {
51 if (value instanceof FiniteUpperCardinality finiteUpperCardinality) {
52 sumFiniteUpperBounds += finiteUpperCardinality.finiteUpperBound();
53 } else if (value instanceof UnboundedUpperCardinality) {
54 countUnbounded += 1;
55 } else {
56 throw new IllegalArgumentException("Unknown UpperCardinality: " + value);
57 }
58 }
59
60 @Override
61 public void remove(UpperCardinality value) {
62 if (value instanceof FiniteUpperCardinality finiteUpperCardinality) {
63 sumFiniteUpperBounds -= finiteUpperCardinality.finiteUpperBound();
64 } else if (value instanceof UnboundedUpperCardinality) {
65 countUnbounded -= 1;
66 } else {
67 throw new IllegalArgumentException("Unknown UpperCardinality: " + value);
68 }
69 }
70
71 @Override
72 public UpperCardinality getResult() {
73 return countUnbounded > 0 ? UpperCardinalities.UNBOUNDED : UpperCardinalities.valueOf(sumFiniteUpperBounds);
74 }
75
76 @Override
77 public boolean isEmpty() {
78 return sumFiniteUpperBounds == 0 && countUnbounded == 0;
79 }
80
81 @Override
82 public StatefulAggregate<UpperCardinality, UpperCardinality> deepCopy() {
83 return new Aggregate(sumFiniteUpperBounds, countUnbounded);
84 }
85 }
86}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTerms.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTerms.java
new file mode 100644
index 00000000..13914f2d
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTerms.java
@@ -0,0 +1,73 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.term.Aggregator;
9import tools.refinery.store.query.term.ConstantTerm;
10import tools.refinery.store.query.term.ExtremeValueAggregator;
11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.comparable.*;
13import tools.refinery.store.representation.cardinality.UpperCardinalities;
14import tools.refinery.store.representation.cardinality.UpperCardinality;
15
16import java.util.Comparator;
17
18public final class UpperCardinalityTerms {
19 public static final Aggregator<UpperCardinality, UpperCardinality> UPPER_CARDINALITY_SUM =
20 UpperCardinalitySumAggregator.INSTANCE;
21 public static final Aggregator<UpperCardinality, UpperCardinality> UPPER_CARDINALITY_MIN =
22 new ExtremeValueAggregator<>(UpperCardinality.class, UpperCardinalities.UNBOUNDED);
23 public static final Aggregator<UpperCardinality, UpperCardinality> UPPER_CARDINALITY_MAX =
24 new ExtremeValueAggregator<>(UpperCardinality.class, UpperCardinalities.ZERO, Comparator.reverseOrder());
25
26 private UpperCardinalityTerms() {
27 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
28 }
29
30 public static Term<UpperCardinality> constant(UpperCardinality value) {
31 return new ConstantTerm<>(UpperCardinality.class, value);
32 }
33
34 public static Term<UpperCardinality> add(Term<UpperCardinality> left, Term<UpperCardinality> right) {
35 return new UpperCardinalityAddTerm(left, right);
36 }
37
38 public static Term<UpperCardinality> mul(Term<UpperCardinality> left, Term<UpperCardinality> right) {
39 return new UpperCardinalityMulTerm(left, right);
40 }
41
42 public static Term<UpperCardinality> min(Term<UpperCardinality> left, Term<UpperCardinality> right) {
43 return new UpperCardinalityMinTerm(left, right);
44 }
45
46 public static Term<UpperCardinality> max(Term<UpperCardinality> left, Term<UpperCardinality> right) {
47 return new UpperCardinalityMaxTerm(left, right);
48 }
49
50 public static Term<Boolean> eq(Term<UpperCardinality> left, Term<UpperCardinality> right) {
51 return new EqTerm<>(UpperCardinality.class, left, right);
52 }
53
54 public static Term<Boolean> notEq(Term<UpperCardinality> left, Term<UpperCardinality> right) {
55 return new NotEqTerm<>(UpperCardinality.class, left, right);
56 }
57
58 public static Term<Boolean> less(Term<UpperCardinality> left, Term<UpperCardinality> right) {
59 return new LessTerm<>(UpperCardinality.class, left, right);
60 }
61
62 public static Term<Boolean> lessEq(Term<UpperCardinality> left, Term<UpperCardinality> right) {
63 return new LessEqTerm<>(UpperCardinality.class, left, right);
64 }
65
66 public static Term<Boolean> greater(Term<UpperCardinality> left, Term<UpperCardinality> right) {
67 return new GreaterTerm<>(UpperCardinality.class, left, right);
68 }
69
70 public static Term<Boolean> greaterEq(Term<UpperCardinality> left, Term<UpperCardinality> right) {
71 return new GreaterEqTerm<>(UpperCardinality.class, left, right);
72 }
73}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java
new file mode 100644
index 00000000..b568b99d
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java
@@ -0,0 +1,754 @@
1/*
2 * Copyright (c) 2021 Rodion Efremov
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
4 *
5 * SPDX-License-Identifier: MIT OR EPL-2.0
6 */
7package tools.refinery.store.query.utils;
8
9import java.util.*;
10
11/**
12 * This class implements an order statistic tree which is based on AVL-trees.
13 * <p>
14 * This class was copied into <i>Refinery</i> from
15 * <a href="https://github.com/coderodde/OrderStatisticTree/tree/546c343b9f5d868e394a079ff32691c9dbfd83e3">https://github.com/coderodde/OrderStatisticTree</a>
16 * and is available under the
17 * <a href="https://github.com/coderodde/OrderStatisticTree/blob/master/LICENSE">MIT License</a>.
18 * We also incorporated changes by <a href="https://github.com/coderodde/OrderStatisticTree/issues/3">Eugene Schava</a>
19 * and cleaned up some linter warnings.
20 *
21 * @param <T> the actual element type.
22 * @author Rodion "rodde" Efremov
23 * @version based on 1.6 (Feb 11, 2016)
24 */
25public class OrderStatisticTree<T extends Comparable<? super T>> implements Set<T> {
26
27 @Override
28 public Iterator<T> iterator() {
29 return new TreeIterator();
30 }
31
32 private final class TreeIterator implements Iterator<T> {
33
34 private Node<T> previousNode;
35 private Node<T> nextNode;
36 private int expectedModCount = modCount;
37
38 TreeIterator() {
39 if (root == null) {
40 nextNode = null;
41 } else {
42 nextNode = minimumNode(root);
43 }
44 }
45
46 @Override
47 public boolean hasNext() {
48 return nextNode != null;
49 }
50
51 @Override
52 public T next() {
53 if (nextNode == null) {
54 throw new NoSuchElementException("Iteration exceeded.");
55 }
56
57 checkConcurrentModification();
58 T datum = nextNode.key;
59 previousNode = nextNode;
60 nextNode = successorOf(nextNode);
61 return datum;
62 }
63
64 @Override
65 public void remove() {
66 if (previousNode == null) {
67 throw new IllegalStateException(
68 nextNode == null ?
69 "Not a single call to next(); nothing to remove." :
70 "Removing the same element twice."
71 );
72 }
73
74 checkConcurrentModification();
75
76 Node<T> x = deleteNode(previousNode);
77 fixAfterModification(x, false);
78
79 if (x == nextNode) {
80 nextNode = previousNode;
81 }
82
83 expectedModCount = ++modCount;
84 size--;
85 previousNode = null;
86 }
87
88 private void checkConcurrentModification() {
89 if (expectedModCount != modCount) {
90 throw new ConcurrentModificationException(
91 "The set was modified while iterating.");
92 }
93 }
94
95 private Node<T> successorOf(Node<T> node) {
96 if (node.right != null) {
97 node = node.right;
98
99 while (node.left != null) {
100 node = node.left;
101 }
102
103 return node;
104 }
105
106 Node<T> parent = node.parent;
107
108 while (parent != null && parent.right == node) {
109 node = parent;
110 parent = parent.parent;
111 }
112
113 return parent;
114 }
115 }
116
117 @Override
118 public Object[] toArray() {
119 Object[] array = new Object[size];
120 Iterator<T> iterator = iterator();
121 int index = 0;
122
123 while (iterator.hasNext()) {
124 array[index++] = iterator.next();
125 }
126
127 return array;
128 }
129
130 @Override
131 public <U> U[] toArray(U[] a) {
132 Iterator<T> iterator = iterator();
133
134 if (size > a.length) {
135 a = Arrays.copyOf(a, size);
136 }
137
138 int index = 0;
139
140 for (; index < size; ++index) {
141 @SuppressWarnings("unchecked")
142 var convertedValue = (U) iterator.next();
143 a[index] = convertedValue;
144 }
145
146 if (index < a.length) {
147 a[index] = null;
148 }
149
150 return a;
151 }
152
153 @Override
154 public boolean containsAll(Collection<?> c) {
155 for (Object element : c) {
156 if (!contains(element)) {
157 return false;
158 }
159 }
160
161 return true;
162 }
163
164 @Override
165 public boolean addAll(Collection<? extends T> c) {
166 boolean modified = false;
167
168 for (T element : c) {
169 if (add(element)) {
170 modified = true;
171 }
172 }
173
174 return modified;
175 }
176
177 @Override
178 public boolean retainAll(Collection<?> c) {
179 if (!c.getClass().equals(HashSet.class)) {
180 c = new HashSet<>(c);
181 }
182
183 Iterator<T> iterator = iterator();
184 boolean modified = false;
185
186 while (iterator.hasNext()) {
187 T element = iterator.next();
188
189 if (!c.contains(element)) {
190 iterator.remove();
191 modified = true;
192 }
193 }
194
195 return modified;
196 }
197
198 @Override
199 public boolean removeAll(Collection<?> c) {
200 boolean modified = false;
201
202 for (Object element : c) {
203 if (remove(element)) {
204 modified = true;
205 }
206 }
207
208 return modified;
209 }
210
211 private static final class Node<T> {
212 T key;
213
214 Node<T> parent;
215 Node<T> left;
216 Node<T> right;
217
218 int height;
219 int count;
220
221 Node(T key) {
222 this.key = key;
223 }
224 }
225
226 private Node<T> root;
227 private int size;
228 private int modCount;
229
230 @Override
231 public boolean add(T element) {
232 Objects.requireNonNull(element, "The input element is null.");
233
234 if (root == null) {
235 root = new Node<>(element);
236 size = 1;
237 modCount++;
238 return true;
239 }
240
241 Node<T> parent = null;
242 Node<T> node = root;
243 int cmp;
244
245 while (node != null) {
246 cmp = element.compareTo(node.key);
247
248 if (cmp == 0) {
249 // The element is already in this tree.
250 return false;
251 }
252
253 parent = node;
254
255 if (cmp < 0) {
256 node = node.left;
257 } else {
258 node = node.right;
259 }
260 }
261
262 Node<T> newnode = new Node<>(element);
263
264 if (element.compareTo(parent.key) < 0) {
265 parent.left = newnode;
266 } else {
267 parent.right = newnode;
268 }
269
270 newnode.parent = parent;
271 size++;
272 modCount++;
273 Node<T> hi = parent;
274 Node<T> lo = newnode;
275
276 while (hi != null) {
277 if (hi.left == lo) {
278 hi.count++;
279 }
280
281 lo = hi;
282 hi = hi.parent;
283 }
284
285 fixAfterModification(newnode, true);
286 return true;
287 }
288
289 @Override
290 public boolean contains(Object o) {
291 @SuppressWarnings("unchecked")
292 T element = (T) o;
293 Node<T> x = root;
294 int cmp;
295
296 while (x != null && (cmp = element.compareTo(x.key)) != 0) {
297 if (cmp < 0) {
298 x = x.left;
299 } else {
300 x = x.right;
301 }
302 }
303
304 return x != null;
305 }
306
307 @Override
308 public boolean remove(Object o) {
309 @SuppressWarnings("unchecked")
310 T element = (T) o;
311 Node<T> x = root;
312 int cmp;
313
314 while (x != null && (cmp = element.compareTo(x.key)) != 0) {
315 if (cmp < 0) {
316 x = x.left;
317 } else {
318 x = x.right;
319 }
320 }
321
322 if (x == null) {
323 return false;
324 }
325
326 x = deleteNode(x);
327 fixAfterModification(x, false);
328 size--;
329 modCount++;
330 return true;
331 }
332
333 public T get(int index) {
334 checkIndex(index);
335 Node<T> node = root;
336
337 while (true) {
338 if (index > node.count) {
339 index -= node.count + 1;
340 node = node.right;
341 } else if (index < node.count) {
342 node = node.left;
343 } else {
344 return node.key;
345 }
346 }
347 }
348
349 public int indexOf(T element) {
350 Node<T> node = root;
351
352 if (root == null) {
353 return -1;
354 }
355
356 int rank = root.count;
357 int cmp;
358
359 while (true) {
360 if ((cmp = element.compareTo(node.key)) < 0) {
361 if (node.left == null) {
362 return -1;
363 }
364
365 rank -= (node.count - node.left.count);
366 node = node.left;
367 } else if (cmp > 0) {
368 if (node.right == null) {
369 return -1;
370 }
371
372 rank += 1 + node.right.count;
373 node = node.right;
374 } else {
375 return rank;
376 }
377 }
378 }
379
380 @Override
381 public int size() {
382 return size;
383 }
384
385 @Override
386 public boolean isEmpty() {
387 return size == 0;
388 }
389
390 @Override
391 public void clear() {
392 modCount += size;
393 root = null;
394 size = 0;
395 }
396
397
398 private void checkIndex(int index) {
399 if (index < 0) {
400 throw new IndexOutOfBoundsException(
401 "The input index is negative: " + index);
402 }
403
404 if (index >= size) {
405 throw new IndexOutOfBoundsException(
406 "The input index is too large: " + index +
407 ", the size of this tree is " + size);
408 }
409 }
410
411 @SuppressWarnings("squid:S3776")
412 private Node<T> deleteNode(Node<T> node) {
413 if (node.left == null && node.right == null) {
414 // 'node' has no children.
415 Node<T> parent = node.parent;
416
417 if (parent == null) {
418 // 'node' is the root node of this tree.
419 root = null;
420 ++modCount;
421 return node;
422 }
423
424 Node<T> lo = node;
425 Node<T> hi = parent;
426
427 while (hi != null) {
428 if (hi.left == lo) {
429 hi.count--;
430 }
431
432 lo = hi;
433 hi = hi.parent;
434 }
435
436 if (node == parent.left) {
437 parent.left = null;
438 } else {
439 parent.right = null;
440 }
441
442 return node;
443 }
444
445 if (node.left != null && node.right != null) {
446 // 'node' has both children.
447 Node<T> successor = minimumNode(node.right);
448 node.key = successor.key;
449 Node<T> child = successor.right;
450 Node<T> parent = successor.parent;
451
452 if (parent.left == successor) {
453 parent.left = child;
454 } else {
455 parent.right = child;
456 }
457
458 if (child != null) {
459 child.parent = parent;
460 }
461
462 Node<T> lo = child;
463 Node<T> hi = parent;
464
465 while (hi != null) {
466 if (hi.left == lo) {
467 hi.count--;
468 }
469
470 lo = hi;
471 hi = hi.parent;
472 }
473
474 return successor;
475 }
476
477 Node<T> child;
478
479 // 'node' has only one child.
480 if (node.left != null) {
481 child = node.left;
482 } else {
483 child = node.right;
484 }
485
486 Node<T> parent = node.parent;
487 child.parent = parent;
488
489 if (parent == null) {
490 root = child;
491 return node;
492 }
493
494 if (node == parent.left) {
495 parent.left = child;
496 } else {
497 parent.right = child;
498 }
499
500 Node<T> hi = parent;
501 Node<T> lo = child;
502
503 while (hi != null) {
504 if (hi.left == lo) {
505 hi.count--;
506 }
507
508 lo = hi;
509 hi = hi.parent;
510 }
511
512 return node;
513
514 }
515
516 private Node<T> minimumNode(Node<T> node) {
517 while (node.left != null) {
518 node = node.left;
519 }
520
521 return node;
522 }
523
524 private int height(Node<T> node) {
525 return node == null ? -1 : node.height;
526 }
527
528 private Node<T> leftRotate(Node<T> node1) {
529 Node<T> node2 = node1.right;
530 node2.parent = node1.parent;
531 node1.parent = node2;
532 node1.right = node2.left;
533 node2.left = node1;
534
535 if (node1.right != null) {
536 node1.right.parent = node1;
537 }
538
539 node1.height = Math.max(height(node1.left), height(node1.right)) + 1;
540 node2.height = Math.max(height(node2.left), height(node2.right)) + 1;
541 node2.count += node1.count + 1;
542 return node2;
543 }
544
545 private Node<T> rightRotate(Node<T> node1) {
546 Node<T> node2 = node1.left;
547 node2.parent = node1.parent;
548 node1.parent = node2;
549 node1.left = node2.right;
550 node2.right = node1;
551
552 if (node1.left != null) {
553 node1.left.parent = node1;
554 }
555
556 node1.height = Math.max(height(node1.left), height(node1.right)) + 1;
557 node2.height = Math.max(height(node2.left), height(node2.right)) + 1;
558 node1.count -= node2.count + 1;
559 return node2;
560 }
561
562 private Node<T> rightLeftRotate(Node<T> node1) {
563 Node<T> node2 = node1.right;
564 node1.right = rightRotate(node2);
565 return leftRotate(node1);
566 }
567
568 private Node<T> leftRightRotate(Node<T> node1) {
569 Node<T> node2 = node1.left;
570 node1.left = leftRotate(node2);
571 return rightRotate(node1);
572 }
573
574 // Fixing an insertion: use insertionMode = true.
575 // Fixing a deletion: use insertionMode = false.
576 @SuppressWarnings("squid:S3776")
577 private void fixAfterModification(Node<T> node, boolean insertionMode) {
578 Node<T> parent = node.parent;
579 Node<T> grandParent;
580 Node<T> subTree;
581
582 while (parent != null) {
583 if (height(parent.left) == height(parent.right) + 2) {
584 grandParent = parent.parent;
585
586 if (height(parent.left.left) >= height(parent.left.right)) {
587 subTree = rightRotate(parent);
588 } else {
589 subTree = leftRightRotate(parent);
590 }
591
592 if (grandParent == null) {
593 root = subTree;
594 } else if (grandParent.left == parent) {
595 grandParent.left = subTree;
596 } else {
597 grandParent.right = subTree;
598 }
599
600 if (grandParent != null) {
601 grandParent.height = Math.max(
602 height(grandParent.left),
603 height(grandParent.right)) + 1;
604 }
605
606 if (insertionMode) {
607 // Whenever fixing after insertion, at most one rotation is
608 // required in order to maintain the balance.
609 return;
610 }
611 } else if (height(parent.right) == height(parent.left) + 2) {
612 grandParent = parent.parent;
613
614 if (height(parent.right.right) >= height(parent.right.left)) {
615 subTree = leftRotate(parent);
616 } else {
617 subTree = rightLeftRotate(parent);
618 }
619
620 if (grandParent == null) {
621 root = subTree;
622 } else if (grandParent.left == parent) {
623 grandParent.left = subTree;
624 } else {
625 grandParent.right = subTree;
626 }
627
628 if (grandParent != null) {
629 grandParent.height =
630 Math.max(height(grandParent.left),
631 height(grandParent.right)) + 1;
632 }
633
634 if (insertionMode) {
635 return;
636 }
637 }
638
639 parent.height = Math.max(height(parent.left),
640 height(parent.right)) + 1;
641 parent = parent.parent;
642 }
643 }
644
645 boolean isHealthy() {
646 if (root == null) {
647 return true;
648 }
649
650 return !containsCycles()
651 && heightsAreCorrect()
652 && isBalanced()
653 && isWellIndexed();
654 }
655
656 private boolean containsCycles() {
657 Set<Node<T>> visitedNodes = new HashSet<>();
658 return containsCycles(root, visitedNodes);
659 }
660
661 private boolean containsCycles(Node<T> current, Set<Node<T>> visitedNodes) {
662 if (current == null) {
663 return false;
664 }
665
666 if (visitedNodes.contains(current)) {
667 return true;
668 }
669
670 visitedNodes.add(current);
671
672 return containsCycles(current.left, visitedNodes)
673 || containsCycles(current.right, visitedNodes);
674 }
675
676 private boolean heightsAreCorrect() {
677 return getHeight(root) == root.height;
678 }
679
680 private int getHeight(Node<T> node) {
681 if (node == null) {
682 return -1;
683 }
684
685 int leftTreeHeight = getHeight(node.left);
686
687 if (leftTreeHeight == Integer.MIN_VALUE) {
688 return Integer.MIN_VALUE;
689 }
690
691 int rightTreeHeight = getHeight(node.right);
692
693 if (rightTreeHeight == Integer.MIN_VALUE) {
694 return Integer.MIN_VALUE;
695 }
696
697 if (node.height == Math.max(leftTreeHeight, rightTreeHeight) + 1) {
698 return node.height;
699 }
700
701 return Integer.MIN_VALUE;
702 }
703
704 private boolean isBalanced() {
705 return isBalanced(root);
706 }
707
708 private boolean isBalanced(Node<T> node) {
709 if (node == null) {
710 return true;
711 }
712
713 if (!isBalanced(node.left)) {
714 return false;
715 }
716
717 if (!isBalanced(node.right)) {
718 return false;
719 }
720
721 int leftHeight = height(node.left);
722 int rightHeight = height(node.right);
723
724 return Math.abs(leftHeight - rightHeight) < 2;
725 }
726
727 private boolean isWellIndexed() {
728 return size == count(root);
729 }
730
731 private int count(Node<T> node) {
732 if (node == null) {
733 return 0;
734 }
735
736 int leftTreeSize = count(node.left);
737
738 if (leftTreeSize == Integer.MIN_VALUE) {
739 return Integer.MIN_VALUE;
740 }
741
742 if (node.count != leftTreeSize) {
743 return Integer.MIN_VALUE;
744 }
745
746 int rightTreeSize = count(node.right);
747
748 if (rightTreeSize == Integer.MIN_VALUE) {
749 return Integer.MIN_VALUE;
750 }
751
752 return leftTreeSize + 1 + rightTreeSize;
753 }
754}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/MapBasedValuation.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/MapBasedValuation.java
new file mode 100644
index 00000000..261ceaa5
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/MapBasedValuation.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.valuation;
7
8import tools.refinery.store.query.term.AnyDataVariable;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Map;
12
13record MapBasedValuation(Map<AnyDataVariable, Object> values) implements Valuation {
14 @Override
15 public <T> T getValue(DataVariable<T> variable) {
16 if (!values.containsKey(variable)) {
17 throw new IllegalArgumentException("No value for variable %s".formatted(variable));
18 }
19 var value = values.get(variable);
20 return variable.getType().cast(value);
21 }
22}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java
new file mode 100644
index 00000000..fc8406aa
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.valuation;
7
8import tools.refinery.store.query.term.AnyDataVariable;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Set;
12
13public record RestrictedValuation(Valuation valuation, Set<AnyDataVariable> allowedVariables) implements Valuation {
14 @Override
15 public <T> T getValue(DataVariable<T> variable) {
16 if (!allowedVariables.contains(variable)) {
17 throw new IllegalArgumentException("Variable %s is not in scope".formatted(variable));
18 }
19 return valuation.getValue(variable);
20 }
21}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java
new file mode 100644
index 00000000..1c14112c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.valuation;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.DataVariable;
10
11public record SubstitutedValuation(Valuation originalValuation, Substitution substitution) implements Valuation {
12 @Override
13 public <T> T getValue(DataVariable<T> variable) {
14 return originalValuation.getValue(substitution.getTypeSafeSubstitute(variable));
15 }
16}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java
new file mode 100644
index 00000000..1588e957
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java
@@ -0,0 +1,37 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.valuation;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.AnyDataVariable;
11import tools.refinery.store.query.term.DataVariable;
12
13import java.util.Map;
14import java.util.Set;
15
16public interface Valuation {
17 <T> T getValue(DataVariable<T> variable);
18
19 default Valuation substitute(@Nullable Substitution substitution) {
20 if (substitution == null) {
21 return this;
22 }
23 return new SubstitutedValuation(this, substitution);
24 }
25
26 default Valuation restrict(Set<? extends AnyDataVariable> allowedVariables) {
27 return new RestrictedValuation(this, Set.copyOf(allowedVariables));
28 }
29
30 static ValuationBuilder builder() {
31 return new ValuationBuilder();
32 }
33
34 static Valuation empty() {
35 return new MapBasedValuation(Map.of());
36 }
37}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/ValuationBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/ValuationBuilder.java
new file mode 100644
index 00000000..7337dbc3
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/ValuationBuilder.java
@@ -0,0 +1,40 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.valuation;
7
8import tools.refinery.store.query.term.AnyDataVariable;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Collections;
12import java.util.HashMap;
13import java.util.Map;
14
15public class ValuationBuilder {
16 private final Map<AnyDataVariable, Object> values = new HashMap<>();
17
18 ValuationBuilder() {
19 }
20
21 public <T> ValuationBuilder put(DataVariable<T> variable, T value) {
22 return putChecked(variable, value);
23 }
24
25 public ValuationBuilder putChecked(AnyDataVariable variable, Object value) {
26 if (value != null && !variable.getType().isInstance(value)) {
27 throw new IllegalArgumentException("Value %s is not an instance of %s"
28 .formatted(value, variable.getType().getName()));
29 }
30 if (values.containsKey(variable)) {
31 throw new IllegalArgumentException("Already has value for variable %s".formatted(variable));
32 }
33 values.put(variable, value);
34 return this;
35 }
36
37 public Valuation build() {
38 return new MapBasedValuation(Collections.unmodifiableMap(values));
39 }
40}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java
new file mode 100644
index 00000000..fd37604e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java
@@ -0,0 +1,110 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.dnf.FunctionalDependency;
10import tools.refinery.store.query.term.Parameter;
11import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.tuple.Tuple;
13import tools.refinery.store.tuple.Tuple1;
14
15import java.util.Arrays;
16import java.util.List;
17import java.util.Objects;
18import java.util.Set;
19import java.util.stream.Collectors;
20import java.util.stream.IntStream;
21
22public abstract class AbstractFunctionView<T> extends SymbolView<T> {
23 private final List<Parameter> parameters;
24
25 protected AbstractFunctionView(Symbol<T> symbol, String name, Parameter outParameter) {
26 super(symbol, name);
27 parameters = createParameters(symbol.arity(), outParameter);
28 }
29
30 @Override
31 public Set<FunctionalDependency<Integer>> getFunctionalDependencies() {
32 var arity = getSymbol().arity();
33 var forEach = IntStream.range(0, arity).boxed().collect(Collectors.toUnmodifiableSet());
34 var unique = Set.of(arity);
35 return Set.of(new FunctionalDependency<>(forEach, unique));
36 }
37
38 @Override
39 public Set<ViewImplication> getImpliedRelationViews() {
40 var symbol = getSymbol();
41 var impliedIndices = IntStream.range(0, symbol.arity()).boxed().toList();
42 var keysView = new KeyOnlyView<>(symbol);
43 return Set.of(new ViewImplication(this, keysView, impliedIndices));
44 }
45
46 @Override
47 protected boolean doFilter(Tuple key, T value) {
48 return true;
49 }
50
51 protected Object forwardMapValue(T value) {
52 return value;
53 }
54
55 protected boolean valueEquals(T value, Object otherForwardMappedValue) {
56 return Objects.equals(otherForwardMappedValue, forwardMapValue(value));
57 }
58
59 @Override
60 public Object[] forwardMap(Tuple key, T value) {
61 int size = key.getSize();
62 Object[] result = new Object[size + 1];
63 for (int i = 0; i < size; i++) {
64 result[i] = Tuple.of(key.get(i));
65 }
66 result[key.getSize()] = forwardMapValue(value);
67 return result;
68 }
69
70 @Override
71 public boolean get(Model model, Object[] tuple) {
72 int[] content = new int[tuple.length - 1];
73 for (int i = 0; i < tuple.length - 1; i++) {
74 if (!(tuple[i] instanceof Tuple1 wrapper)) {
75 return false;
76 }
77 content[i] = wrapper.value0();
78 }
79 Tuple key = Tuple.of(content);
80 var valueInTuple = tuple[tuple.length - 1];
81 T valueInMap = model.getInterpretation(getSymbol()).get(key);
82 return valueEquals(valueInMap, valueInTuple);
83 }
84
85 @Override
86 public List<Parameter> getParameters() {
87 return parameters;
88 }
89
90 @Override
91 public boolean equals(Object o) {
92 if (this == o) return true;
93 if (o == null || getClass() != o.getClass()) return false;
94 if (!super.equals(o)) return false;
95 AbstractFunctionView<?> that = (AbstractFunctionView<?>) o;
96 return Objects.equals(parameters, that.parameters);
97 }
98
99 @Override
100 public int hashCode() {
101 return Objects.hash(super.hashCode(), parameters);
102 }
103
104 private static List<Parameter> createParameters(int symbolArity, Parameter outParameter) {
105 var parameters = new Parameter[symbolArity + 1];
106 Arrays.fill(parameters, Parameter.NODE_OUT);
107 parameters[symbolArity] = outParameter;
108 return List.of(parameters);
109 }
110}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java
new file mode 100644
index 00000000..90b27ebb
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.dnf.FunctionalDependency;
10import tools.refinery.store.representation.AnySymbol;
11import tools.refinery.store.query.Constraint;
12
13import java.util.Set;
14
15public sealed interface AnySymbolView extends Constraint permits SymbolView {
16 AnySymbol getSymbol();
17
18 String getViewName();
19
20 default Set<FunctionalDependency<Integer>> getFunctionalDependencies() {
21 return Set.of();
22 }
23
24 default Set<ViewImplication> getImpliedRelationViews() {
25 return Set.of();
26 }
27
28 boolean get(Model model, Object[] tuple);
29
30 Iterable<Object[]> getAll(Model model);
31}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java
new file mode 100644
index 00000000..abae6e5c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java
@@ -0,0 +1,73 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.tuple.Tuple;
9import tools.refinery.store.representation.Symbol;
10
11import java.util.Objects;
12import java.util.function.BiPredicate;
13import java.util.function.Predicate;
14
15public class FilteredView<T> extends TuplePreservingView<T> {
16 private final BiPredicate<Tuple, T> predicate;
17
18 public FilteredView(Symbol<T> symbol, String name, BiPredicate<Tuple, T> predicate) {
19 super(symbol, name);
20 this.predicate = predicate;
21 }
22
23 public FilteredView(Symbol<T> symbol, BiPredicate<Tuple, T> predicate) {
24 super(symbol);
25 this.predicate = predicate;
26 }
27
28 public FilteredView(Symbol<T> symbol, String name, Predicate<T> predicate) {
29 this(symbol, name, (k, v) -> predicate.test(v));
30 validateDefaultValue(predicate);
31 }
32
33 public FilteredView(Symbol<T> symbol, Predicate<T> predicate) {
34 this(symbol, (k, v) -> predicate.test(v));
35 validateDefaultValue(predicate);
36 }
37
38 @Override
39 protected boolean doFilter(Tuple key, T value) {
40 return this.predicate.test(key, value);
41 }
42
43 @Override
44 public boolean equals(Object o) {
45 if (this == o) return true;
46 if (o == null || getClass() != o.getClass()) return false;
47 if (!super.equals(o)) return false;
48 FilteredView<?> that = (FilteredView<?>) o;
49 return Objects.equals(predicate, that.predicate);
50 }
51
52 @Override
53 public int hashCode() {
54 return Objects.hash(super.hashCode(), predicate);
55 }
56
57 private void validateDefaultValue(Predicate<T> predicate) {
58 var defaultValue = getSymbol().defaultValue();
59 boolean matchesDefaultValue = false;
60 try {
61 matchesDefaultValue = predicate.test(defaultValue);
62 } catch (NullPointerException e) {
63 if (defaultValue != null) {
64 throw e;
65 }
66 // The predicate doesn't need to handle the default value if it is null.
67 }
68 if (matchesDefaultValue) {
69 throw new IllegalArgumentException("Tuples with default value %s cannot be enumerated in %s"
70 .formatted(defaultValue, getSymbol()));
71 }
72 }
73}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenView.java
new file mode 100644
index 00000000..c312330e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenView.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.representation.Symbol;
9import tools.refinery.store.representation.TruthValue;
10import tools.refinery.store.tuple.Tuple;
11
12public class ForbiddenView extends TuplePreservingView<TruthValue> {
13 public ForbiddenView(Symbol<TruthValue> symbol) {
14 super(symbol, "forbidden");
15 }
16
17 @Override
18 protected boolean doFilter(Tuple key, TruthValue value) {
19 return !value.may();
20 }
21}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java
new file mode 100644
index 00000000..74a5be07
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java
@@ -0,0 +1,36 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.query.term.*;
9import tools.refinery.store.representation.Symbol;
10
11import java.util.ArrayList;
12import java.util.List;
13
14public final class FunctionView<T> extends AbstractFunctionView<T> {
15 public FunctionView(Symbol<T> symbol, String name) {
16 super(symbol, name, new Parameter(symbol.valueType(), ParameterDirection.OUT));
17 }
18
19 public FunctionView(Symbol<T> symbol) {
20 this(symbol, "function");
21 }
22
23 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, List<NodeVariable> arguments) {
24 return targetVariable -> {
25 var placeholderVariable = Variable.of(getSymbol().valueType());
26 var argumentsWithPlaceholder = new ArrayList<Variable>(arguments.size() + 1);
27 argumentsWithPlaceholder.addAll(arguments);
28 argumentsWithPlaceholder.add(placeholderVariable);
29 return aggregateBy(placeholderVariable, aggregator, argumentsWithPlaceholder).toLiteral(targetVariable);
30 };
31 }
32
33 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, NodeVariable... arguments) {
34 return aggregate(aggregator, List.of(arguments));
35 }
36}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyView.java
new file mode 100644
index 00000000..f0e4a61e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyView.java
@@ -0,0 +1,41 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.representation.Symbol;
9import tools.refinery.store.tuple.Tuple;
10
11import java.util.Objects;
12
13public final class KeyOnlyView<T> extends TuplePreservingView<T> {
14 public static final String VIEW_NAME = "key";
15
16 private final T defaultValue;
17
18 public KeyOnlyView(Symbol<T> symbol) {
19 super(symbol, VIEW_NAME);
20 defaultValue = symbol.defaultValue();
21 }
22
23 @Override
24 protected boolean doFilter(Tuple key, T value) {
25 return true;
26 }
27
28 @Override
29 public boolean equals(Object o) {
30 if (this == o) return true;
31 if (o == null || getClass() != o.getClass()) return false;
32 if (!super.equals(o)) return false;
33 KeyOnlyView<?> that = (KeyOnlyView<?>) o;
34 return Objects.equals(defaultValue, that.defaultValue);
35 }
36
37 @Override
38 public int hashCode() {
39 return Objects.hash(super.hashCode(), defaultValue);
40 }
41}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayView.java
new file mode 100644
index 00000000..c322e220
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayView.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.representation.Symbol;
9import tools.refinery.store.representation.TruthValue;
10import tools.refinery.store.tuple.Tuple;
11
12public class MayView extends TuplePreservingView<TruthValue> {
13 public MayView(Symbol<TruthValue> symbol) {
14 super(symbol, "may");
15 }
16
17 @Override
18 protected boolean doFilter(Tuple key, TruthValue value) {
19 return value.may();
20 }
21}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustView.java
new file mode 100644
index 00000000..65bb4e4c
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustView.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.representation.Symbol;
9import tools.refinery.store.representation.TruthValue;
10import tools.refinery.store.tuple.Tuple;
11
12public class MustView extends TuplePreservingView<TruthValue> {
13 public MustView(Symbol<TruthValue> symbol) {
14 super(symbol, "must");
15 }
16
17 @Override
18 protected boolean doFilter(Tuple key, TruthValue value) {
19 return value.must();
20 }
21}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/NodeFunctionView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/NodeFunctionView.java
new file mode 100644
index 00000000..fcf11506
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/NodeFunctionView.java
@@ -0,0 +1,20 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.query.term.Parameter;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple1;
11
12public final class NodeFunctionView extends AbstractFunctionView<Tuple1> {
13 public NodeFunctionView(Symbol<Tuple1> symbol, String name) {
14 super(symbol, name, Parameter.NODE_OUT);
15 }
16
17 public NodeFunctionView(Symbol<Tuple1> symbol) {
18 this(symbol, "function");
19 }
20}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/SymbolView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/SymbolView.java
new file mode 100644
index 00000000..cd8bd56b
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/SymbolView.java
@@ -0,0 +1,85 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.map.CursorAsIterator;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.tuple.Tuple;
12
13import java.util.Objects;
14import java.util.UUID;
15
16/**
17 * Represents a view of a {@link Symbol} that can be queried.
18 *
19 * @param <T>
20 * @author Oszkar Semerath
21 */
22public abstract non-sealed class SymbolView<T> implements AnySymbolView {
23 private final Symbol<T> symbol;
24 private final String viewName;
25
26 protected SymbolView(Symbol<T> symbol, String viewName) {
27 this.symbol = symbol;
28 this.viewName = viewName;
29 }
30
31 protected SymbolView(Symbol<T> representation) {
32 this(representation, UUID.randomUUID().toString());
33 }
34
35 @Override
36 public Symbol<T> getSymbol() {
37 return symbol;
38 }
39
40 @Override
41 public String getViewName() {
42 return viewName;
43 }
44
45 @Override
46 public String name() {
47 return symbol.name() + "#" + viewName;
48 }
49
50 public final boolean filter(Tuple key, T value) {
51 return !Objects.equals(symbol.defaultValue(), value) && doFilter(key, value);
52 }
53
54 protected abstract boolean doFilter(Tuple key, T value);
55
56 public abstract Object[] forwardMap(Tuple key, T value);
57
58 @Override
59 public Iterable<Object[]> getAll(Model model) {
60 return (() -> new CursorAsIterator<>(model.getInterpretation(symbol).getAll(), this::forwardMap, this::filter));
61 }
62
63 @Override
64 public String toString() {
65 return name();
66 }
67
68 @Override
69 public String toReferenceString() {
70 return "@RelationView(\"%s\") %s".formatted(viewName, symbol.name());
71 }
72
73 @Override
74 public boolean equals(Object o) {
75 if (this == o) return true;
76 if (o == null || getClass() != o.getClass()) return false;
77 SymbolView<?> that = (SymbolView<?>) o;
78 return Objects.equals(symbol, that.symbol) && Objects.equals(viewName, that.viewName);
79 }
80
81 @Override
82 public int hashCode() {
83 return Objects.hash(getClass(), symbol, viewName);
84 }
85}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java
new file mode 100644
index 00000000..6bc5a708
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java
@@ -0,0 +1,82 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.term.Parameter;
10import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.tuple.Tuple;
12import tools.refinery.store.tuple.Tuple1;
13
14import java.util.Arrays;
15import java.util.List;
16import java.util.Objects;
17
18public abstract class TuplePreservingView<T> extends SymbolView<T> {
19 private final List<Parameter> parameters;
20
21 protected TuplePreservingView(Symbol<T> symbol, String name) {
22 super(symbol, name);
23 this.parameters = createParameters(symbol.arity());
24 }
25
26 protected TuplePreservingView(Symbol<T> symbol) {
27 super(symbol);
28 this.parameters = createParameters(symbol.arity());
29 }
30
31 public Object[] forwardMap(Tuple key) {
32 Object[] result = new Object[key.getSize()];
33 for (int i = 0; i < key.getSize(); i++) {
34 result[i] = Tuple.of(key.get(i));
35 }
36 return result;
37 }
38
39 @Override
40 public Object[] forwardMap(Tuple key, T value) {
41 return forwardMap(key);
42 }
43
44 @Override
45 public boolean get(Model model, Object[] tuple) {
46 int[] content = new int[tuple.length];
47 for (int i = 0; i < tuple.length; i++) {
48 if (!(tuple[i] instanceof Tuple1 wrapper)) {
49 return false;
50 }
51 content[i] = wrapper.value0();
52 }
53 Tuple key = Tuple.of(content);
54 T value = model.getInterpretation(getSymbol()).get(key);
55 return filter(key, value);
56 }
57
58 @Override
59 public List<Parameter> getParameters() {
60 return parameters;
61 }
62
63 @Override
64 public boolean equals(Object o) {
65 if (this == o) return true;
66 if (o == null || getClass() != o.getClass()) return false;
67 if (!super.equals(o)) return false;
68 TuplePreservingView<?> that = (TuplePreservingView<?>) o;
69 return Objects.equals(parameters, that.parameters);
70 }
71
72 @Override
73 public int hashCode() {
74 return Objects.hash(super.hashCode(), parameters);
75 }
76
77 private static List<Parameter> createParameters(int arity) {
78 var parameters = new Parameter[arity];
79 Arrays.fill(parameters, Parameter.NODE_OUT);
80 return List.of(parameters);
81 }
82}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ViewImplication.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ViewImplication.java
new file mode 100644
index 00000000..fc2db9f2
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ViewImplication.java
@@ -0,0 +1,23 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.view;
7
8import java.util.List;
9
10public record ViewImplication(AnySymbolView implyingView, AnySymbolView impliedView, List<Integer> impliedIndices) {
11 public ViewImplication {
12 if (impliedIndices.size() != impliedView.arity()) {
13 throw new IllegalArgumentException("Expected %d implied indices for %s, but %d are provided"
14 .formatted(impliedView.arity(), impliedView, impliedIndices.size()));
15 }
16 for (var index : impliedIndices) {
17 if (impliedView.invalidIndex(index)) {
18 throw new IllegalArgumentException("%d is not a valid index for %s".formatted(index,
19 implyingView));
20 }
21 }
22 }
23}