diff options
Diffstat (limited to 'subprojects/store-query/src/main/java/tools/refinery')
64 files changed, 1533 insertions, 431 deletions
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/InvalidQueryException.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/InvalidQueryException.java new file mode 100644 index 00000000..c39277a0 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/InvalidQueryException.java | |||
@@ -0,0 +1,23 @@ | |||
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; | ||
7 | |||
8 | public class InvalidQueryException extends RuntimeException { | ||
9 | public InvalidQueryException() { | ||
10 | } | ||
11 | |||
12 | public InvalidQueryException(String message) { | ||
13 | super(message); | ||
14 | } | ||
15 | |||
16 | public InvalidQueryException(String message, Throwable cause) { | ||
17 | super(message, cause); | ||
18 | } | ||
19 | |||
20 | public InvalidQueryException(Throwable cause) { | ||
21 | super(cause); | ||
22 | } | ||
23 | } | ||
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 index c62a95b5..332e6381 100644 --- 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 | |||
@@ -8,6 +8,7 @@ package tools.refinery.store.query; | |||
8 | import tools.refinery.store.adapter.ModelAdapterBuilder; | 8 | import tools.refinery.store.adapter.ModelAdapterBuilder; |
9 | import tools.refinery.store.model.ModelStore; | 9 | import tools.refinery.store.model.ModelStore; |
10 | import tools.refinery.store.query.dnf.AnyQuery; | 10 | import tools.refinery.store.query.dnf.AnyQuery; |
11 | import tools.refinery.store.query.rewriter.DnfRewriter; | ||
11 | 12 | ||
12 | import java.util.Collection; | 13 | import java.util.Collection; |
13 | import java.util.List; | 14 | import java.util.List; |
@@ -25,6 +26,8 @@ public interface ModelQueryBuilder extends ModelAdapterBuilder { | |||
25 | 26 | ||
26 | ModelQueryBuilder query(AnyQuery query); | 27 | ModelQueryBuilder query(AnyQuery query); |
27 | 28 | ||
29 | ModelQueryBuilder rewriter(DnfRewriter rewriter); | ||
30 | |||
28 | @Override | 31 | @Override |
29 | ModelQueryStoreAdapter build(ModelStore store); | 32 | ModelQueryStoreAdapter build(ModelStore store); |
30 | } | 33 | } |
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 index f0a950a6..8b67c5c1 100644 --- 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 | |||
@@ -8,6 +8,7 @@ package tools.refinery.store.query; | |||
8 | import tools.refinery.store.adapter.ModelStoreAdapter; | 8 | import tools.refinery.store.adapter.ModelStoreAdapter; |
9 | import tools.refinery.store.model.Model; | 9 | import tools.refinery.store.model.Model; |
10 | import tools.refinery.store.query.dnf.AnyQuery; | 10 | import tools.refinery.store.query.dnf.AnyQuery; |
11 | import tools.refinery.store.query.dnf.Query; | ||
11 | import tools.refinery.store.query.view.AnySymbolView; | 12 | import tools.refinery.store.query.view.AnySymbolView; |
12 | 13 | ||
13 | import java.util.Collection; | 14 | import java.util.Collection; |
@@ -17,6 +18,12 @@ public interface ModelQueryStoreAdapter extends ModelStoreAdapter { | |||
17 | 18 | ||
18 | Collection<AnyQuery> getQueries(); | 19 | Collection<AnyQuery> getQueries(); |
19 | 20 | ||
21 | default AnyQuery getCanonicalQuery(AnyQuery query) { | ||
22 | return getCanonicalQuery((Query<?>) query); | ||
23 | } | ||
24 | |||
25 | <T> Query<T> getCanonicalQuery(Query<T> query); | ||
26 | |||
20 | @Override | 27 | @Override |
21 | ModelQueryAdapter createModelAdapter(Model model); | 28 | ModelQueryAdapter createModelAdapter(Model model); |
22 | } | 29 | } |
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 index b5e7092b..8800a155 100644 --- 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 | |||
@@ -6,13 +6,12 @@ | |||
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import org.jetbrains.annotations.NotNull; | 8 | import org.jetbrains.annotations.NotNull; |
9 | import tools.refinery.store.query.literal.BooleanLiteral; | 9 | import tools.refinery.store.query.Constraint; |
10 | import tools.refinery.store.query.literal.EquivalenceLiteral; | 10 | import tools.refinery.store.query.InvalidQueryException; |
11 | import tools.refinery.store.query.literal.Literal; | 11 | import tools.refinery.store.query.literal.*; |
12 | import tools.refinery.store.query.substitution.MapBasedSubstitution; | 12 | import tools.refinery.store.query.substitution.MapBasedSubstitution; |
13 | import tools.refinery.store.query.substitution.StatelessSubstitution; | 13 | import tools.refinery.store.query.substitution.StatelessSubstitution; |
14 | import tools.refinery.store.query.substitution.Substitution; | 14 | import tools.refinery.store.query.substitution.Substitution; |
15 | import tools.refinery.store.query.term.NodeVariable; | ||
16 | import tools.refinery.store.query.term.ParameterDirection; | 15 | import tools.refinery.store.query.term.ParameterDirection; |
17 | import tools.refinery.store.query.term.Variable; | 16 | import tools.refinery.store.query.term.Variable; |
18 | 17 | ||
@@ -22,8 +21,8 @@ import java.util.function.Function; | |||
22 | class ClausePostProcessor { | 21 | class ClausePostProcessor { |
23 | private final Map<Variable, ParameterInfo> parameters; | 22 | private final Map<Variable, ParameterInfo> parameters; |
24 | private final List<Literal> literals; | 23 | private final List<Literal> literals; |
25 | private final Map<NodeVariable, NodeVariable> representatives = new LinkedHashMap<>(); | 24 | private final Map<Variable, Variable> representatives = new LinkedHashMap<>(); |
26 | private final Map<NodeVariable, Set<NodeVariable>> equivalencePartition = new HashMap<>(); | 25 | private final Map<Variable, Set<Variable>> equivalencePartition = new HashMap<>(); |
27 | private List<Literal> substitutedLiterals; | 26 | private List<Literal> substitutedLiterals; |
28 | private final Set<Variable> existentiallyQuantifiedVariables = new LinkedHashSet<>(); | 27 | private final Set<Variable> existentiallyQuantifiedVariables = new LinkedHashSet<>(); |
29 | private Set<Variable> positiveVariables; | 28 | private Set<Variable> positiveVariables; |
@@ -58,6 +57,9 @@ class ClausePostProcessor { | |||
58 | if (filteredLiterals.isEmpty()) { | 57 | if (filteredLiterals.isEmpty()) { |
59 | return ConstantResult.ALWAYS_TRUE; | 58 | return ConstantResult.ALWAYS_TRUE; |
60 | } | 59 | } |
60 | if (hasContradictoryCall(filteredLiterals)) { | ||
61 | return ConstantResult.ALWAYS_FALSE; | ||
62 | } | ||
61 | var clause = new DnfClause(Collections.unmodifiableSet(positiveVariables), | 63 | var clause = new DnfClause(Collections.unmodifiableSet(positiveVariables), |
62 | Collections.unmodifiableList(filteredLiterals)); | 64 | Collections.unmodifiableList(filteredLiterals)); |
63 | return new ClauseResult(clause); | 65 | return new ClauseResult(clause); |
@@ -67,16 +69,16 @@ class ClausePostProcessor { | |||
67 | for (var literal : literals) { | 69 | for (var literal : literals) { |
68 | if (isPositiveEquivalence(literal)) { | 70 | if (isPositiveEquivalence(literal)) { |
69 | var equivalenceLiteral = (EquivalenceLiteral) literal; | 71 | var equivalenceLiteral = (EquivalenceLiteral) literal; |
70 | mergeVariables(equivalenceLiteral.left(), equivalenceLiteral.right()); | 72 | mergeVariables(equivalenceLiteral.getLeft(), equivalenceLiteral.getRight()); |
71 | } | 73 | } |
72 | } | 74 | } |
73 | } | 75 | } |
74 | 76 | ||
75 | private static boolean isPositiveEquivalence(Literal literal) { | 77 | private static boolean isPositiveEquivalence(Literal literal) { |
76 | return literal instanceof EquivalenceLiteral equivalenceLiteral && equivalenceLiteral.positive(); | 78 | return literal instanceof EquivalenceLiteral equivalenceLiteral && equivalenceLiteral.isPositive(); |
77 | } | 79 | } |
78 | 80 | ||
79 | private void mergeVariables(NodeVariable left, NodeVariable right) { | 81 | private void mergeVariables(Variable left, Variable right) { |
80 | var leftRepresentative = getRepresentative(left); | 82 | var leftRepresentative = getRepresentative(left); |
81 | var rightRepresentative = getRepresentative(right); | 83 | var rightRepresentative = getRepresentative(right); |
82 | var leftInfo = parameters.get(leftRepresentative); | 84 | var leftInfo = parameters.get(leftRepresentative); |
@@ -89,7 +91,7 @@ class ClausePostProcessor { | |||
89 | } | 91 | } |
90 | } | 92 | } |
91 | 93 | ||
92 | private void doMergeVariables(NodeVariable parentRepresentative, NodeVariable newChildRepresentative) { | 94 | private void doMergeVariables(Variable parentRepresentative, Variable newChildRepresentative) { |
93 | var parentSet = getEquivalentVariables(parentRepresentative); | 95 | var parentSet = getEquivalentVariables(parentRepresentative); |
94 | var childSet = getEquivalentVariables(newChildRepresentative); | 96 | var childSet = getEquivalentVariables(newChildRepresentative); |
95 | parentSet.addAll(childSet); | 97 | parentSet.addAll(childSet); |
@@ -99,18 +101,18 @@ class ClausePostProcessor { | |||
99 | } | 101 | } |
100 | } | 102 | } |
101 | 103 | ||
102 | private NodeVariable getRepresentative(NodeVariable variable) { | 104 | private Variable getRepresentative(Variable variable) { |
103 | return representatives.computeIfAbsent(variable, Function.identity()); | 105 | return representatives.computeIfAbsent(variable, Function.identity()); |
104 | } | 106 | } |
105 | 107 | ||
106 | private Set<NodeVariable> getEquivalentVariables(NodeVariable variable) { | 108 | private Set<Variable> getEquivalentVariables(Variable variable) { |
107 | var representative = getRepresentative(variable); | 109 | var representative = getRepresentative(variable); |
108 | if (!representative.equals(variable)) { | 110 | if (!representative.equals(variable)) { |
109 | throw new AssertionError("NodeVariable %s already has a representative %s" | 111 | throw new AssertionError("NodeVariable %s already has a representative %s" |
110 | .formatted(variable, representative)); | 112 | .formatted(variable, representative)); |
111 | } | 113 | } |
112 | return equivalencePartition.computeIfAbsent(variable, key -> { | 114 | return equivalencePartition.computeIfAbsent(variable, key -> { |
113 | var set = new HashSet<NodeVariable>(1); | 115 | var set = new HashSet<Variable>(1); |
114 | set.add(key); | 116 | set.add(key); |
115 | return set; | 117 | return set; |
116 | }); | 118 | }); |
@@ -121,7 +123,7 @@ class ClausePostProcessor { | |||
121 | var left = pair.getKey(); | 123 | var left = pair.getKey(); |
122 | var right = pair.getValue(); | 124 | var right = pair.getValue(); |
123 | if (!left.equals(right) && parameters.containsKey(left) && parameters.containsKey(right)) { | 125 | if (!left.equals(right) && parameters.containsKey(left) && parameters.containsKey(right)) { |
124 | substitutedLiterals.add(left.isEquivalent(right)); | 126 | substitutedLiterals.add(new EquivalenceLiteral(true, left, right)); |
125 | } | 127 | } |
126 | } | 128 | } |
127 | } | 129 | } |
@@ -147,20 +149,7 @@ class ClausePostProcessor { | |||
147 | 149 | ||
148 | private void computeExistentiallyQuantifiedVariables() { | 150 | private void computeExistentiallyQuantifiedVariables() { |
149 | for (var literal : substitutedLiterals) { | 151 | for (var literal : substitutedLiterals) { |
150 | for (var variable : literal.getOutputVariables()) { | 152 | existentiallyQuantifiedVariables.addAll(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 | } | 153 | } |
165 | } | 154 | } |
166 | 155 | ||
@@ -172,7 +161,7 @@ class ClausePostProcessor { | |||
172 | // Inputs count as positive, because they are already bound when we evaluate literals. | 161 | // Inputs count as positive, because they are already bound when we evaluate literals. |
173 | positiveVariables.add(variable); | 162 | positiveVariables.add(variable); |
174 | } else if (!existentiallyQuantifiedVariables.contains(variable)) { | 163 | } else if (!existentiallyQuantifiedVariables.contains(variable)) { |
175 | throw new IllegalArgumentException("Unbound %s parameter %s" | 164 | throw new InvalidQueryException("Unbound %s parameter %s" |
176 | .formatted(ParameterDirection.OUT, variable)); | 165 | .formatted(ParameterDirection.OUT, variable)); |
177 | } | 166 | } |
178 | } | 167 | } |
@@ -184,7 +173,7 @@ class ClausePostProcessor { | |||
184 | var representative = pair.getKey(); | 173 | var representative = pair.getKey(); |
185 | if (!positiveVariables.contains(representative)) { | 174 | if (!positiveVariables.contains(representative)) { |
186 | var variableSet = pair.getValue(); | 175 | var variableSet = pair.getValue(); |
187 | throw new IllegalArgumentException("Variables %s were merged by equivalence but are not bound" | 176 | throw new InvalidQueryException("Variables %s were merged by equivalence but are not bound" |
188 | .formatted(variableSet)); | 177 | .formatted(variableSet)); |
189 | } | 178 | } |
190 | } | 179 | } |
@@ -196,7 +185,7 @@ class ClausePostProcessor { | |||
196 | for (var variable : literal.getPrivateVariables(positiveVariables)) { | 185 | for (var variable : literal.getPrivateVariables(positiveVariables)) { |
197 | var oldLiteral = negativeVariablesMap.put(variable, literal); | 186 | var oldLiteral = negativeVariablesMap.put(variable, literal); |
198 | if (oldLiteral != null) { | 187 | if (oldLiteral != null) { |
199 | throw new IllegalArgumentException("Unbound variable %s appears in multiple literals %s and %s" | 188 | throw new InvalidQueryException("Unbound variable %s appears in multiple literals %s and %s" |
200 | .formatted(variable, oldLiteral, literal)); | 189 | .formatted(variable, oldLiteral, literal)); |
201 | } | 190 | } |
202 | } | 191 | } |
@@ -218,11 +207,60 @@ class ClausePostProcessor { | |||
218 | variable.addToSortedLiterals(); | 207 | variable.addToSortedLiterals(); |
219 | } | 208 | } |
220 | if (!variableToLiteralInputMap.isEmpty()) { | 209 | if (!variableToLiteralInputMap.isEmpty()) { |
221 | throw new IllegalArgumentException("Unbound input variables %s" | 210 | throw new InvalidQueryException("Unbound input variables %s" |
222 | .formatted(variableToLiteralInputMap.keySet())); | 211 | .formatted(variableToLiteralInputMap.keySet())); |
223 | } | 212 | } |
224 | } | 213 | } |
225 | 214 | ||
215 | private boolean hasContradictoryCall(Collection<Literal> filteredLiterals) { | ||
216 | var positiveCalls = new HashMap<Constraint, Set<CallLiteral>>(); | ||
217 | for (var literal : filteredLiterals) { | ||
218 | if (literal instanceof CallLiteral callLiteral && callLiteral.getPolarity() == CallPolarity.POSITIVE) { | ||
219 | var callsOfTarget = positiveCalls.computeIfAbsent(callLiteral.getTarget(), key -> new HashSet<>()); | ||
220 | callsOfTarget.add(callLiteral); | ||
221 | } | ||
222 | } | ||
223 | for (var literal : filteredLiterals) { | ||
224 | if (literal instanceof CallLiteral callLiteral && callLiteral.getPolarity() == CallPolarity.NEGATIVE) { | ||
225 | var callsOfTarget = positiveCalls.get(callLiteral.getTarget()); | ||
226 | if (contradicts(callLiteral, callsOfTarget)) { | ||
227 | return true; | ||
228 | } | ||
229 | } | ||
230 | } | ||
231 | return false; | ||
232 | } | ||
233 | |||
234 | private boolean contradicts(CallLiteral negativeCall, Collection<CallLiteral> positiveCalls) { | ||
235 | if (positiveCalls == null) { | ||
236 | return false; | ||
237 | } | ||
238 | for (var positiveCall : positiveCalls) { | ||
239 | if (contradicts(negativeCall, positiveCall)) { | ||
240 | return true; | ||
241 | } | ||
242 | } | ||
243 | return false; | ||
244 | } | ||
245 | |||
246 | private boolean contradicts(CallLiteral negativeCall, CallLiteral positiveCall) { | ||
247 | var privateVariables = negativeCall.getPrivateVariables(positiveVariables); | ||
248 | var negativeArguments = negativeCall.getArguments(); | ||
249 | var positiveArguments = positiveCall.getArguments(); | ||
250 | int arity = negativeArguments.size(); | ||
251 | for (int i = 0; i < arity; i++) { | ||
252 | var negativeArgument = negativeArguments.get(i); | ||
253 | if (privateVariables.contains(negativeArgument)) { | ||
254 | continue; | ||
255 | } | ||
256 | var positiveArgument = positiveArguments.get(i); | ||
257 | if (!negativeArgument.equals(positiveArgument)) { | ||
258 | return false; | ||
259 | } | ||
260 | } | ||
261 | return true; | ||
262 | } | ||
263 | |||
226 | private class SortableLiteral implements Comparable<SortableLiteral> { | 264 | private class SortableLiteral implements Comparable<SortableLiteral> { |
227 | private final int index; | 265 | private final int index; |
228 | private final Literal literal; | 266 | private final Literal literal; |
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 index 50b245f7..86a1b6b2 100644 --- 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 | |||
@@ -6,9 +6,12 @@ | |||
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.literal.Reduction; | 9 | import tools.refinery.store.query.InvalidQueryException; |
10 | import tools.refinery.store.query.equality.DnfEqualityChecker; | 10 | import tools.refinery.store.query.equality.DnfEqualityChecker; |
11 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 11 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
12 | import tools.refinery.store.query.equality.SubstitutingLiteralEqualityHelper; | ||
13 | import tools.refinery.store.query.equality.SubstitutingLiteralHashCodeHelper; | ||
14 | import tools.refinery.store.query.literal.Reduction; | ||
12 | import tools.refinery.store.query.term.Parameter; | 15 | import tools.refinery.store.query.term.Parameter; |
13 | import tools.refinery.store.query.term.Variable; | 16 | import tools.refinery.store.query.term.Variable; |
14 | 17 | ||
@@ -53,7 +56,7 @@ public final class Dnf implements Constraint { | |||
53 | FunctionalDependency<Variable> functionalDependency) { | 56 | FunctionalDependency<Variable> functionalDependency) { |
54 | for (var variable : toValidate) { | 57 | for (var variable : toValidate) { |
55 | if (!parameterSet.contains(variable)) { | 58 | if (!parameterSet.contains(variable)) { |
56 | throw new IllegalArgumentException( | 59 | throw new InvalidQueryException( |
57 | "Variable %s of functional dependency %s does not appear in the parameter list %s" | 60 | "Variable %s of functional dependency %s does not appear in the parameter list %s" |
58 | .formatted(variable, functionalDependency, symbolicParameters)); | 61 | .formatted(variable, functionalDependency, symbolicParameters)); |
59 | } | 62 | } |
@@ -66,7 +69,7 @@ public final class Dnf implements Constraint { | |||
66 | } | 69 | } |
67 | 70 | ||
68 | public boolean isExplicitlyNamed() { | 71 | public boolean isExplicitlyNamed() { |
69 | return name == null; | 72 | return name != null; |
70 | } | 73 | } |
71 | 74 | ||
72 | public String getUniqueName() { | 75 | public String getUniqueName() { |
@@ -129,7 +132,7 @@ public final class Dnf implements Constraint { | |||
129 | return false; | 132 | return false; |
130 | } | 133 | } |
131 | for (int i = 0; i < numClauses; i++) { | 134 | for (int i = 0; i < numClauses; i++) { |
132 | var literalEqualityHelper = new LiteralEqualityHelper(callEqualityChecker, symbolicParameters, | 135 | var literalEqualityHelper = new SubstitutingLiteralEqualityHelper(callEqualityChecker, symbolicParameters, |
133 | other.symbolicParameters); | 136 | other.symbolicParameters); |
134 | if (!clauses.get(i).equalsWithSubstitution(literalEqualityHelper, other.clauses.get(i))) { | 137 | if (!clauses.get(i).equalsWithSubstitution(literalEqualityHelper, other.clauses.get(i))) { |
135 | return false; | 138 | return false; |
@@ -146,6 +149,18 @@ public final class Dnf implements Constraint { | |||
146 | return false; | 149 | return false; |
147 | } | 150 | } |
148 | 151 | ||
152 | public int hashCodeWithSubstitution() { | ||
153 | var helper = new SubstitutingLiteralHashCodeHelper(); | ||
154 | int result = 0; | ||
155 | for (var symbolicParameter : symbolicParameters) { | ||
156 | result = result * 31 + symbolicParameter.hashCodeWithSubstitution(helper); | ||
157 | } | ||
158 | for (var clause : clauses) { | ||
159 | result = result * 31 + clause.hashCodeWithSubstitution(helper); | ||
160 | } | ||
161 | return result; | ||
162 | } | ||
163 | |||
149 | @Override | 164 | @Override |
150 | public String toString() { | 165 | public String toString() { |
151 | return "%s/%d".formatted(name(), arity()); | 166 | return "%s/%d".formatted(name(), arity()); |
@@ -201,6 +216,13 @@ public final class Dnf implements Constraint { | |||
201 | return new DnfBuilder(name); | 216 | return new DnfBuilder(name); |
202 | } | 217 | } |
203 | 218 | ||
219 | public static DnfBuilder builderFrom(Dnf original) { | ||
220 | var builder = builder(original.name()); | ||
221 | builder.symbolicParameters(original.getSymbolicParameters()); | ||
222 | builder.functionalDependencies(original.getFunctionalDependencies()); | ||
223 | return builder; | ||
224 | } | ||
225 | |||
204 | public static Dnf of(Consumer<DnfBuilder> callback) { | 226 | public static Dnf of(Consumer<DnfBuilder> callback) { |
205 | return of(null, callback); | 227 | return of(null, callback); |
206 | } | 228 | } |
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 index 8e38ca6b..0f9fd366 100644 --- 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 | |||
@@ -5,12 +5,10 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.dnf.callback.*; | 9 | import tools.refinery.store.query.dnf.callback.*; |
9 | import tools.refinery.store.query.literal.Literal; | 10 | import tools.refinery.store.query.literal.Literal; |
10 | import tools.refinery.store.query.term.DataVariable; | 11 | import tools.refinery.store.query.term.*; |
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 | 12 | ||
15 | import java.util.*; | 13 | import java.util.*; |
16 | 14 | ||
@@ -62,6 +60,18 @@ public final class DnfBuilder { | |||
62 | return variable; | 60 | return variable; |
63 | } | 61 | } |
64 | 62 | ||
63 | public Variable parameter(Parameter parameter) { | ||
64 | return parameter(null, parameter); | ||
65 | } | ||
66 | |||
67 | public Variable parameter(String name, Parameter parameter) { | ||
68 | var type = parameter.tryGetType(); | ||
69 | if (type.isPresent()) { | ||
70 | return parameter(name, type.get(), parameter.getDirection()); | ||
71 | } | ||
72 | return parameter(name, parameter.getDirection()); | ||
73 | } | ||
74 | |||
65 | public DnfBuilder parameter(Variable variable) { | 75 | public DnfBuilder parameter(Variable variable) { |
66 | return parameter(variable, ParameterDirection.OUT); | 76 | return parameter(variable, ParameterDirection.OUT); |
67 | } | 77 | } |
@@ -88,7 +98,7 @@ public final class DnfBuilder { | |||
88 | public DnfBuilder symbolicParameter(SymbolicParameter symbolicParameter) { | 98 | public DnfBuilder symbolicParameter(SymbolicParameter symbolicParameter) { |
89 | var variable = symbolicParameter.getVariable(); | 99 | var variable = symbolicParameter.getVariable(); |
90 | if (!parameterVariables.add(variable)) { | 100 | if (!parameterVariables.add(variable)) { |
91 | throw new IllegalArgumentException("Variable %s is already on the parameter list %s" | 101 | throw new InvalidQueryException("Variable %s is already on the parameter list %s" |
92 | .formatted(variable, parameters)); | 102 | .formatted(variable, parameters)); |
93 | } | 103 | } |
94 | parameters.add(symbolicParameter); | 104 | parameters.add(symbolicParameter); |
@@ -129,7 +139,7 @@ public final class DnfBuilder { | |||
129 | } | 139 | } |
130 | 140 | ||
131 | public <T> DnfBuilder clause(Class<T> type1, ClauseCallback1Data1<T> callback) { | 141 | public <T> DnfBuilder clause(Class<T> type1, ClauseCallback1Data1<T> callback) { |
132 | return clause(callback.toLiterals(Variable.of("v1", type1))); | 142 | return clause(callback.toLiterals(Variable.of("d1", type1))); |
133 | } | 143 | } |
134 | 144 | ||
135 | public DnfBuilder clause(ClauseCallback2Data0 callback) { | 145 | public DnfBuilder clause(ClauseCallback2Data0 callback) { |
@@ -206,57 +216,10 @@ public final class DnfBuilder { | |||
206 | } | 216 | } |
207 | 217 | ||
208 | public Dnf build() { | 218 | public Dnf build() { |
209 | var postProcessedClauses = postProcessClauses(); | 219 | var postProcessor = new DnfPostProcessor(parameters, clauses); |
220 | var postProcessedClauses = postProcessor.postProcessClauses(); | ||
210 | return new Dnf(name, Collections.unmodifiableList(parameters), | 221 | return new Dnf(name, Collections.unmodifiableList(parameters), |
211 | Collections.unmodifiableList(functionalDependencies), | 222 | Collections.unmodifiableList(functionalDependencies), |
212 | Collections.unmodifiableList(postProcessedClauses)); | 223 | Collections.unmodifiableList(postProcessedClauses)); |
213 | } | 224 | } |
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 | } | 225 | } |
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 index fdd0d47c..94327bad 100644 --- 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 | |||
@@ -6,6 +6,7 @@ | |||
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.literal.Literal; | 10 | import tools.refinery.store.query.literal.Literal; |
10 | import tools.refinery.store.query.term.Variable; | 11 | import tools.refinery.store.query.term.Variable; |
11 | 12 | ||
@@ -25,4 +26,12 @@ public record DnfClause(Set<Variable> positiveVariables, List<Literal> literals) | |||
25 | } | 26 | } |
26 | return true; | 27 | return true; |
27 | } | 28 | } |
29 | |||
30 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { | ||
31 | int result = 0; | ||
32 | for (var literal : literals) { | ||
33 | result = result * 31 + literal.hashCodeWithSubstitution(helper); | ||
34 | } | ||
35 | return result; | ||
36 | } | ||
28 | } | 37 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfPostProcessor.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfPostProcessor.java new file mode 100644 index 00000000..50236642 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfPostProcessor.java | |||
@@ -0,0 +1,112 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.DnfEqualityChecker; | ||
10 | import tools.refinery.store.query.equality.SubstitutingLiteralEqualityHelper; | ||
11 | import tools.refinery.store.query.equality.SubstitutingLiteralHashCodeHelper; | ||
12 | import tools.refinery.store.query.literal.Literal; | ||
13 | import tools.refinery.store.query.term.ParameterDirection; | ||
14 | import tools.refinery.store.query.term.Variable; | ||
15 | |||
16 | import java.util.*; | ||
17 | |||
18 | class DnfPostProcessor { | ||
19 | private final List<SymbolicParameter> parameters; | ||
20 | private final List<List<Literal>> clauses; | ||
21 | |||
22 | public DnfPostProcessor(List<SymbolicParameter> parameters, List<List<Literal>> clauses) { | ||
23 | this.parameters = parameters; | ||
24 | this.clauses = clauses; | ||
25 | } | ||
26 | |||
27 | public List<DnfClause> postProcessClauses() { | ||
28 | var parameterInfoMap = getParameterInfoMap(); | ||
29 | var postProcessedClauses = new LinkedHashSet<CanonicalClause>(clauses.size()); | ||
30 | int index = 0; | ||
31 | for (var literals : clauses) { | ||
32 | var postProcessor = new ClausePostProcessor(parameterInfoMap, literals); | ||
33 | ClausePostProcessor.Result result; | ||
34 | try { | ||
35 | result = postProcessor.postProcessClause(); | ||
36 | } catch (InvalidQueryException e) { | ||
37 | throw new InvalidClauseException(index, e); | ||
38 | } | ||
39 | if (result instanceof ClausePostProcessor.ClauseResult clauseResult) { | ||
40 | postProcessedClauses.add(new CanonicalClause(clauseResult.clause())); | ||
41 | } else if (result instanceof ClausePostProcessor.ConstantResult constantResult) { | ||
42 | switch (constantResult) { | ||
43 | case ALWAYS_TRUE -> { | ||
44 | var inputVariables = getInputVariables(); | ||
45 | return List.of(new DnfClause(inputVariables, List.of())); | ||
46 | } | ||
47 | case ALWAYS_FALSE -> { | ||
48 | // Skip this clause because it can never match. | ||
49 | } | ||
50 | default -> throw new IllegalStateException("Unexpected ClausePostProcessor.ConstantResult: " + | ||
51 | constantResult); | ||
52 | } | ||
53 | } else { | ||
54 | throw new IllegalStateException("Unexpected ClausePostProcessor.Result: " + result); | ||
55 | } | ||
56 | index++; | ||
57 | } | ||
58 | return postProcessedClauses.stream().map(CanonicalClause::getDnfClause).toList(); | ||
59 | } | ||
60 | |||
61 | private Map<Variable, ClausePostProcessor.ParameterInfo> getParameterInfoMap() { | ||
62 | var mutableParameterInfoMap = new LinkedHashMap<Variable, ClausePostProcessor.ParameterInfo>(); | ||
63 | int arity = parameters.size(); | ||
64 | for (int i = 0; i < arity; i++) { | ||
65 | var parameter = parameters.get(i); | ||
66 | mutableParameterInfoMap.put(parameter.getVariable(), | ||
67 | new ClausePostProcessor.ParameterInfo(parameter.getDirection(), i)); | ||
68 | } | ||
69 | return Collections.unmodifiableMap(mutableParameterInfoMap); | ||
70 | } | ||
71 | |||
72 | private Set<Variable> getInputVariables() { | ||
73 | var inputParameters = new LinkedHashSet<Variable>(); | ||
74 | for (var parameter : parameters) { | ||
75 | if (parameter.getDirection() == ParameterDirection.IN) { | ||
76 | inputParameters.add(parameter.getVariable()); | ||
77 | } | ||
78 | } | ||
79 | return Collections.unmodifiableSet(inputParameters); | ||
80 | } | ||
81 | |||
82 | private class CanonicalClause { | ||
83 | private final DnfClause dnfClause; | ||
84 | |||
85 | public CanonicalClause(DnfClause dnfClause) { | ||
86 | this.dnfClause = dnfClause; | ||
87 | } | ||
88 | |||
89 | public DnfClause getDnfClause() { | ||
90 | return dnfClause; | ||
91 | } | ||
92 | |||
93 | @Override | ||
94 | public boolean equals(Object obj) { | ||
95 | if (this == obj) { | ||
96 | return true; | ||
97 | } | ||
98 | if (obj == null || getClass() != obj.getClass()) { | ||
99 | return false; | ||
100 | } | ||
101 | var otherCanonicalClause = (CanonicalClause) obj; | ||
102 | var helper = new SubstitutingLiteralEqualityHelper(DnfEqualityChecker.DEFAULT, parameters, parameters); | ||
103 | return dnfClause.equalsWithSubstitution(helper, otherCanonicalClause.dnfClause); | ||
104 | } | ||
105 | |||
106 | @Override | ||
107 | public int hashCode() { | ||
108 | var helper = new SubstitutingLiteralHashCodeHelper(parameters); | ||
109 | return dnfClause.hashCodeWithSubstitution(helper); | ||
110 | } | ||
111 | } | ||
112 | } | ||
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 index b00b2cb7..aef07ee3 100644 --- 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 | |||
@@ -5,6 +5,8 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | |||
8 | import java.util.HashSet; | 10 | import java.util.HashSet; |
9 | import java.util.Set; | 11 | import java.util.Set; |
10 | 12 | ||
@@ -13,7 +15,7 @@ public record FunctionalDependency<T>(Set<T> forEach, Set<T> unique) { | |||
13 | var uniqueForEach = new HashSet<>(unique); | 15 | var uniqueForEach = new HashSet<>(unique); |
14 | uniqueForEach.retainAll(forEach); | 16 | uniqueForEach.retainAll(forEach); |
15 | if (!uniqueForEach.isEmpty()) { | 17 | if (!uniqueForEach.isEmpty()) { |
16 | throw new IllegalArgumentException("Variables %s appear on both sides of the functional dependency" | 18 | throw new InvalidQueryException("Variables %s appear on both sides of the functional dependency" |
17 | .formatted(uniqueForEach)); | 19 | .formatted(uniqueForEach)); |
18 | } | 20 | } |
19 | } | 21 | } |
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 index 5a32b1ba..225f6844 100644 --- 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 | |||
@@ -5,6 +5,7 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.literal.CallPolarity; | 9 | import tools.refinery.store.query.literal.CallPolarity; |
9 | import tools.refinery.store.query.term.Aggregator; | 10 | import tools.refinery.store.query.term.Aggregator; |
10 | import tools.refinery.store.query.term.AssignedValue; | 11 | import tools.refinery.store.query.term.AssignedValue; |
@@ -26,14 +27,14 @@ public final class FunctionalQuery<T> extends Query<T> { | |||
26 | var parameter = parameters.get(i); | 27 | var parameter = parameters.get(i); |
27 | var parameterType = parameter.tryGetType(); | 28 | var parameterType = parameter.tryGetType(); |
28 | if (parameterType.isPresent()) { | 29 | if (parameterType.isPresent()) { |
29 | throw new IllegalArgumentException("Expected parameter %s of %s to be a node variable, got %s instead" | 30 | throw new InvalidQueryException("Expected parameter %s of %s to be a node variable, got %s instead" |
30 | .formatted(parameter, dnf, parameterType.get().getName())); | 31 | .formatted(parameter, dnf, parameterType.get().getName())); |
31 | } | 32 | } |
32 | } | 33 | } |
33 | var outputParameter = parameters.get(outputIndex); | 34 | var outputParameter = parameters.get(outputIndex); |
34 | var outputParameterType = outputParameter.tryGetType(); | 35 | var outputParameterType = outputParameter.tryGetType(); |
35 | if (outputParameterType.isEmpty() || !outputParameterType.get().equals(type)) { | 36 | 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 | throw new InvalidQueryException("Expected parameter %s of %s to be %s, but got %s instead".formatted( |
37 | outputParameter, dnf, type, outputParameterType.map(Class::getName).orElse("node"))); | 38 | outputParameter, dnf, type, outputParameterType.map(Class::getName).orElse("node"))); |
38 | } | 39 | } |
39 | this.type = type; | 40 | this.type = type; |
@@ -54,6 +55,16 @@ public final class FunctionalQuery<T> extends Query<T> { | |||
54 | return null; | 55 | return null; |
55 | } | 56 | } |
56 | 57 | ||
58 | @Override | ||
59 | protected FunctionalQuery<T> withDnfInternal(Dnf newDnf) { | ||
60 | return newDnf.asFunction(type); | ||
61 | } | ||
62 | |||
63 | @Override | ||
64 | public FunctionalQuery<T> withDnf(Dnf newDnf) { | ||
65 | return (FunctionalQuery<T>) super.withDnf(newDnf); | ||
66 | } | ||
67 | |||
57 | public AssignedValue<T> call(List<NodeVariable> arguments) { | 68 | public AssignedValue<T> call(List<NodeVariable> arguments) { |
58 | return targetVariable -> { | 69 | return targetVariable -> { |
59 | var argumentsWithTarget = new ArrayList<Variable>(arguments.size() + 1); | 70 | var argumentsWithTarget = new ArrayList<Variable>(arguments.size() + 1); |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/InvalidClauseException.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/InvalidClauseException.java new file mode 100644 index 00000000..747574b9 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/InvalidClauseException.java | |||
@@ -0,0 +1,35 @@ | |||
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.InvalidQueryException; | ||
9 | |||
10 | public class InvalidClauseException extends InvalidQueryException { | ||
11 | private final int clauseIndex; | ||
12 | |||
13 | public InvalidClauseException(int clauseIndex) { | ||
14 | this.clauseIndex = clauseIndex; | ||
15 | } | ||
16 | |||
17 | public InvalidClauseException(int clauseIndex, String message) { | ||
18 | super(message); | ||
19 | this.clauseIndex = clauseIndex; | ||
20 | } | ||
21 | |||
22 | public InvalidClauseException(int clauseIndex, String message, Throwable cause) { | ||
23 | super(message, cause); | ||
24 | this.clauseIndex = clauseIndex; | ||
25 | } | ||
26 | |||
27 | public InvalidClauseException(int clauseIndex, Throwable cause) { | ||
28 | super(cause); | ||
29 | this.clauseIndex = clauseIndex; | ||
30 | } | ||
31 | |||
32 | public int getClauseIndex() { | ||
33 | return clauseIndex; | ||
34 | } | ||
35 | } | ||
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 index aaa52ce6..83fe6ccd 100644 --- 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 | |||
@@ -43,6 +43,29 @@ public abstract sealed class Query<T> implements AnyQuery permits FunctionalQuer | |||
43 | 43 | ||
44 | public abstract T defaultValue(); | 44 | public abstract T defaultValue(); |
45 | 45 | ||
46 | public Query<T> withDnf(Dnf newDnf) { | ||
47 | if (dnf.equals(newDnf)) { | ||
48 | return this; | ||
49 | } | ||
50 | int arity = dnf.arity(); | ||
51 | if (newDnf.arity() != arity) { | ||
52 | throw new IllegalArgumentException("Arity of %s and %s do not match".formatted(dnf, newDnf)); | ||
53 | } | ||
54 | var parameters = dnf.getParameters(); | ||
55 | var newParameters = newDnf.getParameters(); | ||
56 | for (int i = 0; i < arity; i++) { | ||
57 | var parameter = parameters.get(i); | ||
58 | var newParameter = newParameters.get(i); | ||
59 | if (!parameter.matches(newParameter)) { | ||
60 | throw new IllegalArgumentException("Parameter #%d mismatch: %s does not match %s" | ||
61 | .formatted(i, parameter, newParameter)); | ||
62 | } | ||
63 | } | ||
64 | return withDnfInternal(newDnf); | ||
65 | } | ||
66 | |||
67 | protected abstract Query<T> withDnfInternal(Dnf newDnf); | ||
68 | |||
46 | @Override | 69 | @Override |
47 | public boolean equals(Object o) { | 70 | public boolean equals(Object o) { |
48 | if (this == o) return true; | 71 | if (this == o) return true; |
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 index d34a7ace..98f71e11 100644 --- 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 | |||
@@ -5,6 +5,7 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.literal.CallLiteral; | 9 | import tools.refinery.store.query.literal.CallLiteral; |
9 | import tools.refinery.store.query.literal.CallPolarity; | 10 | import tools.refinery.store.query.literal.CallPolarity; |
10 | import tools.refinery.store.query.term.AssignedValue; | 11 | import tools.refinery.store.query.term.AssignedValue; |
@@ -19,7 +20,7 @@ public final class RelationalQuery extends Query<Boolean> { | |||
19 | for (var parameter : dnf.getSymbolicParameters()) { | 20 | for (var parameter : dnf.getSymbolicParameters()) { |
20 | var parameterType = parameter.tryGetType(); | 21 | var parameterType = parameter.tryGetType(); |
21 | if (parameterType.isPresent()) { | 22 | if (parameterType.isPresent()) { |
22 | throw new IllegalArgumentException("Expected parameter %s of %s to be a node variable, got %s instead" | 23 | throw new InvalidQueryException("Expected parameter %s of %s to be a node variable, got %s instead" |
23 | .formatted(parameter, dnf, parameterType.get().getName())); | 24 | .formatted(parameter, dnf, parameterType.get().getName())); |
24 | } | 25 | } |
25 | } | 26 | } |
@@ -40,6 +41,16 @@ public final class RelationalQuery extends Query<Boolean> { | |||
40 | return false; | 41 | return false; |
41 | } | 42 | } |
42 | 43 | ||
44 | @Override | ||
45 | protected RelationalQuery withDnfInternal(Dnf newDnf) { | ||
46 | return newDnf.asRelation(); | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public RelationalQuery withDnf(Dnf newDnf) { | ||
51 | return (RelationalQuery) super.withDnf(newDnf); | ||
52 | } | ||
53 | |||
43 | public CallLiteral call(CallPolarity polarity, List<NodeVariable> arguments) { | 54 | public CallLiteral call(CallPolarity polarity, List<NodeVariable> arguments) { |
44 | return getDnf().call(polarity, Collections.unmodifiableList(arguments)); | 55 | return getDnf().call(polarity, Collections.unmodifiableList(arguments)); |
45 | } | 56 | } |
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 index e0d3ba1f..fe9cefcc 100644 --- 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 | |||
@@ -5,6 +5,7 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
8 | import tools.refinery.store.query.term.Parameter; | 9 | import tools.refinery.store.query.term.Parameter; |
9 | import tools.refinery.store.query.term.ParameterDirection; | 10 | import tools.refinery.store.query.term.ParameterDirection; |
10 | import tools.refinery.store.query.term.Variable; | 11 | import tools.refinery.store.query.term.Variable; |
@@ -23,8 +24,8 @@ public final class SymbolicParameter extends Parameter { | |||
23 | return variable; | 24 | return variable; |
24 | } | 25 | } |
25 | 26 | ||
26 | public boolean isUnifiable() { | 27 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { |
27 | return variable.isUnifiable(); | 28 | return Objects.hash(super.hashCode(), helper.getVariableHashCode(variable)); |
28 | } | 29 | } |
29 | 30 | ||
30 | @Override | 31 | @Override |
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 index 1eeb5723..d6171314 100644 --- 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 | |||
@@ -14,8 +14,7 @@ import tools.refinery.store.util.CycleDetectingMapper; | |||
14 | import java.util.List; | 14 | import java.util.List; |
15 | 15 | ||
16 | public class DeepDnfEqualityChecker implements DnfEqualityChecker { | 16 | public class DeepDnfEqualityChecker implements DnfEqualityChecker { |
17 | private final CycleDetectingMapper<Pair, Boolean> mapper = new CycleDetectingMapper<>(Pair::toString, | 17 | private final CycleDetectingMapper<Pair, Boolean> mapper = new CycleDetectingMapper<>(this::doCheckEqual); |
18 | this::doCheckEqual); | ||
19 | 18 | ||
20 | @Override | 19 | @Override |
21 | public boolean dnfEqual(Dnf left, Dnf right) { | 20 | public boolean dnfEqual(Dnf left, Dnf right) { |
@@ -38,7 +37,7 @@ public class DeepDnfEqualityChecker implements DnfEqualityChecker { | |||
38 | return false; | 37 | return false; |
39 | } | 38 | } |
40 | for (int i = 0; i < numClauses; i++) { | 39 | for (int i = 0; i < numClauses; i++) { |
41 | var literalEqualityHelper = new LiteralEqualityHelper(this, symbolicParameters, | 40 | var literalEqualityHelper = new SubstitutingLiteralEqualityHelper(this, symbolicParameters, |
42 | other.getSymbolicParameters()); | 41 | other.getSymbolicParameters()); |
43 | if (!equalsWithSubstitutionRaw(literalEqualityHelper, clauses.get(i), other.getClauses().get(i))) { | 42 | if (!equalsWithSubstitutionRaw(literalEqualityHelper, clauses.get(i), other.getClauses().get(i))) { |
44 | return false; | 43 | return false; |
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 index 4a8bee3b..e2cfd79b 100644 --- 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 | |||
@@ -7,7 +7,11 @@ package tools.refinery.store.query.equality; | |||
7 | 7 | ||
8 | import tools.refinery.store.query.dnf.Dnf; | 8 | import tools.refinery.store.query.dnf.Dnf; |
9 | 9 | ||
10 | import java.util.Objects; | ||
11 | |||
10 | @FunctionalInterface | 12 | @FunctionalInterface |
11 | public interface DnfEqualityChecker { | 13 | public interface DnfEqualityChecker { |
14 | DnfEqualityChecker DEFAULT = Objects::equals; | ||
15 | |||
12 | boolean dnfEqual(Dnf left, Dnf right); | 16 | boolean dnfEqual(Dnf left, Dnf right); |
13 | } | 17 | } |
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 index 9315fb30..5abc76ce 100644 --- 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 | |||
@@ -6,49 +6,22 @@ | |||
6 | package tools.refinery.store.query.equality; | 6 | package tools.refinery.store.query.equality; |
7 | 7 | ||
8 | import tools.refinery.store.query.dnf.Dnf; | 8 | import tools.refinery.store.query.dnf.Dnf; |
9 | import tools.refinery.store.query.dnf.SymbolicParameter; | ||
10 | import tools.refinery.store.query.term.Variable; | 9 | import tools.refinery.store.query.term.Variable; |
11 | 10 | ||
12 | import java.util.HashMap; | 11 | import java.util.Objects; |
13 | import java.util.List; | ||
14 | import java.util.Map; | ||
15 | 12 | ||
16 | public class LiteralEqualityHelper { | 13 | public interface LiteralEqualityHelper extends DnfEqualityChecker { |
17 | private final DnfEqualityChecker dnfEqualityChecker; | 14 | LiteralEqualityHelper DEFAULT = new LiteralEqualityHelper() { |
18 | private final Map<Variable, Variable> leftToRight; | 15 | @Override |
19 | private final Map<Variable, Variable> rightToLeft; | 16 | public boolean variableEqual(Variable left, Variable right) { |
20 | 17 | return Objects.equals(left, right); | |
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 | } | 18 | } |
35 | } | ||
36 | |||
37 | public boolean dnfEqual(Dnf left, Dnf right) { | ||
38 | return dnfEqualityChecker.dnfEqual(left, right); | ||
39 | } | ||
40 | 19 | ||
41 | public boolean variableEqual(Variable left, Variable right) { | 20 | @Override |
42 | if (checkMapping(leftToRight, left, right) && checkMapping(rightToLeft, right, left)) { | 21 | public boolean dnfEqual(Dnf left, Dnf right) { |
43 | leftToRight.put(left, right); | 22 | return DnfEqualityChecker.DEFAULT.dnfEqual(left, right); |
44 | rightToLeft.put(right, left); | ||
45 | return true; | ||
46 | } | 23 | } |
47 | return false; | 24 | }; |
48 | } | ||
49 | 25 | ||
50 | private static boolean checkMapping(Map<Variable, Variable> map, Variable key, Variable expectedValue) { | 26 | boolean variableEqual(Variable left, Variable right); |
51 | var currentValue = map.get(key); | ||
52 | return currentValue == null || currentValue.equals(expectedValue); | ||
53 | } | ||
54 | } | 27 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralHashCodeHelper.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralHashCodeHelper.java new file mode 100644 index 00000000..5495160a --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralHashCodeHelper.java | |||
@@ -0,0 +1,17 @@ | |||
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.equality; | ||
7 | |||
8 | import tools.refinery.store.query.term.Variable; | ||
9 | |||
10 | import java.util.Objects; | ||
11 | |||
12 | @FunctionalInterface | ||
13 | public interface LiteralHashCodeHelper { | ||
14 | LiteralHashCodeHelper DEFAULT = Objects::hashCode; | ||
15 | |||
16 | int getVariableHashCode(Variable variable); | ||
17 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralEqualityHelper.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralEqualityHelper.java new file mode 100644 index 00000000..50a79e07 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralEqualityHelper.java | |||
@@ -0,0 +1,59 @@ | |||
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.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 SubstitutingLiteralEqualityHelper implements LiteralEqualityHelper { | ||
17 | private final DnfEqualityChecker dnfEqualityChecker; | ||
18 | private final Map<Variable, Variable> leftToRight; | ||
19 | private final Map<Variable, Variable> rightToLeft; | ||
20 | |||
21 | public SubstitutingLiteralEqualityHelper(DnfEqualityChecker dnfEqualityChecker, | ||
22 | List<SymbolicParameter> leftParameters, | ||
23 | List<SymbolicParameter> rightParameters) { | ||
24 | this.dnfEqualityChecker = dnfEqualityChecker; | ||
25 | var arity = leftParameters.size(); | ||
26 | if (arity != rightParameters.size()) { | ||
27 | throw new IllegalArgumentException("Parameter lists have unequal length"); | ||
28 | } | ||
29 | leftToRight = new HashMap<>(arity); | ||
30 | rightToLeft = new HashMap<>(arity); | ||
31 | for (int i = 0; i < arity; i++) { | ||
32 | if (!variableEqual(leftParameters.get(i).getVariable(), rightParameters.get(i).getVariable())) { | ||
33 | throw new IllegalArgumentException("Parameter lists cannot be unified: duplicate parameter " + i); | ||
34 | } | ||
35 | } | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public boolean dnfEqual(Dnf left, Dnf right) { | ||
40 | return dnfEqualityChecker.dnfEqual(left, right); | ||
41 | } | ||
42 | |||
43 | @Override | ||
44 | public boolean variableEqual(Variable left, Variable right) { | ||
45 | if (left.tryGetType().equals(right.tryGetType()) && | ||
46 | checkMapping(leftToRight, left, right) && | ||
47 | checkMapping(rightToLeft, right, left)) { | ||
48 | leftToRight.put(left, right); | ||
49 | rightToLeft.put(right, left); | ||
50 | return true; | ||
51 | } | ||
52 | return false; | ||
53 | } | ||
54 | |||
55 | private static boolean checkMapping(Map<Variable, Variable> map, Variable key, Variable expectedValue) { | ||
56 | var currentValue = map.get(key); | ||
57 | return currentValue == null || currentValue.equals(expectedValue); | ||
58 | } | ||
59 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralHashCodeHelper.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralHashCodeHelper.java new file mode 100644 index 00000000..754f6976 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralHashCodeHelper.java | |||
@@ -0,0 +1,42 @@ | |||
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.equality; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.SymbolicParameter; | ||
9 | import tools.refinery.store.query.term.Variable; | ||
10 | |||
11 | import java.util.LinkedHashMap; | ||
12 | import java.util.List; | ||
13 | import java.util.Map; | ||
14 | |||
15 | public class SubstitutingLiteralHashCodeHelper implements LiteralHashCodeHelper { | ||
16 | private final Map<Variable, Integer> assignedHashCodes = new LinkedHashMap<>(); | ||
17 | |||
18 | // 0 is for {@code null}, so we start with 1. | ||
19 | private int next = 1; | ||
20 | |||
21 | public SubstitutingLiteralHashCodeHelper() { | ||
22 | this(List.of()); | ||
23 | } | ||
24 | |||
25 | public SubstitutingLiteralHashCodeHelper(List<SymbolicParameter> parameters) { | ||
26 | for (var parameter : parameters) { | ||
27 | getVariableHashCode(parameter.getVariable()); | ||
28 | } | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public int getVariableHashCode(Variable variable) { | ||
33 | if (variable == null) { | ||
34 | return 0; | ||
35 | } | ||
36 | return assignedHashCodes.computeIfAbsent(variable, key -> { | ||
37 | int sequenceNumber = next; | ||
38 | next++; | ||
39 | return variable.hashCodeWithSubstitution(sequenceNumber); | ||
40 | }); | ||
41 | } | ||
42 | } | ||
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 index 8ef8e8b4..0e99d441 100644 --- 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 | |||
@@ -6,14 +6,18 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | 12 | import tools.refinery.store.query.substitution.Substitution; |
11 | import tools.refinery.store.query.term.ParameterDirection; | 13 | import tools.refinery.store.query.term.ParameterDirection; |
12 | import tools.refinery.store.query.term.Variable; | 14 | import tools.refinery.store.query.term.Variable; |
13 | 15 | ||
14 | import java.util.*; | 16 | import java.util.*; |
15 | 17 | ||
16 | public abstract class AbstractCallLiteral implements Literal { | 18 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. |
19 | @SuppressWarnings("squid:S2160") | ||
20 | public abstract class AbstractCallLiteral extends AbstractLiteral { | ||
17 | private final Constraint target; | 21 | private final Constraint target; |
18 | private final List<Variable> arguments; | 22 | private final List<Variable> arguments; |
19 | private final Set<Variable> inArguments; | 23 | private final Set<Variable> inArguments; |
@@ -24,7 +28,7 @@ public abstract class AbstractCallLiteral implements Literal { | |||
24 | protected AbstractCallLiteral(Constraint target, List<Variable> arguments) { | 28 | protected AbstractCallLiteral(Constraint target, List<Variable> arguments) { |
25 | int arity = target.arity(); | 29 | int arity = target.arity(); |
26 | if (arguments.size() != arity) { | 30 | if (arguments.size() != arity) { |
27 | throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.name(), | 31 | throw new InvalidQueryException("%s needs %d arguments, but got %s".formatted(target.name(), |
28 | target.arity(), arguments.size())); | 32 | target.arity(), arguments.size())); |
29 | } | 33 | } |
30 | this.target = target; | 34 | this.target = target; |
@@ -36,21 +40,17 @@ public abstract class AbstractCallLiteral implements Literal { | |||
36 | var argument = arguments.get(i); | 40 | var argument = arguments.get(i); |
37 | var parameter = parameters.get(i); | 41 | var parameter = parameters.get(i); |
38 | if (!parameter.isAssignable(argument)) { | 42 | if (!parameter.isAssignable(argument)) { |
39 | throw new IllegalArgumentException("Argument %d of %s is not assignable to parameter %s" | 43 | throw new InvalidQueryException("Argument %d of %s is not assignable to parameter %s" |
40 | .formatted(i, target, parameter)); | 44 | .formatted(i, target, parameter)); |
41 | } | 45 | } |
42 | switch (parameter.getDirection()) { | 46 | switch (parameter.getDirection()) { |
43 | case IN -> { | 47 | case IN -> { |
44 | if (mutableOutArguments.remove(argument)) { | 48 | mutableOutArguments.remove(argument); |
45 | checkInOutUnifiable(argument); | ||
46 | } | ||
47 | mutableInArguments.add(argument); | 49 | mutableInArguments.add(argument); |
48 | } | 50 | } |
49 | case OUT -> { | 51 | case OUT -> { |
50 | if (mutableInArguments.contains(argument)) { | 52 | if (!mutableInArguments.contains(argument)) { |
51 | checkInOutUnifiable(argument); | 53 | mutableOutArguments.add(argument); |
52 | } else if (!mutableOutArguments.add(argument)) { | ||
53 | checkDuplicateOutUnifiable(argument); | ||
54 | } | 54 | } |
55 | } | 55 | } |
56 | } | 56 | } |
@@ -59,19 +59,6 @@ public abstract class AbstractCallLiteral implements Literal { | |||
59 | outArguments = Collections.unmodifiableSet(mutableOutArguments); | 59 | outArguments = Collections.unmodifiableSet(mutableOutArguments); |
60 | } | 60 | } |
61 | 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() { | 62 | public Constraint getTarget() { |
76 | return target; | 63 | return target; |
77 | } | 64 | } |
@@ -110,9 +97,18 @@ public abstract class AbstractCallLiteral implements Literal { | |||
110 | 97 | ||
111 | protected abstract Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments); | 98 | protected abstract Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments); |
112 | 99 | ||
100 | public AbstractCallLiteral withTarget(Constraint newTarget) { | ||
101 | if (Objects.equals(target, newTarget)) { | ||
102 | return this; | ||
103 | } | ||
104 | return withArguments(newTarget, arguments); | ||
105 | } | ||
106 | |||
107 | public abstract AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments); | ||
108 | |||
113 | @Override | 109 | @Override |
114 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | 110 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { |
115 | if (other == null || getClass() != other.getClass()) { | 111 | if (!super.equalsWithSubstitution(helper, other)) { |
116 | return false; | 112 | return false; |
117 | } | 113 | } |
118 | var otherCallLiteral = (AbstractCallLiteral) other; | 114 | var otherCallLiteral = (AbstractCallLiteral) other; |
@@ -129,15 +125,11 @@ public abstract class AbstractCallLiteral implements Literal { | |||
129 | } | 125 | } |
130 | 126 | ||
131 | @Override | 127 | @Override |
132 | public boolean equals(Object o) { | 128 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { |
133 | if (this == o) return true; | 129 | int result = super.hashCodeWithSubstitution(helper) * 31 + target.hashCode(); |
134 | if (o == null || getClass() != o.getClass()) return false; | 130 | for (var argument : arguments) { |
135 | AbstractCallLiteral that = (AbstractCallLiteral) o; | 131 | result = result * 31 + helper.getVariableHashCode(argument); |
136 | return target.equals(that.target) && arguments.equals(that.arguments); | 132 | } |
137 | } | 133 | return result; |
138 | |||
139 | @Override | ||
140 | public int hashCode() { | ||
141 | return Objects.hash(getClass(), target, arguments); | ||
142 | } | 134 | } |
143 | } | 135 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCountLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCountLiteral.java new file mode 100644 index 00000000..9bb572c0 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCountLiteral.java | |||
@@ -0,0 +1,107 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.InvalidQueryException; | ||
10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
12 | import tools.refinery.store.query.term.ConstantTerm; | ||
13 | import tools.refinery.store.query.term.DataVariable; | ||
14 | import tools.refinery.store.query.term.Variable; | ||
15 | |||
16 | import java.util.List; | ||
17 | import java.util.Objects; | ||
18 | import java.util.Set; | ||
19 | |||
20 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. | ||
21 | @SuppressWarnings("squid:S2160") | ||
22 | public abstract class AbstractCountLiteral<T> extends AbstractCallLiteral { | ||
23 | private final Class<T> resultType; | ||
24 | private final DataVariable<T> resultVariable; | ||
25 | |||
26 | protected AbstractCountLiteral(Class<T> resultType, DataVariable<T> resultVariable, Constraint target, | ||
27 | List<Variable> arguments) { | ||
28 | super(target, arguments); | ||
29 | if (!resultVariable.getType().equals(resultType)) { | ||
30 | throw new InvalidQueryException("Count result variable %s must be of type %s, got %s instead".formatted( | ||
31 | resultVariable, resultType, resultVariable.getType().getName())); | ||
32 | } | ||
33 | if (arguments.contains(resultVariable)) { | ||
34 | throw new InvalidQueryException("Count result variable %s must not appear in the argument list" | ||
35 | .formatted(resultVariable)); | ||
36 | } | ||
37 | this.resultType = resultType; | ||
38 | this.resultVariable = resultVariable; | ||
39 | } | ||
40 | |||
41 | public Class<T> getResultType() { | ||
42 | return resultType; | ||
43 | } | ||
44 | |||
45 | public DataVariable<T> getResultVariable() { | ||
46 | return resultVariable; | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public Set<Variable> getOutputVariables() { | ||
51 | return Set.of(resultVariable); | ||
52 | } | ||
53 | |||
54 | protected abstract T zero(); | ||
55 | |||
56 | protected abstract T one(); | ||
57 | |||
58 | @Override | ||
59 | public Literal reduce() { | ||
60 | var reduction = getTarget().getReduction(); | ||
61 | return switch (reduction) { | ||
62 | case ALWAYS_FALSE -> getResultVariable().assign(new ConstantTerm<>(resultType, zero())); | ||
63 | // The only way a constant {@code true} predicate can be called in a negative position is to have all of | ||
64 | // its arguments bound as input variables. Thus, there will only be a single match. | ||
65 | case ALWAYS_TRUE -> getResultVariable().assign(new ConstantTerm<>(resultType, one())); | ||
66 | case NOT_REDUCIBLE -> this; | ||
67 | }; | ||
68 | } | ||
69 | |||
70 | @Override | ||
71 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | ||
72 | if (!super.equalsWithSubstitution(helper, other)) { | ||
73 | return false; | ||
74 | } | ||
75 | var otherCountLiteral = (AbstractCountLiteral<?>) other; | ||
76 | return Objects.equals(resultType, otherCountLiteral.resultType) && | ||
77 | helper.variableEqual(resultVariable, otherCountLiteral.resultVariable); | ||
78 | } | ||
79 | |||
80 | @Override | ||
81 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { | ||
82 | return Objects.hash(super.hashCodeWithSubstitution(helper), resultType, | ||
83 | helper.getVariableHashCode(resultVariable)); | ||
84 | } | ||
85 | |||
86 | protected abstract String operatorName(); | ||
87 | |||
88 | @Override | ||
89 | public String toString() { | ||
90 | var builder = new StringBuilder(); | ||
91 | builder.append(resultVariable); | ||
92 | builder.append(" is "); | ||
93 | builder.append(operatorName()); | ||
94 | builder.append(' '); | ||
95 | builder.append(getTarget().toReferenceString()); | ||
96 | builder.append('('); | ||
97 | var argumentIterator = getArguments().iterator(); | ||
98 | if (argumentIterator.hasNext()) { | ||
99 | builder.append(argumentIterator.next()); | ||
100 | while (argumentIterator.hasNext()) { | ||
101 | builder.append(", ").append(argumentIterator.next()); | ||
102 | } | ||
103 | } | ||
104 | builder.append(')'); | ||
105 | return builder.toString(); | ||
106 | } | ||
107 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractLiteral.java new file mode 100644 index 00000000..7d3cabd7 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractLiteral.java | |||
@@ -0,0 +1,34 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
10 | |||
11 | public abstract class AbstractLiteral implements Literal { | ||
12 | @Override | ||
13 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | ||
14 | return other != null && getClass() == other.getClass(); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { | ||
19 | return getClass().hashCode(); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public boolean equals(Object o) { | ||
24 | if (this == o) return true; | ||
25 | if (o == null || getClass() != o.getClass()) return false; | ||
26 | AbstractLiteral that = (AbstractLiteral) o; | ||
27 | return equalsWithSubstitution(LiteralEqualityHelper.DEFAULT, that); | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public int hashCode() { | ||
32 | return hashCodeWithSubstitution(LiteralHashCodeHelper.DEFAULT); | ||
33 | } | ||
34 | } | ||
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 index 3a5eb5c7..e3acfacc 100644 --- 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 | |||
@@ -6,7 +6,9 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | 12 | import tools.refinery.store.query.substitution.Substitution; |
11 | import tools.refinery.store.query.term.*; | 13 | import tools.refinery.store.query.term.*; |
12 | 14 | ||
@@ -14,6 +16,8 @@ import java.util.List; | |||
14 | import java.util.Objects; | 16 | import java.util.Objects; |
15 | import java.util.Set; | 17 | import java.util.Set; |
16 | 18 | ||
19 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. | ||
20 | @SuppressWarnings("squid:S2160") | ||
17 | public class AggregationLiteral<R, T> extends AbstractCallLiteral { | 21 | public class AggregationLiteral<R, T> extends AbstractCallLiteral { |
18 | private final DataVariable<R> resultVariable; | 22 | private final DataVariable<R> resultVariable; |
19 | private final DataVariable<T> inputVariable; | 23 | private final DataVariable<T> inputVariable; |
@@ -23,19 +27,19 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral { | |||
23 | DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) { | 27 | DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) { |
24 | super(target, arguments); | 28 | super(target, arguments); |
25 | if (!inputVariable.getType().equals(aggregator.getInputType())) { | 29 | if (!inputVariable.getType().equals(aggregator.getInputType())) { |
26 | throw new IllegalArgumentException("Input variable %s must of type %s, got %s instead".formatted( | 30 | throw new InvalidQueryException("Input variable %s must of type %s, got %s instead".formatted( |
27 | inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName())); | 31 | inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName())); |
28 | } | 32 | } |
29 | if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(inputVariable)) { | 33 | if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(inputVariable)) { |
30 | throw new IllegalArgumentException("Input variable %s must be bound with direction %s in the argument list" | 34 | throw new InvalidQueryException("Input variable %s must be bound with direction %s in the argument list" |
31 | .formatted(inputVariable, ParameterDirection.OUT)); | 35 | .formatted(inputVariable, ParameterDirection.OUT)); |
32 | } | 36 | } |
33 | if (!resultVariable.getType().equals(aggregator.getResultType())) { | 37 | if (!resultVariable.getType().equals(aggregator.getResultType())) { |
34 | throw new IllegalArgumentException("Result variable %s must of type %s, got %s instead".formatted( | 38 | throw new InvalidQueryException("Result variable %s must of type %s, got %s instead".formatted( |
35 | resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName())); | 39 | resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName())); |
36 | } | 40 | } |
37 | if (arguments.contains(resultVariable)) { | 41 | if (arguments.contains(resultVariable)) { |
38 | throw new IllegalArgumentException("Result variable %s must not appear in the argument list".formatted( | 42 | throw new InvalidQueryException("Result variable %s must not appear in the argument list".formatted( |
39 | resultVariable)); | 43 | resultVariable)); |
40 | } | 44 | } |
41 | this.resultVariable = resultVariable; | 45 | this.resultVariable = resultVariable; |
@@ -63,7 +67,7 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral { | |||
63 | @Override | 67 | @Override |
64 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | 68 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { |
65 | if (positiveVariablesInClause.contains(inputVariable)) { | 69 | if (positiveVariablesInClause.contains(inputVariable)) { |
66 | throw new IllegalArgumentException("Aggregation variable %s must not be bound".formatted(inputVariable)); | 70 | throw new InvalidQueryException("Aggregation variable %s must not be bound".formatted(inputVariable)); |
67 | } | 71 | } |
68 | return super.getInputVariables(positiveVariablesInClause); | 72 | return super.getInputVariables(positiveVariablesInClause); |
69 | } | 73 | } |
@@ -77,7 +81,7 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral { | |||
77 | yield emptyValue == null ? BooleanLiteral.FALSE : | 81 | yield emptyValue == null ? BooleanLiteral.FALSE : |
78 | resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), emptyValue)); | 82 | resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), emptyValue)); |
79 | } | 83 | } |
80 | case ALWAYS_TRUE -> throw new IllegalArgumentException("Trying to aggregate over an infinite set"); | 84 | case ALWAYS_TRUE -> throw new InvalidQueryException("Trying to aggregate over an infinite set"); |
81 | case NOT_REDUCIBLE -> this; | 85 | case NOT_REDUCIBLE -> this; |
82 | }; | 86 | }; |
83 | } | 87 | } |
@@ -89,6 +93,11 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral { | |||
89 | } | 93 | } |
90 | 94 | ||
91 | @Override | 95 | @Override |
96 | public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) { | ||
97 | return new AggregationLiteral<>(resultVariable, aggregator, inputVariable, newTarget, newArguments); | ||
98 | } | ||
99 | |||
100 | @Override | ||
92 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | 101 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { |
93 | if (!super.equalsWithSubstitution(helper, other)) { | 102 | if (!super.equalsWithSubstitution(helper, other)) { |
94 | return false; | 103 | return false; |
@@ -100,18 +109,9 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral { | |||
100 | } | 109 | } |
101 | 110 | ||
102 | @Override | 111 | @Override |
103 | public boolean equals(Object o) { | 112 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { |
104 | if (this == o) return true; | 113 | return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(resultVariable), |
105 | if (o == null || getClass() != o.getClass()) return false; | 114 | helper.getVariableHashCode(inputVariable), aggregator); |
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 | } | 115 | } |
116 | 116 | ||
117 | @Override | 117 | @Override |
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 index dbf999a2..dadf487f 100644 --- 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 | |||
@@ -5,7 +5,9 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | 11 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.DataVariable; | 12 | import tools.refinery.store.query.term.DataVariable; |
11 | import tools.refinery.store.query.term.Term; | 13 | import tools.refinery.store.query.term.Term; |
@@ -15,17 +17,32 @@ import java.util.Collections; | |||
15 | import java.util.Objects; | 17 | import java.util.Objects; |
16 | import java.util.Set; | 18 | import java.util.Set; |
17 | 19 | ||
18 | public record AssignLiteral<T>(DataVariable<T> variable, Term<T> term) implements Literal { | 20 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. |
19 | public AssignLiteral { | 21 | @SuppressWarnings("squid:S2160") |
22 | public class AssignLiteral<T> extends AbstractLiteral { | ||
23 | private final DataVariable<T> variable; | ||
24 | private final Term<T> term; | ||
25 | |||
26 | public AssignLiteral(DataVariable<T> variable, Term<T> term) { | ||
20 | if (!term.getType().equals(variable.getType())) { | 27 | if (!term.getType().equals(variable.getType())) { |
21 | throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( | 28 | throw new InvalidQueryException("Term %s must be of type %s, got %s instead".formatted( |
22 | term, variable.getType().getName(), term.getType().getName())); | 29 | term, variable.getType().getName(), term.getType().getName())); |
23 | } | 30 | } |
24 | var inputVariables = term.getInputVariables(); | 31 | var inputVariables = term.getInputVariables(); |
25 | if (inputVariables.contains(variable)) { | 32 | if (inputVariables.contains(variable)) { |
26 | throw new IllegalArgumentException("Result variable %s must not appear in the term %s".formatted( | 33 | throw new InvalidQueryException("Result variable %s must not appear in the term %s".formatted( |
27 | variable, term)); | 34 | variable, term)); |
28 | } | 35 | } |
36 | this.variable = variable; | ||
37 | this.term = term; | ||
38 | } | ||
39 | |||
40 | public DataVariable<T> getVariable() { | ||
41 | return variable; | ||
42 | } | ||
43 | |||
44 | public Term<T> getTerm() { | ||
45 | return term; | ||
29 | } | 46 | } |
30 | 47 | ||
31 | @Override | 48 | @Override |
@@ -53,27 +70,19 @@ public record AssignLiteral<T>(DataVariable<T> variable, Term<T> term) implement | |||
53 | if (other == null || getClass() != other.getClass()) { | 70 | if (other == null || getClass() != other.getClass()) { |
54 | return false; | 71 | return false; |
55 | } | 72 | } |
56 | var otherLetLiteral = (AssignLiteral<?>) other; | 73 | var otherAssignLiteral = (AssignLiteral<?>) other; |
57 | return helper.variableEqual(variable, otherLetLiteral.variable) && term.equalsWithSubstitution(helper, | 74 | return helper.variableEqual(variable, otherAssignLiteral.variable) && |
58 | otherLetLiteral.term); | 75 | term.equalsWithSubstitution(helper, otherAssignLiteral.term); |
59 | } | ||
60 | |||
61 | @Override | ||
62 | public String toString() { | ||
63 | return "%s is (%s)".formatted(variable, term); | ||
64 | } | 76 | } |
65 | 77 | ||
66 | @Override | 78 | @Override |
67 | public boolean equals(Object obj) { | 79 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { |
68 | if (obj == this) return true; | 80 | return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(variable), |
69 | if (obj == null || obj.getClass() != this.getClass()) return false; | 81 | term.hashCodeWithSubstitution(helper)); |
70 | var that = (AssignLiteral<?>) obj; | ||
71 | return Objects.equals(this.variable, that.variable) && | ||
72 | Objects.equals(this.term, that.term); | ||
73 | } | 82 | } |
74 | 83 | ||
75 | @Override | 84 | @Override |
76 | public int hashCode() { | 85 | public String toString() { |
77 | return Objects.hash(getClass(), variable, term); | 86 | return "%s is (%s)".formatted(variable, term); |
78 | } | 87 | } |
79 | } | 88 | } |
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 index f312d202..6cd320da 100644 --- 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 | |||
@@ -6,6 +6,7 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | 10 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.Variable; | 11 | import tools.refinery.store.query.term.Variable; |
11 | 12 | ||
@@ -53,6 +54,11 @@ public enum BooleanLiteral implements CanNegate<BooleanLiteral> { | |||
53 | } | 54 | } |
54 | 55 | ||
55 | @Override | 56 | @Override |
57 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { | ||
58 | return hashCode(); | ||
59 | } | ||
60 | |||
61 | @Override | ||
56 | public String toString() { | 62 | public String toString() { |
57 | return Boolean.toString(value); | 63 | return Boolean.toString(value); |
58 | } | 64 | } |
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 index 29772aee..2d0e4e97 100644 --- 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 | |||
@@ -6,13 +6,17 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | 12 | import tools.refinery.store.query.substitution.Substitution; |
11 | import tools.refinery.store.query.term.ParameterDirection; | 13 | import tools.refinery.store.query.term.ParameterDirection; |
12 | import tools.refinery.store.query.term.Variable; | 14 | import tools.refinery.store.query.term.Variable; |
13 | 15 | ||
14 | import java.util.*; | 16 | import java.util.*; |
15 | 17 | ||
18 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. | ||
19 | @SuppressWarnings("squid:S2160") | ||
16 | public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> { | 20 | public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> { |
17 | private final CallPolarity polarity; | 21 | private final CallPolarity polarity; |
18 | 22 | ||
@@ -22,10 +26,14 @@ public final class CallLiteral extends AbstractCallLiteral implements CanNegate< | |||
22 | int arity = target.arity(); | 26 | int arity = target.arity(); |
23 | if (polarity.isTransitive()) { | 27 | if (polarity.isTransitive()) { |
24 | if (arity != 2) { | 28 | if (arity != 2) { |
25 | throw new IllegalArgumentException("Transitive closures can only take binary relations"); | 29 | throw new InvalidQueryException("Transitive closures can only take binary relations"); |
26 | } | 30 | } |
27 | if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) { | 31 | if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) { |
28 | throw new IllegalArgumentException("Transitive closures can only be computed over nodes"); | 32 | throw new InvalidQueryException("Transitive closures can only be computed over nodes"); |
33 | } | ||
34 | if (parameters.get(0).getDirection() != ParameterDirection.OUT || | ||
35 | parameters.get(1).getDirection() != ParameterDirection.OUT) { | ||
36 | throw new InvalidQueryException("Transitive closures cannot take input parameters"); | ||
29 | } | 37 | } |
30 | } | 38 | } |
31 | this.polarity = polarity; | 39 | this.polarity = polarity; |
@@ -85,22 +93,18 @@ public final class CallLiteral extends AbstractCallLiteral implements CanNegate< | |||
85 | } | 93 | } |
86 | 94 | ||
87 | @Override | 95 | @Override |
88 | public CallLiteral negate() { | 96 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { |
89 | return new CallLiteral(polarity.negate(), getTarget(), getArguments()); | 97 | return Objects.hash(super.hashCodeWithSubstitution(helper), polarity); |
90 | } | 98 | } |
91 | 99 | ||
92 | @Override | 100 | @Override |
93 | public boolean equals(Object o) { | 101 | public CallLiteral negate() { |
94 | if (this == o) return true; | 102 | return new CallLiteral(polarity.negate(), getTarget(), getArguments()); |
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 | } | 103 | } |
100 | 104 | ||
101 | @Override | 105 | @Override |
102 | public int hashCode() { | 106 | public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) { |
103 | return Objects.hash(super.hashCode(), polarity); | 107 | return new CallLiteral(polarity, newTarget, newArguments); |
104 | } | 108 | } |
105 | 109 | ||
106 | @Override | 110 | @Override |
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 index ca70b0fd..716c7109 100644 --- 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 | |||
@@ -5,6 +5,8 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | |||
8 | public enum CallPolarity { | 10 | public enum CallPolarity { |
9 | POSITIVE(true, false), | 11 | POSITIVE(true, false), |
10 | NEGATIVE(false, false), | 12 | NEGATIVE(false, false), |
@@ -31,7 +33,7 @@ public enum CallPolarity { | |||
31 | return switch (this) { | 33 | return switch (this) { |
32 | case POSITIVE -> NEGATIVE; | 34 | case POSITIVE -> NEGATIVE; |
33 | case NEGATIVE -> POSITIVE; | 35 | case NEGATIVE -> POSITIVE; |
34 | case TRANSITIVE -> throw new IllegalArgumentException("Transitive polarity cannot be negated"); | 36 | case TRANSITIVE -> throw new InvalidQueryException("Transitive polarity cannot be negated"); |
35 | }; | 37 | }; |
36 | } | 38 | } |
37 | } | 39 | } |
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/CheckLiteral.java index 1ca04c77..dfedd2cb 100644 --- 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/CheckLiteral.java | |||
@@ -5,22 +5,35 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | 11 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.ConstantTerm; | 12 | import tools.refinery.store.query.term.ConstantTerm; |
11 | import tools.refinery.store.query.term.Term; | 13 | import tools.refinery.store.query.term.Term; |
12 | import tools.refinery.store.query.term.Variable; | 14 | import tools.refinery.store.query.term.Variable; |
15 | import tools.refinery.store.query.term.bool.BoolNotTerm; | ||
16 | import tools.refinery.store.query.term.bool.BoolTerms; | ||
13 | 17 | ||
14 | import java.util.Collections; | 18 | import java.util.Collections; |
15 | import java.util.Objects; | 19 | import java.util.Objects; |
16 | import java.util.Set; | 20 | import java.util.Set; |
17 | 21 | ||
18 | public record AssumeLiteral(Term<Boolean> term) implements Literal { | 22 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. |
19 | public AssumeLiteral { | 23 | @SuppressWarnings("squid:S2160") |
24 | public class CheckLiteral extends AbstractLiteral implements CanNegate<CheckLiteral> { | ||
25 | private final Term<Boolean> term; | ||
26 | |||
27 | public CheckLiteral(Term<Boolean> term) { | ||
20 | if (!term.getType().equals(Boolean.class)) { | 28 | if (!term.getType().equals(Boolean.class)) { |
21 | throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( | 29 | throw new InvalidQueryException("Term %s must be of type %s, got %s instead".formatted( |
22 | term, Boolean.class.getName(), term.getType().getName())); | 30 | term, Boolean.class.getName(), term.getType().getName())); |
23 | } | 31 | } |
32 | this.term = term; | ||
33 | } | ||
34 | |||
35 | public Term<Boolean> getTerm() { | ||
36 | return term; | ||
24 | } | 37 | } |
25 | 38 | ||
26 | @Override | 39 | @Override |
@@ -38,10 +51,17 @@ public record AssumeLiteral(Term<Boolean> term) implements Literal { | |||
38 | return Set.of(); | 51 | return Set.of(); |
39 | } | 52 | } |
40 | 53 | ||
41 | |||
42 | @Override | 54 | @Override |
43 | public Literal substitute(Substitution substitution) { | 55 | public Literal substitute(Substitution substitution) { |
44 | return new AssumeLiteral(term.substitute(substitution)); | 56 | return new CheckLiteral(term.substitute(substitution)); |
57 | } | ||
58 | |||
59 | @Override | ||
60 | public CheckLiteral negate() { | ||
61 | if (term instanceof BoolNotTerm notTerm) { | ||
62 | return new CheckLiteral(notTerm.getBody()); | ||
63 | } | ||
64 | return new CheckLiteral(BoolTerms.not(term)); | ||
45 | } | 65 | } |
46 | 66 | ||
47 | @Override | 67 | @Override |
@@ -49,11 +69,16 @@ public record AssumeLiteral(Term<Boolean> term) implements Literal { | |||
49 | if (other == null || getClass() != other.getClass()) { | 69 | if (other == null || getClass() != other.getClass()) { |
50 | return false; | 70 | return false; |
51 | } | 71 | } |
52 | var otherAssumeLiteral = (AssumeLiteral) other; | 72 | var otherAssumeLiteral = (CheckLiteral) other; |
53 | return term.equalsWithSubstitution(helper, otherAssumeLiteral.term); | 73 | return term.equalsWithSubstitution(helper, otherAssumeLiteral.term); |
54 | } | 74 | } |
55 | 75 | ||
56 | @Override | 76 | @Override |
77 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { | ||
78 | return Objects.hash(super.hashCodeWithSubstitution(helper), term.hashCodeWithSubstitution(helper)); | ||
79 | } | ||
80 | |||
81 | @Override | ||
57 | public Literal reduce() { | 82 | public Literal reduce() { |
58 | if (term instanceof ConstantTerm<Boolean> constantTerm) { | 83 | if (term instanceof ConstantTerm<Boolean> constantTerm) { |
59 | // Return {@link BooleanLiteral#FALSE} for {@code false} or {@code null} literals. | 84 | // Return {@link BooleanLiteral#FALSE} for {@code false} or {@code null} literals. |
@@ -67,17 +92,4 @@ public record AssumeLiteral(Term<Boolean> term) implements Literal { | |||
67 | public String toString() { | 92 | public String toString() { |
68 | return "(%s)".formatted(term); | 93 | return "(%s)".formatted(term); |
69 | } | 94 | } |
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 | } | 95 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Connectivity.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Connectivity.java new file mode 100644 index 00000000..a058094d --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Connectivity.java | |||
@@ -0,0 +1,18 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import java.util.Locale; | ||
9 | |||
10 | public enum Connectivity { | ||
11 | WEAK, | ||
12 | STRONG; | ||
13 | |||
14 | @Override | ||
15 | public String toString() { | ||
16 | return name().toLowerCase(Locale.ROOT); | ||
17 | } | ||
18 | } | ||
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 index 73545620..d83bd584 100644 --- 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 | |||
@@ -6,6 +6,7 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | 10 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.NodeVariable; | 11 | import tools.refinery.store.query.term.NodeVariable; |
11 | import tools.refinery.store.query.term.Variable; | 12 | import tools.refinery.store.query.term.Variable; |
@@ -13,7 +14,24 @@ import tools.refinery.store.query.term.Variable; | |||
13 | import java.util.Objects; | 14 | import java.util.Objects; |
14 | import java.util.Set; | 15 | import java.util.Set; |
15 | 16 | ||
16 | public record ConstantLiteral(NodeVariable variable, int nodeId) implements Literal { | 17 | public class ConstantLiteral extends AbstractLiteral { |
18 | private final NodeVariable variable; | ||
19 | private final int nodeId; | ||
20 | |||
21 | public ConstantLiteral(NodeVariable variable, int nodeId) { | ||
22 | this.variable = variable; | ||
23 | this.nodeId = nodeId; | ||
24 | } | ||
25 | |||
26 | public NodeVariable getVariable() { | ||
27 | return variable; | ||
28 | } | ||
29 | |||
30 | public int getNodeId() { | ||
31 | return nodeId; | ||
32 | } | ||
33 | |||
34 | |||
17 | @Override | 35 | @Override |
18 | public Set<Variable> getOutputVariables() { | 36 | public Set<Variable> getOutputVariables() { |
19 | return Set.of(variable); | 37 | return Set.of(variable); |
@@ -43,23 +61,13 @@ public record ConstantLiteral(NodeVariable variable, int nodeId) implements Lite | |||
43 | return helper.variableEqual(variable, otherConstantLiteral.variable) && nodeId == otherConstantLiteral.nodeId; | 61 | return helper.variableEqual(variable, otherConstantLiteral.variable) && nodeId == otherConstantLiteral.nodeId; |
44 | } | 62 | } |
45 | 63 | ||
46 | |||
47 | @Override | ||
48 | public String toString() { | ||
49 | return "%s === @Constant %d".formatted(variable, nodeId); | ||
50 | } | ||
51 | |||
52 | @Override | 64 | @Override |
53 | public boolean equals(Object obj) { | 65 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { |
54 | if (obj == this) return true; | 66 | return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(variable), nodeId); |
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 | } | 67 | } |
60 | 68 | ||
61 | @Override | 69 | @Override |
62 | public int hashCode() { | 70 | public String toString() { |
63 | return Objects.hash(getClass(), variable, nodeId); | 71 | return "%s === @Constant %d".formatted(variable, nodeId); |
64 | } | 72 | } |
65 | } | 73 | } |
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 index 4d4749c8..3d078d89 100644 --- 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 | |||
@@ -6,96 +6,40 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | 9 | import tools.refinery.store.query.substitution.Substitution; |
11 | import tools.refinery.store.query.term.DataVariable; | 10 | import tools.refinery.store.query.term.DataVariable; |
12 | import tools.refinery.store.query.term.Variable; | 11 | import tools.refinery.store.query.term.Variable; |
13 | import tools.refinery.store.query.term.int_.IntTerms; | ||
14 | 12 | ||
15 | import java.util.List; | 13 | 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 | 14 | ||
15 | public class CountLiteral extends AbstractCountLiteral<Integer> { | ||
22 | public CountLiteral(DataVariable<Integer> resultVariable, Constraint target, List<Variable> arguments) { | 16 | public CountLiteral(DataVariable<Integer> resultVariable, Constraint target, List<Variable> arguments) { |
23 | super(target, arguments); | 17 | super(Integer.class, resultVariable, 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 | } | 18 | } |
38 | 19 | ||
39 | @Override | 20 | @Override |
40 | public Set<Variable> getOutputVariables() { | 21 | protected Integer zero() { |
41 | return Set.of(resultVariable); | 22 | return 0; |
42 | } | 23 | } |
43 | 24 | ||
44 | @Override | 25 | @Override |
45 | public Literal reduce() { | 26 | protected Integer one() { |
46 | var reduction = getTarget().getReduction(); | 27 | return 1; |
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 | } | 28 | } |
55 | 29 | ||
56 | @Override | 30 | @Override |
57 | protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) { | 31 | protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) { |
58 | return new CountLiteral(substitution.getTypeSafeSubstitute(resultVariable), getTarget(), substitutedArguments); | 32 | return new CountLiteral(substitution.getTypeSafeSubstitute(getResultVariable()), getTarget(), |
59 | } | 33 | substitutedArguments); |
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 | } | 34 | } |
78 | 35 | ||
79 | @Override | 36 | @Override |
80 | public int hashCode() { | 37 | public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) { |
81 | return Objects.hash(super.hashCode(), resultVariable); | 38 | return new CountLiteral(getResultVariable(), newTarget, newArguments); |
82 | } | 39 | } |
83 | 40 | ||
84 | @Override | 41 | @Override |
85 | public String toString() { | 42 | protected String operatorName() { |
86 | var builder = new StringBuilder(); | 43 | return "count"; |
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 | } | 44 | } |
101 | } | 45 | } |
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 index 28ba7625..7343f709 100644 --- 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 | |||
@@ -5,16 +5,44 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | 11 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | import tools.refinery.store.query.term.Variable; | 12 | import tools.refinery.store.query.term.Variable; |
12 | 13 | ||
13 | import java.util.Objects; | 14 | import java.util.Objects; |
14 | import java.util.Set; | 15 | import java.util.Set; |
15 | 16 | ||
16 | public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariable right) | 17 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. |
17 | implements CanNegate<EquivalenceLiteral> { | 18 | @SuppressWarnings("squid:S2160") |
19 | public final class EquivalenceLiteral extends AbstractLiteral implements CanNegate<EquivalenceLiteral> { | ||
20 | private final boolean positive; | ||
21 | private final Variable left; | ||
22 | private final Variable right; | ||
23 | |||
24 | public EquivalenceLiteral(boolean positive, Variable left, Variable right) { | ||
25 | if (!left.tryGetType().equals(right.tryGetType())) { | ||
26 | throw new InvalidQueryException("Variables %s and %s of different type cannot be equivalent" | ||
27 | .formatted(left, right)); | ||
28 | } | ||
29 | this.positive = positive; | ||
30 | this.left = left; | ||
31 | this.right = right; | ||
32 | } | ||
33 | |||
34 | public boolean isPositive() { | ||
35 | return positive; | ||
36 | } | ||
37 | |||
38 | public Variable getLeft() { | ||
39 | return left; | ||
40 | } | ||
41 | |||
42 | public Variable getRight() { | ||
43 | return right; | ||
44 | } | ||
45 | |||
18 | @Override | 46 | @Override |
19 | public Set<Variable> getOutputVariables() { | 47 | public Set<Variable> getOutputVariables() { |
20 | return Set.of(left); | 48 | return Set.of(left); |
@@ -37,8 +65,8 @@ public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariab | |||
37 | 65 | ||
38 | @Override | 66 | @Override |
39 | public EquivalenceLiteral substitute(Substitution substitution) { | 67 | public EquivalenceLiteral substitute(Substitution substitution) { |
40 | return new EquivalenceLiteral(positive, substitution.getTypeSafeSubstitute(left), | 68 | return new EquivalenceLiteral(positive, substitution.getSubstitute(left), |
41 | substitution.getTypeSafeSubstitute(right)); | 69 | substitution.getSubstitute(right)); |
42 | } | 70 | } |
43 | 71 | ||
44 | @Override | 72 | @Override |
@@ -55,27 +83,18 @@ public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariab | |||
55 | return false; | 83 | return false; |
56 | } | 84 | } |
57 | var otherEquivalenceLiteral = (EquivalenceLiteral) other; | 85 | var otherEquivalenceLiteral = (EquivalenceLiteral) other; |
58 | return helper.variableEqual(left, otherEquivalenceLiteral.left) && helper.variableEqual(right, | 86 | return helper.variableEqual(left, otherEquivalenceLiteral.left) && |
59 | otherEquivalenceLiteral.right); | 87 | helper.variableEqual(right, otherEquivalenceLiteral.right); |
60 | } | ||
61 | |||
62 | @Override | ||
63 | public String toString() { | ||
64 | return "%s %s %s".formatted(left, positive ? "===" : "!==", right); | ||
65 | } | 88 | } |
66 | 89 | ||
67 | @Override | 90 | @Override |
68 | public boolean equals(Object obj) { | 91 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { |
69 | if (obj == this) return true; | 92 | return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(left), |
70 | if (obj == null || obj.getClass() != this.getClass()) return false; | 93 | helper.getVariableHashCode(right)); |
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 | } | 94 | } |
76 | 95 | ||
77 | @Override | 96 | @Override |
78 | public int hashCode() { | 97 | public String toString() { |
79 | return Objects.hash(getClass(), positive, left, right); | 98 | return "%s %s %s".formatted(left, positive ? "===" : "!==", right); |
80 | } | 99 | } |
81 | } | 100 | } |
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 index ce6c11fe..cb16ab00 100644 --- 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 | |||
@@ -6,6 +6,7 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | 10 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.Variable; | 11 | import tools.refinery.store.query.term.Variable; |
11 | 12 | ||
@@ -26,4 +27,6 @@ public interface Literal { | |||
26 | 27 | ||
27 | @SuppressWarnings("BooleanMethodIsAlwaysInverted") | 28 | @SuppressWarnings("BooleanMethodIsAlwaysInverted") |
28 | boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other); | 29 | boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other); |
30 | |||
31 | int hashCodeWithSubstitution(LiteralHashCodeHelper helper); | ||
29 | } | 32 | } |
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 index b3a87811..6056da45 100644 --- 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 | |||
@@ -16,7 +16,7 @@ public final class Literals { | |||
16 | return literal.negate(); | 16 | return literal.negate(); |
17 | } | 17 | } |
18 | 18 | ||
19 | public static AssumeLiteral assume(Term<Boolean> term) { | 19 | public static CheckLiteral check(Term<Boolean> term) { |
20 | return new AssumeLiteral(term); | 20 | return new CheckLiteral(term); |
21 | } | 21 | } |
22 | } | 22 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RepresentativeElectionLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RepresentativeElectionLiteral.java new file mode 100644 index 00000000..f7323947 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RepresentativeElectionLiteral.java | |||
@@ -0,0 +1,119 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.InvalidQueryException; | ||
10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
12 | import tools.refinery.store.query.substitution.Substitution; | ||
13 | import tools.refinery.store.query.term.NodeVariable; | ||
14 | import tools.refinery.store.query.term.ParameterDirection; | ||
15 | import tools.refinery.store.query.term.Variable; | ||
16 | |||
17 | import java.util.List; | ||
18 | import java.util.Set; | ||
19 | |||
20 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. | ||
21 | @SuppressWarnings("squid:S2160") | ||
22 | public class RepresentativeElectionLiteral extends AbstractCallLiteral { | ||
23 | private final Connectivity connectivity; | ||
24 | |||
25 | public RepresentativeElectionLiteral(Connectivity connectivity, Constraint target, NodeVariable specific, | ||
26 | NodeVariable representative) { | ||
27 | this(connectivity, target, List.of(specific, representative)); | ||
28 | } | ||
29 | |||
30 | private RepresentativeElectionLiteral(Connectivity connectivity, Constraint target, List<Variable> arguments) { | ||
31 | super(target, arguments); | ||
32 | this.connectivity = connectivity; | ||
33 | var parameters = target.getParameters(); | ||
34 | int arity = target.arity(); | ||
35 | if (arity != 2) { | ||
36 | throw new InvalidQueryException("SCCs can only take binary relations"); | ||
37 | } | ||
38 | if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) { | ||
39 | throw new InvalidQueryException("SCCs can only be computed over nodes"); | ||
40 | } | ||
41 | if (parameters.get(0).getDirection() != ParameterDirection.OUT || | ||
42 | parameters.get(1).getDirection() != ParameterDirection.OUT) { | ||
43 | throw new InvalidQueryException("SCCs cannot take input parameters"); | ||
44 | } | ||
45 | } | ||
46 | |||
47 | public Connectivity getConnectivity() { | ||
48 | return connectivity; | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) { | ||
53 | return new RepresentativeElectionLiteral(connectivity, getTarget(), substitutedArguments); | ||
54 | } | ||
55 | |||
56 | @Override | ||
57 | public Set<Variable> getOutputVariables() { | ||
58 | return getArgumentsOfDirection(ParameterDirection.OUT); | ||
59 | } | ||
60 | |||
61 | @Override | ||
62 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
63 | return Set.of(); | ||
64 | } | ||
65 | |||
66 | @Override | ||
67 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
68 | return Set.of(); | ||
69 | } | ||
70 | |||
71 | @Override | ||
72 | public Literal reduce() { | ||
73 | var reduction = getTarget().getReduction(); | ||
74 | return switch (reduction) { | ||
75 | case ALWAYS_FALSE -> BooleanLiteral.FALSE; | ||
76 | case ALWAYS_TRUE -> throw new InvalidQueryException( | ||
77 | "Trying to elect representatives over an infinite set"); | ||
78 | case NOT_REDUCIBLE -> this; | ||
79 | }; | ||
80 | } | ||
81 | |||
82 | @Override | ||
83 | public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) { | ||
84 | return new RepresentativeElectionLiteral(connectivity, newTarget, newArguments); | ||
85 | } | ||
86 | |||
87 | @Override | ||
88 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | ||
89 | if (!super.equalsWithSubstitution(helper, other)) { | ||
90 | return false; | ||
91 | } | ||
92 | var otherRepresentativeElectionLiteral = (RepresentativeElectionLiteral) other; | ||
93 | return connectivity.equals(otherRepresentativeElectionLiteral.connectivity); | ||
94 | } | ||
95 | |||
96 | @Override | ||
97 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { | ||
98 | return super.hashCodeWithSubstitution(helper) * 31 + connectivity.hashCode(); | ||
99 | } | ||
100 | |||
101 | @Override | ||
102 | public String toString() { | ||
103 | var builder = new StringBuilder(); | ||
104 | builder.append("@Representative(\""); | ||
105 | builder.append(connectivity); | ||
106 | builder.append("\") "); | ||
107 | builder.append(getTarget().toReferenceString()); | ||
108 | builder.append("("); | ||
109 | var argumentIterator = getArguments().iterator(); | ||
110 | if (argumentIterator.hasNext()) { | ||
111 | builder.append(argumentIterator.next()); | ||
112 | while (argumentIterator.hasNext()) { | ||
113 | builder.append(", ").append(argumentIterator.next()); | ||
114 | } | ||
115 | } | ||
116 | builder.append(")"); | ||
117 | return builder.toString(); | ||
118 | } | ||
119 | } | ||
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 index a710c64d..dcfe6cc5 100644 --- 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 | |||
@@ -28,7 +28,7 @@ public abstract class AbstractResultSet<T> implements ResultSet<T> { | |||
28 | } | 28 | } |
29 | 29 | ||
30 | @Override | 30 | @Override |
31 | public Query<T> getQuery() { | 31 | public Query<T> getCanonicalQuery() { |
32 | return query; | 32 | return query; |
33 | } | 33 | } |
34 | 34 | ||
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 index 02809477..5b75b103 100644 --- 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 | |||
@@ -11,7 +11,7 @@ import tools.refinery.store.query.dnf.AnyQuery; | |||
11 | public sealed interface AnyResultSet permits ResultSet { | 11 | public sealed interface AnyResultSet permits ResultSet { |
12 | ModelQueryAdapter getAdapter(); | 12 | ModelQueryAdapter getAdapter(); |
13 | 13 | ||
14 | AnyQuery getQuery(); | 14 | AnyQuery getCanonicalQuery(); |
15 | 15 | ||
16 | int size(); | 16 | int size(); |
17 | } | 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 index 2795a44b..991b1e32 100644 --- 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 | |||
@@ -18,7 +18,7 @@ public record EmptyResultSet<T>(ModelQueryAdapter adapter, Query<T> query) imple | |||
18 | } | 18 | } |
19 | 19 | ||
20 | @Override | 20 | @Override |
21 | public Query<T> getQuery() { | 21 | public Query<T> getCanonicalQuery() { |
22 | return query; | 22 | return query; |
23 | } | 23 | } |
24 | 24 | ||
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 index 39006d65..df12b967 100644 --- 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 | |||
@@ -17,7 +17,7 @@ public class OrderedResultSet<T> implements AutoCloseable, ResultSet<T> { | |||
17 | private final ResultSet<T> resultSet; | 17 | private final ResultSet<T> resultSet; |
18 | private final OrderStatisticTree<Tuple> tree = new OrderStatisticTree<>(); | 18 | private final OrderStatisticTree<Tuple> tree = new OrderStatisticTree<>(); |
19 | private final ResultSetListener<T> listener = (key, fromValue, toValue) -> { | 19 | private final ResultSetListener<T> listener = (key, fromValue, toValue) -> { |
20 | var defaultValue = getQuery().defaultValue(); | 20 | var defaultValue = getCanonicalQuery().defaultValue(); |
21 | if (Objects.equals(defaultValue, toValue)) { | 21 | if (Objects.equals(defaultValue, toValue)) { |
22 | tree.remove(key); | 22 | tree.remove(key); |
23 | } else { | 23 | } else { |
@@ -45,8 +45,8 @@ public class OrderedResultSet<T> implements AutoCloseable, ResultSet<T> { | |||
45 | } | 45 | } |
46 | 46 | ||
47 | @Override | 47 | @Override |
48 | public Query<T> getQuery() { | 48 | public Query<T> getCanonicalQuery() { |
49 | return resultSet.getQuery(); | 49 | return resultSet.getCanonicalQuery(); |
50 | } | 50 | } |
51 | 51 | ||
52 | @Override | 52 | @Override |
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 index 33d1ea95..a6e99784 100644 --- 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 | |||
@@ -10,7 +10,7 @@ import tools.refinery.store.query.dnf.Query; | |||
10 | import tools.refinery.store.tuple.Tuple; | 10 | import tools.refinery.store.tuple.Tuple; |
11 | 11 | ||
12 | public non-sealed interface ResultSet<T> extends AnyResultSet { | 12 | public non-sealed interface ResultSet<T> extends AnyResultSet { |
13 | Query<T> getQuery(); | 13 | Query<T> getCanonicalQuery(); |
14 | 14 | ||
15 | T get(Tuple parameters); | 15 | T get(Tuple parameters); |
16 | 16 | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/AbstractRecursiveRewriter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/AbstractRecursiveRewriter.java new file mode 100644 index 00000000..fb4c14a7 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/AbstractRecursiveRewriter.java | |||
@@ -0,0 +1,26 @@ | |||
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.rewriter; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | import tools.refinery.store.query.equality.DnfEqualityChecker; | ||
10 | import tools.refinery.store.util.CycleDetectingMapper; | ||
11 | |||
12 | public abstract class AbstractRecursiveRewriter implements DnfRewriter { | ||
13 | private final CycleDetectingMapper<Dnf, Dnf> mapper = new CycleDetectingMapper<>(Dnf::name, this::map); | ||
14 | |||
15 | @Override | ||
16 | public Dnf rewrite(Dnf dnf) { | ||
17 | return mapper.map(dnf); | ||
18 | } | ||
19 | |||
20 | protected Dnf map(Dnf dnf) { | ||
21 | var result = doRewrite(dnf); | ||
22 | return dnf.equalsWithSubstitution(DnfEqualityChecker.DEFAULT, result) ? dnf : result; | ||
23 | } | ||
24 | |||
25 | protected abstract Dnf doRewrite(Dnf dnf); | ||
26 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/ClauseInputParameterResolver.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/ClauseInputParameterResolver.java new file mode 100644 index 00000000..aa06a05a --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/ClauseInputParameterResolver.java | |||
@@ -0,0 +1,160 @@ | |||
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.rewriter; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | import tools.refinery.store.query.dnf.DnfClause; | ||
10 | import tools.refinery.store.query.literal.*; | ||
11 | import tools.refinery.store.query.substitution.Substitution; | ||
12 | import tools.refinery.store.query.term.ParameterDirection; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | |||
15 | import java.util.*; | ||
16 | |||
17 | class ClauseInputParameterResolver { | ||
18 | private final InputParameterResolver rewriter; | ||
19 | private final String dnfName; | ||
20 | private final int clauseIndex; | ||
21 | private final Set<Variable> positiveVariables = new LinkedHashSet<>(); | ||
22 | private final List<Literal> inlinedLiterals = new ArrayList<>(); | ||
23 | private final Deque<Literal> workList; | ||
24 | private int helperIndex = 0; | ||
25 | |||
26 | public ClauseInputParameterResolver(InputParameterResolver rewriter, List<Literal> context, DnfClause clause, | ||
27 | String dnfName, int clauseIndex) { | ||
28 | this.rewriter = rewriter; | ||
29 | this.dnfName = dnfName; | ||
30 | this.clauseIndex = clauseIndex; | ||
31 | workList = new ArrayDeque<>(clause.literals().size() + context.size()); | ||
32 | for (var literal : context) { | ||
33 | workList.addLast(literal); | ||
34 | } | ||
35 | for (var literal : clause.literals()) { | ||
36 | workList.addLast(literal); | ||
37 | } | ||
38 | } | ||
39 | |||
40 | public List<Literal> rewriteClause() { | ||
41 | while (!workList.isEmpty()) { | ||
42 | var literal = workList.removeFirst(); | ||
43 | processLiteral(literal); | ||
44 | } | ||
45 | return inlinedLiterals; | ||
46 | } | ||
47 | |||
48 | private void processLiteral(Literal literal) { | ||
49 | if (!(literal instanceof AbstractCallLiteral abstractCallLiteral) || | ||
50 | !(abstractCallLiteral.getTarget() instanceof Dnf targetDnf)) { | ||
51 | markAsDone(literal); | ||
52 | return; | ||
53 | } | ||
54 | boolean hasInputParameter = hasInputParameter(targetDnf); | ||
55 | if (!hasInputParameter) { | ||
56 | targetDnf = rewriter.rewrite(targetDnf); | ||
57 | } | ||
58 | if (inlinePositiveClause(abstractCallLiteral, targetDnf)) { | ||
59 | return; | ||
60 | } | ||
61 | if (eliminateDoubleNegation(abstractCallLiteral, targetDnf)) { | ||
62 | return; | ||
63 | } | ||
64 | if (hasInputParameter) { | ||
65 | rewriteWithCurrentContext(abstractCallLiteral, targetDnf); | ||
66 | return; | ||
67 | } | ||
68 | markAsDone(abstractCallLiteral.withTarget(targetDnf)); | ||
69 | } | ||
70 | |||
71 | private void markAsDone(Literal literal) { | ||
72 | positiveVariables.addAll(literal.getOutputVariables()); | ||
73 | inlinedLiterals.add(literal); | ||
74 | } | ||
75 | |||
76 | private boolean inlinePositiveClause(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf) { | ||
77 | var targetLiteral = getSingleLiteral(abstractCallLiteral, targetDnf, CallPolarity.POSITIVE); | ||
78 | if (targetLiteral == null) { | ||
79 | return false; | ||
80 | } | ||
81 | var substitution = asSubstitution(abstractCallLiteral, targetDnf); | ||
82 | var substitutedLiteral = targetLiteral.substitute(substitution); | ||
83 | workList.addFirst(substitutedLiteral); | ||
84 | return true; | ||
85 | } | ||
86 | |||
87 | private boolean eliminateDoubleNegation(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf) { | ||
88 | var targetLiteral = getSingleLiteral(abstractCallLiteral, targetDnf, CallPolarity.NEGATIVE); | ||
89 | if (!(targetLiteral instanceof CallLiteral targetCallLiteral) || | ||
90 | targetCallLiteral.getPolarity() != CallPolarity.NEGATIVE) { | ||
91 | return false; | ||
92 | } | ||
93 | var substitution = asSubstitution(abstractCallLiteral, targetDnf); | ||
94 | var substitutedLiteral = (CallLiteral) targetCallLiteral.substitute(substitution); | ||
95 | workList.addFirst(substitutedLiteral.negate()); | ||
96 | return true; | ||
97 | } | ||
98 | |||
99 | private void rewriteWithCurrentContext(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf) { | ||
100 | var contextBuilder = Dnf.builder("%s#clause%d#helper%d".formatted(dnfName, clauseIndex, helperIndex)); | ||
101 | helperIndex++; | ||
102 | contextBuilder.parameters(positiveVariables, ParameterDirection.OUT); | ||
103 | contextBuilder.clause(inlinedLiterals); | ||
104 | var contextDnf = contextBuilder.build(); | ||
105 | var contextCall = new CallLiteral(CallPolarity.POSITIVE, contextDnf, List.copyOf(positiveVariables)); | ||
106 | inlinedLiterals.clear(); | ||
107 | var substitution = Substitution.builder().renewing().build(); | ||
108 | var context = new ArrayList<Literal>(); | ||
109 | context.add(contextCall.substitute(substitution)); | ||
110 | int arity = targetDnf.arity(); | ||
111 | for (int i = 0; i < arity; i++) { | ||
112 | var parameter = targetDnf.getSymbolicParameters().get(i).getVariable(); | ||
113 | var argument = abstractCallLiteral.getArguments().get(i); | ||
114 | context.add(new EquivalenceLiteral(true, parameter, substitution.getSubstitute(argument))); | ||
115 | } | ||
116 | var rewrittenDnf = rewriter.rewriteWithContext(context, targetDnf); | ||
117 | workList.addFirst(abstractCallLiteral.withTarget(rewrittenDnf)); | ||
118 | workList.addFirst(contextCall); | ||
119 | } | ||
120 | |||
121 | private static boolean hasInputParameter(Dnf targetDnf) { | ||
122 | for (var parameter : targetDnf.getParameters()) { | ||
123 | if (parameter.getDirection() != ParameterDirection.OUT) { | ||
124 | return true; | ||
125 | } | ||
126 | } | ||
127 | return false; | ||
128 | } | ||
129 | |||
130 | private static Literal getSingleLiteral(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf, | ||
131 | CallPolarity polarity) { | ||
132 | if (!(abstractCallLiteral instanceof CallLiteral callLiteral) || | ||
133 | callLiteral.getPolarity() != polarity) { | ||
134 | return null; | ||
135 | } | ||
136 | var clauses = targetDnf.getClauses(); | ||
137 | if (clauses.size() != 1) { | ||
138 | return null; | ||
139 | } | ||
140 | var targetLiterals = clauses.get(0).literals(); | ||
141 | if (targetLiterals.size() != 1) { | ||
142 | return null; | ||
143 | } | ||
144 | return targetLiterals.get(0); | ||
145 | } | ||
146 | |||
147 | private static Substitution asSubstitution(AbstractCallLiteral callLiteral, Dnf targetDnf) { | ||
148 | var builder = Substitution.builder().renewing(); | ||
149 | var arguments = callLiteral.getArguments(); | ||
150 | var parameters = targetDnf.getSymbolicParameters(); | ||
151 | int arity = arguments.size(); | ||
152 | if (parameters.size() != arity) { | ||
153 | throw new IllegalArgumentException("Call %s of %s arity mismatch".formatted(callLiteral, targetDnf)); | ||
154 | } | ||
155 | for (int i = 0; i < arity; i++) { | ||
156 | builder.putChecked(parameters.get(i).getVariable(), arguments.get(i)); | ||
157 | } | ||
158 | return builder.build(); | ||
159 | } | ||
160 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/CompositeRewriter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/CompositeRewriter.java new file mode 100644 index 00000000..5b4f65e5 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/CompositeRewriter.java | |||
@@ -0,0 +1,29 @@ | |||
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.rewriter; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | |||
10 | import java.util.ArrayList; | ||
11 | import java.util.List; | ||
12 | |||
13 | public class CompositeRewriter implements DnfRewriter { | ||
14 | private final List<DnfRewriter> rewriterList = new ArrayList<>(); | ||
15 | |||
16 | public void addFirst(DnfRewriter rewriter) { | ||
17 | rewriterList.add(rewriter); | ||
18 | } | ||
19 | |||
20 | @Override | ||
21 | public Dnf rewrite(Dnf dnf) { | ||
22 | Dnf rewrittenDnf = dnf; | ||
23 | for (int i = rewriterList.size() - 1; i >= 0; i--) { | ||
24 | var rewriter = rewriterList.get(i); | ||
25 | rewrittenDnf = rewriter.rewrite(rewrittenDnf); | ||
26 | } | ||
27 | return rewrittenDnf; | ||
28 | } | ||
29 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DnfRewriter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DnfRewriter.java new file mode 100644 index 00000000..5d8359d1 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DnfRewriter.java | |||
@@ -0,0 +1,24 @@ | |||
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.rewriter; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.AnyQuery; | ||
9 | import tools.refinery.store.query.dnf.Dnf; | ||
10 | import tools.refinery.store.query.dnf.Query; | ||
11 | |||
12 | @FunctionalInterface | ||
13 | public interface DnfRewriter { | ||
14 | Dnf rewrite(Dnf dnf); | ||
15 | |||
16 | default AnyQuery rewrite(AnyQuery query) { | ||
17 | return rewrite((Query<?>) query); | ||
18 | } | ||
19 | |||
20 | default <T> Query<T> rewrite(Query<T> query) { | ||
21 | var rewrittenDnf = rewrite(query.getDnf()); | ||
22 | return query.withDnf(rewrittenDnf); | ||
23 | } | ||
24 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DuplicateDnfRemover.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DuplicateDnfRemover.java new file mode 100644 index 00000000..0c786470 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DuplicateDnfRemover.java | |||
@@ -0,0 +1,98 @@ | |||
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.rewriter; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | import tools.refinery.store.query.dnf.DnfClause; | ||
10 | import tools.refinery.store.query.dnf.Query; | ||
11 | import tools.refinery.store.query.equality.DnfEqualityChecker; | ||
12 | import tools.refinery.store.query.literal.AbstractCallLiteral; | ||
13 | import tools.refinery.store.query.literal.Literal; | ||
14 | |||
15 | import java.util.ArrayList; | ||
16 | import java.util.HashMap; | ||
17 | import java.util.List; | ||
18 | import java.util.Map; | ||
19 | |||
20 | public class DuplicateDnfRemover extends AbstractRecursiveRewriter { | ||
21 | private final Map<CanonicalDnf, Dnf> dnfCache = new HashMap<>(); | ||
22 | private final Map<Dnf, Query<?>> queryCache = new HashMap<>(); | ||
23 | |||
24 | @Override | ||
25 | protected Dnf map(Dnf dnf) { | ||
26 | var result = super.map(dnf); | ||
27 | return dnfCache.computeIfAbsent(new CanonicalDnf(result), CanonicalDnf::getDnf); | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | protected Dnf doRewrite(Dnf dnf) { | ||
32 | var builder = Dnf.builderFrom(dnf); | ||
33 | for (var clause : dnf.getClauses()) { | ||
34 | builder.clause(rewriteClause(clause)); | ||
35 | } | ||
36 | return builder.build(); | ||
37 | } | ||
38 | |||
39 | private List<Literal> rewriteClause(DnfClause clause) { | ||
40 | var originalLiterals = clause.literals(); | ||
41 | var literals = new ArrayList<Literal>(originalLiterals.size()); | ||
42 | for (var literal : originalLiterals) { | ||
43 | var rewrittenLiteral = literal; | ||
44 | if (literal instanceof AbstractCallLiteral abstractCallLiteral && | ||
45 | abstractCallLiteral.getTarget() instanceof Dnf targetDnf) { | ||
46 | var rewrittenTarget = rewrite(targetDnf); | ||
47 | rewrittenLiteral = abstractCallLiteral.withTarget(rewrittenTarget); | ||
48 | } | ||
49 | literals.add(rewrittenLiteral); | ||
50 | } | ||
51 | return literals; | ||
52 | } | ||
53 | |||
54 | @Override | ||
55 | public <T> Query<T> rewrite(Query<T> query) { | ||
56 | var rewrittenDnf = rewrite(query.getDnf()); | ||
57 | // {@code withDnf} will always return the appropriate type. | ||
58 | @SuppressWarnings("unchecked") | ||
59 | var rewrittenQuery = (Query<T>) queryCache.computeIfAbsent(rewrittenDnf, query::withDnf); | ||
60 | return rewrittenQuery; | ||
61 | } | ||
62 | |||
63 | private static class CanonicalDnf { | ||
64 | private final Dnf dnf; | ||
65 | private final int hash; | ||
66 | |||
67 | public CanonicalDnf(Dnf dnf) { | ||
68 | this.dnf = dnf; | ||
69 | hash = dnf.hashCodeWithSubstitution(); | ||
70 | } | ||
71 | |||
72 | public Dnf getDnf() { | ||
73 | return dnf; | ||
74 | } | ||
75 | |||
76 | @Override | ||
77 | public boolean equals(Object obj) { | ||
78 | if (this == obj) { | ||
79 | return true; | ||
80 | } | ||
81 | if (obj == null || getClass() != obj.getClass()) { | ||
82 | return false; | ||
83 | } | ||
84 | var otherCanonicalDnf = (CanonicalDnf) obj; | ||
85 | return dnf.equalsWithSubstitution(DnfEqualityChecker.DEFAULT, otherCanonicalDnf.dnf); | ||
86 | } | ||
87 | |||
88 | @Override | ||
89 | public int hashCode() { | ||
90 | return hash; | ||
91 | } | ||
92 | |||
93 | @Override | ||
94 | public String toString() { | ||
95 | return dnf.name(); | ||
96 | } | ||
97 | } | ||
98 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/InputParameterResolver.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/InputParameterResolver.java new file mode 100644 index 00000000..cd8a2e7d --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/InputParameterResolver.java | |||
@@ -0,0 +1,51 @@ | |||
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.rewriter; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | import tools.refinery.store.query.dnf.DnfBuilder; | ||
10 | import tools.refinery.store.query.literal.Literal; | ||
11 | import tools.refinery.store.query.term.ParameterDirection; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | |||
14 | import java.util.HashSet; | ||
15 | import java.util.List; | ||
16 | |||
17 | public class InputParameterResolver extends AbstractRecursiveRewriter { | ||
18 | @Override | ||
19 | protected Dnf doRewrite(Dnf dnf) { | ||
20 | return rewriteWithContext(List.of(), dnf); | ||
21 | } | ||
22 | |||
23 | Dnf rewriteWithContext(List<Literal> context, Dnf dnf) { | ||
24 | var dnfName = dnf.name(); | ||
25 | var builder = Dnf.builder(dnfName); | ||
26 | createSymbolicParameters(context, dnf, builder); | ||
27 | builder.functionalDependencies(dnf.getFunctionalDependencies()); | ||
28 | var clauses = dnf.getClauses(); | ||
29 | int clauseCount = clauses.size(); | ||
30 | for (int i = 0; i < clauseCount; i++) { | ||
31 | var clause = clauses.get(i); | ||
32 | var clauseRewriter = new ClauseInputParameterResolver(this, context, clause, dnfName, i); | ||
33 | builder.clause(clauseRewriter.rewriteClause()); | ||
34 | } | ||
35 | return builder.build(); | ||
36 | } | ||
37 | |||
38 | private static void createSymbolicParameters(List<Literal> context, Dnf dnf, DnfBuilder builder) { | ||
39 | var positiveInContext = new HashSet<Variable>(); | ||
40 | for (var literal : context) { | ||
41 | positiveInContext.addAll(literal.getOutputVariables()); | ||
42 | } | ||
43 | for (var symbolicParameter : dnf.getSymbolicParameters()) { | ||
44 | var variable = symbolicParameter.getVariable(); | ||
45 | var isOutput = symbolicParameter.getDirection() == ParameterDirection.OUT || | ||
46 | positiveInContext.contains(variable); | ||
47 | var direction = isOutput ? ParameterDirection.OUT : ParameterDirection.IN; | ||
48 | builder.parameter(variable, direction); | ||
49 | } | ||
50 | } | ||
51 | } | ||
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 index d0ae3c12..5cecc35b 100644 --- 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 | |||
@@ -6,6 +6,7 @@ | |||
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | 10 | ||
10 | import java.util.Objects; | 11 | import java.util.Objects; |
11 | 12 | ||
@@ -17,13 +18,18 @@ public abstract class AbstractTerm<T> implements Term<T> { | |||
17 | } | 18 | } |
18 | 19 | ||
19 | @Override | 20 | @Override |
21 | public Class<T> getType() { | ||
22 | return type; | ||
23 | } | ||
24 | |||
25 | @Override | ||
20 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { | 26 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { |
21 | return getClass().equals(other.getClass()) && type.equals(other.getType()); | 27 | return other != null && getClass() == other.getClass() && type.equals(other.getType()); |
22 | } | 28 | } |
23 | 29 | ||
24 | @Override | 30 | @Override |
25 | public Class<T> getType() { | 31 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { |
26 | return type; | 32 | return Objects.hash(getClass(), type); |
27 | } | 33 | } |
28 | 34 | ||
29 | @Override | 35 | @Override |
@@ -31,11 +37,11 @@ public abstract class AbstractTerm<T> implements Term<T> { | |||
31 | if (this == o) return true; | 37 | if (this == o) return true; |
32 | if (o == null || getClass() != o.getClass()) return false; | 38 | if (o == null || getClass() != o.getClass()) return false; |
33 | AbstractTerm<?> that = (AbstractTerm<?>) o; | 39 | AbstractTerm<?> that = (AbstractTerm<?>) o; |
34 | return type.equals(that.type); | 40 | return equalsWithSubstitution(LiteralEqualityHelper.DEFAULT, that); |
35 | } | 41 | } |
36 | 42 | ||
37 | @Override | 43 | @Override |
38 | public int hashCode() { | 44 | public int hashCode() { |
39 | return Objects.hash(getClass(), type); | 45 | return hashCodeWithSubstitution(LiteralHashCodeHelper.DEFAULT); |
40 | } | 46 | } |
41 | } | 47 | } |
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 index 192c39c5..3801bc11 100644 --- 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 | |||
@@ -6,6 +6,7 @@ | |||
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | import org.jetbrains.annotations.Nullable; | 8 | import org.jetbrains.annotations.Nullable; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | 11 | ||
11 | import java.util.Optional; | 12 | import java.util.Optional; |
@@ -22,8 +23,18 @@ public abstract sealed class AnyDataVariable extends Variable implements AnyTerm | |||
22 | } | 23 | } |
23 | 24 | ||
24 | @Override | 25 | @Override |
26 | public boolean isNodeVariable() { | ||
27 | return false; | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public boolean isDataVariable() { | ||
32 | return true; | ||
33 | } | ||
34 | |||
35 | @Override | ||
25 | public NodeVariable asNodeVariable() { | 36 | public NodeVariable asNodeVariable() { |
26 | throw new IllegalStateException("%s is a data variable".formatted(this)); | 37 | throw new InvalidQueryException("%s is a data variable".formatted(this)); |
27 | } | 38 | } |
28 | 39 | ||
29 | @Override | 40 | @Override |
@@ -37,11 +48,6 @@ public abstract sealed class AnyDataVariable extends Variable implements AnyTerm | |||
37 | } | 48 | } |
38 | 49 | ||
39 | @Override | 50 | @Override |
40 | public boolean isUnifiable() { | ||
41 | return false; | ||
42 | } | ||
43 | |||
44 | @Override | ||
45 | public abstract AnyDataVariable renew(@Nullable String name); | 51 | public abstract AnyDataVariable renew(@Nullable String name); |
46 | 52 | ||
47 | @Override | 53 | @Override |
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 index c12c0166..f136b68d 100644 --- 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 | |||
@@ -6,6 +6,7 @@ | |||
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | 10 | import tools.refinery.store.query.substitution.Substitution; |
10 | 11 | ||
11 | import java.util.Set; | 12 | import java.util.Set; |
@@ -17,5 +18,7 @@ public sealed interface AnyTerm permits AnyDataVariable, Term { | |||
17 | 18 | ||
18 | boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other); | 19 | boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other); |
19 | 20 | ||
21 | int hashCodeWithSubstitution(LiteralHashCodeHelper helper); | ||
22 | |||
20 | Set<AnyDataVariable> getInputVariables(); | 23 | Set<AnyDataVariable> getInputVariables(); |
21 | } | 24 | } |
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 index 8ad17839..cdbf592a 100644 --- 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 | |||
@@ -5,7 +5,9 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | 11 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.valuation.Valuation; | 12 | import tools.refinery.store.query.valuation.Valuation; |
11 | 13 | ||
@@ -14,6 +16,8 @@ import java.util.HashSet; | |||
14 | import java.util.Objects; | 16 | import java.util.Objects; |
15 | import java.util.Set; | 17 | import java.util.Set; |
16 | 18 | ||
19 | // {@link Object#equals(Object)} is implemented by {@link AbstractTerm}. | ||
20 | @SuppressWarnings("squid:S2160") | ||
17 | public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> { | 21 | public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> { |
18 | private final Class<T1> leftType; | 22 | private final Class<T1> leftType; |
19 | private final Class<T2> rightType; | 23 | private final Class<T2> rightType; |
@@ -23,11 +27,11 @@ public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> { | |||
23 | protected BinaryTerm(Class<R> type, Class<T1> leftType, Class<T2> rightType, Term<T1> left, Term<T2> right) { | 27 | protected BinaryTerm(Class<R> type, Class<T1> leftType, Class<T2> rightType, Term<T1> left, Term<T2> right) { |
24 | super(type); | 28 | super(type); |
25 | if (!left.getType().equals(leftType)) { | 29 | if (!left.getType().equals(leftType)) { |
26 | throw new IllegalArgumentException("Expected left %s to be of type %s, got %s instead".formatted( | 30 | throw new InvalidQueryException("Expected left %s to be of type %s, got %s instead".formatted( |
27 | left, leftType.getName(), left.getType().getName())); | 31 | left, leftType.getName(), left.getType().getName())); |
28 | } | 32 | } |
29 | if (!right.getType().equals(rightType)) { | 33 | if (!right.getType().equals(rightType)) { |
30 | throw new IllegalArgumentException("Expected right %s to be of type %s, got %s instead".formatted( | 34 | throw new InvalidQueryException("Expected right %s to be of type %s, got %s instead".formatted( |
31 | right, rightType.getName(), right.getType().getName())); | 35 | right, rightType.getName(), right.getType().getName())); |
32 | } | 36 | } |
33 | this.leftType = leftType; | 37 | this.leftType = leftType; |
@@ -80,6 +84,12 @@ public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> { | |||
80 | } | 84 | } |
81 | 85 | ||
82 | @Override | 86 | @Override |
87 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { | ||
88 | return Objects.hash(super.hashCodeWithSubstitution(helper), leftType.hashCode(), rightType.hashCode(), | ||
89 | left.hashCodeWithSubstitution(helper), right.hashCodeWithSubstitution(helper)); | ||
90 | } | ||
91 | |||
92 | @Override | ||
83 | public Term<R> substitute(Substitution substitution) { | 93 | public Term<R> substitute(Substitution substitution) { |
84 | return doSubstitute(substitution, left.substitute(substitution), right.substitute(substitution)); | 94 | return doSubstitute(substitution, left.substitute(substitution), right.substitute(substitution)); |
85 | } | 95 | } |
@@ -93,21 +103,4 @@ public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> { | |||
93 | inputVariables.addAll(right.getInputVariables()); | 103 | inputVariables.addAll(right.getInputVariables()); |
94 | return Collections.unmodifiableSet(inputVariables); | 104 | return Collections.unmodifiableSet(inputVariables); |
95 | } | 105 | } |
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 | } | 106 | } |
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 index 2f6c56d1..415ae286 100644 --- 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 | |||
@@ -5,20 +5,24 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | 11 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.valuation.Valuation; | 12 | import tools.refinery.store.query.valuation.Valuation; |
11 | 13 | ||
12 | import java.util.Objects; | 14 | import java.util.Objects; |
13 | import java.util.Set; | 15 | import java.util.Set; |
14 | 16 | ||
17 | // {@link Object#equals(Object)} is implemented by {@link AbstractTerm}. | ||
18 | @SuppressWarnings("squid:S2160") | ||
15 | public final class ConstantTerm<T> extends AbstractTerm<T> { | 19 | public final class ConstantTerm<T> extends AbstractTerm<T> { |
16 | private final T value; | 20 | private final T value; |
17 | 21 | ||
18 | public ConstantTerm(Class<T> type, T value) { | 22 | public ConstantTerm(Class<T> type, T value) { |
19 | super(type); | 23 | super(type); |
20 | if (value != null && !type.isInstance(value)) { | 24 | if (value != null && !type.isInstance(value)) { |
21 | throw new IllegalArgumentException("Value %s is not an instance of %s".formatted(value, type.getName())); | 25 | throw new InvalidQueryException("Value %s is not an instance of %s".formatted(value, type.getName())); |
22 | } | 26 | } |
23 | this.value = value; | 27 | this.value = value; |
24 | } | 28 | } |
@@ -47,6 +51,11 @@ public final class ConstantTerm<T> extends AbstractTerm<T> { | |||
47 | } | 51 | } |
48 | 52 | ||
49 | @Override | 53 | @Override |
54 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { | ||
55 | return Objects.hash(super.hashCodeWithSubstitution(helper), Objects.hash(value)); | ||
56 | } | ||
57 | |||
58 | @Override | ||
50 | public Set<AnyDataVariable> getInputVariables() { | 59 | public Set<AnyDataVariable> getInputVariables() { |
51 | return Set.of(); | 60 | return Set.of(); |
52 | } | 61 | } |
@@ -55,17 +64,4 @@ public final class ConstantTerm<T> extends AbstractTerm<T> { | |||
55 | public String toString() { | 64 | public String toString() { |
56 | return value.toString(); | 65 | return value.toString(); |
57 | } | 66 | } |
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 | } | 67 | } |
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 index 00950360..2206b522 100644 --- 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 | |||
@@ -6,7 +6,10 @@ | |||
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | import org.jetbrains.annotations.Nullable; | 8 | import org.jetbrains.annotations.Nullable; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
12 | import tools.refinery.store.query.literal.EquivalenceLiteral; | ||
10 | import tools.refinery.store.query.literal.Literal; | 13 | import tools.refinery.store.query.literal.Literal; |
11 | import tools.refinery.store.query.substitution.Substitution; | 14 | import tools.refinery.store.query.substitution.Substitution; |
12 | import tools.refinery.store.query.valuation.Valuation; | 15 | import tools.refinery.store.query.valuation.Valuation; |
@@ -39,8 +42,8 @@ public final class DataVariable<T> extends AnyDataVariable implements Term<T> { | |||
39 | @Override | 42 | @Override |
40 | public <U> DataVariable<U> asDataVariable(Class<U> newType) { | 43 | public <U> DataVariable<U> asDataVariable(Class<U> newType) { |
41 | if (!getType().equals(newType)) { | 44 | if (!getType().equals(newType)) { |
42 | throw new IllegalStateException("%s is not of type %s but of type %s".formatted(this, newType.getName(), | 45 | throw new InvalidQueryException("%s is not of type %s but of type %s" |
43 | getType().getName())); | 46 | .formatted(this, newType.getName(), getType().getName())); |
44 | } | 47 | } |
45 | @SuppressWarnings("unchecked") | 48 | @SuppressWarnings("unchecked") |
46 | var result = (DataVariable<U>) this; | 49 | var result = (DataVariable<U>) this; |
@@ -62,6 +65,16 @@ public final class DataVariable<T> extends AnyDataVariable implements Term<T> { | |||
62 | return other instanceof DataVariable<?> dataVariable && helper.variableEqual(this, dataVariable); | 65 | return other instanceof DataVariable<?> dataVariable && helper.variableEqual(this, dataVariable); |
63 | } | 66 | } |
64 | 67 | ||
68 | @Override | ||
69 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { | ||
70 | return helper.getVariableHashCode(this); | ||
71 | } | ||
72 | |||
73 | @Override | ||
74 | public int hashCodeWithSubstitution(int sequenceNumber) { | ||
75 | return Objects.hash(type, sequenceNumber); | ||
76 | } | ||
77 | |||
65 | public Literal assign(AssignedValue<T> value) { | 78 | public Literal assign(AssignedValue<T> value) { |
66 | return value.toLiteral(this); | 79 | return value.toLiteral(this); |
67 | } | 80 | } |
@@ -79,4 +92,12 @@ public final class DataVariable<T> extends AnyDataVariable implements Term<T> { | |||
79 | public int hashCode() { | 92 | public int hashCode() { |
80 | return Objects.hash(super.hashCode(), type); | 93 | return Objects.hash(super.hashCode(), type); |
81 | } | 94 | } |
95 | |||
96 | public EquivalenceLiteral isEquivalent(DataVariable<T> other) { | ||
97 | return new EquivalenceLiteral(true, this, other); | ||
98 | } | ||
99 | |||
100 | public EquivalenceLiteral notEquivalent(DataVariable<T> other) { | ||
101 | return new EquivalenceLiteral(false, this, other); | ||
102 | } | ||
82 | } | 103 | } |
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 index a2f3261f..53c32e20 100644 --- 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 | |||
@@ -6,6 +6,7 @@ | |||
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | import org.jetbrains.annotations.Nullable; | 8 | import org.jetbrains.annotations.Nullable; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.literal.ConstantLiteral; | 10 | import tools.refinery.store.query.literal.ConstantLiteral; |
10 | import tools.refinery.store.query.literal.EquivalenceLiteral; | 11 | import tools.refinery.store.query.literal.EquivalenceLiteral; |
11 | 12 | ||
@@ -22,11 +23,6 @@ public final class NodeVariable extends Variable { | |||
22 | } | 23 | } |
23 | 24 | ||
24 | @Override | 25 | @Override |
25 | public boolean isUnifiable() { | ||
26 | return true; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public NodeVariable renew(@Nullable String name) { | 26 | public NodeVariable renew(@Nullable String name) { |
31 | return Variable.of(name); | 27 | return Variable.of(name); |
32 | } | 28 | } |
@@ -37,13 +33,28 @@ public final class NodeVariable extends Variable { | |||
37 | } | 33 | } |
38 | 34 | ||
39 | @Override | 35 | @Override |
36 | public boolean isNodeVariable() { | ||
37 | return true; | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public boolean isDataVariable() { | ||
42 | return false; | ||
43 | } | ||
44 | |||
45 | @Override | ||
40 | public NodeVariable asNodeVariable() { | 46 | public NodeVariable asNodeVariable() { |
41 | return this; | 47 | return this; |
42 | } | 48 | } |
43 | 49 | ||
44 | @Override | 50 | @Override |
45 | public <T> DataVariable<T> asDataVariable(Class<T> type) { | 51 | public <T> DataVariable<T> asDataVariable(Class<T> type) { |
46 | throw new IllegalStateException("%s is a node variable".formatted(this)); | 52 | throw new InvalidQueryException("%s is a node variable".formatted(this)); |
53 | } | ||
54 | |||
55 | @Override | ||
56 | public int hashCodeWithSubstitution(int sequenceNumber) { | ||
57 | return sequenceNumber; | ||
47 | } | 58 | } |
48 | 59 | ||
49 | public ConstantLiteral isConstant(int value) { | 60 | public ConstantLiteral isConstant(int value) { |
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 index e5a0cdf1..577ac6e0 100644 --- 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 | |||
@@ -9,11 +9,15 @@ import java.util.Objects; | |||
9 | import java.util.Optional; | 9 | import java.util.Optional; |
10 | 10 | ||
11 | public class Parameter { | 11 | public class Parameter { |
12 | public static final Parameter NODE_OUT = new Parameter(null, ParameterDirection.OUT); | 12 | public static final Parameter NODE_OUT = new Parameter(null); |
13 | 13 | ||
14 | private final Class<?> dataType; | 14 | private final Class<?> dataType; |
15 | private final ParameterDirection direction; | 15 | private final ParameterDirection direction; |
16 | 16 | ||
17 | public Parameter(Class<?> dataType) { | ||
18 | this(dataType, ParameterDirection.OUT); | ||
19 | } | ||
20 | |||
17 | public Parameter(Class<?> dataType, ParameterDirection direction) { | 21 | public Parameter(Class<?> dataType, ParameterDirection direction) { |
18 | this.dataType = dataType; | 22 | this.dataType = dataType; |
19 | this.direction = direction; | 23 | this.direction = direction; |
@@ -35,6 +39,10 @@ public class Parameter { | |||
35 | return direction; | 39 | return direction; |
36 | } | 40 | } |
37 | 41 | ||
42 | public boolean matches(Parameter other) { | ||
43 | return Objects.equals(dataType, other.dataType) && direction == other.direction; | ||
44 | } | ||
45 | |||
38 | public boolean isAssignable(Variable variable) { | 46 | public boolean isAssignable(Variable variable) { |
39 | if (variable instanceof AnyDataVariable dataVariable) { | 47 | if (variable instanceof AnyDataVariable dataVariable) { |
40 | return dataVariable.getType().equals(dataType); | 48 | return dataVariable.getType().equals(dataType); |
@@ -50,7 +58,7 @@ public class Parameter { | |||
50 | if (this == o) return true; | 58 | if (this == o) return true; |
51 | if (o == null || getClass() != o.getClass()) return false; | 59 | if (o == null || getClass() != o.getClass()) return false; |
52 | Parameter parameter = (Parameter) o; | 60 | Parameter parameter = (Parameter) o; |
53 | return Objects.equals(dataType, parameter.dataType) && direction == parameter.direction; | 61 | return matches(parameter); |
54 | } | 62 | } |
55 | 63 | ||
56 | @Override | 64 | @Override |
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 index cd0739be..da83f3c3 100644 --- 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 | |||
@@ -6,8 +6,8 @@ | |||
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | public enum ParameterDirection { | 8 | public enum ParameterDirection { |
9 | OUT("@Out"), | 9 | OUT("out"), |
10 | IN("@In"); | 10 | IN("in"); |
11 | 11 | ||
12 | private final String name; | 12 | private final String name; |
13 | 13 | ||
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 index a46ebe31..a464ece5 100644 --- 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 | |||
@@ -5,13 +5,17 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | 11 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.valuation.Valuation; | 12 | import tools.refinery.store.query.valuation.Valuation; |
11 | 13 | ||
12 | import java.util.Objects; | 14 | import java.util.Objects; |
13 | import java.util.Set; | 15 | import java.util.Set; |
14 | 16 | ||
17 | // {@link Object#equals(Object)} is implemented by {@link AbstractTerm}. | ||
18 | @SuppressWarnings("squid:S2160") | ||
15 | public abstract class UnaryTerm<R, T> extends AbstractTerm<R> { | 19 | public abstract class UnaryTerm<R, T> extends AbstractTerm<R> { |
16 | private final Class<T> bodyType; | 20 | private final Class<T> bodyType; |
17 | private final Term<T> body; | 21 | private final Term<T> body; |
@@ -19,7 +23,7 @@ public abstract class UnaryTerm<R, T> extends AbstractTerm<R> { | |||
19 | protected UnaryTerm(Class<R> type, Class<T> bodyType, Term<T> body) { | 23 | protected UnaryTerm(Class<R> type, Class<T> bodyType, Term<T> body) { |
20 | super(type); | 24 | super(type); |
21 | if (!body.getType().equals(bodyType)) { | 25 | if (!body.getType().equals(bodyType)) { |
22 | throw new IllegalArgumentException("Expected body %s to be of type %s, got %s instead".formatted(body, | 26 | throw new InvalidQueryException("Expected body %s to be of type %s, got %s instead".formatted(body, |
23 | bodyType.getName(), body.getType().getName())); | 27 | bodyType.getName(), body.getType().getName())); |
24 | } | 28 | } |
25 | this.bodyType = bodyType; | 29 | this.bodyType = bodyType; |
@@ -52,6 +56,11 @@ public abstract class UnaryTerm<R, T> extends AbstractTerm<R> { | |||
52 | } | 56 | } |
53 | 57 | ||
54 | @Override | 58 | @Override |
59 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { | ||
60 | return Objects.hash(super.hashCodeWithSubstitution(helper), bodyType, body.hashCodeWithSubstitution(helper)); | ||
61 | } | ||
62 | |||
63 | @Override | ||
55 | public Term<R> substitute(Substitution substitution) { | 64 | public Term<R> substitute(Substitution substitution) { |
56 | return doSubstitute(substitution, body.substitute(substitution)); | 65 | return doSubstitute(substitution, body.substitute(substitution)); |
57 | } | 66 | } |
@@ -62,18 +71,4 @@ public abstract class UnaryTerm<R, T> extends AbstractTerm<R> { | |||
62 | public Set<AnyDataVariable> getInputVariables() { | 71 | public Set<AnyDataVariable> getInputVariables() { |
63 | return body.getInputVariables(); | 72 | return body.getInputVariables(); |
64 | } | 73 | } |
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 | } | 74 | } |
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 index a0268c8e..1b553704 100644 --- 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 | |||
@@ -38,16 +38,20 @@ public abstract sealed class Variable permits AnyDataVariable, NodeVariable { | |||
38 | return uniqueName; | 38 | return uniqueName; |
39 | } | 39 | } |
40 | 40 | ||
41 | public abstract boolean isUnifiable(); | ||
42 | |||
43 | public abstract Variable renew(@Nullable String name); | 41 | public abstract Variable renew(@Nullable String name); |
44 | 42 | ||
45 | public abstract Variable renew(); | 43 | public abstract Variable renew(); |
46 | 44 | ||
45 | public abstract boolean isNodeVariable(); | ||
46 | |||
47 | public abstract boolean isDataVariable(); | ||
48 | |||
47 | public abstract NodeVariable asNodeVariable(); | 49 | public abstract NodeVariable asNodeVariable(); |
48 | 50 | ||
49 | public abstract <T> DataVariable<T> asDataVariable(Class<T> type); | 51 | public abstract <T> DataVariable<T> asDataVariable(Class<T> type); |
50 | 52 | ||
53 | public abstract int hashCodeWithSubstitution(int sequenceNumber); | ||
54 | |||
51 | @Override | 55 | @Override |
52 | public String toString() { | 56 | public String toString() { |
53 | return getName(); | 57 | return getName(); |
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 index 5bbd3081..d31f00a2 100644 --- 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 | |||
@@ -70,7 +70,7 @@ public class UpperCardinalitySumAggregator implements StatefulAggregator<UpperCa | |||
70 | 70 | ||
71 | @Override | 71 | @Override |
72 | public UpperCardinality getResult() { | 72 | public UpperCardinality getResult() { |
73 | return countUnbounded > 0 ? UpperCardinalities.UNBOUNDED : UpperCardinalities.valueOf(sumFiniteUpperBounds); | 73 | return countUnbounded > 0 ? UpperCardinalities.UNBOUNDED : UpperCardinalities.atMost(sumFiniteUpperBounds); |
74 | } | 74 | } |
75 | 75 | ||
76 | @Override | 76 | @Override |
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 index b568b99d..2d8a10d1 100644 --- 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 | |||
@@ -2,7 +2,7 @@ | |||
2 | * Copyright (c) 2021 Rodion Efremov | 2 | * Copyright (c) 2021 Rodion Efremov |
3 | * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/> | 3 | * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/> |
4 | * | 4 | * |
5 | * SPDX-License-Identifier: MIT OR EPL-2.0 | 5 | * SPDX-License-Identifier: MIT |
6 | */ | 6 | */ |
7 | package tools.refinery.store.query.utils; | 7 | package tools.refinery.store.query.utils; |
8 | 8 | ||
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 index fd37604e..f130fa59 100644 --- 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 | |||
@@ -5,6 +5,7 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.view; | 6 | package tools.refinery.store.query.view; |
7 | 7 | ||
8 | import tools.refinery.store.map.CursorAsIterator; | ||
8 | import tools.refinery.store.model.Model; | 9 | import tools.refinery.store.model.Model; |
9 | import tools.refinery.store.query.dnf.FunctionalDependency; | 10 | import tools.refinery.store.query.dnf.FunctionalDependency; |
10 | import tools.refinery.store.query.term.Parameter; | 11 | import tools.refinery.store.query.term.Parameter; |
@@ -83,6 +84,20 @@ public abstract class AbstractFunctionView<T> extends SymbolView<T> { | |||
83 | } | 84 | } |
84 | 85 | ||
85 | @Override | 86 | @Override |
87 | public boolean canIndexSlot(int slot) { | ||
88 | return slot >= 0 && slot < getSymbol().arity(); | ||
89 | } | ||
90 | |||
91 | @Override | ||
92 | public Iterable<Object[]> getAdjacent(Model model, int slot, Object value) { | ||
93 | if (!(value instanceof Tuple1 tuple1)) { | ||
94 | return Set.of(); | ||
95 | } | ||
96 | return (() -> new CursorAsIterator<>(model.getInterpretation(getSymbol()).getAdjacent(slot, tuple1.get(0)), | ||
97 | this::forwardMap, this::filter)); | ||
98 | } | ||
99 | |||
100 | @Override | ||
86 | public List<Parameter> getParameters() { | 101 | public List<Parameter> getParameters() { |
87 | return parameters; | 102 | return parameters; |
88 | } | 103 | } |
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 index 90b27ebb..7e9bf6df 100644 --- 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 | |||
@@ -28,4 +28,12 @@ public sealed interface AnySymbolView extends Constraint permits SymbolView { | |||
28 | boolean get(Model model, Object[] tuple); | 28 | boolean get(Model model, Object[] tuple); |
29 | 29 | ||
30 | Iterable<Object[]> getAll(Model model); | 30 | Iterable<Object[]> getAll(Model model); |
31 | |||
32 | default Iterable<Object[]> getAdjacent(Model model, int slot, Object value) { | ||
33 | throw new IllegalArgumentException("Cannot index slot " + slot); | ||
34 | } | ||
35 | |||
36 | default boolean canIndexSlot(int slot) { | ||
37 | return false; | ||
38 | } | ||
31 | } | 39 | } |
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 index abae6e5c..924277ed 100644 --- 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 | |||
@@ -5,6 +5,7 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.view; | 6 | package tools.refinery.store.query.view; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.tuple.Tuple; | 9 | import tools.refinery.store.tuple.Tuple; |
9 | import tools.refinery.store.representation.Symbol; | 10 | import tools.refinery.store.representation.Symbol; |
10 | 11 | ||
@@ -66,7 +67,7 @@ public class FilteredView<T> extends TuplePreservingView<T> { | |||
66 | // The predicate doesn't need to handle the default value if it is null. | 67 | // The predicate doesn't need to handle the default value if it is null. |
67 | } | 68 | } |
68 | if (matchesDefaultValue) { | 69 | if (matchesDefaultValue) { |
69 | throw new IllegalArgumentException("Tuples with default value %s cannot be enumerated in %s" | 70 | throw new InvalidQueryException("Tuples with default value %s cannot be enumerated in %s" |
70 | .formatted(defaultValue, getSymbol())); | 71 | .formatted(defaultValue, getSymbol())); |
71 | } | 72 | } |
72 | } | 73 | } |
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 index 6bc5a708..ed12cd9d 100644 --- 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 | |||
@@ -5,6 +5,7 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.view; | 6 | package tools.refinery.store.query.view; |
7 | 7 | ||
8 | import tools.refinery.store.map.CursorAsIterator; | ||
8 | import tools.refinery.store.model.Model; | 9 | import tools.refinery.store.model.Model; |
9 | import tools.refinery.store.query.term.Parameter; | 10 | import tools.refinery.store.query.term.Parameter; |
10 | import tools.refinery.store.representation.Symbol; | 11 | import tools.refinery.store.representation.Symbol; |
@@ -14,6 +15,7 @@ import tools.refinery.store.tuple.Tuple1; | |||
14 | import java.util.Arrays; | 15 | import java.util.Arrays; |
15 | import java.util.List; | 16 | import java.util.List; |
16 | import java.util.Objects; | 17 | import java.util.Objects; |
18 | import java.util.Set; | ||
17 | 19 | ||
18 | public abstract class TuplePreservingView<T> extends SymbolView<T> { | 20 | public abstract class TuplePreservingView<T> extends SymbolView<T> { |
19 | private final List<Parameter> parameters; | 21 | private final List<Parameter> parameters; |
@@ -56,6 +58,20 @@ public abstract class TuplePreservingView<T> extends SymbolView<T> { | |||
56 | } | 58 | } |
57 | 59 | ||
58 | @Override | 60 | @Override |
61 | public boolean canIndexSlot(int slot) { | ||
62 | return slot >= 0 && slot < getSymbol().arity(); | ||
63 | } | ||
64 | |||
65 | @Override | ||
66 | public Iterable<Object[]> getAdjacent(Model model, int slot, Object value) { | ||
67 | if (!(value instanceof Tuple1 tuple1)) { | ||
68 | return Set.of(); | ||
69 | } | ||
70 | return (() -> new CursorAsIterator<>(model.getInterpretation(getSymbol()).getAdjacent(slot, tuple1.get(0)), | ||
71 | this::forwardMap, this::filter)); | ||
72 | } | ||
73 | |||
74 | @Override | ||
59 | public List<Parameter> getParameters() { | 75 | public List<Parameter> getParameters() { |
60 | return parameters; | 76 | return parameters; |
61 | } | 77 | } |