diff options
author | 2023-07-24 14:37:16 +0200 | |
---|---|---|
committer | 2023-07-24 14:37:16 +0200 | |
commit | 8789304690384d19ad829286560aec5ed0917976 (patch) | |
tree | b157e78e8b6a3f2fb3d0eee6e1aa10d2c16e0204 /subprojects/store-query/src | |
parent | decreasing steps in fast fuzz tests (diff) | |
parent | Merge pull request #27 from kris7t/ordered-result-set (diff) | |
download | refinery-8789304690384d19ad829286560aec5ed0917976.tar.gz refinery-8789304690384d19ad829286560aec5ed0917976.tar.zst refinery-8789304690384d19ad829286560aec5ed0917976.zip |
Merge branch 'graphs4value:main' into datastructure
Diffstat (limited to 'subprojects/store-query/src')
176 files changed, 11128 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 | */ | ||
6 | package tools.refinery.store.query; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.literal.*; | ||
10 | import tools.refinery.store.query.term.*; | ||
11 | |||
12 | import java.util.List; | ||
13 | |||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelAdapter; | ||
9 | import tools.refinery.store.query.dnf.AnyQuery; | ||
10 | import tools.refinery.store.query.dnf.Query; | ||
11 | import tools.refinery.store.query.resultset.AnyResultSet; | ||
12 | import tools.refinery.store.query.resultset.ResultSet; | ||
13 | |||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelAdapterBuilder; | ||
9 | import tools.refinery.store.model.ModelStore; | ||
10 | import tools.refinery.store.query.dnf.AnyQuery; | ||
11 | |||
12 | import java.util.Collection; | ||
13 | import java.util.List; | ||
14 | |||
15 | @SuppressWarnings("UnusedReturnValue") | ||
16 | public 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 | */ | ||
6 | package tools.refinery.store.query; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelStoreAdapter; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.query.dnf.AnyQuery; | ||
11 | import tools.refinery.store.query.view.AnySymbolView; | ||
12 | |||
13 | import java.util.Collection; | ||
14 | |||
15 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.callback.*; | ||
9 | import tools.refinery.store.query.literal.Literal; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | import tools.refinery.store.query.term.ParameterDirection; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | |||
14 | import java.util.Collection; | ||
15 | import java.util.List; | ||
16 | import java.util.Set; | ||
17 | |||
18 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import org.jetbrains.annotations.NotNull; | ||
9 | import tools.refinery.store.query.literal.BooleanLiteral; | ||
10 | import tools.refinery.store.query.literal.EquivalenceLiteral; | ||
11 | import tools.refinery.store.query.literal.Literal; | ||
12 | import tools.refinery.store.query.substitution.MapBasedSubstitution; | ||
13 | import tools.refinery.store.query.substitution.StatelessSubstitution; | ||
14 | import tools.refinery.store.query.substitution.Substitution; | ||
15 | import tools.refinery.store.query.term.NodeVariable; | ||
16 | import tools.refinery.store.query.term.ParameterDirection; | ||
17 | import tools.refinery.store.query.term.Variable; | ||
18 | |||
19 | import java.util.*; | ||
20 | import java.util.function.Function; | ||
21 | |||
22 | class 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 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.literal.Reduction; | ||
10 | import tools.refinery.store.query.equality.DnfEqualityChecker; | ||
11 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
12 | import tools.refinery.store.query.term.Parameter; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | |||
15 | import java.util.Collection; | ||
16 | import java.util.Collections; | ||
17 | import java.util.List; | ||
18 | import java.util.Set; | ||
19 | import java.util.function.Consumer; | ||
20 | import java.util.stream.Collectors; | ||
21 | |||
22 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.callback.*; | ||
9 | import tools.refinery.store.query.literal.Literal; | ||
10 | import tools.refinery.store.query.term.DataVariable; | ||
11 | import tools.refinery.store.query.term.NodeVariable; | ||
12 | import tools.refinery.store.query.term.ParameterDirection; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | |||
15 | import java.util.*; | ||
16 | |||
17 | @SuppressWarnings("UnusedReturnValue") | ||
18 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.literal.Literal; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | import java.util.List; | ||
13 | import java.util.Set; | ||
14 | |||
15 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import java.util.UUID; | ||
9 | |||
10 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import java.util.HashSet; | ||
9 | import java.util.Set; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import tools.refinery.store.query.literal.CallPolarity; | ||
9 | import tools.refinery.store.query.term.Aggregator; | ||
10 | import tools.refinery.store.query.term.AssignedValue; | ||
11 | import tools.refinery.store.query.term.NodeVariable; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | |||
14 | import java.util.ArrayList; | ||
15 | import java.util.List; | ||
16 | import java.util.Objects; | ||
17 | |||
18 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import tools.refinery.store.query.term.DataVariable; | ||
9 | |||
10 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.callback.*; | ||
9 | import tools.refinery.store.query.term.ParameterDirection; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | import java.util.Objects; | ||
13 | |||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import tools.refinery.store.query.term.DataVariable; | ||
9 | |||
10 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import tools.refinery.store.query.literal.CallLiteral; | ||
9 | import tools.refinery.store.query.literal.CallPolarity; | ||
10 | import tools.refinery.store.query.term.AssignedValue; | ||
11 | import tools.refinery.store.query.term.NodeVariable; | ||
12 | |||
13 | import java.util.Collections; | ||
14 | import java.util.List; | ||
15 | |||
16 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import tools.refinery.store.query.term.Parameter; | ||
9 | import tools.refinery.store.query.term.ParameterDirection; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | import java.util.Objects; | ||
13 | |||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | |||
10 | import java.util.Collection; | ||
11 | |||
12 | @FunctionalInterface | ||
13 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | @FunctionalInterface | ||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | @FunctionalInterface | ||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | @FunctionalInterface | ||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | import java.util.Collection; | ||
13 | |||
14 | @FunctionalInterface | ||
15 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | @FunctionalInterface | ||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | @FunctionalInterface | ||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | import java.util.Collection; | ||
13 | |||
14 | @FunctionalInterface | ||
15 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | import java.util.Collection; | ||
13 | |||
14 | @FunctionalInterface | ||
15 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | @FunctionalInterface | ||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | @FunctionalInterface | ||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | import java.util.Collection; | ||
13 | |||
14 | @FunctionalInterface | ||
15 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | import java.util.Collection; | ||
13 | |||
14 | @FunctionalInterface | ||
15 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | import java.util.Collection; | ||
13 | |||
14 | @FunctionalInterface | ||
15 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | @FunctionalInterface | ||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.FunctionalQueryBuilder; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | @FunctionalInterface | ||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.FunctionalQueryBuilder; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | @FunctionalInterface | ||
13 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.FunctionalQueryBuilder; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | @FunctionalInterface | ||
13 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.FunctionalQueryBuilder; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | @FunctionalInterface | ||
13 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.FunctionalQueryBuilder; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | @FunctionalInterface | ||
13 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.QueryBuilder; | ||
9 | |||
10 | @FunctionalInterface | ||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.QueryBuilder; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | |||
11 | @FunctionalInterface | ||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.QueryBuilder; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | |||
11 | @FunctionalInterface | ||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.QueryBuilder; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | |||
11 | @FunctionalInterface | ||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.QueryBuilder; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | |||
11 | @FunctionalInterface | ||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.equality; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | import tools.refinery.store.query.dnf.DnfClause; | ||
10 | import tools.refinery.store.query.dnf.SymbolicParameter; | ||
11 | import tools.refinery.store.query.literal.Literal; | ||
12 | import tools.refinery.store.util.CycleDetectingMapper; | ||
13 | |||
14 | import java.util.List; | ||
15 | |||
16 | public 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 | */ | ||
6 | package tools.refinery.store.query.equality; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | |||
10 | @FunctionalInterface | ||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.equality; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | import tools.refinery.store.query.dnf.SymbolicParameter; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | import java.util.HashMap; | ||
13 | import java.util.List; | ||
14 | import java.util.Map; | ||
15 | |||
16 | public 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 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | ||
11 | import tools.refinery.store.query.term.ParameterDirection; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | |||
14 | import java.util.*; | ||
15 | |||
16 | public 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 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | ||
11 | import tools.refinery.store.query.term.*; | ||
12 | |||
13 | import java.util.List; | ||
14 | import java.util.Objects; | ||
15 | import java.util.Set; | ||
16 | |||
17 | public 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 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.term.DataVariable; | ||
11 | import tools.refinery.store.query.term.Term; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | |||
14 | import java.util.Collections; | ||
15 | import java.util.Objects; | ||
16 | import java.util.Set; | ||
17 | |||
18 | public 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 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.term.ConstantTerm; | ||
11 | import tools.refinery.store.query.term.Term; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | |||
14 | import java.util.Collections; | ||
15 | import java.util.Objects; | ||
16 | import java.util.Set; | ||
17 | |||
18 | public 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 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | import java.util.Set; | ||
13 | |||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | ||
11 | import tools.refinery.store.query.term.ParameterDirection; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | |||
14 | import java.util.*; | ||
15 | |||
16 | public 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 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | public 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 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | public 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 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | import tools.refinery.store.query.term.Variable; | ||
12 | |||
13 | import java.util.Objects; | ||
14 | import java.util.Set; | ||
15 | |||
16 | public 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 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | ||
11 | import tools.refinery.store.query.term.DataVariable; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | import tools.refinery.store.query.term.int_.IntTerms; | ||
14 | |||
15 | import java.util.List; | ||
16 | import java.util.Objects; | ||
17 | import java.util.Set; | ||
18 | |||
19 | public 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 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | import tools.refinery.store.query.term.Variable; | ||
12 | |||
13 | import java.util.Objects; | ||
14 | import java.util.Set; | ||
15 | |||
16 | public 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 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | import java.util.Set; | ||
13 | |||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.term.Term; | ||
9 | |||
10 | public 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 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | public 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 | */ | ||
6 | package tools.refinery.store.query.resultset; | ||
7 | |||
8 | import tools.refinery.store.query.ModelQueryAdapter; | ||
9 | import tools.refinery.store.query.dnf.Query; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | import java.util.ArrayList; | ||
13 | import java.util.List; | ||
14 | |||
15 | public 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 | */ | ||
6 | package tools.refinery.store.query.resultset; | ||
7 | |||
8 | import tools.refinery.store.query.ModelQueryAdapter; | ||
9 | import tools.refinery.store.query.dnf.AnyQuery; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.resultset; | ||
7 | |||
8 | import tools.refinery.store.map.Cursor; | ||
9 | import tools.refinery.store.map.Cursors; | ||
10 | import tools.refinery.store.query.ModelQueryAdapter; | ||
11 | import tools.refinery.store.query.dnf.Query; | ||
12 | import tools.refinery.store.tuple.Tuple; | ||
13 | |||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.resultset; | ||
7 | |||
8 | import tools.refinery.store.map.Cursor; | ||
9 | import tools.refinery.store.query.ModelQueryAdapter; | ||
10 | import tools.refinery.store.query.dnf.Query; | ||
11 | import tools.refinery.store.query.utils.OrderStatisticTree; | ||
12 | import tools.refinery.store.tuple.Tuple; | ||
13 | |||
14 | import java.util.Objects; | ||
15 | |||
16 | public 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 | */ | ||
6 | package tools.refinery.store.query.resultset; | ||
7 | |||
8 | import tools.refinery.store.map.Cursor; | ||
9 | import tools.refinery.store.query.dnf.Query; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.resultset; | ||
7 | |||
8 | import tools.refinery.store.tuple.Tuple; | ||
9 | |||
10 | @FunctionalInterface | ||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.substitution; | ||
7 | |||
8 | import tools.refinery.store.query.term.Variable; | ||
9 | |||
10 | import java.util.Map; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.substitution; | ||
7 | |||
8 | import tools.refinery.store.query.term.Variable; | ||
9 | |||
10 | import java.util.HashMap; | ||
11 | import java.util.Map; | ||
12 | |||
13 | public 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 | */ | ||
6 | package tools.refinery.store.query.substitution; | ||
7 | |||
8 | import tools.refinery.store.query.term.Variable; | ||
9 | |||
10 | public 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 | */ | ||
6 | package tools.refinery.store.query.substitution; | ||
7 | |||
8 | import tools.refinery.store.query.term.DataVariable; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | @FunctionalInterface | ||
13 | public 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 | */ | ||
6 | package tools.refinery.store.query.substitution; | ||
7 | |||
8 | import tools.refinery.store.query.term.DataVariable; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | import java.util.Collections; | ||
13 | import java.util.HashMap; | ||
14 | import java.util.List; | ||
15 | import java.util.Map; | ||
16 | |||
17 | @SuppressWarnings("UnusedReturnValue") | ||
18 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | |||
10 | import java.util.Objects; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import java.util.stream.Stream; | ||
9 | |||
10 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import org.jetbrains.annotations.Nullable; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
10 | |||
11 | import java.util.Optional; | ||
12 | import java.util.Set; | ||
13 | |||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | |||
11 | import java.util.Set; | ||
12 | |||
13 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | |||
10 | @FunctionalInterface | ||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.valuation.Valuation; | ||
11 | |||
12 | import java.util.Collections; | ||
13 | import java.util.HashSet; | ||
14 | import java.util.Objects; | ||
15 | import java.util.Set; | ||
16 | |||
17 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.valuation.Valuation; | ||
11 | |||
12 | import java.util.Objects; | ||
13 | import java.util.Set; | ||
14 | |||
15 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import org.jetbrains.annotations.Nullable; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
10 | import tools.refinery.store.query.literal.Literal; | ||
11 | import tools.refinery.store.query.substitution.Substitution; | ||
12 | import tools.refinery.store.query.valuation.Valuation; | ||
13 | |||
14 | import java.util.Objects; | ||
15 | |||
16 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import java.util.Comparator; | ||
9 | import java.util.Objects; | ||
10 | import java.util.SortedMap; | ||
11 | import java.util.TreeMap; | ||
12 | |||
13 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import org.jetbrains.annotations.Nullable; | ||
9 | import tools.refinery.store.query.literal.ConstantLiteral; | ||
10 | import tools.refinery.store.query.literal.EquivalenceLiteral; | ||
11 | |||
12 | import java.util.Optional; | ||
13 | |||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import java.util.Objects; | ||
9 | import java.util.Optional; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import java.util.stream.Stream; | ||
9 | |||
10 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import java.util.stream.Stream; | ||
9 | |||
10 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import tools.refinery.store.query.literal.AssignLiteral; | ||
9 | import tools.refinery.store.query.literal.Literal; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | ||
11 | import tools.refinery.store.query.valuation.Valuation; | ||
12 | |||
13 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.valuation.Valuation; | ||
11 | |||
12 | import java.util.Objects; | ||
13 | import java.util.Set; | ||
14 | |||
15 | public 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 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import org.jetbrains.annotations.Nullable; | ||
9 | import tools.refinery.store.query.dnf.DnfUtils; | ||
10 | |||
11 | import java.util.Objects; | ||
12 | import java.util.Optional; | ||
13 | |||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.bool; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.bool; | ||
7 | |||
8 | import tools.refinery.store.query.term.BinaryTerm; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.bool; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | import tools.refinery.store.query.term.UnaryTerm; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.bool; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.bool; | ||
7 | |||
8 | import tools.refinery.store.query.term.ConstantTerm; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.bool; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.comparable; | ||
7 | |||
8 | import tools.refinery.store.query.term.BinaryTerm; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.comparable; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.comparable; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.comparable; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.comparable; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.comparable; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.comparable; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.term.BinaryTerm; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.term.StatelessAggregator; | ||
9 | |||
10 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.term.Aggregator; | ||
9 | import tools.refinery.store.query.term.ConstantTerm; | ||
10 | import tools.refinery.store.query.term.ExtremeValueAggregator; | ||
11 | import tools.refinery.store.query.term.Term; | ||
12 | import tools.refinery.store.query.term.comparable.*; | ||
13 | |||
14 | import java.util.Comparator; | ||
15 | |||
16 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.term.Term; | ||
9 | import tools.refinery.store.query.term.UnaryTerm; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | import tools.refinery.store.query.term.UnaryTerm; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | import tools.refinery.store.query.term.UnaryTerm; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.term.BinaryTerm; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.term.StatefulAggregate; | ||
9 | import tools.refinery.store.query.term.StatefulAggregator; | ||
10 | |||
11 | import java.util.Map; | ||
12 | import java.util.TreeMap; | ||
13 | |||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.term.Aggregator; | ||
9 | import tools.refinery.store.query.term.ConstantTerm; | ||
10 | import tools.refinery.store.query.term.ExtremeValueAggregator; | ||
11 | import tools.refinery.store.query.term.Term; | ||
12 | import tools.refinery.store.query.term.comparable.*; | ||
13 | |||
14 | import java.util.Comparator; | ||
15 | |||
16 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.term.Term; | ||
9 | import tools.refinery.store.query.term.UnaryTerm; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import tools.refinery.store.query.term.BinaryTerm; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import tools.refinery.store.query.term.StatefulAggregate; | ||
9 | import tools.refinery.store.query.term.StatefulAggregator; | ||
10 | import tools.refinery.store.representation.cardinality.FiniteUpperCardinality; | ||
11 | import tools.refinery.store.representation.cardinality.UnboundedUpperCardinality; | ||
12 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
13 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
14 | |||
15 | public 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 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import tools.refinery.store.query.term.Aggregator; | ||
9 | import tools.refinery.store.query.term.ConstantTerm; | ||
10 | import tools.refinery.store.query.term.ExtremeValueAggregator; | ||
11 | import tools.refinery.store.query.term.Term; | ||
12 | import tools.refinery.store.query.term.comparable.*; | ||
13 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
14 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
15 | |||
16 | import java.util.Comparator; | ||
17 | |||
18 | public 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 | */ | ||
7 | package tools.refinery.store.query.utils; | ||
8 | |||
9 | import 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 | */ | ||
25 | public 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 | */ | ||
6 | package tools.refinery.store.query.valuation; | ||
7 | |||
8 | import tools.refinery.store.query.term.AnyDataVariable; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | import java.util.Map; | ||
12 | |||
13 | record 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 | */ | ||
6 | package tools.refinery.store.query.valuation; | ||
7 | |||
8 | import tools.refinery.store.query.term.AnyDataVariable; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | import java.util.Set; | ||
12 | |||
13 | public 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 | */ | ||
6 | package tools.refinery.store.query.valuation; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | public 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 | */ | ||
6 | package tools.refinery.store.query.valuation; | ||
7 | |||
8 | import org.jetbrains.annotations.Nullable; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.term.AnyDataVariable; | ||
11 | import tools.refinery.store.query.term.DataVariable; | ||
12 | |||
13 | import java.util.Map; | ||
14 | import java.util.Set; | ||
15 | |||
16 | public 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 | */ | ||
6 | package tools.refinery.store.query.valuation; | ||
7 | |||
8 | import tools.refinery.store.query.term.AnyDataVariable; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | import java.util.Collections; | ||
12 | import java.util.HashMap; | ||
13 | import java.util.Map; | ||
14 | |||
15 | public 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 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.query.dnf.FunctionalDependency; | ||
10 | import tools.refinery.store.query.term.Parameter; | ||
11 | import tools.refinery.store.representation.Symbol; | ||
12 | import tools.refinery.store.tuple.Tuple; | ||
13 | import tools.refinery.store.tuple.Tuple1; | ||
14 | |||
15 | import java.util.Arrays; | ||
16 | import java.util.List; | ||
17 | import java.util.Objects; | ||
18 | import java.util.Set; | ||
19 | import java.util.stream.Collectors; | ||
20 | import java.util.stream.IntStream; | ||
21 | |||
22 | public 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 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.query.dnf.FunctionalDependency; | ||
10 | import tools.refinery.store.representation.AnySymbol; | ||
11 | import tools.refinery.store.query.Constraint; | ||
12 | |||
13 | import java.util.Set; | ||
14 | |||
15 | public 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 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.tuple.Tuple; | ||
9 | import tools.refinery.store.representation.Symbol; | ||
10 | |||
11 | import java.util.Objects; | ||
12 | import java.util.function.BiPredicate; | ||
13 | import java.util.function.Predicate; | ||
14 | |||
15 | public 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 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.representation.Symbol; | ||
9 | import tools.refinery.store.representation.TruthValue; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.query.term.*; | ||
9 | import tools.refinery.store.representation.Symbol; | ||
10 | |||
11 | import java.util.ArrayList; | ||
12 | import java.util.List; | ||
13 | |||
14 | public 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 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.representation.Symbol; | ||
9 | import tools.refinery.store.tuple.Tuple; | ||
10 | |||
11 | import java.util.Objects; | ||
12 | |||
13 | public 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 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.representation.Symbol; | ||
9 | import tools.refinery.store.representation.TruthValue; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.representation.Symbol; | ||
9 | import tools.refinery.store.representation.TruthValue; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.query.term.Parameter; | ||
9 | import tools.refinery.store.representation.Symbol; | ||
10 | import tools.refinery.store.tuple.Tuple1; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.map.CursorAsIterator; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.representation.Symbol; | ||
11 | import tools.refinery.store.tuple.Tuple; | ||
12 | |||
13 | import java.util.Objects; | ||
14 | import 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 | */ | ||
22 | public 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 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.query.term.Parameter; | ||
10 | import tools.refinery.store.representation.Symbol; | ||
11 | import tools.refinery.store.tuple.Tuple; | ||
12 | import tools.refinery.store.tuple.Tuple1; | ||
13 | |||
14 | import java.util.Arrays; | ||
15 | import java.util.List; | ||
16 | import java.util.Objects; | ||
17 | |||
18 | public 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 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import java.util.List; | ||
9 | |||
10 | public 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 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java new file mode 100644 index 00000000..e17496e3 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java | |||
@@ -0,0 +1,210 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import org.junit.jupiter.params.ParameterizedTest; | ||
10 | import org.junit.jupiter.params.provider.CsvSource; | ||
11 | import tools.refinery.store.query.literal.BooleanLiteral; | ||
12 | import tools.refinery.store.query.term.NodeVariable; | ||
13 | import tools.refinery.store.query.term.ParameterDirection; | ||
14 | import tools.refinery.store.query.term.Variable; | ||
15 | import tools.refinery.store.query.term.bool.BoolTerms; | ||
16 | import tools.refinery.store.query.view.KeyOnlyView; | ||
17 | import tools.refinery.store.query.view.SymbolView; | ||
18 | import tools.refinery.store.representation.Symbol; | ||
19 | |||
20 | import java.util.List; | ||
21 | |||
22 | import static org.hamcrest.MatcherAssert.assertThat; | ||
23 | import static tools.refinery.store.query.literal.Literals.assume; | ||
24 | import static tools.refinery.store.query.literal.Literals.not; | ||
25 | import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo; | ||
26 | |||
27 | class DnfBuilderLiteralEliminationTest { | ||
28 | private final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
29 | private final SymbolView<Boolean> friendView = new KeyOnlyView<>(friend); | ||
30 | private final NodeVariable p = Variable.of("p"); | ||
31 | private final NodeVariable q = Variable.of("q"); | ||
32 | private final Dnf trueDnf = Dnf.builder().parameter(p, ParameterDirection.IN).clause().build(); | ||
33 | private final Dnf falseDnf = Dnf.builder().parameter(p).build(); | ||
34 | |||
35 | @Test | ||
36 | void eliminateTrueTest() { | ||
37 | var actual = Dnf.builder() | ||
38 | .parameters(p, q) | ||
39 | .clause(BooleanLiteral.TRUE, friendView.call(p, q)) | ||
40 | .build(); | ||
41 | var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); | ||
42 | |||
43 | assertThat(actual, structurallyEqualTo(expected)); | ||
44 | } | ||
45 | |||
46 | @Test | ||
47 | void eliminateTrueAssumptionTest() { | ||
48 | var actual = Dnf.builder() | ||
49 | .parameters(p, q) | ||
50 | .clause(assume(BoolTerms.constant(true)), friendView.call(p, q)) | ||
51 | .build(); | ||
52 | var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); | ||
53 | |||
54 | assertThat(actual, structurallyEqualTo(expected)); | ||
55 | } | ||
56 | |||
57 | @Test | ||
58 | void eliminateFalseTest() { | ||
59 | var actual = Dnf.builder() | ||
60 | .parameters(p, q) | ||
61 | .clause(friendView.call(p, q)) | ||
62 | .clause(friendView.call(q, p), BooleanLiteral.FALSE) | ||
63 | .build(); | ||
64 | var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); | ||
65 | |||
66 | assertThat(actual, structurallyEqualTo(expected)); | ||
67 | } | ||
68 | |||
69 | @ParameterizedTest | ||
70 | @CsvSource(value = { | ||
71 | "false", | ||
72 | "null" | ||
73 | }, nullValues = "null") | ||
74 | void eliminateFalseAssumptionTest(Boolean value) { | ||
75 | var actual = Dnf.builder() | ||
76 | .parameters(p, q) | ||
77 | .clause(friendView.call(p, q)) | ||
78 | .clause(friendView.call(q, p), assume(BoolTerms.constant(value))) | ||
79 | .build(); | ||
80 | var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); | ||
81 | |||
82 | assertThat(actual, structurallyEqualTo(expected)); | ||
83 | } | ||
84 | |||
85 | @Test | ||
86 | void alwaysTrueTest() { | ||
87 | var actual = Dnf.builder() | ||
88 | .parameters(List.of(p, q), ParameterDirection.IN) | ||
89 | .clause(friendView.call(p, q)) | ||
90 | .clause(BooleanLiteral.TRUE) | ||
91 | .build(); | ||
92 | var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build(); | ||
93 | |||
94 | assertThat(actual, structurallyEqualTo(expected)); | ||
95 | } | ||
96 | |||
97 | @Test | ||
98 | void alwaysFalseTest() { | ||
99 | var actual = Dnf.builder() | ||
100 | .parameters(p, q) | ||
101 | .clause(friendView.call(p, q), BooleanLiteral.FALSE) | ||
102 | .build(); | ||
103 | var expected = Dnf.builder().parameters(p, q).build(); | ||
104 | |||
105 | assertThat(actual, structurallyEqualTo(expected)); | ||
106 | } | ||
107 | |||
108 | @Test | ||
109 | void eliminateTrueDnfTest() { | ||
110 | var actual = Dnf.builder() | ||
111 | .parameters(p, q) | ||
112 | .clause(trueDnf.call(q), friendView.call(p, q)) | ||
113 | .build(); | ||
114 | var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); | ||
115 | |||
116 | assertThat(actual, structurallyEqualTo(expected)); | ||
117 | } | ||
118 | |||
119 | @Test | ||
120 | void eliminateFalseDnfTest() { | ||
121 | var actual = Dnf.builder() | ||
122 | .parameters(p, q) | ||
123 | .clause(friendView.call(p, q)) | ||
124 | .clause(friendView.call(q, p), falseDnf.call(q)) | ||
125 | .build(); | ||
126 | var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); | ||
127 | |||
128 | assertThat(actual, structurallyEqualTo(expected)); | ||
129 | } | ||
130 | |||
131 | @Test | ||
132 | void alwaysTrueDnfTest() { | ||
133 | var actual = Dnf.builder() | ||
134 | .parameters(List.of(p, q), ParameterDirection.IN) | ||
135 | .clause(friendView.call(p, q)) | ||
136 | .clause(trueDnf.call(q)) | ||
137 | .build(); | ||
138 | var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build(); | ||
139 | |||
140 | assertThat(actual, structurallyEqualTo(expected)); | ||
141 | } | ||
142 | |||
143 | @Test | ||
144 | void alwaysFalseDnfTest() { | ||
145 | var actual = Dnf.builder() | ||
146 | .parameters(p, q) | ||
147 | .clause(friendView.call(p, q), falseDnf.call(q)) | ||
148 | .build(); | ||
149 | var expected = Dnf.builder().parameters(p, q).build(); | ||
150 | |||
151 | assertThat(actual, structurallyEqualTo(expected)); | ||
152 | } | ||
153 | |||
154 | @Test | ||
155 | void eliminateNotFalseDnfTest() { | ||
156 | var actual = Dnf.builder() | ||
157 | .parameters(p, q) | ||
158 | .clause(not(falseDnf.call(q)), friendView.call(p, q)) | ||
159 | .build(); | ||
160 | var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); | ||
161 | |||
162 | assertThat(actual, structurallyEqualTo(expected)); | ||
163 | } | ||
164 | |||
165 | @Test | ||
166 | void eliminateNotTrueDnfTest() { | ||
167 | var actual = Dnf.builder() | ||
168 | .parameters(p, q) | ||
169 | .clause(friendView.call(p, q)) | ||
170 | .clause(friendView.call(q, p), not(trueDnf.call(q))) | ||
171 | .build(); | ||
172 | var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); | ||
173 | |||
174 | assertThat(actual, structurallyEqualTo(expected)); | ||
175 | } | ||
176 | |||
177 | @Test | ||
178 | void alwaysNotFalseDnfTest() { | ||
179 | var actual = Dnf.builder() | ||
180 | .parameters(List.of(p, q), ParameterDirection.IN) | ||
181 | .clause(friendView.call(p, q)) | ||
182 | .clause(not(falseDnf.call(q))) | ||
183 | .build(); | ||
184 | var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build(); | ||
185 | |||
186 | assertThat(actual, structurallyEqualTo(expected)); | ||
187 | } | ||
188 | |||
189 | @Test | ||
190 | void alwaysNotTrueDnfTest() { | ||
191 | var actual = Dnf.builder() | ||
192 | .parameters(p, q) | ||
193 | .clause(friendView.call(p, q), not(trueDnf.call(q))) | ||
194 | .build(); | ||
195 | var expected = Dnf.builder().parameters(p, q).build(); | ||
196 | |||
197 | assertThat(actual, structurallyEqualTo(expected)); | ||
198 | } | ||
199 | |||
200 | @Test | ||
201 | void removeDuplicateTest() { | ||
202 | var actual = Dnf.of(builder -> builder.clause((p, q) -> List.of( | ||
203 | friendView.call(p, q), | ||
204 | friendView.call(p, q) | ||
205 | ))); | ||
206 | var expected = Dnf.of(builder -> builder.clause((p, q) -> List.of(friendView.call(p, q)))); | ||
207 | |||
208 | assertThat(actual, structurallyEqualTo(expected)); | ||
209 | } | ||
210 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderVariableUnificationTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderVariableUnificationTest.java new file mode 100644 index 00000000..fc40c7b3 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderVariableUnificationTest.java | |||
@@ -0,0 +1,325 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.term.ParameterDirection; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | import tools.refinery.store.query.view.KeyOnlyView; | ||
12 | import tools.refinery.store.query.view.SymbolView; | ||
13 | import tools.refinery.store.representation.Symbol; | ||
14 | |||
15 | import java.util.List; | ||
16 | |||
17 | import static org.hamcrest.MatcherAssert.assertThat; | ||
18 | import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo; | ||
19 | |||
20 | class DnfBuilderVariableUnificationTest { | ||
21 | private final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
22 | private final Symbol<Boolean> children = Symbol.of("children", 2); | ||
23 | private final SymbolView<Boolean> friendView = new KeyOnlyView<>(friend); | ||
24 | private final SymbolView<Boolean> childrenView = new KeyOnlyView<>(children); | ||
25 | |||
26 | @Test | ||
27 | void equalToParameterTest() { | ||
28 | var actual = Dnf.of(builder -> { | ||
29 | var p = builder.parameter("p"); | ||
30 | builder.clause(q -> List.of( | ||
31 | friendView.call(p, q), | ||
32 | p.isEquivalent(q) | ||
33 | )); | ||
34 | }); | ||
35 | |||
36 | var expectedP = Variable.of("p"); | ||
37 | assertThat(actual, structurallyEqualTo( | ||
38 | List.of(new SymbolicParameter(expectedP, ParameterDirection.OUT)), | ||
39 | List.of( | ||
40 | List.of(friendView.call(expectedP, expectedP)) | ||
41 | ) | ||
42 | )); | ||
43 | } | ||
44 | |||
45 | @Test | ||
46 | void equalToParameterReverseTest() { | ||
47 | var actual = Dnf.of(builder -> { | ||
48 | var p = builder.parameter("p"); | ||
49 | builder.clause(q -> List.of( | ||
50 | friendView.call(p, q), | ||
51 | q.isEquivalent(p) | ||
52 | )); | ||
53 | }); | ||
54 | |||
55 | var expectedP = Variable.of("p"); | ||
56 | assertThat(actual, structurallyEqualTo( | ||
57 | List.of(new SymbolicParameter(expectedP, ParameterDirection.OUT)), | ||
58 | List.of( | ||
59 | List.of(friendView.call(expectedP, expectedP)) | ||
60 | ) | ||
61 | )); | ||
62 | } | ||
63 | |||
64 | @Test | ||
65 | void equalQuantifiedTest() { | ||
66 | var actual = Dnf.of(builder -> builder.clause((p, q) -> List.of( | ||
67 | friendView.call(p, q), | ||
68 | p.isEquivalent(q) | ||
69 | ))); | ||
70 | |||
71 | var expectedP = Variable.of("p"); | ||
72 | assertThat(actual, structurallyEqualTo( | ||
73 | List.of(), | ||
74 | List.of( | ||
75 | List.of(friendView.call(expectedP, expectedP)) | ||
76 | ) | ||
77 | )); | ||
78 | } | ||
79 | |||
80 | @Test | ||
81 | void equalQuantifiedTransitiveTest() { | ||
82 | var actual = Dnf.of(builder -> builder.clause((p, q, r) -> List.of( | ||
83 | friendView.call(p, q), | ||
84 | p.isEquivalent(q), | ||
85 | childrenView.call(p, r), | ||
86 | q.isEquivalent(r) | ||
87 | ))); | ||
88 | |||
89 | var expectedP = Variable.of("p"); | ||
90 | assertThat(actual, structurallyEqualTo( | ||
91 | List.of(), | ||
92 | List.of( | ||
93 | List.of(friendView.call(expectedP, expectedP), childrenView.call(expectedP, expectedP)) | ||
94 | ) | ||
95 | )); | ||
96 | } | ||
97 | |||
98 | @Test | ||
99 | void equalQuantifiedTransitiveRemoveDuplicateTest() { | ||
100 | var actual = Dnf.of(builder -> builder.clause((p, q, r) -> List.of( | ||
101 | friendView.call(p, q), | ||
102 | p.isEquivalent(q), | ||
103 | friendView.call(p, r), | ||
104 | q.isEquivalent(r) | ||
105 | ))); | ||
106 | |||
107 | var expectedP = Variable.of("p"); | ||
108 | assertThat(actual, structurallyEqualTo( | ||
109 | List.of(), | ||
110 | List.of( | ||
111 | List.of(friendView.call(expectedP, expectedP)) | ||
112 | ) | ||
113 | )); | ||
114 | } | ||
115 | |||
116 | @Test | ||
117 | void parametersEqualTest() { | ||
118 | var actual = Dnf.of(builder -> { | ||
119 | var p = builder.parameter("p"); | ||
120 | var q = builder.parameter("q"); | ||
121 | builder.clause( | ||
122 | friendView.call(p, q), | ||
123 | p.isEquivalent(q) | ||
124 | ); | ||
125 | }); | ||
126 | |||
127 | var expectedP = Variable.of("p"); | ||
128 | var expectedQ = Variable.of("q"); | ||
129 | assertThat(actual, structurallyEqualTo( | ||
130 | List.of( | ||
131 | new SymbolicParameter(expectedP, ParameterDirection.OUT), | ||
132 | new SymbolicParameter(expectedQ, ParameterDirection.OUT) | ||
133 | ), | ||
134 | List.of( | ||
135 | List.of(friendView.call(expectedP, expectedP), expectedQ.isEquivalent(expectedP)) | ||
136 | ) | ||
137 | )); | ||
138 | } | ||
139 | |||
140 | @Test | ||
141 | void parametersEqualTransitiveTest() { | ||
142 | var actual = Dnf.of(builder -> { | ||
143 | var p = builder.parameter("p"); | ||
144 | var q = builder.parameter("q"); | ||
145 | var r = builder.parameter("r"); | ||
146 | builder.clause( | ||
147 | friendView.call(p, q), | ||
148 | childrenView.call(p, r), | ||
149 | p.isEquivalent(q), | ||
150 | r.isEquivalent(q) | ||
151 | ); | ||
152 | }); | ||
153 | |||
154 | var expectedP = Variable.of("p"); | ||
155 | var expectedQ = Variable.of("q"); | ||
156 | var expectedR = Variable.of("r"); | ||
157 | assertThat(actual, structurallyEqualTo( | ||
158 | List.of( | ||
159 | new SymbolicParameter(expectedP, ParameterDirection.OUT), | ||
160 | new SymbolicParameter(expectedQ, ParameterDirection.OUT), | ||
161 | new SymbolicParameter(expectedR, ParameterDirection.OUT) | ||
162 | ), | ||
163 | List.of( | ||
164 | List.of( | ||
165 | friendView.call(expectedP, expectedP), | ||
166 | expectedQ.isEquivalent(expectedP), | ||
167 | expectedR.isEquivalent(expectedP), | ||
168 | childrenView.call(expectedP, expectedP) | ||
169 | ) | ||
170 | ) | ||
171 | )); | ||
172 | } | ||
173 | |||
174 | @Test | ||
175 | void parameterAndQuantifiedEqualsTest() { | ||
176 | var actual = Dnf.of(builder -> { | ||
177 | var p = builder.parameter("p"); | ||
178 | var q = builder.parameter("q"); | ||
179 | builder.clause((r) -> List.of( | ||
180 | friendView.call(p, r), | ||
181 | p.isEquivalent(r), | ||
182 | childrenView.call(q, r), | ||
183 | q.isEquivalent(r) | ||
184 | )); | ||
185 | }); | ||
186 | |||
187 | |||
188 | var expectedP = Variable.of("p"); | ||
189 | var expectedQ = Variable.of("q"); | ||
190 | assertThat(actual, structurallyEqualTo( | ||
191 | List.of( | ||
192 | new SymbolicParameter(expectedP, ParameterDirection.OUT), | ||
193 | new SymbolicParameter(expectedQ, ParameterDirection.OUT) | ||
194 | ), | ||
195 | List.of( | ||
196 | List.of( | ||
197 | friendView.call(expectedP, expectedP), | ||
198 | expectedQ.isEquivalent(expectedP), | ||
199 | childrenView.call(expectedP, expectedP) | ||
200 | ) | ||
201 | ) | ||
202 | )); | ||
203 | } | ||
204 | |||
205 | @Test | ||
206 | void parameterAndQuantifiedEqualsReverseFirstTest() { | ||
207 | var actual = Dnf.of(builder -> { | ||
208 | var p = builder.parameter("p"); | ||
209 | var q = builder.parameter("q"); | ||
210 | builder.clause((r) -> List.of( | ||
211 | friendView.call(p, r), | ||
212 | r.isEquivalent(p), | ||
213 | childrenView.call(q, r), | ||
214 | q.isEquivalent(r) | ||
215 | )); | ||
216 | }); | ||
217 | |||
218 | var expectedP = Variable.of("p"); | ||
219 | var expectedQ = Variable.of("q"); | ||
220 | assertThat(actual, structurallyEqualTo( | ||
221 | List.of( | ||
222 | new SymbolicParameter(expectedP, ParameterDirection.OUT), | ||
223 | new SymbolicParameter(expectedQ, ParameterDirection.OUT) | ||
224 | ), | ||
225 | List.of( | ||
226 | List.of( | ||
227 | friendView.call(expectedP, expectedP), | ||
228 | expectedQ.isEquivalent(expectedP), | ||
229 | childrenView.call(expectedP, expectedP) | ||
230 | ) | ||
231 | ) | ||
232 | )); | ||
233 | } | ||
234 | |||
235 | @Test | ||
236 | void parameterAndQuantifiedEqualsReverseSecondTest() { | ||
237 | var actual = Dnf.of(builder -> { | ||
238 | var p = builder.parameter("p"); | ||
239 | var q = builder.parameter("q"); | ||
240 | builder.clause((r) -> List.of( | ||
241 | friendView.call(p, r), | ||
242 | p.isEquivalent(r), | ||
243 | childrenView.call(q, r), | ||
244 | r.isEquivalent(q) | ||
245 | )); | ||
246 | }); | ||
247 | |||
248 | var expectedP = Variable.of("p"); | ||
249 | var expectedQ = Variable.of("q"); | ||
250 | assertThat(actual, structurallyEqualTo( | ||
251 | List.of( | ||
252 | new SymbolicParameter(expectedP, ParameterDirection.OUT), | ||
253 | new SymbolicParameter(expectedQ, ParameterDirection.OUT) | ||
254 | ), | ||
255 | List.of( | ||
256 | List.of( | ||
257 | friendView.call(expectedP, expectedP), | ||
258 | expectedQ.isEquivalent(expectedP), | ||
259 | childrenView.call(expectedP, expectedP) | ||
260 | ) | ||
261 | ) | ||
262 | )); | ||
263 | } | ||
264 | |||
265 | @Test | ||
266 | void parameterAndQuantifiedEqualsReverseBoth() { | ||
267 | var actual = Dnf.of(builder -> { | ||
268 | var p = builder.parameter("p"); | ||
269 | var q = builder.parameter("q"); | ||
270 | builder.clause((r) -> List.of( | ||
271 | friendView.call(p, r), | ||
272 | p.isEquivalent(r), | ||
273 | childrenView.call(q, r), | ||
274 | r.isEquivalent(q) | ||
275 | )); | ||
276 | }); | ||
277 | |||
278 | var expectedP = Variable.of("p"); | ||
279 | var expectedQ = Variable.of("q"); | ||
280 | assertThat(actual, structurallyEqualTo( | ||
281 | List.of( | ||
282 | new SymbolicParameter(expectedP, ParameterDirection.OUT), | ||
283 | new SymbolicParameter(expectedQ, ParameterDirection.OUT) | ||
284 | ), | ||
285 | List.of( | ||
286 | List.of( | ||
287 | friendView.call(expectedP, expectedP), | ||
288 | expectedQ.isEquivalent(expectedP), | ||
289 | childrenView.call(expectedP, expectedP) | ||
290 | ) | ||
291 | ) | ||
292 | )); | ||
293 | } | ||
294 | |||
295 | @Test | ||
296 | void parameterAndTwoQuantifiedEqualsTest() { | ||
297 | var actual = Dnf.of(builder -> { | ||
298 | var p = builder.parameter("p"); | ||
299 | var q = builder.parameter("q"); | ||
300 | builder.clause((r, s) -> List.of( | ||
301 | r.isEquivalent(s), | ||
302 | friendView.call(p, r), | ||
303 | p.isEquivalent(r), | ||
304 | childrenView.call(q, s), | ||
305 | q.isEquivalent(s) | ||
306 | )); | ||
307 | }); | ||
308 | |||
309 | var expectedP = Variable.of("p"); | ||
310 | var expectedQ = Variable.of("q"); | ||
311 | assertThat(actual, structurallyEqualTo( | ||
312 | List.of( | ||
313 | new SymbolicParameter(expectedP, ParameterDirection.OUT), | ||
314 | new SymbolicParameter(expectedQ, ParameterDirection.OUT) | ||
315 | ), | ||
316 | List.of( | ||
317 | List.of( | ||
318 | friendView.call(expectedP, expectedP), | ||
319 | expectedQ.isEquivalent(expectedP), | ||
320 | childrenView.call(expectedP, expectedP) | ||
321 | ) | ||
322 | ) | ||
323 | )); | ||
324 | } | ||
325 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java new file mode 100644 index 00000000..d75d7f17 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java | |||
@@ -0,0 +1,157 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | import tools.refinery.store.query.term.ParameterDirection; | ||
11 | import tools.refinery.store.query.term.Variable; | ||
12 | import tools.refinery.store.query.view.AnySymbolView; | ||
13 | import tools.refinery.store.query.view.KeyOnlyView; | ||
14 | import tools.refinery.store.representation.Symbol; | ||
15 | |||
16 | import static org.hamcrest.MatcherAssert.assertThat; | ||
17 | import static org.hamcrest.Matchers.is; | ||
18 | import static tools.refinery.store.query.literal.Literals.not; | ||
19 | |||
20 | class DnfToDefinitionStringTest { | ||
21 | private static final Symbol<Boolean> person = Symbol.of("person", 1); | ||
22 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
23 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
24 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
25 | private static final NodeVariable p = Variable.of("p"); | ||
26 | private static final NodeVariable q = Variable.of("q"); | ||
27 | |||
28 | @Test | ||
29 | void noClausesTest() { | ||
30 | var dnf = Dnf.builder("Example").parameter(p).build(); | ||
31 | |||
32 | assertThat(dnf.toDefinitionString(), is(""" | ||
33 | pred Example(p) <-> | ||
34 | <no clauses>. | ||
35 | """)); | ||
36 | } | ||
37 | |||
38 | @Test | ||
39 | void noParametersTest() { | ||
40 | var dnf = Dnf.builder("Example").build(); | ||
41 | |||
42 | assertThat(dnf.toDefinitionString(), is(""" | ||
43 | pred Example() <-> | ||
44 | <no clauses>. | ||
45 | """)); | ||
46 | } | ||
47 | |||
48 | @Test | ||
49 | void emptyClauseTest() { | ||
50 | var dnf = Dnf.builder("Example").parameter(p, ParameterDirection.IN).clause().build(); | ||
51 | |||
52 | assertThat(dnf.toDefinitionString(), is(""" | ||
53 | pred Example(@In p) <-> | ||
54 | <empty>. | ||
55 | """)); | ||
56 | } | ||
57 | |||
58 | @Test | ||
59 | void relationViewPositiveTest() { | ||
60 | var dnf = Dnf.builder("Example").parameter(p).clause(friendView.call(p, q)).build(); | ||
61 | |||
62 | assertThat(dnf.toDefinitionString(), is(""" | ||
63 | pred Example(p) <-> | ||
64 | @RelationView("key") friend(p, q). | ||
65 | """)); | ||
66 | } | ||
67 | |||
68 | @Test | ||
69 | void relationViewNegativeTest() { | ||
70 | var dnf = Dnf.builder("Example") | ||
71 | .parameter(p, ParameterDirection.IN) | ||
72 | .clause(not(friendView.call(p, q))) | ||
73 | .build(); | ||
74 | |||
75 | assertThat(dnf.toDefinitionString(), is(""" | ||
76 | pred Example(@In p) <-> | ||
77 | !(@RelationView("key") friend(p, q)). | ||
78 | """)); | ||
79 | } | ||
80 | |||
81 | @Test | ||
82 | void relationViewTransitiveTest() { | ||
83 | var dnf = Dnf.builder("Example").parameter(p).clause(friendView.callTransitive(p, q)).build(); | ||
84 | |||
85 | assertThat(dnf.toDefinitionString(), is(""" | ||
86 | pred Example(p) <-> | ||
87 | @RelationView("key") friend+(p, q). | ||
88 | """)); | ||
89 | } | ||
90 | |||
91 | @Test | ||
92 | void multipleParametersTest() { | ||
93 | var dnf = Dnf.builder("Example").parameters(p, q).clause(friendView.call(p, q)).build(); | ||
94 | |||
95 | assertThat(dnf.toDefinitionString(), is(""" | ||
96 | pred Example(p, q) <-> | ||
97 | @RelationView("key") friend(p, q). | ||
98 | """)); | ||
99 | } | ||
100 | |||
101 | @Test | ||
102 | void multipleLiteralsTest() { | ||
103 | var dnf = Dnf.builder("Example") | ||
104 | .parameter(p) | ||
105 | .clause( | ||
106 | personView.call(p), | ||
107 | personView.call(q), | ||
108 | friendView.call(p, q) | ||
109 | ) | ||
110 | .build(); | ||
111 | |||
112 | assertThat(dnf.toDefinitionString(), is(""" | ||
113 | pred Example(p) <-> | ||
114 | @RelationView("key") person(p), | ||
115 | @RelationView("key") person(q), | ||
116 | @RelationView("key") friend(p, q). | ||
117 | """)); | ||
118 | } | ||
119 | |||
120 | @Test | ||
121 | void multipleClausesTest() { | ||
122 | var dnf = Dnf.builder("Example") | ||
123 | .parameter(p) | ||
124 | .clause(friendView.call(p, q)) | ||
125 | .clause(friendView.call(q, p)) | ||
126 | .build(); | ||
127 | |||
128 | assertThat(dnf.toDefinitionString(), is(""" | ||
129 | pred Example(p) <-> | ||
130 | @RelationView("key") friend(p, q) | ||
131 | ; | ||
132 | @RelationView("key") friend(q, p). | ||
133 | """)); | ||
134 | } | ||
135 | |||
136 | @Test | ||
137 | void dnfTest() { | ||
138 | var r = Variable.of("r"); | ||
139 | var s = Variable.of("s"); | ||
140 | var called = Dnf.builder("Called").parameters(r, s).clause(friendView.call(r, s)).build(); | ||
141 | var dnf = Dnf.builder("Example") | ||
142 | .parameter(p) | ||
143 | .clause( | ||
144 | personView.call(p), | ||
145 | personView.call(q), | ||
146 | not(called.call(p, q)) | ||
147 | ) | ||
148 | .build(); | ||
149 | |||
150 | assertThat(dnf.toDefinitionString(), is(""" | ||
151 | pred Example(p) <-> | ||
152 | @RelationView("key") person(p), | ||
153 | @RelationView("key") person(q), | ||
154 | !(@Dnf Called(p, q)). | ||
155 | """)); | ||
156 | } | ||
157 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java new file mode 100644 index 00000000..e22dbb21 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java | |||
@@ -0,0 +1,112 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | import tools.refinery.store.query.term.ParameterDirection; | ||
11 | import tools.refinery.store.query.term.Variable; | ||
12 | import tools.refinery.store.query.view.AnySymbolView; | ||
13 | import tools.refinery.store.query.view.KeyOnlyView; | ||
14 | import tools.refinery.store.representation.Symbol; | ||
15 | |||
16 | import java.util.List; | ||
17 | |||
18 | import static org.hamcrest.MatcherAssert.assertThat; | ||
19 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
20 | import static tools.refinery.store.query.literal.Literals.not; | ||
21 | import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo; | ||
22 | |||
23 | class TopologicalSortTest { | ||
24 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
25 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
26 | private static final Dnf example = Dnf.of("example", builder -> { | ||
27 | var a = builder.parameter("a", ParameterDirection.IN); | ||
28 | var b = builder.parameter("b", ParameterDirection.IN); | ||
29 | var c = builder.parameter("c", ParameterDirection.OUT); | ||
30 | var d = builder.parameter("d", ParameterDirection.OUT); | ||
31 | builder.clause( | ||
32 | friendView.call(a, b), | ||
33 | friendView.call(b, c), | ||
34 | friendView.call(c, d) | ||
35 | ); | ||
36 | }); | ||
37 | private static final NodeVariable p = Variable.of("p"); | ||
38 | private static final NodeVariable q = Variable.of("q"); | ||
39 | private static final NodeVariable r = Variable.of("r"); | ||
40 | private static final NodeVariable s = Variable.of("s"); | ||
41 | private static final NodeVariable t = Variable.of("t"); | ||
42 | |||
43 | @Test | ||
44 | void topologicalSortTest() { | ||
45 | var actual = Dnf.builder("Actual") | ||
46 | .parameter(p, ParameterDirection.IN) | ||
47 | .parameter(q, ParameterDirection.OUT) | ||
48 | .clause( | ||
49 | not(friendView.call(p, q)), | ||
50 | example.call(p, q, r, s), | ||
51 | example.call(r, t, q, s), | ||
52 | friendView.call(r, t) | ||
53 | ) | ||
54 | .build(); | ||
55 | |||
56 | assertThat(actual, structurallyEqualTo( | ||
57 | List.of( | ||
58 | new SymbolicParameter(p, ParameterDirection.IN), | ||
59 | new SymbolicParameter(q, ParameterDirection.OUT) | ||
60 | ), | ||
61 | List.of( | ||
62 | List.of( | ||
63 | friendView.call(r, t), | ||
64 | example.call(r, t, q, s), | ||
65 | not(friendView.call(p, q)), | ||
66 | example.call(p, q, r, s) | ||
67 | ) | ||
68 | ) | ||
69 | )); | ||
70 | } | ||
71 | |||
72 | @Test | ||
73 | void missingInputTest() { | ||
74 | var builder = Dnf.builder("Actual") | ||
75 | .parameter(p, ParameterDirection.OUT) | ||
76 | .parameter(q, ParameterDirection.OUT) | ||
77 | .clause( | ||
78 | not(friendView.call(p, q)), | ||
79 | example.call(p, q, r, s), | ||
80 | example.call(r, t, q, s), | ||
81 | friendView.call(r, t) | ||
82 | ); | ||
83 | assertThrows(IllegalArgumentException.class, builder::build); | ||
84 | } | ||
85 | |||
86 | @Test | ||
87 | void missingVariableTest() { | ||
88 | var builder = Dnf.builder("Actual") | ||
89 | .parameter(p, ParameterDirection.IN) | ||
90 | .parameter(q, ParameterDirection.OUT) | ||
91 | .clause( | ||
92 | not(friendView.call(p, q)), | ||
93 | example.call(p, q, r, s), | ||
94 | example.call(r, t, q, s) | ||
95 | ); | ||
96 | assertThrows(IllegalArgumentException.class, builder::build); | ||
97 | } | ||
98 | |||
99 | @Test | ||
100 | void circularDependencyTest() { | ||
101 | var builder = Dnf.builder("Actual") | ||
102 | .parameter(p, ParameterDirection.IN) | ||
103 | .parameter(q, ParameterDirection.OUT) | ||
104 | .clause( | ||
105 | not(friendView.call(p, q)), | ||
106 | example.call(p, q, r, s), | ||
107 | example.call(r, t, q, s), | ||
108 | example.call(p, q, r, t) | ||
109 | ); | ||
110 | assertThrows(IllegalArgumentException.class, builder::build); | ||
111 | } | ||
112 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java new file mode 100644 index 00000000..c52d26b2 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java | |||
@@ -0,0 +1,428 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import org.junit.jupiter.params.ParameterizedTest; | ||
9 | import org.junit.jupiter.params.provider.Arguments; | ||
10 | import org.junit.jupiter.params.provider.MethodSource; | ||
11 | import tools.refinery.store.query.literal.BooleanLiteral; | ||
12 | import tools.refinery.store.query.literal.Literal; | ||
13 | import tools.refinery.store.query.term.DataVariable; | ||
14 | import tools.refinery.store.query.term.NodeVariable; | ||
15 | import tools.refinery.store.query.term.ParameterDirection; | ||
16 | import tools.refinery.store.query.term.Variable; | ||
17 | import tools.refinery.store.query.view.AnySymbolView; | ||
18 | import tools.refinery.store.query.view.FunctionView; | ||
19 | import tools.refinery.store.query.view.KeyOnlyView; | ||
20 | import tools.refinery.store.representation.Symbol; | ||
21 | |||
22 | import java.util.ArrayList; | ||
23 | import java.util.List; | ||
24 | import java.util.stream.Stream; | ||
25 | |||
26 | import static org.hamcrest.MatcherAssert.assertThat; | ||
27 | import static org.hamcrest.Matchers.hasItem; | ||
28 | import static org.hamcrest.Matchers.not; | ||
29 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; | ||
30 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
31 | import static tools.refinery.store.query.literal.Literals.assume; | ||
32 | import static tools.refinery.store.query.literal.Literals.not; | ||
33 | import static tools.refinery.store.query.term.int_.IntTerms.*; | ||
34 | |||
35 | class VariableDirectionTest { | ||
36 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); | ||
37 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
38 | private static final Symbol<Integer> age = Symbol.of("age", 1, Integer.class); | ||
39 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
40 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
41 | private static final FunctionView<Integer> ageView = new FunctionView<>(age); | ||
42 | private static final NodeVariable p = Variable.of("p"); | ||
43 | private static final NodeVariable q = Variable.of("q"); | ||
44 | private static final DataVariable<Integer> x = Variable.of("x", Integer.class); | ||
45 | private static final DataVariable<Integer> y = Variable.of("y", Integer.class); | ||
46 | private static final DataVariable<Integer> z = Variable.of("z", Integer.class); | ||
47 | |||
48 | @ParameterizedTest | ||
49 | @MethodSource("clausesWithVariableInput") | ||
50 | void unboundOutVariableTest(List<? extends Literal> clause) { | ||
51 | var builder = Dnf.builder().parameter(p, ParameterDirection.OUT).clause(clause); | ||
52 | assertThrows(IllegalArgumentException.class, builder::build); | ||
53 | } | ||
54 | |||
55 | @ParameterizedTest | ||
56 | @MethodSource("clausesWithVariableInput") | ||
57 | void unboundInVariableTest(List<? extends Literal> clause) { | ||
58 | var builder = Dnf.builder().parameter(p, ParameterDirection.IN).clause(clause); | ||
59 | var dnf = assertDoesNotThrow(builder::build); | ||
60 | var clauses = dnf.getClauses(); | ||
61 | if (clauses.size() > 0) { | ||
62 | assertThat(clauses.get(0).positiveVariables(), hasItem(p)); | ||
63 | } | ||
64 | } | ||
65 | |||
66 | @ParameterizedTest | ||
67 | @MethodSource("clausesWithVariableInput") | ||
68 | void boundPrivateVariableTest(List<? extends Literal> clause) { | ||
69 | var clauseWithBinding = new ArrayList<Literal>(clause); | ||
70 | clauseWithBinding.add(personView.call(p)); | ||
71 | var builder = Dnf.builder().clause(clauseWithBinding); | ||
72 | var dnf = assertDoesNotThrow(builder::build); | ||
73 | var clauses = dnf.getClauses(); | ||
74 | if (clauses.size() > 0) { | ||
75 | assertThat(clauses.get(0).positiveVariables(), hasItem(p)); | ||
76 | } | ||
77 | } | ||
78 | |||
79 | static Stream<Arguments> clausesWithVariableInput() { | ||
80 | return Stream.concat( | ||
81 | clausesNotBindingVariable(), | ||
82 | literalToClauseArgumentStream(literalsWithRequiredVariableInput()) | ||
83 | ); | ||
84 | } | ||
85 | |||
86 | @ParameterizedTest | ||
87 | @MethodSource("clausesNotBindingVariable") | ||
88 | void unboundPrivateVariableTest(List<? extends Literal> clause) { | ||
89 | var builder = Dnf.builder().clause(clause); | ||
90 | var dnf = assertDoesNotThrow(builder::build); | ||
91 | var clauses = dnf.getClauses(); | ||
92 | if (clauses.size() > 0) { | ||
93 | assertThat(clauses.get(0).positiveVariables(), not(hasItem(p))); | ||
94 | } | ||
95 | } | ||
96 | |||
97 | @ParameterizedTest | ||
98 | @MethodSource("clausesNotBindingVariable") | ||
99 | void unboundByEquivalencePrivateVariableTest(List<? extends Literal> clause) { | ||
100 | var r = Variable.of("r"); | ||
101 | var clauseWithEquivalence = new ArrayList<Literal>(clause); | ||
102 | clauseWithEquivalence.add(r.isEquivalent(p)); | ||
103 | var builder = Dnf.builder().clause(clauseWithEquivalence); | ||
104 | assertThrows(IllegalArgumentException.class, builder::build); | ||
105 | } | ||
106 | |||
107 | static Stream<Arguments> clausesNotBindingVariable() { | ||
108 | return Stream.concat( | ||
109 | Stream.of( | ||
110 | Arguments.of(List.of()), | ||
111 | Arguments.of(List.of(BooleanLiteral.TRUE)), | ||
112 | Arguments.of(List.of(BooleanLiteral.FALSE)) | ||
113 | ), | ||
114 | literalToClauseArgumentStream(literalsWithPrivateVariable()) | ||
115 | ); | ||
116 | } | ||
117 | |||
118 | @ParameterizedTest | ||
119 | @MethodSource("literalsWithPrivateVariable") | ||
120 | void unboundTwicePrivateVariableTest(Literal literal) { | ||
121 | var builder = Dnf.builder().clause(not(personView.call(p)), literal); | ||
122 | assertThrows(IllegalArgumentException.class, builder::build); | ||
123 | } | ||
124 | |||
125 | @ParameterizedTest | ||
126 | @MethodSource("literalsWithPrivateVariable") | ||
127 | void unboundTwiceByEquivalencePrivateVariableTest(Literal literal) { | ||
128 | var r = Variable.of("r"); | ||
129 | var builder = Dnf.builder().clause(not(personView.call(r)), r.isEquivalent(p), literal); | ||
130 | assertThrows(IllegalArgumentException.class, builder::build); | ||
131 | } | ||
132 | |||
133 | static Stream<Arguments> literalsWithPrivateVariable() { | ||
134 | var dnfWithOutput = Dnf.builder("WithOutput") | ||
135 | .parameter(p, ParameterDirection.OUT) | ||
136 | .parameter(q, ParameterDirection.OUT) | ||
137 | .clause(friendView.call(p, q)) | ||
138 | .build(); | ||
139 | var dnfWithOutputToAggregate = Dnf.builder("WithOutputToAggregate") | ||
140 | .parameter(p, ParameterDirection.OUT) | ||
141 | .parameter(q, ParameterDirection.OUT) | ||
142 | .parameter(x, ParameterDirection.OUT) | ||
143 | .clause( | ||
144 | friendView.call(p, q), | ||
145 | ageView.call(q, x) | ||
146 | ) | ||
147 | .build(); | ||
148 | |||
149 | return Stream.of( | ||
150 | Arguments.of(not(friendView.call(p, q))), | ||
151 | Arguments.of(y.assign(friendView.count(p, q))), | ||
152 | Arguments.of(y.assign(ageView.aggregate(INT_SUM, p))), | ||
153 | Arguments.of(not(dnfWithOutput.call(p, q))), | ||
154 | Arguments.of(y.assign(dnfWithOutput.count(p, q))), | ||
155 | Arguments.of(y.assign(dnfWithOutputToAggregate.aggregateBy(z, INT_SUM, p, q, z))) | ||
156 | ); | ||
157 | } | ||
158 | |||
159 | @ParameterizedTest | ||
160 | @MethodSource("literalsWithRequiredVariableInput") | ||
161 | void unboundPrivateVariableTest(Literal literal) { | ||
162 | var builder = Dnf.builder().clause(literal); | ||
163 | assertThrows(IllegalArgumentException.class, builder::build); | ||
164 | } | ||
165 | |||
166 | @ParameterizedTest | ||
167 | @MethodSource("literalsWithRequiredVariableInput") | ||
168 | void boundPrivateVariableInputTest(Literal literal) { | ||
169 | var builder = Dnf.builder().clause(personView.call(p), literal); | ||
170 | var dnf = assertDoesNotThrow(builder::build); | ||
171 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(p)); | ||
172 | } | ||
173 | |||
174 | static Stream<Arguments> literalsWithRequiredVariableInput() { | ||
175 | var dnfWithInput = Dnf.builder("WithInput") | ||
176 | .parameter(p, ParameterDirection.IN) | ||
177 | .parameter(q, ParameterDirection.OUT) | ||
178 | .clause(friendView.call(p, q)).build(); | ||
179 | var dnfWithInputToAggregate = Dnf.builder("WithInputToAggregate") | ||
180 | .parameter(p, ParameterDirection.IN) | ||
181 | .parameter(q, ParameterDirection.OUT) | ||
182 | .parameter(x, ParameterDirection.OUT) | ||
183 | .clause( | ||
184 | friendView.call(p, q), | ||
185 | ageView.call(q, x) | ||
186 | ).build(); | ||
187 | |||
188 | return Stream.of( | ||
189 | Arguments.of(dnfWithInput.call(p, q)), | ||
190 | Arguments.of(dnfWithInput.call(p, p)), | ||
191 | Arguments.of(not(dnfWithInput.call(p, q))), | ||
192 | Arguments.of(not(dnfWithInput.call(p, p))), | ||
193 | Arguments.of(y.assign(dnfWithInput.count(p, q))), | ||
194 | Arguments.of(y.assign(dnfWithInput.count(p, p))), | ||
195 | Arguments.of(y.assign(dnfWithInputToAggregate.aggregateBy(z, INT_SUM, p, q, z))), | ||
196 | Arguments.of(y.assign(dnfWithInputToAggregate.aggregateBy(z, INT_SUM, p, p, z))) | ||
197 | ); | ||
198 | } | ||
199 | |||
200 | @ParameterizedTest | ||
201 | @MethodSource("literalsWithVariableOutput") | ||
202 | void boundParameterTest(Literal literal) { | ||
203 | var builder = Dnf.builder().parameter(p, ParameterDirection.OUT).clause(literal); | ||
204 | var dnf = assertDoesNotThrow(builder::build); | ||
205 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(p)); | ||
206 | } | ||
207 | |||
208 | @ParameterizedTest | ||
209 | @MethodSource("literalsWithVariableOutput") | ||
210 | void boundTwiceParameterTest(Literal literal) { | ||
211 | var builder = Dnf.builder().parameter(p, ParameterDirection.IN).clause(literal); | ||
212 | var dnf = assertDoesNotThrow(builder::build); | ||
213 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(p)); | ||
214 | } | ||
215 | |||
216 | @ParameterizedTest | ||
217 | @MethodSource("literalsWithVariableOutput") | ||
218 | void boundPrivateVariableOutputTest(Literal literal) { | ||
219 | var dnfWithInput = Dnf.builder("WithInput") | ||
220 | .parameter(p, ParameterDirection.IN) | ||
221 | .clause(personView.call(p)) | ||
222 | .build(); | ||
223 | var builder = Dnf.builder().clause(dnfWithInput.call(p), literal); | ||
224 | var dnf = assertDoesNotThrow(builder::build); | ||
225 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(p)); | ||
226 | } | ||
227 | |||
228 | @ParameterizedTest | ||
229 | @MethodSource("literalsWithVariableOutput") | ||
230 | void boundTwicePrivateVariableOutputTest(Literal literal) { | ||
231 | var builder = Dnf.builder().clause(personView.call(p), literal); | ||
232 | var dnf = assertDoesNotThrow(builder::build); | ||
233 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(p)); | ||
234 | } | ||
235 | |||
236 | static Stream<Arguments> literalsWithVariableOutput() { | ||
237 | var dnfWithOutput = Dnf.builder("WithOutput") | ||
238 | .parameter(p, ParameterDirection.OUT) | ||
239 | .parameter(q, ParameterDirection.OUT) | ||
240 | .clause(friendView.call(p, q)) | ||
241 | .build(); | ||
242 | |||
243 | return Stream.of( | ||
244 | Arguments.of(friendView.call(p, q)), | ||
245 | Arguments.of(dnfWithOutput.call(p, q)) | ||
246 | ); | ||
247 | } | ||
248 | |||
249 | @ParameterizedTest | ||
250 | @MethodSource("clausesWithDataVariableInput") | ||
251 | void unboundOutDataVariableTest(List<? extends Literal> clause) { | ||
252 | var builder = Dnf.builder().parameter(x, ParameterDirection.OUT).clause(clause); | ||
253 | assertThrows(IllegalArgumentException.class, builder::build); | ||
254 | } | ||
255 | |||
256 | @ParameterizedTest | ||
257 | @MethodSource("clausesWithDataVariableInput") | ||
258 | void unboundInDataVariableTest(List<? extends Literal> clause) { | ||
259 | var builder = Dnf.builder().parameter(x, ParameterDirection.IN).clause(clause); | ||
260 | var dnf = assertDoesNotThrow(builder::build); | ||
261 | var clauses = dnf.getClauses(); | ||
262 | if (clauses.size() > 0) { | ||
263 | assertThat(clauses.get(0).positiveVariables(), hasItem(x)); | ||
264 | } | ||
265 | } | ||
266 | |||
267 | @ParameterizedTest | ||
268 | @MethodSource("clausesWithDataVariableInput") | ||
269 | void boundPrivateDataVariableTest(List<? extends Literal> clause) { | ||
270 | var clauseWithBinding = new ArrayList<Literal>(clause); | ||
271 | clauseWithBinding.add(x.assign(constant(27))); | ||
272 | var builder = Dnf.builder().clause(clauseWithBinding); | ||
273 | var dnf = assertDoesNotThrow(builder::build); | ||
274 | var clauses = dnf.getClauses(); | ||
275 | if (clauses.size() > 0) { | ||
276 | assertThat(clauses.get(0).positiveVariables(), hasItem(x)); | ||
277 | } | ||
278 | } | ||
279 | |||
280 | static Stream<Arguments> clausesWithDataVariableInput() { | ||
281 | return Stream.concat( | ||
282 | clausesNotBindingDataVariable(), | ||
283 | literalToClauseArgumentStream(literalsWithRequiredDataVariableInput()) | ||
284 | ); | ||
285 | } | ||
286 | |||
287 | @ParameterizedTest | ||
288 | @MethodSource("clausesNotBindingDataVariable") | ||
289 | void unboundPrivateDataVariableTest(List<? extends Literal> clause) { | ||
290 | var builder = Dnf.builder().clause(clause); | ||
291 | var dnf = assertDoesNotThrow(builder::build); | ||
292 | var clauses = dnf.getClauses(); | ||
293 | if (clauses.size() > 0) { | ||
294 | assertThat(clauses.get(0).positiveVariables(), not(hasItem(x))); | ||
295 | } | ||
296 | } | ||
297 | |||
298 | static Stream<Arguments> clausesNotBindingDataVariable() { | ||
299 | return Stream.concat( | ||
300 | Stream.of( | ||
301 | Arguments.of(List.of()), | ||
302 | Arguments.of(List.of(BooleanLiteral.TRUE)), | ||
303 | Arguments.of(List.of(BooleanLiteral.FALSE)) | ||
304 | ), | ||
305 | literalToClauseArgumentStream(literalsWithPrivateDataVariable()) | ||
306 | ); | ||
307 | } | ||
308 | |||
309 | @ParameterizedTest | ||
310 | @MethodSource("literalsWithPrivateDataVariable") | ||
311 | void unboundTwicePrivateDataVariableTest(Literal literal) { | ||
312 | var builder = Dnf.builder().clause(not(ageView.call(p, x)), literal); | ||
313 | assertThrows(IllegalArgumentException.class, builder::build); | ||
314 | } | ||
315 | |||
316 | static Stream<Arguments> literalsWithPrivateDataVariable() { | ||
317 | var dnfWithOutput = Dnf.builder("WithDataOutput") | ||
318 | .parameter(y, ParameterDirection.OUT) | ||
319 | .parameter(q, ParameterDirection.OUT) | ||
320 | .clause(ageView.call(q, y)) | ||
321 | .build(); | ||
322 | |||
323 | return Stream.of( | ||
324 | Arguments.of(not(ageView.call(q, x))), | ||
325 | Arguments.of(y.assign(ageView.count(q, x))), | ||
326 | Arguments.of(not(dnfWithOutput.call(x, q))) | ||
327 | ); | ||
328 | } | ||
329 | |||
330 | @ParameterizedTest | ||
331 | @MethodSource("literalsWithRequiredDataVariableInput") | ||
332 | void unboundPrivateDataVariableTest(Literal literal) { | ||
333 | var builder = Dnf.builder().clause(literal); | ||
334 | assertThrows(IllegalArgumentException.class, builder::build); | ||
335 | } | ||
336 | |||
337 | static Stream<Arguments> literalsWithRequiredDataVariableInput() { | ||
338 | var dnfWithInput = Dnf.builder("WithDataInput") | ||
339 | .parameter(y, ParameterDirection.IN) | ||
340 | .parameter(q, ParameterDirection.OUT) | ||
341 | .clause(ageView.call(q, x)) | ||
342 | .build(); | ||
343 | // We are passing {@code y} to the parameter named {@code right} of {@code greaterEq}. | ||
344 | @SuppressWarnings("SuspiciousNameCombination") | ||
345 | var dnfWithInputToAggregate = Dnf.builder("WithDataInputToAggregate") | ||
346 | .parameter(y, ParameterDirection.IN) | ||
347 | .parameter(q, ParameterDirection.OUT) | ||
348 | .parameter(x, ParameterDirection.OUT) | ||
349 | .clause( | ||
350 | friendView.call(p, q), | ||
351 | ageView.call(q, x), | ||
352 | assume(greaterEq(x, y)) | ||
353 | ) | ||
354 | .build(); | ||
355 | |||
356 | return Stream.of( | ||
357 | Arguments.of(dnfWithInput.call(x, q)), | ||
358 | Arguments.of(not(dnfWithInput.call(x, q))), | ||
359 | Arguments.of(y.assign(dnfWithInput.count(x, q))), | ||
360 | Arguments.of(y.assign(dnfWithInputToAggregate.aggregateBy(z, INT_SUM, x, q, z))) | ||
361 | ); | ||
362 | } | ||
363 | |||
364 | @ParameterizedTest | ||
365 | @MethodSource("literalsWithDataVariableOutput") | ||
366 | void boundDataParameterTest(Literal literal) { | ||
367 | var builder = Dnf.builder().parameter(x, ParameterDirection.OUT).clause(literal); | ||
368 | var dnf = assertDoesNotThrow(builder::build); | ||
369 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(x)); | ||
370 | } | ||
371 | |||
372 | @ParameterizedTest | ||
373 | @MethodSource("literalsWithDataVariableOutput") | ||
374 | void boundTwiceDataParameterTest(Literal literal) { | ||
375 | var builder = Dnf.builder().parameter(x, ParameterDirection.IN).clause(literal); | ||
376 | assertThrows(IllegalArgumentException.class, builder::build); | ||
377 | } | ||
378 | |||
379 | @ParameterizedTest | ||
380 | @MethodSource("literalsWithDataVariableOutput") | ||
381 | void boundPrivateDataVariableOutputTest(Literal literal) { | ||
382 | var dnfWithInput = Dnf.builder("WithInput") | ||
383 | .parameter(x, ParameterDirection.IN) | ||
384 | .clause(assume(greaterEq(x, constant(24)))) | ||
385 | .build(); | ||
386 | var builder = Dnf.builder().clause(dnfWithInput.call(x), literal); | ||
387 | var dnf = assertDoesNotThrow(builder::build); | ||
388 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(x)); | ||
389 | } | ||
390 | |||
391 | @ParameterizedTest | ||
392 | @MethodSource("literalsWithDataVariableOutput") | ||
393 | void boundTwicePrivateDataVariableOutputTest(Literal literal) { | ||
394 | var builder = Dnf.builder().clause(x.assign(constant(27)), literal); | ||
395 | assertThrows(IllegalArgumentException.class, builder::build); | ||
396 | } | ||
397 | |||
398 | static Stream<Arguments> literalsWithDataVariableOutput() { | ||
399 | var dnfWithOutput = Dnf.builder("WithOutput") | ||
400 | .parameter(q, ParameterDirection.OUT) | ||
401 | .clause(personView.call(q)) | ||
402 | .build(); | ||
403 | var dnfWithDataOutput = Dnf.builder("WithDataOutput") | ||
404 | .parameter(y, ParameterDirection.OUT) | ||
405 | .parameter(q, ParameterDirection.OUT) | ||
406 | .clause(ageView.call(q, y)) | ||
407 | .build(); | ||
408 | var dnfWithOutputToAggregate = Dnf.builder("WithDataOutputToAggregate") | ||
409 | .parameter(q, ParameterDirection.OUT) | ||
410 | .parameter(y, ParameterDirection.OUT) | ||
411 | .clause(ageView.call(q, y)) | ||
412 | .build(); | ||
413 | |||
414 | return Stream.of( | ||
415 | Arguments.of(x.assign(constant(24))), | ||
416 | Arguments.of(ageView.call(q, x)), | ||
417 | Arguments.of(x.assign(personView.count(q))), | ||
418 | Arguments.of(x.assign(ageView.aggregate(INT_SUM, q))), | ||
419 | Arguments.of(dnfWithDataOutput.call(x, q)), | ||
420 | Arguments.of(x.assign(dnfWithOutput.count(q))), | ||
421 | Arguments.of(x.assign(dnfWithOutputToAggregate.aggregateBy(z, INT_SUM, q, z))) | ||
422 | ); | ||
423 | } | ||
424 | |||
425 | private static Stream<Arguments> literalToClauseArgumentStream(Stream<Arguments> literalArgumentsStream) { | ||
426 | return literalArgumentsStream.map(arguments -> Arguments.of(List.of(arguments.get()[0]))); | ||
427 | } | ||
428 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java new file mode 100644 index 00000000..35910e08 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java | |||
@@ -0,0 +1,88 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.Constraint; | ||
10 | import tools.refinery.store.query.dnf.Dnf; | ||
11 | import tools.refinery.store.query.term.*; | ||
12 | |||
13 | import java.util.List; | ||
14 | import java.util.Set; | ||
15 | |||
16 | import static org.hamcrest.MatcherAssert.assertThat; | ||
17 | import static org.hamcrest.Matchers.containsInAnyOrder; | ||
18 | import static org.hamcrest.Matchers.empty; | ||
19 | import static org.junit.jupiter.api.Assertions.assertAll; | ||
20 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
21 | import static tools.refinery.store.query.literal.Literals.not; | ||
22 | import static tools.refinery.store.query.term.int_.IntTerms.INT_SUM; | ||
23 | import static tools.refinery.store.query.term.int_.IntTerms.constant; | ||
24 | |||
25 | class AggregationLiteralTest { | ||
26 | private static final NodeVariable p = Variable.of("p"); | ||
27 | private static final DataVariable<Integer> x = Variable.of("x", Integer.class); | ||
28 | private static final DataVariable<Integer> y = Variable.of("y", Integer.class); | ||
29 | private static final DataVariable<Integer> z = Variable.of("z", Integer.class); | ||
30 | private static final Constraint fakeConstraint = new Constraint() { | ||
31 | @Override | ||
32 | public String name() { | ||
33 | return getClass().getName(); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public List<Parameter> getParameters() { | ||
38 | return List.of( | ||
39 | new Parameter(null, ParameterDirection.OUT), | ||
40 | new Parameter(Integer.class, ParameterDirection.OUT) | ||
41 | ); | ||
42 | } | ||
43 | }; | ||
44 | |||
45 | @Test | ||
46 | void parameterDirectionTest() { | ||
47 | var literal = x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y)); | ||
48 | assertAll( | ||
49 | () -> assertThat(literal.getOutputVariables(), containsInAnyOrder(x)), | ||
50 | () -> assertThat(literal.getInputVariables(Set.of()), empty()), | ||
51 | () -> assertThat(literal.getInputVariables(Set.of(p)), containsInAnyOrder(p)), | ||
52 | () -> assertThat(literal.getPrivateVariables(Set.of()), containsInAnyOrder(p, y)), | ||
53 | () -> assertThat(literal.getPrivateVariables(Set.of(p)), containsInAnyOrder(y)) | ||
54 | ); | ||
55 | } | ||
56 | |||
57 | @Test | ||
58 | void missingAggregationVariableTest() { | ||
59 | var aggregation = fakeConstraint.aggregateBy(y, INT_SUM, p, z); | ||
60 | assertThrows(IllegalArgumentException.class, () -> x.assign(aggregation)); | ||
61 | } | ||
62 | |||
63 | @Test | ||
64 | void circularAggregationVariableTest() { | ||
65 | var aggregation = fakeConstraint.aggregateBy(x, INT_SUM, p, x); | ||
66 | assertThrows(IllegalArgumentException.class, () -> x.assign(aggregation)); | ||
67 | } | ||
68 | |||
69 | @Test | ||
70 | void unboundTwiceVariableTest() { | ||
71 | var builder = Dnf.builder() | ||
72 | .clause( | ||
73 | not(fakeConstraint.call(p, y)), | ||
74 | x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y)) | ||
75 | ); | ||
76 | assertThrows(IllegalArgumentException.class, builder::build); | ||
77 | } | ||
78 | |||
79 | @Test | ||
80 | void unboundBoundVariableTest() { | ||
81 | var builder = Dnf.builder() | ||
82 | .clause( | ||
83 | y.assign(constant(27)), | ||
84 | x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y)) | ||
85 | ); | ||
86 | assertThrows(IllegalArgumentException.class, builder::build); | ||
87 | } | ||
88 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/CallLiteralTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/CallLiteralTest.java new file mode 100644 index 00000000..a01c6586 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/CallLiteralTest.java | |||
@@ -0,0 +1,94 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.Constraint; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | import tools.refinery.store.query.term.Parameter; | ||
12 | import tools.refinery.store.query.term.ParameterDirection; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | |||
15 | import java.util.List; | ||
16 | import java.util.Set; | ||
17 | |||
18 | import static org.hamcrest.MatcherAssert.assertThat; | ||
19 | import static org.hamcrest.Matchers.containsInAnyOrder; | ||
20 | import static org.hamcrest.Matchers.empty; | ||
21 | import static org.junit.jupiter.api.Assertions.assertAll; | ||
22 | import static tools.refinery.store.query.literal.Literals.not; | ||
23 | |||
24 | class CallLiteralTest { | ||
25 | private static final NodeVariable p = Variable.of("p"); | ||
26 | private static final NodeVariable q = Variable.of("q"); | ||
27 | private static final NodeVariable r = Variable.of("r"); | ||
28 | private static final NodeVariable s = Variable.of("s"); | ||
29 | |||
30 | private static final Constraint fakeConstraint = new Constraint() { | ||
31 | @Override | ||
32 | public String name() { | ||
33 | return getClass().getName(); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public List<Parameter> getParameters() { | ||
38 | return List.of( | ||
39 | new Parameter(null, ParameterDirection.IN), | ||
40 | new Parameter(null, ParameterDirection.IN), | ||
41 | new Parameter(null, ParameterDirection.OUT), | ||
42 | new Parameter(null, ParameterDirection.OUT) | ||
43 | ); | ||
44 | } | ||
45 | }; | ||
46 | |||
47 | @Test | ||
48 | void notRepeatedPositiveDirectionTest() { | ||
49 | var literal = fakeConstraint.call(p, q, r, s); | ||
50 | assertAll( | ||
51 | () -> assertThat(literal.getOutputVariables(), containsInAnyOrder(r, s)), | ||
52 | () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p, q)), | ||
53 | () -> assertThat(literal.getInputVariables(Set.of(p, q, r)), containsInAnyOrder(p, q)), | ||
54 | () -> assertThat(literal.getPrivateVariables(Set.of()), empty()), | ||
55 | () -> assertThat(literal.getPrivateVariables(Set.of(p, q, r)), empty()) | ||
56 | ); | ||
57 | } | ||
58 | |||
59 | @Test | ||
60 | void notRepeatedNegativeDirectionTest() { | ||
61 | var literal = not(fakeConstraint.call(p, q, r, s)); | ||
62 | assertAll( | ||
63 | () -> assertThat(literal.getOutputVariables(), empty()), | ||
64 | () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p, q)), | ||
65 | () -> assertThat(literal.getInputVariables(Set.of(p, q, r)), containsInAnyOrder(p, q, r)), | ||
66 | () -> assertThat(literal.getPrivateVariables(Set.of()), containsInAnyOrder(r, s)), | ||
67 | () -> assertThat(literal.getPrivateVariables(Set.of(p, q, r)), containsInAnyOrder(s)) | ||
68 | ); | ||
69 | } | ||
70 | |||
71 | @Test | ||
72 | void repeatedPositiveDirectionTest() { | ||
73 | var literal = fakeConstraint.call(p, p, q, q); | ||
74 | assertAll( | ||
75 | () -> assertThat(literal.getOutputVariables(), containsInAnyOrder(q)), | ||
76 | () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p)), | ||
77 | () -> assertThat(literal.getInputVariables(Set.of(p, q)), containsInAnyOrder(p)), | ||
78 | () -> assertThat(literal.getPrivateVariables(Set.of()), empty()), | ||
79 | () -> assertThat(literal.getPrivateVariables(Set.of(p, q)), empty()) | ||
80 | ); | ||
81 | } | ||
82 | |||
83 | @Test | ||
84 | void repeatedNegativeDirectionTest() { | ||
85 | var literal = not(fakeConstraint.call(p, p, q, q)); | ||
86 | assertAll( | ||
87 | () -> assertThat(literal.getOutputVariables(), empty()), | ||
88 | () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p)), | ||
89 | () -> assertThat(literal.getInputVariables(Set.of(p, q)), containsInAnyOrder(p, q)), | ||
90 | () -> assertThat(literal.getPrivateVariables(Set.of()), containsInAnyOrder(q)), | ||
91 | () -> assertThat(literal.getPrivateVariables(Set.of(p, q)), empty()) | ||
92 | ); | ||
93 | } | ||
94 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/TermSubstitutionTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/TermSubstitutionTest.java new file mode 100644 index 00000000..1cbc101a --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/TermSubstitutionTest.java | |||
@@ -0,0 +1,97 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import org.junit.jupiter.api.Assertions; | ||
9 | import org.junit.jupiter.params.ParameterizedTest; | ||
10 | import org.junit.jupiter.params.provider.Arguments; | ||
11 | import org.junit.jupiter.params.provider.MethodSource; | ||
12 | import tools.refinery.store.query.dnf.Dnf; | ||
13 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
14 | import tools.refinery.store.query.substitution.Substitution; | ||
15 | import tools.refinery.store.query.term.bool.BoolTerms; | ||
16 | import tools.refinery.store.query.term.int_.IntTerms; | ||
17 | import tools.refinery.store.query.term.real.RealTerms; | ||
18 | import tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms; | ||
19 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
20 | |||
21 | import java.util.List; | ||
22 | import java.util.stream.Stream; | ||
23 | |||
24 | class TermSubstitutionTest { | ||
25 | private final static DataVariable<Integer> intA = Variable.of("intA", Integer.class); | ||
26 | private final static DataVariable<Integer> intB = Variable.of("intB", Integer.class); | ||
27 | private final static DataVariable<Double> realA = Variable.of("realA", Double.class); | ||
28 | private final static DataVariable<Double> realB = Variable.of("realB", Double.class); | ||
29 | private final static DataVariable<Boolean> boolA = Variable.of("boolA", Boolean.class); | ||
30 | private final static DataVariable<Boolean> boolB = Variable.of("boolB", Boolean.class); | ||
31 | private final static DataVariable<UpperCardinality> upperCardinalityA = Variable.of("upperCardinalityA", | ||
32 | UpperCardinality.class); | ||
33 | private final static DataVariable<UpperCardinality> upperCardinalityB = Variable.of("upperCardinalityB", | ||
34 | UpperCardinality.class); | ||
35 | private final static Substitution substitution = Substitution.builder() | ||
36 | .put(intA, intB) | ||
37 | .put(intB, intA) | ||
38 | .put(realA, realB) | ||
39 | .put(realB, realA) | ||
40 | .put(boolA, boolB) | ||
41 | .put(boolB, boolA) | ||
42 | .put(upperCardinalityA, upperCardinalityB) | ||
43 | .put(upperCardinalityB, upperCardinalityA) | ||
44 | .build(); | ||
45 | |||
46 | @ParameterizedTest | ||
47 | @MethodSource | ||
48 | void substitutionTest(AnyTerm term) { | ||
49 | var substitutedTerm1 = term.substitute(substitution); | ||
50 | Assertions.assertNotEquals(term, substitutedTerm1, "Original term is not equal to substituted term"); | ||
51 | var helper = new LiteralEqualityHelper(Dnf::equals, List.of(), List.of()); | ||
52 | Assertions.assertTrue(term.equalsWithSubstitution(helper, substitutedTerm1), "Terms are equal by helper"); | ||
53 | // The {@link #substitution} is its own inverse. | ||
54 | var substitutedTerm2 = substitutedTerm1.substitute(substitution); | ||
55 | Assertions.assertEquals(term, substitutedTerm2, "Original term is not equal to back-substituted term"); | ||
56 | } | ||
57 | |||
58 | static Stream<Arguments> substitutionTest() { | ||
59 | return Stream.of( | ||
60 | Arguments.of(IntTerms.plus(intA)), | ||
61 | Arguments.of(IntTerms.minus(intA)), | ||
62 | Arguments.of(IntTerms.add(intA, intB)), | ||
63 | Arguments.of(IntTerms.sub(intA, intB)), | ||
64 | Arguments.of(IntTerms.mul(intA, intB)), | ||
65 | Arguments.of(IntTerms.div(intA, intB)), | ||
66 | Arguments.of(IntTerms.pow(intA, intB)), | ||
67 | Arguments.of(IntTerms.min(intA, intB)), | ||
68 | Arguments.of(IntTerms.max(intA, intB)), | ||
69 | Arguments.of(IntTerms.eq(intA, intB)), | ||
70 | Arguments.of(IntTerms.notEq(intA, intB)), | ||
71 | Arguments.of(IntTerms.less(intA, intB)), | ||
72 | Arguments.of(IntTerms.lessEq(intA, intB)), | ||
73 | Arguments.of(IntTerms.greater(intA, intB)), | ||
74 | Arguments.of(IntTerms.greaterEq(intA, intB)), | ||
75 | Arguments.of(IntTerms.asInt(realA)), | ||
76 | Arguments.of(RealTerms.plus(realA)), | ||
77 | Arguments.of(RealTerms.minus(realA)), | ||
78 | Arguments.of(RealTerms.add(realA, realB)), | ||
79 | Arguments.of(RealTerms.sub(realA, realB)), | ||
80 | Arguments.of(RealTerms.mul(realA, realB)), | ||
81 | Arguments.of(RealTerms.div(realA, realB)), | ||
82 | Arguments.of(RealTerms.pow(realA, realB)), | ||
83 | Arguments.of(RealTerms.min(realA, realB)), | ||
84 | Arguments.of(RealTerms.max(realA, realB)), | ||
85 | Arguments.of(RealTerms.asReal(intA)), | ||
86 | Arguments.of(BoolTerms.not(boolA)), | ||
87 | Arguments.of(BoolTerms.and(boolA, boolB)), | ||
88 | Arguments.of(BoolTerms.or(boolA, boolB)), | ||
89 | Arguments.of(BoolTerms.xor(boolA, boolB)), | ||
90 | Arguments.of(RealTerms.eq(realA, realB)), | ||
91 | Arguments.of(UpperCardinalityTerms.add(upperCardinalityA, upperCardinalityB)), | ||
92 | Arguments.of(UpperCardinalityTerms.mul(upperCardinalityA, upperCardinalityB)), | ||
93 | Arguments.of(UpperCardinalityTerms.min(upperCardinalityA, upperCardinalityB)), | ||
94 | Arguments.of(UpperCardinalityTerms.max(upperCardinalityA, upperCardinalityB)) | ||
95 | ); | ||
96 | } | ||
97 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/bool/BoolTermsEvaluateTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/bool/BoolTermsEvaluateTest.java new file mode 100644 index 00000000..beff705e --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/bool/BoolTermsEvaluateTest.java | |||
@@ -0,0 +1,75 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.bool; | ||
7 | |||
8 | import org.junit.jupiter.params.ParameterizedTest; | ||
9 | import org.junit.jupiter.params.provider.CsvSource; | ||
10 | import tools.refinery.store.query.valuation.Valuation; | ||
11 | |||
12 | import static org.hamcrest.MatcherAssert.assertThat; | ||
13 | import static org.hamcrest.Matchers.is; | ||
14 | |||
15 | class BoolTermsEvaluateTest { | ||
16 | @ParameterizedTest(name = "!{0} == {1}") | ||
17 | @CsvSource(value = { | ||
18 | "false, true", | ||
19 | "true, false", | ||
20 | "null, null" | ||
21 | }, nullValues = "null") | ||
22 | void notTest(Boolean a, Boolean result) { | ||
23 | var term = BoolTerms.not(BoolTerms.constant(a)); | ||
24 | assertThat(term.getType(), is(Boolean.class)); | ||
25 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
26 | } | ||
27 | |||
28 | @ParameterizedTest(name = "{0} && {1} == {2}") | ||
29 | @CsvSource(value = { | ||
30 | "false, false, false", | ||
31 | "false, true, false", | ||
32 | "true, false, false", | ||
33 | "true, true, true", | ||
34 | "false, null, null", | ||
35 | "null, false, null", | ||
36 | "null, null, null" | ||
37 | }, nullValues = "null") | ||
38 | void andTest(Boolean a, Boolean b, Boolean result) { | ||
39 | var term = BoolTerms.and(BoolTerms.constant(a), BoolTerms.constant(b)); | ||
40 | assertThat(term.getType(), is(Boolean.class)); | ||
41 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
42 | } | ||
43 | |||
44 | @ParameterizedTest(name = "{0} || {1} == {2}") | ||
45 | @CsvSource(value = { | ||
46 | "false, false, false", | ||
47 | "false, true, true", | ||
48 | "true, false, true", | ||
49 | "true, true, true", | ||
50 | "true, null, null", | ||
51 | "null, true, null", | ||
52 | "null, null, null" | ||
53 | }, nullValues = "null") | ||
54 | void orTest(Boolean a, Boolean b, Boolean result) { | ||
55 | var term = BoolTerms.or(BoolTerms.constant(a), BoolTerms.constant(b)); | ||
56 | assertThat(term.getType(), is(Boolean.class)); | ||
57 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
58 | } | ||
59 | |||
60 | @ParameterizedTest(name = "{0} ^^ {1} == {2}") | ||
61 | @CsvSource(value = { | ||
62 | "false, false, false", | ||
63 | "false, true, true", | ||
64 | "true, false, true", | ||
65 | "true, true, false", | ||
66 | "false, null, null", | ||
67 | "null, false, null", | ||
68 | "null, null, null" | ||
69 | }, nullValues = "null") | ||
70 | void xorTest(Boolean a, Boolean b, Boolean result) { | ||
71 | var term = BoolTerms.xor(BoolTerms.constant(a), BoolTerms.constant(b)); | ||
72 | assertThat(term.getType(), is(Boolean.class)); | ||
73 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
74 | } | ||
75 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/int_/IntTermsEvaluateTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/int_/IntTermsEvaluateTest.java new file mode 100644 index 00000000..abe50d75 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/int_/IntTermsEvaluateTest.java | |||
@@ -0,0 +1,259 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import org.junit.jupiter.params.ParameterizedTest; | ||
9 | import org.junit.jupiter.params.provider.Arguments; | ||
10 | import org.junit.jupiter.params.provider.CsvSource; | ||
11 | import org.junit.jupiter.params.provider.MethodSource; | ||
12 | import tools.refinery.store.query.term.real.RealTerms; | ||
13 | import tools.refinery.store.query.valuation.Valuation; | ||
14 | |||
15 | import java.util.stream.Stream; | ||
16 | |||
17 | import static org.hamcrest.Matchers.is; | ||
18 | import static org.hamcrest.MatcherAssert.assertThat; | ||
19 | |||
20 | class IntTermsEvaluateTest { | ||
21 | @ParameterizedTest(name = "+{0} == {1}") | ||
22 | @CsvSource(value = { | ||
23 | "2, 2", | ||
24 | "null, null" | ||
25 | }, nullValues = "null") | ||
26 | void plusTest(Integer a, Integer result) { | ||
27 | var term = IntTerms.plus(IntTerms.constant(a)); | ||
28 | assertThat(term.getType(), is(Integer.class)); | ||
29 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
30 | } | ||
31 | |||
32 | @ParameterizedTest(name = "-{0} == {1}") | ||
33 | @CsvSource(value = { | ||
34 | "2, -2", | ||
35 | "null, null" | ||
36 | }, nullValues = "null") | ||
37 | void minusTest(Integer a, Integer result) { | ||
38 | var term = IntTerms.minus(IntTerms.constant(a)); | ||
39 | assertThat(term.getType(), is(Integer.class)); | ||
40 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
41 | } | ||
42 | |||
43 | @ParameterizedTest(name = "{0} + {1} == {2}") | ||
44 | @CsvSource(value = { | ||
45 | "1, 2, 3", | ||
46 | "null, 2, null", | ||
47 | "1, null, null", | ||
48 | "null, null, null" | ||
49 | }, nullValues = "null") | ||
50 | void addTest(Integer a, Integer b, Integer result) { | ||
51 | var term = IntTerms.add(IntTerms.constant(a), IntTerms.constant(b)); | ||
52 | assertThat(term.getType(), is(Integer.class)); | ||
53 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
54 | } | ||
55 | |||
56 | @ParameterizedTest(name = "{0} - {1} == {2}") | ||
57 | @CsvSource(value = { | ||
58 | "1, 3, -2", | ||
59 | "null, 3, null", | ||
60 | "1, null, null", | ||
61 | "null, null, null" | ||
62 | }, nullValues = "null") | ||
63 | void subTest(Integer a, Integer b, Integer result) { | ||
64 | var term = IntTerms.sub(IntTerms.constant(a), IntTerms.constant(b)); | ||
65 | assertThat(term.getType(), is(Integer.class)); | ||
66 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
67 | } | ||
68 | |||
69 | @ParameterizedTest(name = "{0} * {1} == {2}") | ||
70 | @CsvSource(value = { | ||
71 | "2, 3, 6", | ||
72 | "null, 3, null", | ||
73 | "2, null, null", | ||
74 | "null, null, null" | ||
75 | }, nullValues = "null") | ||
76 | void mulTest(Integer a, Integer b, Integer result) { | ||
77 | var term = IntTerms.mul(IntTerms.constant(a), IntTerms.constant(b)); | ||
78 | assertThat(term.getType(), is(Integer.class)); | ||
79 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
80 | } | ||
81 | |||
82 | @ParameterizedTest(name = "{0} * {1} == {2}") | ||
83 | @CsvSource(value = { | ||
84 | "6, 3, 2", | ||
85 | "7, 3, 2", | ||
86 | "6, 0, null", | ||
87 | "null, 3, null", | ||
88 | "6, null, null", | ||
89 | "null, null, null" | ||
90 | }, nullValues = "null") | ||
91 | void divTest(Integer a, Integer b, Integer result) { | ||
92 | var term = IntTerms.div(IntTerms.constant(a), IntTerms.constant(b)); | ||
93 | assertThat(term.getType(), is(Integer.class)); | ||
94 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
95 | } | ||
96 | |||
97 | @ParameterizedTest(name = "{0} ** {1} == {2}") | ||
98 | @CsvSource(value = { | ||
99 | "1, 0, 1", | ||
100 | "1, 3, 1", | ||
101 | "1, -3, null", | ||
102 | "2, 0, 1", | ||
103 | "2, 2, 4", | ||
104 | "2, 3, 8", | ||
105 | "2, 4, 16", | ||
106 | "2, 5, 32", | ||
107 | "2, 6, 64", | ||
108 | "2, -3, null", | ||
109 | "null, 3, null", | ||
110 | "2, null, null", | ||
111 | "null, null, null" | ||
112 | }, nullValues = "null") | ||
113 | void powTest(Integer a, Integer b, Integer result) { | ||
114 | var term = IntTerms.pow(IntTerms.constant(a), IntTerms.constant(b)); | ||
115 | assertThat(term.getType(), is(Integer.class)); | ||
116 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
117 | } | ||
118 | |||
119 | @ParameterizedTest(name = "min({0}, {1}) == {2}") | ||
120 | @CsvSource(value = { | ||
121 | "1, 2, 1", | ||
122 | "2, 1, 1", | ||
123 | "null, 2, null", | ||
124 | "1, null, null", | ||
125 | "null, null, null" | ||
126 | }, nullValues = "null") | ||
127 | void minTest(Integer a, Integer b, Integer result) { | ||
128 | var term = IntTerms.min(IntTerms.constant(a), IntTerms.constant(b)); | ||
129 | assertThat(term.getType(), is(Integer.class)); | ||
130 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
131 | } | ||
132 | |||
133 | @ParameterizedTest(name = "max({0}, {1}) == {2}") | ||
134 | @CsvSource(value = { | ||
135 | "1, 2, 2", | ||
136 | "2, 1, 2", | ||
137 | "null, 2, null", | ||
138 | "1, null, null", | ||
139 | "null, null, null" | ||
140 | }, nullValues = "null") | ||
141 | void maxTest(Integer a, Integer b, Integer result) { | ||
142 | var term = IntTerms.max(IntTerms.constant(a), IntTerms.constant(b)); | ||
143 | assertThat(term.getType(), is(Integer.class)); | ||
144 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
145 | } | ||
146 | |||
147 | @ParameterizedTest(name = "({0} == {1}) == {2}") | ||
148 | @CsvSource(value = { | ||
149 | "1, 1, true", | ||
150 | "1, 2, false", | ||
151 | "null, 1, null", | ||
152 | "1, null, null", | ||
153 | "null, null, null" | ||
154 | }, nullValues = "null") | ||
155 | void eqTest(Integer a, Integer b, Boolean result) { | ||
156 | var term = IntTerms.eq(IntTerms.constant(a), IntTerms.constant(b)); | ||
157 | assertThat(term.getType(), is(Boolean.class)); | ||
158 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
159 | } | ||
160 | |||
161 | @ParameterizedTest(name = "({0} != {1}) == {2}") | ||
162 | @CsvSource(value = { | ||
163 | "1, 1, false", | ||
164 | "1, 2, true", | ||
165 | "null, 1, null", | ||
166 | "1, null, null", | ||
167 | "null, null, null" | ||
168 | }, nullValues = "null") | ||
169 | void notEqTest(Integer a, Integer b, Boolean result) { | ||
170 | var term = IntTerms.notEq(IntTerms.constant(a), IntTerms.constant(b)); | ||
171 | assertThat(term.getType(), is(Boolean.class)); | ||
172 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
173 | } | ||
174 | |||
175 | @ParameterizedTest(name = "({0} < {1}) == {2}") | ||
176 | @CsvSource(value = { | ||
177 | "1, -2, false", | ||
178 | "1, 1, false", | ||
179 | "1, 2, true", | ||
180 | "null, 1, null", | ||
181 | "1, null, null", | ||
182 | "null, null, null" | ||
183 | }, nullValues = "null") | ||
184 | void lessTest(Integer a, Integer b, Boolean result) { | ||
185 | var term = IntTerms.less(IntTerms.constant(a), IntTerms.constant(b)); | ||
186 | assertThat(term.getType(), is(Boolean.class)); | ||
187 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
188 | } | ||
189 | |||
190 | @ParameterizedTest(name = "({0} <= {1}) == {2}") | ||
191 | @CsvSource(value = { | ||
192 | "1, -2, false", | ||
193 | "1, 1, true", | ||
194 | "1, 2, true", | ||
195 | "null, 1, null", | ||
196 | "1, null, null", | ||
197 | "null, null, null" | ||
198 | }, nullValues = "null") | ||
199 | void lessEqTest(Integer a, Integer b, Boolean result) { | ||
200 | var term = IntTerms.lessEq(IntTerms.constant(a), IntTerms.constant(b)); | ||
201 | assertThat(term.getType(), is(Boolean.class)); | ||
202 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
203 | } | ||
204 | |||
205 | @ParameterizedTest(name = "({0} > {1}) == {2}") | ||
206 | @CsvSource(value = { | ||
207 | "1, -2, true", | ||
208 | "1, 1, false", | ||
209 | "1, 2, false", | ||
210 | "null, 1, null", | ||
211 | "1, null, null", | ||
212 | "null, null, null" | ||
213 | }, nullValues = "null") | ||
214 | void greaterTest(Integer a, Integer b, Boolean result) { | ||
215 | var term = IntTerms.greater(IntTerms.constant(a), IntTerms.constant(b)); | ||
216 | assertThat(term.getType(), is(Boolean.class)); | ||
217 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
218 | } | ||
219 | |||
220 | @ParameterizedTest(name = "({0} >= {1}) == {2}") | ||
221 | @CsvSource(value = { | ||
222 | "1, -2, true", | ||
223 | "1, 1, true", | ||
224 | "1, 2, false", | ||
225 | "null, 1, null", | ||
226 | "1, null, null", | ||
227 | "null, null, null" | ||
228 | }, nullValues = "null") | ||
229 | void greaterEqTest(Integer a, Integer b, Boolean result) { | ||
230 | var term = IntTerms.greaterEq(IntTerms.constant(a), IntTerms.constant(b)); | ||
231 | assertThat(term.getType(), is(Boolean.class)); | ||
232 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
233 | } | ||
234 | |||
235 | @ParameterizedTest(name = "{0} as int == {1}") | ||
236 | @MethodSource | ||
237 | void asIntTest(Double a, Integer result) { | ||
238 | var term = IntTerms.asInt(RealTerms.constant(a)); | ||
239 | assertThat(term.getType(), is(Integer.class)); | ||
240 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
241 | } | ||
242 | |||
243 | static Stream<Arguments> asIntTest() { | ||
244 | return Stream.of( | ||
245 | Arguments.of(2.0, 2), | ||
246 | Arguments.of(2.1, 2), | ||
247 | Arguments.of(2.9, 2), | ||
248 | Arguments.of(-2.0, -2), | ||
249 | Arguments.of(-2.1, -2), | ||
250 | Arguments.of(-2.9, -2), | ||
251 | Arguments.of(0.0, 0), | ||
252 | Arguments.of(-0.0, 0), | ||
253 | Arguments.of(Double.POSITIVE_INFINITY, Integer.MAX_VALUE), | ||
254 | Arguments.of(Double.NEGATIVE_INFINITY, Integer.MIN_VALUE), | ||
255 | Arguments.of(Double.NaN, null), | ||
256 | Arguments.of(null, null) | ||
257 | ); | ||
258 | } | ||
259 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/real/RealTermEvaluateTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/real/RealTermEvaluateTest.java new file mode 100644 index 00000000..6a8eebf1 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/real/RealTermEvaluateTest.java | |||
@@ -0,0 +1,238 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import org.hamcrest.Matcher; | ||
9 | import org.junit.jupiter.params.ParameterizedTest; | ||
10 | import org.junit.jupiter.params.provider.CsvSource; | ||
11 | import tools.refinery.store.query.term.int_.IntTerms; | ||
12 | import tools.refinery.store.query.valuation.Valuation; | ||
13 | |||
14 | import static org.hamcrest.MatcherAssert.assertThat; | ||
15 | import static org.hamcrest.Matchers.*; | ||
16 | |||
17 | class RealTermEvaluateTest { | ||
18 | public static final double TOLERANCE = 1e-6; | ||
19 | |||
20 | private static Matcher<Double> closeToOrNull(Double expected) { | ||
21 | return expected == null ? nullValue(Double.class) : closeTo(expected, TOLERANCE); | ||
22 | } | ||
23 | |||
24 | @ParameterizedTest(name = "+{0} == {1}") | ||
25 | @CsvSource(value = { | ||
26 | "2.5, 2.5", | ||
27 | "null, null" | ||
28 | }, nullValues = "null") | ||
29 | void plusTest(Double a, Double result) { | ||
30 | var term = RealTerms.plus(RealTerms.constant(a)); | ||
31 | assertThat(term.getType(), is(Double.class)); | ||
32 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
33 | } | ||
34 | |||
35 | @ParameterizedTest(name = "-{0} == {1}") | ||
36 | @CsvSource(value = { | ||
37 | "2.5, -2.5", | ||
38 | "null, null" | ||
39 | }, nullValues = "null") | ||
40 | void minusTest(Double a, Double result) { | ||
41 | var term = RealTerms.minus(RealTerms.constant(a)); | ||
42 | assertThat(term.getType(), is(Double.class)); | ||
43 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
44 | } | ||
45 | |||
46 | @ParameterizedTest(name = "{0} + {1} == {2}") | ||
47 | @CsvSource(value = { | ||
48 | "1.2, 2.3, 3.5", | ||
49 | "null, 2.3, null", | ||
50 | "1.2, null, null", | ||
51 | "null, null, null" | ||
52 | }, nullValues = "null") | ||
53 | void addTest(Double a, Double b, Double result) { | ||
54 | var term = RealTerms.add(RealTerms.constant(a), RealTerms.constant(b)); | ||
55 | assertThat(term.getType(), is(Double.class)); | ||
56 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
57 | } | ||
58 | |||
59 | @ParameterizedTest(name = "{0} - {1} == {2}") | ||
60 | @CsvSource(value = { | ||
61 | "1.2, 3.4, -2.2", | ||
62 | "null, 3.4, null", | ||
63 | "1.2, null, null", | ||
64 | "null, null, null" | ||
65 | }, nullValues = "null") | ||
66 | void subTest(Double a, Double b, Double result) { | ||
67 | var term = RealTerms.sub(RealTerms.constant(a), RealTerms.constant(b)); | ||
68 | assertThat(term.getType(), is(Double.class)); | ||
69 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
70 | } | ||
71 | |||
72 | @ParameterizedTest(name = "{0} * {1} == {2}") | ||
73 | @CsvSource(value = { | ||
74 | "2.3, 3.4, 7.82", | ||
75 | "null, 3.4, null", | ||
76 | "2.3, null, null", | ||
77 | "null, null, null" | ||
78 | }, nullValues = "null") | ||
79 | void mulTest(Double a, Double b, Double result) { | ||
80 | var term = RealTerms.mul(RealTerms.constant(a), RealTerms.constant(b)); | ||
81 | assertThat(term.getType(), is(Double.class)); | ||
82 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
83 | } | ||
84 | |||
85 | @ParameterizedTest(name = "{0} * {1} == {2}") | ||
86 | @CsvSource(value = { | ||
87 | "7.82, 3.4, 2.3", | ||
88 | "null, 3.4, null", | ||
89 | "7.82, null, null", | ||
90 | "null, null, null" | ||
91 | }, nullValues = "null") | ||
92 | void divTest(Double a, Double b, Double result) { | ||
93 | var term = RealTerms.div(RealTerms.constant(a), RealTerms.constant(b)); | ||
94 | assertThat(term.getType(), is(Double.class)); | ||
95 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
96 | } | ||
97 | |||
98 | @ParameterizedTest(name = "{0} ** {1} == {2}") | ||
99 | @CsvSource(value = { | ||
100 | "2.0, 6.0, 64.0", | ||
101 | "null, 6.0, null", | ||
102 | "2.0, null, null", | ||
103 | "null, null, null" | ||
104 | }, nullValues = "null") | ||
105 | void powTest(Double a, Double b, Double result) { | ||
106 | var term = RealTerms.pow(RealTerms.constant(a), RealTerms.constant(b)); | ||
107 | assertThat(term.getType(), is(Double.class)); | ||
108 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
109 | } | ||
110 | |||
111 | @ParameterizedTest(name = "min({0}, {1}) == {2}") | ||
112 | @CsvSource(value = { | ||
113 | "1.5, 2.7, 1.5", | ||
114 | "2.7, 1.5, 1.5", | ||
115 | "null, 2.7, null", | ||
116 | "1.5, null, null", | ||
117 | "null, null, null" | ||
118 | }, nullValues = "null") | ||
119 | void minTest(Double a, Double b, Double result) { | ||
120 | var term = RealTerms.min(RealTerms.constant(a), RealTerms.constant(b)); | ||
121 | assertThat(term.getType(), is(Double.class)); | ||
122 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
123 | } | ||
124 | |||
125 | @ParameterizedTest(name = "max({0}, {1}) == {2}") | ||
126 | @CsvSource(value = { | ||
127 | "1.5, 2.7, 2.7", | ||
128 | "2.7, 1.7, 2.7", | ||
129 | "null, 2.7, null", | ||
130 | "1.5, null, null", | ||
131 | "null, null, null" | ||
132 | }, nullValues = "null") | ||
133 | void maxTest(Double a, Double b, Double result) { | ||
134 | var term = RealTerms.max(RealTerms.constant(a), RealTerms.constant(b)); | ||
135 | assertThat(term.getType(), is(Double.class)); | ||
136 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
137 | } | ||
138 | |||
139 | @ParameterizedTest(name = "({0} == {1}) == {2}") | ||
140 | @CsvSource(value = { | ||
141 | "1.5, 1.5, true", | ||
142 | "1.5, 2.7, false", | ||
143 | "null, 1.5, null", | ||
144 | "1.5, null, null", | ||
145 | "null, null, null" | ||
146 | }, nullValues = "null") | ||
147 | void eqTest(Double a, Double b, Boolean result) { | ||
148 | var term = RealTerms.eq(RealTerms.constant(a), RealTerms.constant(b)); | ||
149 | assertThat(term.getType(), is(Boolean.class)); | ||
150 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
151 | } | ||
152 | |||
153 | @ParameterizedTest(name = "({0} != {1}) == {2}") | ||
154 | @CsvSource(value = { | ||
155 | "1.5, 1.5, false", | ||
156 | "1.5, 2.7, true", | ||
157 | "null, 1.5, null", | ||
158 | "1.5, null, null", | ||
159 | "null, null, null" | ||
160 | }, nullValues = "null") | ||
161 | void notEqTest(Double a, Double b, Boolean result) { | ||
162 | var term = RealTerms.notEq(RealTerms.constant(a), RealTerms.constant(b)); | ||
163 | assertThat(term.getType(), is(Boolean.class)); | ||
164 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
165 | } | ||
166 | |||
167 | @ParameterizedTest(name = "({0} < {1}) == {2}") | ||
168 | @CsvSource(value = { | ||
169 | "1.5, -2.7, false", | ||
170 | "1.5, 1.5, false", | ||
171 | "1.5, 2.7, true", | ||
172 | "null, 1.5, null", | ||
173 | "1.5, null, null", | ||
174 | "null, null, null" | ||
175 | }, nullValues = "null") | ||
176 | void lessTest(Double a, Double b, Boolean result) { | ||
177 | var term = RealTerms.less(RealTerms.constant(a), RealTerms.constant(b)); | ||
178 | assertThat(term.getType(), is(Boolean.class)); | ||
179 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
180 | } | ||
181 | |||
182 | @ParameterizedTest(name = "({0} <= {1}) == {2}") | ||
183 | @CsvSource(value = { | ||
184 | "1.5, -2.7, false", | ||
185 | "1.5, 1.5, true", | ||
186 | "1.5, 2.7, true", | ||
187 | "null, 1.5, null", | ||
188 | "1.5, null, null", | ||
189 | "null, null, null" | ||
190 | }, nullValues = "null") | ||
191 | void lessEqTest(Double a, Double b, Boolean result) { | ||
192 | var term = RealTerms.lessEq(RealTerms.constant(a), RealTerms.constant(b)); | ||
193 | assertThat(term.getType(), is(Boolean.class)); | ||
194 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
195 | } | ||
196 | |||
197 | @ParameterizedTest(name = "({0} > {1}) == {2}") | ||
198 | @CsvSource(value = { | ||
199 | "1.5, -2.7, true", | ||
200 | "1.5, 1.5, false", | ||
201 | "1.5, 2.7, false", | ||
202 | "null, 1.5, null", | ||
203 | "1.5, null, null", | ||
204 | "null, null, null" | ||
205 | }, nullValues = "null") | ||
206 | void greaterTest(Double a, Double b, Boolean result) { | ||
207 | var term = RealTerms.greater(RealTerms.constant(a), RealTerms.constant(b)); | ||
208 | assertThat(term.getType(), is(Boolean.class)); | ||
209 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
210 | } | ||
211 | |||
212 | @ParameterizedTest(name = "({0} >= {1}) == {2}") | ||
213 | @CsvSource(value = { | ||
214 | "1.5, -2.7, true", | ||
215 | "1.5, 1.5, true", | ||
216 | "1.5, 2.7, false", | ||
217 | "null, 1.5, null", | ||
218 | "1.5, null, null", | ||
219 | "null, null, null" | ||
220 | }, nullValues = "null") | ||
221 | void greaterEqTest(Double a, Double b, Boolean result) { | ||
222 | var term = RealTerms.greaterEq(RealTerms.constant(a), RealTerms.constant(b)); | ||
223 | assertThat(term.getType(), is(Boolean.class)); | ||
224 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
225 | } | ||
226 | |||
227 | @ParameterizedTest(name = "{0} as real == {1}") | ||
228 | @CsvSource(value = { | ||
229 | "0, 0.0", | ||
230 | "5, 5.0", | ||
231 | "null, null" | ||
232 | }, nullValues = "null") | ||
233 | void asRealTest(Integer a, Double result) { | ||
234 | var term = RealTerms.asReal(IntTerms.constant(a)); | ||
235 | assertThat(term.getType(), is(Double.class)); | ||
236 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
237 | } | ||
238 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java new file mode 100644 index 00000000..31baf36e --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java | |||
@@ -0,0 +1,56 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import org.junit.jupiter.params.ParameterizedTest; | ||
9 | import org.junit.jupiter.params.provider.Arguments; | ||
10 | import org.junit.jupiter.params.provider.MethodSource; | ||
11 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
12 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
13 | |||
14 | import java.util.List; | ||
15 | import java.util.stream.Stream; | ||
16 | |||
17 | import static org.hamcrest.MatcherAssert.assertThat; | ||
18 | import static org.hamcrest.Matchers.is; | ||
19 | |||
20 | class UpperCardinalitySumAggregatorStreamTest { | ||
21 | @ParameterizedTest | ||
22 | @MethodSource | ||
23 | void testStream(List<UpperCardinality> list, UpperCardinality expected) { | ||
24 | var result = UpperCardinalitySumAggregator.INSTANCE.aggregateStream(list.stream()); | ||
25 | assertThat(result, is(expected)); | ||
26 | } | ||
27 | |||
28 | static Stream<Arguments> testStream() { | ||
29 | return Stream.of( | ||
30 | Arguments.of(List.of(), UpperCardinalities.ZERO), | ||
31 | Arguments.of(List.of(UpperCardinality.of(3)), UpperCardinality.of(3)), | ||
32 | Arguments.of( | ||
33 | List.of( | ||
34 | UpperCardinality.of(2), | ||
35 | UpperCardinality.of(3) | ||
36 | ), | ||
37 | UpperCardinality.of(5) | ||
38 | ), | ||
39 | Arguments.of(List.of(UpperCardinalities.UNBOUNDED), UpperCardinalities.UNBOUNDED), | ||
40 | Arguments.of( | ||
41 | List.of( | ||
42 | UpperCardinalities.UNBOUNDED, | ||
43 | UpperCardinalities.UNBOUNDED | ||
44 | ), | ||
45 | UpperCardinalities.UNBOUNDED | ||
46 | ), | ||
47 | Arguments.of( | ||
48 | List.of( | ||
49 | UpperCardinalities.UNBOUNDED, | ||
50 | UpperCardinality.of(3) | ||
51 | ), | ||
52 | UpperCardinalities.UNBOUNDED | ||
53 | ) | ||
54 | ); | ||
55 | } | ||
56 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorTest.java new file mode 100644 index 00000000..780cd0ab --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorTest.java | |||
@@ -0,0 +1,80 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import org.junit.jupiter.api.BeforeEach; | ||
9 | import org.junit.jupiter.api.Test; | ||
10 | import tools.refinery.store.query.term.StatefulAggregate; | ||
11 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
12 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
13 | |||
14 | import static org.hamcrest.MatcherAssert.assertThat; | ||
15 | import static org.hamcrest.Matchers.is; | ||
16 | |||
17 | class UpperCardinalitySumAggregatorTest { | ||
18 | private StatefulAggregate<UpperCardinality, UpperCardinality> accumulator; | ||
19 | |||
20 | @BeforeEach | ||
21 | void beforeEach() { | ||
22 | accumulator = UpperCardinalitySumAggregator.INSTANCE.createEmptyAggregate(); | ||
23 | } | ||
24 | |||
25 | @Test | ||
26 | void emptyAggregationTest() { | ||
27 | assertThat(accumulator.getResult(), is(UpperCardinality.of(0))); | ||
28 | } | ||
29 | |||
30 | @Test | ||
31 | void singleBoundedTest() { | ||
32 | accumulator.add(UpperCardinality.of(3)); | ||
33 | assertThat(accumulator.getResult(), is(UpperCardinality.of(3))); | ||
34 | } | ||
35 | |||
36 | @Test | ||
37 | void multipleBoundedTest() { | ||
38 | accumulator.add(UpperCardinality.of(2)); | ||
39 | accumulator.add(UpperCardinality.of(3)); | ||
40 | assertThat(accumulator.getResult(), is(UpperCardinality.of(5))); | ||
41 | } | ||
42 | |||
43 | @Test | ||
44 | void singleUnboundedTest() { | ||
45 | accumulator.add(UpperCardinalities.UNBOUNDED); | ||
46 | assertThat(accumulator.getResult(), is(UpperCardinalities.UNBOUNDED)); | ||
47 | } | ||
48 | |||
49 | @Test | ||
50 | void multipleUnboundedTest() { | ||
51 | accumulator.add(UpperCardinalities.UNBOUNDED); | ||
52 | accumulator.add(UpperCardinalities.UNBOUNDED); | ||
53 | assertThat(accumulator.getResult(), is(UpperCardinalities.UNBOUNDED)); | ||
54 | } | ||
55 | |||
56 | @Test | ||
57 | void removeBoundedTest() { | ||
58 | accumulator.add(UpperCardinality.of(2)); | ||
59 | accumulator.add(UpperCardinality.of(3)); | ||
60 | accumulator.remove(UpperCardinality.of(2)); | ||
61 | assertThat(accumulator.getResult(), is(UpperCardinality.of(3))); | ||
62 | } | ||
63 | |||
64 | @Test | ||
65 | void removeAllUnboundedTest() { | ||
66 | accumulator.add(UpperCardinalities.UNBOUNDED); | ||
67 | accumulator.add(UpperCardinality.of(3)); | ||
68 | accumulator.remove(UpperCardinalities.UNBOUNDED); | ||
69 | assertThat(accumulator.getResult(), is(UpperCardinality.of(3))); | ||
70 | } | ||
71 | |||
72 | @Test | ||
73 | void removeSomeUnboundedTest() { | ||
74 | accumulator.add(UpperCardinalities.UNBOUNDED); | ||
75 | accumulator.add(UpperCardinalities.UNBOUNDED); | ||
76 | accumulator.add(UpperCardinality.of(3)); | ||
77 | accumulator.remove(UpperCardinalities.UNBOUNDED); | ||
78 | assertThat(accumulator.getResult(), is(UpperCardinalities.UNBOUNDED)); | ||
79 | } | ||
80 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java new file mode 100644 index 00000000..9d0f3bde --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java | |||
@@ -0,0 +1,104 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import org.junit.jupiter.params.ParameterizedTest; | ||
9 | import org.junit.jupiter.params.provider.Arguments; | ||
10 | import org.junit.jupiter.params.provider.MethodSource; | ||
11 | import tools.refinery.store.query.valuation.Valuation; | ||
12 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
13 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
14 | |||
15 | import java.util.stream.Stream; | ||
16 | |||
17 | import static org.hamcrest.MatcherAssert.assertThat; | ||
18 | import static org.hamcrest.Matchers.is; | ||
19 | |||
20 | class UpperCardinalityTermsEvaluateTest { | ||
21 | @ParameterizedTest(name = "min({0}, {1}) == {2}") | ||
22 | @MethodSource | ||
23 | void minTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { | ||
24 | var term = UpperCardinalityTerms.min(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b)); | ||
25 | assertThat(term.getType(), is(UpperCardinality.class)); | ||
26 | assertThat(term.evaluate(Valuation.empty()), is(expected)); | ||
27 | } | ||
28 | |||
29 | static Stream<Arguments> minTest() { | ||
30 | return Stream.of( | ||
31 | Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)), | ||
32 | Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(0)), | ||
33 | Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(0)), | ||
34 | Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinality.of(0)), | ||
35 | Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinality.of(0)), | ||
36 | Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), | ||
37 | Arguments.of(UpperCardinality.of(1), null, null), | ||
38 | Arguments.of(null, UpperCardinality.of(1), null), | ||
39 | Arguments.of(null, null, null) | ||
40 | ); | ||
41 | } | ||
42 | |||
43 | @ParameterizedTest(name = "max({0}, {1}) == {2}") | ||
44 | @MethodSource | ||
45 | void maxTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { | ||
46 | var term = UpperCardinalityTerms.max(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b)); | ||
47 | assertThat(term.getType(), is(UpperCardinality.class)); | ||
48 | assertThat(term.evaluate(Valuation.empty()), is(expected)); | ||
49 | } | ||
50 | |||
51 | static Stream<Arguments> maxTest() { | ||
52 | return Stream.of( | ||
53 | Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)), | ||
54 | Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(1)), | ||
55 | Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(1)), | ||
56 | Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), | ||
57 | Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinalities.UNBOUNDED), | ||
58 | Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), | ||
59 | Arguments.of(UpperCardinality.of(1), null, null), | ||
60 | Arguments.of(null, UpperCardinality.of(1), null), | ||
61 | Arguments.of(null, null, null) | ||
62 | ); | ||
63 | } | ||
64 | |||
65 | @ParameterizedTest(name = "{0} + {1} == {2}") | ||
66 | @MethodSource | ||
67 | void addTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { | ||
68 | var term = UpperCardinalityTerms.add(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b)); | ||
69 | assertThat(term.getType(), is(UpperCardinality.class)); | ||
70 | assertThat(term.evaluate(Valuation.empty()), is(expected)); | ||
71 | } | ||
72 | |||
73 | static Stream<Arguments> addTest() { | ||
74 | return Stream.of( | ||
75 | Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(5)), | ||
76 | Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), | ||
77 | Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED), | ||
78 | Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), | ||
79 | Arguments.of(UpperCardinality.of(1), null, null), | ||
80 | Arguments.of(null, UpperCardinality.of(1), null), | ||
81 | Arguments.of(null, null, null) | ||
82 | ); | ||
83 | } | ||
84 | |||
85 | @ParameterizedTest(name = "{0} * {1} == {2}") | ||
86 | @MethodSource | ||
87 | void mulTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { | ||
88 | var term = UpperCardinalityTerms.mul(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b)); | ||
89 | assertThat(term.getType(), is(UpperCardinality.class)); | ||
90 | assertThat(term.evaluate(Valuation.empty()), is(expected)); | ||
91 | } | ||
92 | |||
93 | static Stream<Arguments> mulTest() { | ||
94 | return Stream.of( | ||
95 | Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(6)), | ||
96 | Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), | ||
97 | Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED), | ||
98 | Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), | ||
99 | Arguments.of(UpperCardinality.of(1), null, null), | ||
100 | Arguments.of(null, UpperCardinality.of(1), null), | ||
101 | Arguments.of(null, null, null) | ||
102 | ); | ||
103 | } | ||
104 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToRawTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToRawTest.java new file mode 100644 index 00000000..d447e99c --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToRawTest.java | |||
@@ -0,0 +1,159 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.tests; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.dnf.Dnf; | ||
10 | import tools.refinery.store.query.dnf.SymbolicParameter; | ||
11 | import tools.refinery.store.query.term.NodeVariable; | ||
12 | import tools.refinery.store.query.term.ParameterDirection; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | import tools.refinery.store.query.view.AnySymbolView; | ||
15 | import tools.refinery.store.query.view.KeyOnlyView; | ||
16 | import tools.refinery.store.representation.Symbol; | ||
17 | |||
18 | import java.util.List; | ||
19 | |||
20 | import static org.hamcrest.CoreMatchers.containsString; | ||
21 | import static org.hamcrest.MatcherAssert.assertThat; | ||
22 | import static org.hamcrest.Matchers.allOf; | ||
23 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
24 | import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo; | ||
25 | |||
26 | class StructurallyEqualToRawTest { | ||
27 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); | ||
28 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
29 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
30 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
31 | private static final NodeVariable p = Variable.of("p"); | ||
32 | private static final NodeVariable q = Variable.of("q"); | ||
33 | |||
34 | @Test | ||
35 | void flatEqualsTest() { | ||
36 | var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(p)).build(); | ||
37 | |||
38 | assertThat(actual, structurallyEqualTo( | ||
39 | List.of(new SymbolicParameter(q, ParameterDirection.OUT)), | ||
40 | List.of(List.of(personView.call(q))) | ||
41 | )); | ||
42 | } | ||
43 | |||
44 | @Test | ||
45 | void flatNotEqualsTest() { | ||
46 | var actual = Dnf.builder("Actual").parameters(p).clause(friendView.call(p, q)).build(); | ||
47 | |||
48 | var assertion = structurallyEqualTo( | ||
49 | List.of(new SymbolicParameter(q, ParameterDirection.OUT)), | ||
50 | List.of(List.of(friendView.call(q, q))) | ||
51 | ); | ||
52 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
53 | } | ||
54 | |||
55 | @Test | ||
56 | void deepEqualsTest() { | ||
57 | var actual = Dnf.builder("Actual").parameters(q).clause( | ||
58 | Dnf.builder("Actual2").parameters(p).clause(personView.call(p)).build().call(q) | ||
59 | ).build(); | ||
60 | |||
61 | assertThat(actual, structurallyEqualTo( | ||
62 | List.of(new SymbolicParameter(q, ParameterDirection.OUT)), | ||
63 | List.of( | ||
64 | List.of( | ||
65 | Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q) | ||
66 | ) | ||
67 | ) | ||
68 | )); | ||
69 | } | ||
70 | |||
71 | @Test | ||
72 | void deepNotEqualsTest() { | ||
73 | var actual = Dnf.builder("Actual").parameter(q).clause( | ||
74 | Dnf.builder("Actual2").parameters(p).clause(friendView.call(p, q)).build().call(q) | ||
75 | ).build(); | ||
76 | |||
77 | var assertion = structurallyEqualTo( | ||
78 | List.of(new SymbolicParameter(q, ParameterDirection.OUT)), | ||
79 | List.of( | ||
80 | List.of( | ||
81 | Dnf.builder("Expected2") | ||
82 | .parameters(p) | ||
83 | .clause(friendView.call(p, p)) | ||
84 | .build() | ||
85 | .call(q) | ||
86 | ) | ||
87 | ) | ||
88 | ); | ||
89 | var error = assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
90 | assertThat(error.getMessage(), allOf(containsString("Expected2"), containsString("Actual2"))); | ||
91 | } | ||
92 | |||
93 | @Test | ||
94 | void parameterListLengthMismatchTest() { | ||
95 | var actual = Dnf.builder("Actual").parameters(p, q).clause( | ||
96 | friendView.call(p, q) | ||
97 | ).build(); | ||
98 | |||
99 | var assertion = structurallyEqualTo( | ||
100 | List.of(new SymbolicParameter(p, ParameterDirection.OUT)), | ||
101 | List.of(List.of(friendView.call(p, p))) | ||
102 | ); | ||
103 | |||
104 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
105 | } | ||
106 | |||
107 | @Test | ||
108 | void parameterDirectionMismatchTest() { | ||
109 | var actual = Dnf.builder("Actual").parameter(p, ParameterDirection.IN).clause( | ||
110 | personView.call(p) | ||
111 | ).build(); | ||
112 | |||
113 | var assertion = structurallyEqualTo( | ||
114 | List.of(new SymbolicParameter(p, ParameterDirection.OUT)), | ||
115 | List.of(List.of(personView.call(p))) | ||
116 | ); | ||
117 | |||
118 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
119 | } | ||
120 | |||
121 | @Test | ||
122 | void clauseCountMismatchTest() { | ||
123 | var actual = Dnf.builder("Actual").parameters(p, q).clause( | ||
124 | friendView.call(p, q) | ||
125 | ).build(); | ||
126 | |||
127 | var assertion = structurallyEqualTo( | ||
128 | List.of( | ||
129 | new SymbolicParameter(p, ParameterDirection.OUT), | ||
130 | new SymbolicParameter(q, ParameterDirection.OUT) | ||
131 | ), | ||
132 | List.of( | ||
133 | List.of(friendView.call(p, q)), | ||
134 | List.of(friendView.call(q, p)) | ||
135 | ) | ||
136 | ); | ||
137 | |||
138 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
139 | } | ||
140 | |||
141 | @Test | ||
142 | void literalCountMismatchTest() { | ||
143 | var actual = Dnf.builder("Actual").parameters(p, q).clause( | ||
144 | friendView.call(p, q) | ||
145 | ).build(); | ||
146 | |||
147 | var assertion = structurallyEqualTo( | ||
148 | List.of( | ||
149 | new SymbolicParameter(p, ParameterDirection.OUT), | ||
150 | new SymbolicParameter(q, ParameterDirection.OUT) | ||
151 | ), | ||
152 | List.of( | ||
153 | List.of(friendView.call(p, q), friendView.call(q, p)) | ||
154 | ) | ||
155 | ); | ||
156 | |||
157 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
158 | } | ||
159 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java new file mode 100644 index 00000000..f716b805 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java | |||
@@ -0,0 +1,127 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.tests; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.dnf.Dnf; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | import tools.refinery.store.query.term.ParameterDirection; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | import tools.refinery.store.query.view.AnySymbolView; | ||
14 | import tools.refinery.store.query.view.KeyOnlyView; | ||
15 | import tools.refinery.store.representation.Symbol; | ||
16 | |||
17 | import static org.hamcrest.CoreMatchers.containsString; | ||
18 | import static org.hamcrest.MatcherAssert.assertThat; | ||
19 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
20 | import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo; | ||
21 | |||
22 | class StructurallyEqualToTest { | ||
23 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); | ||
24 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
25 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
26 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
27 | private static final NodeVariable p = Variable.of("p"); | ||
28 | private static final NodeVariable q = Variable.of("q"); | ||
29 | |||
30 | @Test | ||
31 | void flatEqualsTest() { | ||
32 | var expected = Dnf.builder("Expected").parameters(q).clause(personView.call(q)).build(); | ||
33 | var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(p)).build(); | ||
34 | |||
35 | assertThat(actual, structurallyEqualTo(expected)); | ||
36 | } | ||
37 | |||
38 | @Test | ||
39 | void flatNotEqualsTest() { | ||
40 | var expected = Dnf.builder("Expected").parameters(q).clause(friendView.call(q, q)).build(); | ||
41 | var actual = Dnf.builder("Actual").parameters(p).clause(friendView.call(p, q)).build(); | ||
42 | |||
43 | var assertion = structurallyEqualTo(expected); | ||
44 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
45 | } | ||
46 | |||
47 | @Test | ||
48 | void deepEqualsTest() { | ||
49 | var expected = Dnf.builder("Expected").parameters(q).clause( | ||
50 | Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q) | ||
51 | ).build(); | ||
52 | var actual = Dnf.builder("Actual").parameters(q).clause( | ||
53 | Dnf.builder("Actual2").parameters(p).clause(personView.call(p)).build().call(q) | ||
54 | ).build(); | ||
55 | |||
56 | assertThat(actual, structurallyEqualTo(expected)); | ||
57 | } | ||
58 | |||
59 | @Test | ||
60 | void deepNotEqualsTest() { | ||
61 | var expected = Dnf.builder("Expected").parameters(q).clause( | ||
62 | Dnf.builder("Expected2").parameters(p).clause(friendView.call(p, p)).build().call(q) | ||
63 | ).build(); | ||
64 | var actual = Dnf.builder("Actual").parameter(q).clause( | ||
65 | Dnf.builder("Actual2").parameters(p).clause(friendView.call(p, q)).build().call(q) | ||
66 | ).build(); | ||
67 | |||
68 | var assertion = structurallyEqualTo(expected); | ||
69 | var error = assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
70 | assertThat(error.getMessage(), containsString(" called from Expected/1 ")); | ||
71 | } | ||
72 | |||
73 | @Test | ||
74 | void parameterListLengthMismatchTest() { | ||
75 | var expected = Dnf.builder("Expected").parameter(p).clause( | ||
76 | friendView.call(p, p) | ||
77 | ).build(); | ||
78 | var actual = Dnf.builder("Actual").parameters(p, q).clause( | ||
79 | friendView.call(p, q) | ||
80 | ).build(); | ||
81 | |||
82 | var assertion = structurallyEqualTo(expected); | ||
83 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
84 | } | ||
85 | |||
86 | @Test | ||
87 | void parameterDirectionMismatchTest() { | ||
88 | var expected = Dnf.builder("Expected").parameter(p, ParameterDirection.OUT).clause( | ||
89 | personView.call(p) | ||
90 | ).build(); | ||
91 | var actual = Dnf.builder("Actual").parameter(p, ParameterDirection.IN).clause( | ||
92 | personView.call(p) | ||
93 | ).build(); | ||
94 | |||
95 | var assertion = structurallyEqualTo(expected); | ||
96 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
97 | } | ||
98 | |||
99 | @Test | ||
100 | void clauseCountMismatchTest() { | ||
101 | var expected = Dnf.builder("Expected") | ||
102 | .parameters(p, q) | ||
103 | .clause(friendView.call(p, q)) | ||
104 | .clause(friendView.call(q, p)) | ||
105 | .build(); | ||
106 | var actual = Dnf.builder("Actual").parameters(p, q).clause( | ||
107 | friendView.call(p, q) | ||
108 | ).build(); | ||
109 | |||
110 | var assertion = structurallyEqualTo(expected); | ||
111 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
112 | } | ||
113 | |||
114 | @Test | ||
115 | void literalCountMismatchTest() { | ||
116 | var expected = Dnf.builder("Expected").parameters(p, q).clause( | ||
117 | friendView.call(p, q), | ||
118 | friendView.call(q, p) | ||
119 | ).build(); | ||
120 | var actual = Dnf.builder("Actual").parameters(p, q).clause( | ||
121 | friendView.call(p, q) | ||
122 | ).build(); | ||
123 | |||
124 | var assertion = structurallyEqualTo(expected); | ||
125 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
126 | } | ||
127 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java new file mode 100644 index 00000000..cbb48603 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java | |||
@@ -0,0 +1,634 @@ | |||
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 | */ | ||
7 | package tools.refinery.store.query.utils; | ||
8 | |||
9 | import org.junit.jupiter.api.BeforeEach; | ||
10 | import org.junit.jupiter.api.Test; | ||
11 | import org.junit.jupiter.params.ParameterizedTest; | ||
12 | import org.junit.jupiter.params.provider.Arguments; | ||
13 | import org.junit.jupiter.params.provider.MethodSource; | ||
14 | |||
15 | import java.util.*; | ||
16 | import java.util.stream.Stream; | ||
17 | |||
18 | import static org.junit.jupiter.api.Assertions.*; | ||
19 | |||
20 | /** | ||
21 | * Tests for an order statistic tree which is based on AVL-trees. | ||
22 | * <p> | ||
23 | * This class was copied into <i>Refinery</i> from | ||
24 | * <a href="https://github.com/coderodde/OrderStatisticTree/tree/546c343b9f5d868e394a079ff32691c9dbfd83e3">https://github.com/coderodde/OrderStatisticTree</a> | ||
25 | * and is available under the | ||
26 | * <a href="https://github.com/coderodde/OrderStatisticTree/blob/master/LICENSE">MIT License</a>. | ||
27 | * We also migrated the code to Junit 5, cleaned up some linter warnings, and made the tests deterministic by fixing | ||
28 | * the random seeds. | ||
29 | * | ||
30 | * @author Rodion "rodde" Efremov | ||
31 | * @version based on 1.6 (Feb 11, 2016) | ||
32 | */ | ||
33 | class OrderStatisticTreeTest { | ||
34 | private final OrderStatisticTree<Integer> tree = new OrderStatisticTree<>(); | ||
35 | |||
36 | private final TreeSet<Integer> set = new TreeSet<>(); | ||
37 | |||
38 | @BeforeEach | ||
39 | void before() { | ||
40 | tree.clear(); | ||
41 | set.clear(); | ||
42 | } | ||
43 | |||
44 | @Test | ||
45 | void testAdd() { | ||
46 | assertEquals(set.isEmpty(), tree.isEmpty()); | ||
47 | |||
48 | for (int i = 10; i < 30; i += 2) { | ||
49 | assertTrue(tree.isHealthy()); | ||
50 | assertEquals(set.contains(i), tree.contains(i)); | ||
51 | assertEquals(set.add(i), tree.add(i)); | ||
52 | assertEquals(set.add(i), tree.add(i)); | ||
53 | assertEquals(set.contains(i), tree.contains(i)); | ||
54 | assertTrue(tree.isHealthy()); | ||
55 | } | ||
56 | |||
57 | assertEquals(set.isEmpty(), tree.isEmpty()); | ||
58 | } | ||
59 | |||
60 | @Test | ||
61 | void testAddAll() { | ||
62 | for (int i = 0; i < 10; ++i) { | ||
63 | assertEquals(set.add(i), tree.add(i)); | ||
64 | } | ||
65 | |||
66 | Collection<Integer> coll = Arrays.asList(10, 9, 7, 11, 12); | ||
67 | |||
68 | assertEquals(set.addAll(coll), tree.addAll(coll)); | ||
69 | assertEquals(set.size(), tree.size()); | ||
70 | |||
71 | for (int i = -10; i < 20; ++i) { | ||
72 | assertEquals(set.contains(i), tree.contains(i)); | ||
73 | } | ||
74 | } | ||
75 | |||
76 | @Test | ||
77 | void testClear() { | ||
78 | for (int i = 0; i < 2000; ++i) { | ||
79 | set.add(i); | ||
80 | tree.add(i); | ||
81 | } | ||
82 | |||
83 | assertEquals(set.size(), tree.size()); | ||
84 | set.clear(); | ||
85 | tree.clear(); | ||
86 | // We expect {@code tree.size()} to always be 0, but we also test for it. | ||
87 | //noinspection ConstantValue | ||
88 | assertEquals(0, tree.size()); | ||
89 | } | ||
90 | |||
91 | @Test | ||
92 | void testContains() { | ||
93 | for (int i = 100; i < 200; i += 3) { | ||
94 | assertTrue(tree.isHealthy()); | ||
95 | assertEquals(set.add(i), tree.add(i)); | ||
96 | assertTrue(tree.isHealthy()); | ||
97 | } | ||
98 | |||
99 | assertEquals(set.size(), tree.size()); | ||
100 | |||
101 | for (int i = 0; i < 300; ++i) { | ||
102 | assertEquals(set.contains(i), tree.contains(i)); | ||
103 | } | ||
104 | } | ||
105 | |||
106 | @Test | ||
107 | void testContainsAll() { | ||
108 | for (int i = 0; i < 50; ++i) { | ||
109 | set.add(i); | ||
110 | tree.add(i); | ||
111 | } | ||
112 | |||
113 | Collection<Integer> coll = new HashSet<>(); | ||
114 | |||
115 | for (int i = 10; i < 20; ++i) { | ||
116 | coll.add(i); | ||
117 | } | ||
118 | |||
119 | assertEquals(set.containsAll(coll), tree.containsAll(coll)); | ||
120 | coll.add(100); | ||
121 | assertEquals(set.containsAll(coll), tree.containsAll(coll)); | ||
122 | } | ||
123 | |||
124 | @Test | ||
125 | void testRemove() { | ||
126 | for (int i = 0; i < 200; ++i) { | ||
127 | assertEquals(set.add(i), tree.add(i)); | ||
128 | } | ||
129 | |||
130 | for (int i = 50; i < 150; i += 2) { | ||
131 | assertEquals(set.remove(i), tree.remove(i)); | ||
132 | assertTrue(tree.isHealthy()); | ||
133 | } | ||
134 | |||
135 | for (int i = -100; i < 300; ++i) { | ||
136 | assertEquals(set.contains(i), tree.contains(i)); | ||
137 | } | ||
138 | } | ||
139 | |||
140 | @Test | ||
141 | void testRemoveLast() { | ||
142 | tree.add(1); | ||
143 | tree.remove(1); | ||
144 | assertEquals(0, tree.size()); | ||
145 | } | ||
146 | |||
147 | @Test | ||
148 | void testRemoveAll() { | ||
149 | for (int i = 0; i < 40; ++i) { | ||
150 | set.add(i); | ||
151 | tree.add(i); | ||
152 | } | ||
153 | |||
154 | Collection<Integer> coll = new HashSet<>(); | ||
155 | |||
156 | for (int i = 10; i < 20; ++i) { | ||
157 | coll.add(i); | ||
158 | } | ||
159 | |||
160 | assertEquals(set.removeAll(coll), tree.removeAll(coll)); | ||
161 | |||
162 | for (int i = -10; i < 50; ++i) { | ||
163 | assertEquals(set.contains(i), tree.contains(i)); | ||
164 | } | ||
165 | |||
166 | assertEquals(set.removeAll(coll), tree.removeAll(coll)); | ||
167 | |||
168 | for (int i = -10; i < 50; ++i) { | ||
169 | assertEquals(set.contains(i), tree.contains(i)); | ||
170 | } | ||
171 | } | ||
172 | |||
173 | @Test | ||
174 | void testSize() { | ||
175 | for (int i = 0; i < 200; ++i) { | ||
176 | assertEquals(set.size(), tree.size()); | ||
177 | assertEquals(set.add(i), tree.add(i)); | ||
178 | assertEquals(set.size(), tree.size()); | ||
179 | } | ||
180 | } | ||
181 | |||
182 | @Test | ||
183 | void testIndexOf() { | ||
184 | for (int i = 0; i < 100; ++i) { | ||
185 | assertTrue(tree.add(i * 2)); | ||
186 | } | ||
187 | |||
188 | for (int i = 0; i < 100; ++i) { | ||
189 | assertEquals(i, tree.indexOf(2 * i)); | ||
190 | } | ||
191 | |||
192 | for (int i = 100; i < 150; ++i) { | ||
193 | assertEquals(-1, tree.indexOf(2 * i)); | ||
194 | } | ||
195 | } | ||
196 | |||
197 | @Test | ||
198 | void testEmpty() { | ||
199 | assertEquals(set.isEmpty(), tree.isEmpty()); | ||
200 | set.add(0); | ||
201 | tree.add(0); | ||
202 | assertEquals(set.isEmpty(), tree.isEmpty()); | ||
203 | } | ||
204 | |||
205 | @Test | ||
206 | void testEmptyTreeGetThrowsOnNegativeIndex() { | ||
207 | assertThrows(IndexOutOfBoundsException.class, () -> tree.get(-1)); | ||
208 | } | ||
209 | |||
210 | @Test | ||
211 | void testEmptyTreeSelectThrowsOnTooLargeIndex() { | ||
212 | assertThrows(IndexOutOfBoundsException.class, () -> tree.get(0)); | ||
213 | } | ||
214 | |||
215 | @Test | ||
216 | void testSelectThrowsOnNegativeIndex() { | ||
217 | for (int i = 0; i < 5; ++i) { | ||
218 | tree.add(i); | ||
219 | } | ||
220 | |||
221 | assertThrows(IndexOutOfBoundsException.class, () -> tree.get(-1)); | ||
222 | } | ||
223 | |||
224 | @Test | ||
225 | void testSelectThrowsOnTooLargeIndex() { | ||
226 | for (int i = 0; i < 5; ++i) { | ||
227 | tree.add(i); | ||
228 | } | ||
229 | |||
230 | assertThrows(IndexOutOfBoundsException.class, () -> tree.get(5)); | ||
231 | } | ||
232 | |||
233 | @Test | ||
234 | void testGet() { | ||
235 | for (int i = 0; i < 100; i += 3) { | ||
236 | tree.add(i); | ||
237 | } | ||
238 | |||
239 | for (int i = 0; i < tree.size(); ++i) { | ||
240 | assertEquals(Integer.valueOf(3 * i), tree.get(i)); | ||
241 | } | ||
242 | } | ||
243 | |||
244 | @Test | ||
245 | void findBug() { | ||
246 | tree.add(0); | ||
247 | assertTrue(tree.isHealthy()); | ||
248 | |||
249 | tree.add(-1); | ||
250 | tree.remove(-1); | ||
251 | assertTrue(tree.isHealthy()); | ||
252 | |||
253 | tree.add(1); | ||
254 | tree.remove(1); | ||
255 | assertTrue(tree.isHealthy()); | ||
256 | |||
257 | tree.add(-1); | ||
258 | tree.add(1); | ||
259 | tree.remove(0); | ||
260 | assertTrue(tree.isHealthy()); | ||
261 | |||
262 | tree.clear(); | ||
263 | tree.add(0); | ||
264 | tree.add(-1); | ||
265 | tree.add(10); | ||
266 | tree.add(5); | ||
267 | tree.add(15); | ||
268 | tree.add(11); | ||
269 | tree.add(30); | ||
270 | tree.add(7); | ||
271 | |||
272 | tree.remove(-1); | ||
273 | |||
274 | assertTrue(tree.isHealthy()); | ||
275 | } | ||
276 | |||
277 | @ParameterizedTest(name = "seed = {0}") | ||
278 | @MethodSource("seedSource") | ||
279 | void tryReproduceTheCounterBug(long seed) { | ||
280 | Random random = new Random(seed); | ||
281 | List<Integer> list = new ArrayList<>(); | ||
282 | |||
283 | for (int i = 0; i < 10; ++i) { | ||
284 | int number = random.nextInt(1000); | ||
285 | list.add(number); | ||
286 | tree.add(number); | ||
287 | assertTrue(tree.isHealthy()); | ||
288 | } | ||
289 | |||
290 | for (Integer i : list) { | ||
291 | tree.remove(i); | ||
292 | boolean healthy = tree.isHealthy(); | ||
293 | assertTrue(healthy); | ||
294 | } | ||
295 | } | ||
296 | |||
297 | @Test | ||
298 | void testEmptyIterator() { | ||
299 | var iterator = tree.iterator(); | ||
300 | assertThrows(NoSuchElementException.class, iterator::next); | ||
301 | } | ||
302 | |||
303 | @Test | ||
304 | void testIteratorThrowsOnDoubleRemove() { | ||
305 | for (int i = 10; i < 20; ++i) { | ||
306 | set.add(i); | ||
307 | tree.add(i); | ||
308 | } | ||
309 | |||
310 | Iterator<Integer> iterator1 = set.iterator(); | ||
311 | Iterator<Integer> iterator2 = tree.iterator(); | ||
312 | |||
313 | for (int i = 0; i < 3; ++i) { | ||
314 | assertEquals(iterator1.next(), iterator2.next()); | ||
315 | } | ||
316 | |||
317 | iterator1.remove(); | ||
318 | iterator2.remove(); | ||
319 | |||
320 | assertThrows(IllegalStateException.class, iterator1::remove); | ||
321 | assertThrows(IllegalStateException.class, iterator2::remove); | ||
322 | } | ||
323 | |||
324 | @Test | ||
325 | void testIterator() { | ||
326 | for (int i = 0; i < 5; ++i) { | ||
327 | tree.add(i); | ||
328 | set.add(i); | ||
329 | } | ||
330 | |||
331 | Iterator<Integer> iterator1 = set.iterator(); | ||
332 | Iterator<Integer> iterator2 = tree.iterator(); | ||
333 | |||
334 | for (int i = 0; i < 5; ++i) { | ||
335 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
336 | assertEquals(iterator1.next(), iterator2.next()); | ||
337 | } | ||
338 | |||
339 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
340 | |||
341 | assertThrows(NoSuchElementException.class, iterator1::next); | ||
342 | assertThrows(NoSuchElementException.class, iterator2::next); | ||
343 | } | ||
344 | |||
345 | @Test | ||
346 | void testRemoveBeforeNextThrowsEmpty() { | ||
347 | var setIterator = set.iterator(); | ||
348 | assertThrows(IllegalStateException.class, setIterator::remove); | ||
349 | |||
350 | var treeIterator = tree.iterator(); | ||
351 | assertThrows(IllegalStateException.class, treeIterator::remove); | ||
352 | } | ||
353 | |||
354 | @Test | ||
355 | void testRemoveThrowsWithoutNext() { | ||
356 | for (int i = 0; i < 10; ++i) { | ||
357 | tree.add(i); | ||
358 | set.add(i); | ||
359 | } | ||
360 | |||
361 | Iterator<Integer> iterator1 = set.iterator(); | ||
362 | Iterator<Integer> iterator2 = tree.iterator(); | ||
363 | |||
364 | for (int i = 0; i < 4; ++i) { | ||
365 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
366 | assertEquals(iterator1.next(), iterator2.next()); | ||
367 | } | ||
368 | |||
369 | iterator1.remove(); | ||
370 | iterator2.remove(); | ||
371 | |||
372 | assertThrows(IllegalStateException.class, iterator1::remove); | ||
373 | assertThrows(IllegalStateException.class, iterator2::remove); | ||
374 | } | ||
375 | |||
376 | @Test | ||
377 | void testRetainAll() { | ||
378 | for (int i = 0; i < 100; ++i) { | ||
379 | set.add(i); | ||
380 | tree.add(i); | ||
381 | } | ||
382 | |||
383 | Collection<Integer> coll = Arrays.asList(26, 29, 25); | ||
384 | |||
385 | assertEquals(set.retainAll(coll), tree.retainAll(coll)); | ||
386 | assertEquals(set.size(), tree.size()); | ||
387 | |||
388 | assertTrue(set.containsAll(tree)); | ||
389 | assertTrue(tree.containsAll(set)); | ||
390 | } | ||
391 | |||
392 | @Test | ||
393 | void testIteratorRemove() { | ||
394 | for (int i = 10; i < 16; ++i) { | ||
395 | assertEquals(set.add(i), tree.add(i)); | ||
396 | } | ||
397 | |||
398 | Iterator<Integer> iterator1 = set.iterator(); | ||
399 | Iterator<Integer> iterator2 = tree.iterator(); | ||
400 | |||
401 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
402 | assertEquals(iterator1.next(), iterator2.next()); | ||
403 | |||
404 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
405 | assertEquals(iterator1.next(), iterator2.next()); | ||
406 | |||
407 | iterator1.remove(); // remove 11 | ||
408 | iterator2.remove(); | ||
409 | |||
410 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
411 | assertEquals(iterator1.next(), iterator2.next()); | ||
412 | |||
413 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
414 | assertEquals(iterator1.next(), iterator2.next()); | ||
415 | |||
416 | iterator1.remove(); // remove 13 | ||
417 | iterator2.remove(); | ||
418 | |||
419 | assertEquals(set.size(), tree.size()); | ||
420 | |||
421 | for (int i = 10; i < 16; ++i) { | ||
422 | assertEquals(set.contains(i), tree.contains(i)); | ||
423 | } | ||
424 | } | ||
425 | |||
426 | @ParameterizedTest(name = "seed = {0}") | ||
427 | @MethodSource("seedSource") | ||
428 | void testIteratorBruteForce(long seed) { | ||
429 | for (int i = 0; i < 1000; ++i) { | ||
430 | assertEquals(set.add(i), tree.add(i)); | ||
431 | } | ||
432 | |||
433 | Iterator<Integer> iterator1 = set.iterator(); | ||
434 | Iterator<Integer> iterator2 = tree.iterator(); | ||
435 | |||
436 | Random random = new Random(seed); | ||
437 | |||
438 | while (true) { | ||
439 | if (!iterator1.hasNext()) { | ||
440 | assertFalse(iterator2.hasNext()); | ||
441 | break; | ||
442 | } | ||
443 | |||
444 | boolean toRemove = random.nextBoolean(); | ||
445 | |||
446 | if (toRemove) { | ||
447 | boolean thrown = false; | ||
448 | |||
449 | try { | ||
450 | iterator1.remove(); | ||
451 | } catch (IllegalStateException ex) { | ||
452 | thrown = true; | ||
453 | } | ||
454 | |||
455 | if (thrown) { | ||
456 | assertThrows(IllegalStateException.class, iterator2::remove); | ||
457 | } else { | ||
458 | iterator2.remove(); | ||
459 | } | ||
460 | } else { | ||
461 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
462 | |||
463 | if (iterator1.hasNext()) { | ||
464 | assertEquals(iterator1.next(), iterator2.next()); | ||
465 | } else { | ||
466 | break; | ||
467 | } | ||
468 | } | ||
469 | } | ||
470 | |||
471 | assertEquals(set.size(), tree.size()); | ||
472 | assertTrue(tree.isHealthy()); | ||
473 | assertTrue(set.containsAll(tree)); | ||
474 | assertTrue(tree.containsAll(set)); | ||
475 | } | ||
476 | |||
477 | @Test | ||
478 | void testIteratorConcurrentModification() { | ||
479 | for (int i = 0; i < 100; ++i) { | ||
480 | set.add(i); | ||
481 | tree.add(i); | ||
482 | } | ||
483 | |||
484 | Iterator<Integer> iterator1 = set.iterator(); | ||
485 | Iterator<Integer> iterator2 = tree.iterator(); | ||
486 | |||
487 | set.remove(10); | ||
488 | tree.remove(10); | ||
489 | |||
490 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
491 | |||
492 | boolean thrown = false; | ||
493 | |||
494 | try { | ||
495 | iterator1.next(); | ||
496 | } catch (ConcurrentModificationException ex) { | ||
497 | thrown = true; | ||
498 | } | ||
499 | |||
500 | if (thrown) { | ||
501 | assertThrows(ConcurrentModificationException.class, iterator2::next); | ||
502 | } else { | ||
503 | iterator2.next(); | ||
504 | } | ||
505 | } | ||
506 | |||
507 | @Test | ||
508 | void testIteratorConcurrentRemove() { | ||
509 | for (int i = 10; i < 20; ++i) { | ||
510 | set.add(i); | ||
511 | tree.add(i); | ||
512 | } | ||
513 | |||
514 | Iterator<Integer> iterator1 = set.iterator(); | ||
515 | Iterator<Integer> iterator2 = tree.iterator(); | ||
516 | |||
517 | for (int i = 0; i < 4; ++i) { | ||
518 | iterator1.next(); | ||
519 | iterator2.next(); | ||
520 | } | ||
521 | |||
522 | // None of them contains 2, should not change the modification count. | ||
523 | set.remove(2); | ||
524 | tree.remove(2); | ||
525 | |||
526 | iterator1.remove(); | ||
527 | iterator2.remove(); | ||
528 | |||
529 | iterator1.next(); | ||
530 | iterator2.next(); | ||
531 | |||
532 | set.remove(12); | ||
533 | tree.remove(12); | ||
534 | |||
535 | // Both of them should throw. | ||
536 | assertThrows(ConcurrentModificationException.class, iterator1::remove); | ||
537 | assertThrows(ConcurrentModificationException.class, iterator2::remove); | ||
538 | } | ||
539 | |||
540 | @Test | ||
541 | void testConcurrentOrIllegalStateOnRemove() { | ||
542 | for (int i = 0; i < 10; ++i) { | ||
543 | set.add(i); | ||
544 | tree.add(i); | ||
545 | } | ||
546 | |||
547 | Iterator<Integer> iterator1 = set.iterator(); | ||
548 | Iterator<Integer> iterator2 = tree.iterator(); | ||
549 | |||
550 | set.add(100); | ||
551 | tree.add(100); | ||
552 | |||
553 | assertThrows(IllegalStateException.class, iterator1::remove); | ||
554 | assertThrows(IllegalStateException.class, iterator2::remove); | ||
555 | } | ||
556 | |||
557 | @Test | ||
558 | void testConcurrentIterators() { | ||
559 | for (int i = 0; i < 10; ++i) { | ||
560 | set.add(i); | ||
561 | tree.add(i); | ||
562 | } | ||
563 | |||
564 | Iterator<Integer> iterator1a = set.iterator(); | ||
565 | Iterator<Integer> iterator1b = set.iterator(); | ||
566 | Iterator<Integer> iterator2a = tree.iterator(); | ||
567 | Iterator<Integer> iterator2b = tree.iterator(); | ||
568 | |||
569 | for (int i = 0; i < 3; ++i) { | ||
570 | iterator1a.next(); | ||
571 | iterator2a.next(); | ||
572 | } | ||
573 | |||
574 | iterator1a.remove(); | ||
575 | iterator2a.remove(); | ||
576 | |||
577 | assertEquals(iterator1b.hasNext(), iterator2b.hasNext()); | ||
578 | |||
579 | assertThrows(ConcurrentModificationException.class, iterator1b::next); | ||
580 | assertThrows(ConcurrentModificationException.class, iterator2b::next); | ||
581 | } | ||
582 | |||
583 | @ParameterizedTest(name = "seed = {0}") | ||
584 | @MethodSource("seedSource") | ||
585 | void testToArray(long seed) { | ||
586 | Random r = new Random(seed); | ||
587 | |||
588 | for (int i = 0; i < 50; ++i) { | ||
589 | int num = r.nextInt(); | ||
590 | set.add(num); | ||
591 | tree.add(num); | ||
592 | } | ||
593 | |||
594 | assertArrayEquals(set.toArray(), tree.toArray()); | ||
595 | } | ||
596 | |||
597 | @Test | ||
598 | void testToArrayGeneric() { | ||
599 | for (int i = 0; i < 100; ++i) { | ||
600 | set.add(i); | ||
601 | tree.add(i); | ||
602 | } | ||
603 | |||
604 | Integer[] array1before = new Integer[99]; | ||
605 | Integer[] array2before = new Integer[99]; | ||
606 | |||
607 | Integer[] array1after = set.toArray(array1before); | ||
608 | Integer[] array2after = tree.toArray(array2before); | ||
609 | |||
610 | assertNotSame(array1before, array1after); | ||
611 | assertNotSame(array2before, array2after); | ||
612 | assertArrayEquals(array1after, array2after); | ||
613 | |||
614 | set.remove(1); | ||
615 | tree.remove(1); | ||
616 | |||
617 | array1after = set.toArray(array1before); | ||
618 | array2after = tree.toArray(array2before); | ||
619 | |||
620 | assertSame(array1before, array1after); | ||
621 | assertSame(array2before, array2after); | ||
622 | assertArrayEquals(array1after, array2after); | ||
623 | } | ||
624 | |||
625 | static Stream<Arguments> seedSource() { | ||
626 | return Stream.of( | ||
627 | Arguments.of(0L), | ||
628 | Arguments.of(1L), | ||
629 | Arguments.of(2L), | ||
630 | Arguments.of(3L), | ||
631 | Arguments.of(4L) | ||
632 | ); | ||
633 | } | ||
634 | } | ||
diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java new file mode 100644 index 00000000..6a3301b3 --- /dev/null +++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java | |||
@@ -0,0 +1,68 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.tests; | ||
7 | |||
8 | import org.hamcrest.Description; | ||
9 | import tools.refinery.store.query.dnf.Dnf; | ||
10 | import tools.refinery.store.query.dnf.SymbolicParameter; | ||
11 | import tools.refinery.store.query.equality.DeepDnfEqualityChecker; | ||
12 | import tools.refinery.store.query.literal.Literal; | ||
13 | |||
14 | import java.util.List; | ||
15 | |||
16 | class MismatchDescribingDnfEqualityChecker extends DeepDnfEqualityChecker { | ||
17 | private final Description description; | ||
18 | private boolean raw; | ||
19 | private boolean needsDescription = true; | ||
20 | |||
21 | MismatchDescribingDnfEqualityChecker(Description description) { | ||
22 | this.description = description; | ||
23 | } | ||
24 | |||
25 | public boolean needsDescription() { | ||
26 | return needsDescription; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public boolean dnfEqualRaw(List<SymbolicParameter> symbolicParameters, List<? extends List<? extends Literal>> clauses, Dnf other) { | ||
31 | try { | ||
32 | raw = true; | ||
33 | boolean result = super.dnfEqualRaw(symbolicParameters, clauses, other); | ||
34 | if (!result && needsDescription) { | ||
35 | description.appendText("was ").appendText(other.toDefinitionString()); | ||
36 | } | ||
37 | return false; | ||
38 | } finally { | ||
39 | raw = false; | ||
40 | } | ||
41 | } | ||
42 | |||
43 | @Override | ||
44 | protected boolean doCheckEqual(Pair pair) { | ||
45 | boolean result = super.doCheckEqual(pair); | ||
46 | if (!result && needsDescription) { | ||
47 | describeMismatch(pair); | ||
48 | // Only describe the first found (innermost) mismatch. | ||
49 | needsDescription = false; | ||
50 | } | ||
51 | return result; | ||
52 | } | ||
53 | |||
54 | private void describeMismatch(Pair pair) { | ||
55 | var inProgress = getInProgress(); | ||
56 | int size = inProgress.size(); | ||
57 | if (size <= 1 && !raw) { | ||
58 | description.appendText("was ").appendText(pair.right().toDefinitionString()); | ||
59 | return; | ||
60 | } | ||
61 | var last = inProgress.get(size - 1); | ||
62 | description.appendText("expected ").appendText(last.left().toDefinitionString()); | ||
63 | for (int i = size - 2; i >= 0; i--) { | ||
64 | description.appendText(" called from ").appendText(inProgress.get(i).left().toString()); | ||
65 | } | ||
66 | description.appendText(" was not structurally equal to ").appendText(last.right().toDefinitionString()); | ||
67 | } | ||
68 | } | ||
diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java new file mode 100644 index 00000000..cd449a6a --- /dev/null +++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java | |||
@@ -0,0 +1,46 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.tests; | ||
7 | |||
8 | import org.hamcrest.Matcher; | ||
9 | import tools.refinery.store.query.dnf.Dnf; | ||
10 | import tools.refinery.store.query.dnf.SymbolicParameter; | ||
11 | import tools.refinery.store.query.literal.Literal; | ||
12 | |||
13 | import java.util.List; | ||
14 | |||
15 | public final class QueryMatchers { | ||
16 | private QueryMatchers() { | ||
17 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
18 | } | ||
19 | |||
20 | /** | ||
21 | * Compare two {@link Dnf} instances up to renaming of variables. | ||
22 | * | ||
23 | * @param expected The expected {@link Dnf} instance. | ||
24 | * @return A Hamcrest matcher for equality up to renaming of variables. | ||
25 | */ | ||
26 | public static Matcher<Dnf> structurallyEqualTo(Dnf expected) { | ||
27 | return new StructurallyEqualTo(expected); | ||
28 | } | ||
29 | |||
30 | /** | ||
31 | * Compare a {@link Dnf} instance to another predicate in DNF form without constructing it. | ||
32 | * <p> | ||
33 | * This matcher should be used instead of {@link #structurallyEqualTo(Dnf)} when the validation and | ||
34 | * pre-processing associated with the {@link Dnf} constructor, i.e., validation of parameter directions, | ||
35 | * topological sorting of literals, and the reduction of trivial predicates is not desired. In particular, this | ||
36 | * matcher can be used to test for exact order of literal after pre-processing. | ||
37 | * | ||
38 | * @param expectedSymbolicParameters The expected list of symbolic parameters. | ||
39 | * @param expectedLiterals The expected clauses. Each clause is represented by a list of literals. | ||
40 | * @return A Hamcrest matcher for equality up to renaming of variables. | ||
41 | */ | ||
42 | public static Matcher<Dnf> structurallyEqualTo(List<SymbolicParameter> expectedSymbolicParameters, | ||
43 | List<? extends List<? extends Literal>> expectedLiterals) { | ||
44 | return new StructurallyEqualToRaw(expectedSymbolicParameters, expectedLiterals); | ||
45 | } | ||
46 | } | ||
diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java new file mode 100644 index 00000000..86149141 --- /dev/null +++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.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 | */ | ||
6 | package tools.refinery.store.query.tests; | ||
7 | |||
8 | import org.hamcrest.Description; | ||
9 | import org.hamcrest.TypeSafeMatcher; | ||
10 | import tools.refinery.store.query.dnf.Dnf; | ||
11 | import tools.refinery.store.query.equality.DeepDnfEqualityChecker; | ||
12 | |||
13 | public class StructurallyEqualTo extends TypeSafeMatcher<Dnf> { | ||
14 | private final Dnf expected; | ||
15 | |||
16 | public StructurallyEqualTo(Dnf expected) { | ||
17 | this.expected = expected; | ||
18 | } | ||
19 | |||
20 | @Override | ||
21 | protected boolean matchesSafely(Dnf item) { | ||
22 | var checker = new DeepDnfEqualityChecker(); | ||
23 | return checker.dnfEqual(expected, item); | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | protected void describeMismatchSafely(Dnf item, Description mismatchDescription) { | ||
28 | var describingChecker = new MismatchDescribingDnfEqualityChecker(mismatchDescription); | ||
29 | if (describingChecker.dnfEqual(expected, item)) { | ||
30 | throw new IllegalStateException("Mismatched Dnf was matching on repeated comparison"); | ||
31 | } | ||
32 | if (describingChecker.needsDescription()) { | ||
33 | super.describeMismatchSafely(item, mismatchDescription); | ||
34 | } | ||
35 | } | ||
36 | |||
37 | @Override | ||
38 | public void describeTo(Description description) { | ||
39 | description.appendText("structurally equal to ").appendText(expected.toDefinitionString()); | ||
40 | } | ||
41 | } | ||
diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualToRaw.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualToRaw.java new file mode 100644 index 00000000..2f8c2944 --- /dev/null +++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualToRaw.java | |||
@@ -0,0 +1,51 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.tests; | ||
7 | |||
8 | import org.hamcrest.Description; | ||
9 | import org.hamcrest.TypeSafeMatcher; | ||
10 | import tools.refinery.store.query.dnf.Dnf; | ||
11 | import tools.refinery.store.query.dnf.SymbolicParameter; | ||
12 | import tools.refinery.store.query.equality.DeepDnfEqualityChecker; | ||
13 | import tools.refinery.store.query.literal.Literal; | ||
14 | |||
15 | import java.util.List; | ||
16 | |||
17 | public class StructurallyEqualToRaw extends TypeSafeMatcher<Dnf> { | ||
18 | private final List<SymbolicParameter> expectedSymbolicParameters; | ||
19 | private final List<? extends List<? extends Literal>> expectedClauses; | ||
20 | |||
21 | public StructurallyEqualToRaw(List<SymbolicParameter> expectedSymbolicParameters, | ||
22 | List<? extends List<? extends Literal>> expectedClauses) { | ||
23 | this.expectedSymbolicParameters = expectedSymbolicParameters; | ||
24 | this.expectedClauses = expectedClauses; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | protected boolean matchesSafely(Dnf item) { | ||
29 | var checker = new DeepDnfEqualityChecker(); | ||
30 | return checker.dnfEqualRaw(expectedSymbolicParameters, expectedClauses, item); | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | protected void describeMismatchSafely(Dnf item, Description mismatchDescription) { | ||
35 | var describingChecker = new MismatchDescribingDnfEqualityChecker(mismatchDescription); | ||
36 | if (describingChecker.dnfEqualRaw(expectedSymbolicParameters, expectedClauses, item)) { | ||
37 | throw new IllegalStateException("Mismatched Dnf was matching on repeated comparison"); | ||
38 | } | ||
39 | if (describingChecker.needsDescription()) { | ||
40 | super.describeMismatchSafely(item, mismatchDescription); | ||
41 | } | ||
42 | } | ||
43 | |||
44 | @Override | ||
45 | public void describeTo(Description description) { | ||
46 | description.appendText("structurally equal to ") | ||
47 | .appendValueList("(", ", ", ")", expectedSymbolicParameters) | ||
48 | .appendText(" <-> ") | ||
49 | .appendValueList("", ", ", ".", expectedClauses); | ||
50 | } | ||
51 | } | ||