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/main/java | |
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/main/java')
155 files changed, 7679 insertions, 0 deletions
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java new file mode 100644 index 00000000..916fb35c --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java | |||
@@ -0,0 +1,72 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
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 | } | ||