aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store-query/src/main/java
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-06-25 21:42:43 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-06-29 02:23:00 +0200
commit79a3fd092c79efc204d2980b07728258372871c4 (patch)
treee515ff637c1d1288281284d62d33175dd5d19f35 /subprojects/store-query/src/main/java
parentfeat: ordered query ResultSet (diff)
downloadrefinery-79a3fd092c79efc204d2980b07728258372871c4.tar.gz
refinery-79a3fd092c79efc204d2980b07728258372871c4.tar.zst
refinery-79a3fd092c79efc204d2980b07728258372871c4.zip
refactor: query equality and hash code
Allow computing hash codes up to the renaming of variables. Also introduces CheckLiteral instead of AssumeLiteral for more straightforward naming.
Diffstat (limited to 'subprojects/store-query/src/main/java')
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java4
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java18
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java9
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java5
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java2
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java4
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java49
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralHashCodeHelper.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralEqualityHelper.java59
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralHashCodeHelper.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractLiteral.java34
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java18
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java42
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java6
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java20
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CheckLiteral.java (renamed from subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java)45
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java38
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java49
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java3
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java4
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java3
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java26
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java11
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java5
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java2
30 files changed, 388 insertions, 211 deletions
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..1a45c20a 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
@@ -67,13 +67,13 @@ class ClausePostProcessor {
67 for (var literal : literals) { 67 for (var literal : literals) {
68 if (isPositiveEquivalence(literal)) { 68 if (isPositiveEquivalence(literal)) {
69 var equivalenceLiteral = (EquivalenceLiteral) literal; 69 var equivalenceLiteral = (EquivalenceLiteral) literal;
70 mergeVariables(equivalenceLiteral.left(), equivalenceLiteral.right()); 70 mergeVariables(equivalenceLiteral.getLeft(), equivalenceLiteral.getRight());
71 } 71 }
72 } 72 }
73 } 73 }
74 74
75 private static boolean isPositiveEquivalence(Literal literal) { 75 private static boolean isPositiveEquivalence(Literal literal) {
76 return literal instanceof EquivalenceLiteral equivalenceLiteral && equivalenceLiteral.positive(); 76 return literal instanceof EquivalenceLiteral equivalenceLiteral && equivalenceLiteral.isPositive();
77 } 77 }
78 78
79 private void mergeVariables(NodeVariable left, NodeVariable right) { 79 private void mergeVariables(NodeVariable left, NodeVariable right) {
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..7a3e2a1e 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,11 @@
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.literal.Reduction;
10import tools.refinery.store.query.equality.DnfEqualityChecker; 9import tools.refinery.store.query.equality.DnfEqualityChecker;
11import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
11import tools.refinery.store.query.equality.SubstitutingLiteralEqualityHelper;
12import tools.refinery.store.query.equality.SubstitutingLiteralHashCodeHelper;
13import tools.refinery.store.query.literal.Reduction;
12import tools.refinery.store.query.term.Parameter; 14import tools.refinery.store.query.term.Parameter;
13import tools.refinery.store.query.term.Variable; 15import tools.refinery.store.query.term.Variable;
14 16
@@ -129,7 +131,7 @@ public final class Dnf implements Constraint {
129 return false; 131 return false;
130 } 132 }
131 for (int i = 0; i < numClauses; i++) { 133 for (int i = 0; i < numClauses; i++) {
132 var literalEqualityHelper = new LiteralEqualityHelper(callEqualityChecker, symbolicParameters, 134 var literalEqualityHelper = new SubstitutingLiteralEqualityHelper(callEqualityChecker, symbolicParameters,
133 other.symbolicParameters); 135 other.symbolicParameters);
134 if (!clauses.get(i).equalsWithSubstitution(literalEqualityHelper, other.clauses.get(i))) { 136 if (!clauses.get(i).equalsWithSubstitution(literalEqualityHelper, other.clauses.get(i))) {
135 return false; 137 return false;
@@ -146,6 +148,18 @@ public final class Dnf implements Constraint {
146 return false; 148 return false;
147 } 149 }
148 150
151 public int hashCodeWithSubstitution() {
152 var helper = new SubstitutingLiteralHashCodeHelper();
153 int result = 0;
154 for (var symbolicParameter : symbolicParameters) {
155 result = result * 31 + symbolicParameter.hashCodeWithSubstitution(helper);
156 }
157 for (var clause : clauses) {
158 result = result * 31 + clause.hashCodeWithSubstitution(helper);
159 }
160 return result;
161 }
162
149 @Override 163 @Override
150 public String toString() { 164 public String toString() {
151 return "%s/%d".formatted(name(), arity()); 165 return "%s/%d".formatted(name(), arity());
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 @@
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.literal.Literal; 10import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.term.Variable; 11import 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/SymbolicParameter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java
index e0d3ba1f..1e96cf27 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 */
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.equality.LiteralHashCodeHelper;
8import tools.refinery.store.query.term.Parameter; 9import tools.refinery.store.query.term.Parameter;
9import tools.refinery.store.query.term.ParameterDirection; 10import tools.refinery.store.query.term.ParameterDirection;
10import tools.refinery.store.query.term.Variable; 11import tools.refinery.store.query.term.Variable;
@@ -27,6 +28,10 @@ public final class SymbolicParameter extends Parameter {
27 return variable.isUnifiable(); 28 return variable.isUnifiable();
28 } 29 }
29 30
31 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
32 return Objects.hash(super.hashCode(), helper.getVariableHashCode(variable));
33 }
34
30 @Override 35 @Override
31 public String toString() { 36 public String toString() {
32 var direction = getDirection(); 37 var direction = getDirection();
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..a212b3f5 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
@@ -38,7 +38,7 @@ public class DeepDnfEqualityChecker implements DnfEqualityChecker {
38 return false; 38 return false;
39 } 39 }
40 for (int i = 0; i < numClauses; i++) { 40 for (int i = 0; i < numClauses; i++) {
41 var literalEqualityHelper = new LiteralEqualityHelper(this, symbolicParameters, 41 var literalEqualityHelper = new SubstitutingLiteralEqualityHelper(this, symbolicParameters,
42 other.getSymbolicParameters()); 42 other.getSymbolicParameters());
43 if (!equalsWithSubstitutionRaw(literalEqualityHelper, clauses.get(i), other.getClauses().get(i))) { 43 if (!equalsWithSubstitutionRaw(literalEqualityHelper, clauses.get(i), other.getClauses().get(i))) {
44 return false; 44 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
8import tools.refinery.store.query.dnf.Dnf; 8import tools.refinery.store.query.dnf.Dnf;
9 9
10import java.util.Objects;
11
10@FunctionalInterface 12@FunctionalInterface
11public interface DnfEqualityChecker { 13public 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 @@
6package tools.refinery.store.query.equality; 6package tools.refinery.store.query.equality;
7 7
8import tools.refinery.store.query.dnf.Dnf; 8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.SymbolicParameter;
10import tools.refinery.store.query.term.Variable; 9import tools.refinery.store.query.term.Variable;
11 10
12import java.util.HashMap; 11import java.util.Objects;
13import java.util.List;
14import java.util.Map;
15 12
16public class LiteralEqualityHelper { 13public 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 */
6package tools.refinery.store.query.equality;
7
8import tools.refinery.store.query.term.Variable;
9
10import java.util.Objects;
11
12@FunctionalInterface
13public 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 */
6package tools.refinery.store.query.equality;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.SymbolicParameter;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.HashMap;
13import java.util.List;
14import java.util.Map;
15
16public class 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..a40ecd58
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralHashCodeHelper.java
@@ -0,0 +1,30 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.equality;
7
8import tools.refinery.store.query.term.Variable;
9
10import java.util.LinkedHashMap;
11import java.util.Map;
12
13public class SubstitutingLiteralHashCodeHelper implements LiteralHashCodeHelper {
14 private final Map<Variable, Integer> assignedHashCodes = new LinkedHashMap<>();
15
16 // 0 is for {@code null}, so we start with 1.
17 private int next = 1;
18
19 @Override
20 public int getVariableHashCode(Variable variable) {
21 if (variable == null) {
22 return 0;
23 }
24 return assignedHashCodes.computeIfAbsent(variable, key -> {
25 int sequenceNumber = next;
26 next++;
27 return variable.hashCodeWithSubstitution(sequenceNumber);
28 });
29 }
30}
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..a355dc3b 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
@@ -7,13 +7,14 @@ package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.ParameterDirection; 12import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable; 13import tools.refinery.store.query.term.Variable;
13 14
14import java.util.*; 15import java.util.*;
15 16
16public abstract class AbstractCallLiteral implements Literal { 17public abstract class AbstractCallLiteral extends AbstractLiteral {
17 private final Constraint target; 18 private final Constraint target;
18 private final List<Variable> arguments; 19 private final List<Variable> arguments;
19 private final Set<Variable> inArguments; 20 private final Set<Variable> inArguments;
@@ -112,7 +113,7 @@ public abstract class AbstractCallLiteral implements Literal {
112 113
113 @Override 114 @Override
114 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { 115 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
115 if (other == null || getClass() != other.getClass()) { 116 if (!super.equalsWithSubstitution(helper, other)) {
116 return false; 117 return false;
117 } 118 }
118 var otherCallLiteral = (AbstractCallLiteral) other; 119 var otherCallLiteral = (AbstractCallLiteral) other;
@@ -129,15 +130,11 @@ public abstract class AbstractCallLiteral implements Literal {
129 } 130 }
130 131
131 @Override 132 @Override
132 public boolean equals(Object o) { 133 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
133 if (this == o) return true; 134 int result = super.hashCodeWithSubstitution(helper) * 31 + target.hashCode();
134 if (o == null || getClass() != o.getClass()) return false; 135 for (var argument : arguments) {
135 AbstractCallLiteral that = (AbstractCallLiteral) o; 136 result = result * 31 + helper.getVariableHashCode(argument);
136 return target.equals(that.target) && arguments.equals(that.arguments); 137 }
137 } 138 return result;
138
139 @Override
140 public int hashCode() {
141 return Objects.hash(getClass(), target, arguments);
142 } 139 }
143} 140}
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 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10
11public 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..615fd493 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
@@ -7,6 +7,7 @@ package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.*; 12import tools.refinery.store.query.term.*;
12 13
@@ -14,6 +15,8 @@ import java.util.List;
14import java.util.Objects; 15import java.util.Objects;
15import java.util.Set; 16import java.util.Set;
16 17
18// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
19@SuppressWarnings("squid:S2160")
17public class AggregationLiteral<R, T> extends AbstractCallLiteral { 20public class AggregationLiteral<R, T> extends AbstractCallLiteral {
18 private final DataVariable<R> resultVariable; 21 private final DataVariable<R> resultVariable;
19 private final DataVariable<T> inputVariable; 22 private final DataVariable<T> inputVariable;
@@ -100,18 +103,9 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral {
100 } 103 }
101 104
102 @Override 105 @Override
103 public boolean equals(Object o) { 106 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
104 if (this == o) return true; 107 return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(resultVariable),
105 if (o == null || getClass() != o.getClass()) return false; 108 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 } 109 }
116 110
117 @Override 111 @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..d8a4b494 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
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.DataVariable; 11import tools.refinery.store.query.term.DataVariable;
11import tools.refinery.store.query.term.Term; 12import tools.refinery.store.query.term.Term;
@@ -15,8 +16,11 @@ import java.util.Collections;
15import java.util.Objects; 16import java.util.Objects;
16import java.util.Set; 17import java.util.Set;
17 18
18public record AssignLiteral<T>(DataVariable<T> variable, Term<T> term) implements Literal { 19public class AssignLiteral<T> extends AbstractLiteral {
19 public AssignLiteral { 20 private final DataVariable<T> variable;
21 private final Term<T> term;
22
23 public AssignLiteral(DataVariable<T> variable, Term<T> term) {
20 if (!term.getType().equals(variable.getType())) { 24 if (!term.getType().equals(variable.getType())) {
21 throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( 25 throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted(
22 term, variable.getType().getName(), term.getType().getName())); 26 term, variable.getType().getName(), term.getType().getName()));
@@ -26,6 +30,16 @@ public record AssignLiteral<T>(DataVariable<T> variable, Term<T> term) implement
26 throw new IllegalArgumentException("Result variable %s must not appear in the term %s".formatted( 30 throw new IllegalArgumentException("Result variable %s must not appear in the term %s".formatted(
27 variable, term)); 31 variable, term));
28 } 32 }
33 this.variable = variable;
34 this.term = term;
35 }
36
37 public DataVariable<T> getVariable() {
38 return variable;
39 }
40
41 public Term<T> getTerm() {
42 return term;
29 } 43 }
30 44
31 @Override 45 @Override
@@ -53,27 +67,19 @@ public record AssignLiteral<T>(DataVariable<T> variable, Term<T> term) implement
53 if (other == null || getClass() != other.getClass()) { 67 if (other == null || getClass() != other.getClass()) {
54 return false; 68 return false;
55 } 69 }
56 var otherLetLiteral = (AssignLiteral<?>) other; 70 var otherAssignLiteral = (AssignLiteral<?>) other;
57 return helper.variableEqual(variable, otherLetLiteral.variable) && term.equalsWithSubstitution(helper, 71 return helper.variableEqual(variable, otherAssignLiteral.variable) &&
58 otherLetLiteral.term); 72 term.equalsWithSubstitution(helper, otherAssignLiteral.term);
59 }
60
61 @Override
62 public String toString() {
63 return "%s is (%s)".formatted(variable, term);
64 } 73 }
65 74
66 @Override 75 @Override
67 public boolean equals(Object obj) { 76 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
68 if (obj == this) return true; 77 return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(variable),
69 if (obj == null || obj.getClass() != this.getClass()) return false; 78 term.hashCodeWithSubstitution(helper));
70 var that = (AssignLiteral<?>) obj;
71 return Objects.equals(this.variable, that.variable) &&
72 Objects.equals(this.term, that.term);
73 } 79 }
74 80
75 @Override 81 @Override
76 public int hashCode() { 82 public String toString() {
77 return Objects.hash(getClass(), variable, term); 83 return "%s is (%s)".formatted(variable, term);
78 } 84 }
79} 85}
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 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.Variable; 11import 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..a311dada 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
@@ -7,12 +7,15 @@ package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.ParameterDirection; 12import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable; 13import tools.refinery.store.query.term.Variable;
13 14
14import java.util.*; 15import java.util.*;
15 16
17// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
18@SuppressWarnings("squid:S2160")
16public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> { 19public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> {
17 private final CallPolarity polarity; 20 private final CallPolarity polarity;
18 21
@@ -85,22 +88,13 @@ public final class CallLiteral extends AbstractCallLiteral implements CanNegate<
85 } 88 }
86 89
87 @Override 90 @Override
88 public CallLiteral negate() { 91 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
89 return new CallLiteral(polarity.negate(), getTarget(), getArguments()); 92 return Objects.hash(super.hashCodeWithSubstitution(helper), polarity);
90 } 93 }
91 94
92 @Override 95 @Override
93 public boolean equals(Object o) { 96 public CallLiteral negate() {
94 if (this == o) return true; 97 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 }
100
101 @Override
102 public int hashCode() {
103 return Objects.hash(super.hashCode(), polarity);
104 } 98 }
105 99
106 @Override 100 @Override
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..1271183a 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
@@ -6,21 +6,31 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.ConstantTerm; 11import tools.refinery.store.query.term.ConstantTerm;
11import tools.refinery.store.query.term.Term; 12import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.Variable; 13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.term.bool.BoolNotTerm;
15import tools.refinery.store.query.term.bool.BoolTerms;
13 16
14import java.util.Collections; 17import java.util.Collections;
15import java.util.Objects; 18import java.util.Objects;
16import java.util.Set; 19import java.util.Set;
17 20
18public record AssumeLiteral(Term<Boolean> term) implements Literal { 21public class CheckLiteral extends AbstractLiteral implements CanNegate<CheckLiteral> {
19 public AssumeLiteral { 22 private final Term<Boolean> term;
23
24 public CheckLiteral(Term<Boolean> term) {
20 if (!term.getType().equals(Boolean.class)) { 25 if (!term.getType().equals(Boolean.class)) {
21 throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( 26 throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted(
22 term, Boolean.class.getName(), term.getType().getName())); 27 term, Boolean.class.getName(), term.getType().getName()));
23 } 28 }
29 this.term = term;
30 }
31
32 public Term<Boolean> getTerm() {
33 return term;
24 } 34 }
25 35
26 @Override 36 @Override
@@ -38,10 +48,17 @@ public record AssumeLiteral(Term<Boolean> term) implements Literal {
38 return Set.of(); 48 return Set.of();
39 } 49 }
40 50
41
42 @Override 51 @Override
43 public Literal substitute(Substitution substitution) { 52 public Literal substitute(Substitution substitution) {
44 return new AssumeLiteral(term.substitute(substitution)); 53 return new CheckLiteral(term.substitute(substitution));
54 }
55
56 @Override
57 public CheckLiteral negate() {
58 if (term instanceof BoolNotTerm notTerm) {
59 return new CheckLiteral(notTerm.getBody());
60 }
61 return new CheckLiteral(BoolTerms.not(term));
45 } 62 }
46 63
47 @Override 64 @Override
@@ -49,11 +66,16 @@ public record AssumeLiteral(Term<Boolean> term) implements Literal {
49 if (other == null || getClass() != other.getClass()) { 66 if (other == null || getClass() != other.getClass()) {
50 return false; 67 return false;
51 } 68 }
52 var otherAssumeLiteral = (AssumeLiteral) other; 69 var otherAssumeLiteral = (CheckLiteral) other;
53 return term.equalsWithSubstitution(helper, otherAssumeLiteral.term); 70 return term.equalsWithSubstitution(helper, otherAssumeLiteral.term);
54 } 71 }
55 72
56 @Override 73 @Override
74 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
75 return Objects.hash(super.hashCodeWithSubstitution(helper), term.hashCodeWithSubstitution(helper));
76 }
77
78 @Override
57 public Literal reduce() { 79 public Literal reduce() {
58 if (term instanceof ConstantTerm<Boolean> constantTerm) { 80 if (term instanceof ConstantTerm<Boolean> constantTerm) {
59 // Return {@link BooleanLiteral#FALSE} for {@code false} or {@code null} literals. 81 // Return {@link BooleanLiteral#FALSE} for {@code false} or {@code null} literals.
@@ -67,17 +89,4 @@ public record AssumeLiteral(Term<Boolean> term) implements Literal {
67 public String toString() { 89 public String toString() {
68 return "(%s)".formatted(term); 90 return "(%s)".formatted(term);
69 } 91 }
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} 92}
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 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.NodeVariable; 11import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.Variable; 12import tools.refinery.store.query.term.Variable;
@@ -13,7 +14,24 @@ import tools.refinery.store.query.term.Variable;
13import java.util.Objects; 14import java.util.Objects;
14import java.util.Set; 15import java.util.Set;
15 16
16public record ConstantLiteral(NodeVariable variable, int nodeId) implements Literal { 17public 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..ac4b8788 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
@@ -7,6 +7,7 @@ package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.DataVariable; 12import tools.refinery.store.query.term.DataVariable;
12import tools.refinery.store.query.term.Variable; 13import tools.refinery.store.query.term.Variable;
@@ -16,6 +17,8 @@ import java.util.List;
16import java.util.Objects; 17import java.util.Objects;
17import java.util.Set; 18import java.util.Set;
18 19
20// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
21@SuppressWarnings("squid:S2160")
19public class CountLiteral extends AbstractCallLiteral { 22public class CountLiteral extends AbstractCallLiteral {
20 private final DataVariable<Integer> resultVariable; 23 private final DataVariable<Integer> resultVariable;
21 24
@@ -68,17 +71,8 @@ public class CountLiteral extends AbstractCallLiteral {
68 } 71 }
69 72
70 @Override 73 @Override
71 public boolean equals(Object o) { 74 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
72 if (this == o) return true; 75 return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(resultVariable));
73 if (o == null || getClass() != o.getClass()) return false;
74 if (!super.equals(o)) return false;
75 CountLiteral that = (CountLiteral) o;
76 return resultVariable.equals(that.resultVariable);
77 }
78
79 @Override
80 public int hashCode() {
81 return Objects.hash(super.hashCode(), resultVariable);
82 } 76 }
83 77
84 @Override 78 @Override
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..61b7344f 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
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.NodeVariable; 11import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.Variable; 12import tools.refinery.store.query.term.Variable;
@@ -13,8 +14,29 @@ import tools.refinery.store.query.term.Variable;
13import java.util.Objects; 14import java.util.Objects;
14import java.util.Set; 15import java.util.Set;
15 16
16public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariable right) 17public final class EquivalenceLiteral extends AbstractLiteral implements CanNegate<EquivalenceLiteral> {
17 implements CanNegate<EquivalenceLiteral> { 18 private final boolean positive;
19 private final NodeVariable left;
20 private final NodeVariable right;
21
22 public EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariable right) {
23 this.positive = positive;
24 this.left = left;
25 this.right = right;
26 }
27
28 public boolean isPositive() {
29 return positive;
30 }
31
32 public NodeVariable getLeft() {
33 return left;
34 }
35
36 public NodeVariable getRight() {
37 return right;
38 }
39
18 @Override 40 @Override
19 public Set<Variable> getOutputVariables() { 41 public Set<Variable> getOutputVariables() {
20 return Set.of(left); 42 return Set.of(left);
@@ -55,27 +77,18 @@ public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariab
55 return false; 77 return false;
56 } 78 }
57 var otherEquivalenceLiteral = (EquivalenceLiteral) other; 79 var otherEquivalenceLiteral = (EquivalenceLiteral) other;
58 return helper.variableEqual(left, otherEquivalenceLiteral.left) && helper.variableEqual(right, 80 return helper.variableEqual(left, otherEquivalenceLiteral.left) &&
59 otherEquivalenceLiteral.right); 81 helper.variableEqual(right, otherEquivalenceLiteral.right);
60 }
61
62 @Override
63 public String toString() {
64 return "%s %s %s".formatted(left, positive ? "===" : "!==", right);
65 } 82 }
66 83
67 @Override 84 @Override
68 public boolean equals(Object obj) { 85 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
69 if (obj == this) return true; 86 return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(left),
70 if (obj == null || obj.getClass() != this.getClass()) return false; 87 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 } 88 }
76 89
77 @Override 90 @Override
78 public int hashCode() { 91 public String toString() {
79 return Objects.hash(getClass(), positive, left, right); 92 return "%s %s %s".formatted(left, positive ? "===" : "!==", right);
80 } 93 }
81} 94}
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 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.Variable; 11import 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/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 @@
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9 10
10import java.util.Objects; 11import 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/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 @@
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
10 11
11import java.util.Set; 12import 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..09c86db6 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
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.valuation.Valuation; 11import tools.refinery.store.query.valuation.Valuation;
11 12
@@ -14,6 +15,8 @@ import java.util.HashSet;
14import java.util.Objects; 15import java.util.Objects;
15import java.util.Set; 16import java.util.Set;
16 17
18// {@link Object#equals(Object)} is implemented by {@link AbstractTerm}.
19@SuppressWarnings("squid:S2160")
17public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> { 20public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> {
18 private final Class<T1> leftType; 21 private final Class<T1> leftType;
19 private final Class<T2> rightType; 22 private final Class<T2> rightType;
@@ -80,6 +83,12 @@ public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> {
80 } 83 }
81 84
82 @Override 85 @Override
86 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
87 return Objects.hash(super.hashCodeWithSubstitution(helper), leftType.hashCode(), rightType.hashCode(),
88 left.hashCodeWithSubstitution(helper), right.hashCodeWithSubstitution(helper));
89 }
90
91 @Override
83 public Term<R> substitute(Substitution substitution) { 92 public Term<R> substitute(Substitution substitution) {
84 return doSubstitute(substitution, left.substitute(substitution), right.substitute(substitution)); 93 return doSubstitute(substitution, left.substitute(substitution), right.substitute(substitution));
85 } 94 }
@@ -93,21 +102,4 @@ public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> {
93 inputVariables.addAll(right.getInputVariables()); 102 inputVariables.addAll(right.getInputVariables());
94 return Collections.unmodifiableSet(inputVariables); 103 return Collections.unmodifiableSet(inputVariables);
95 } 104 }
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} 105}
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..e722c84f 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
@@ -6,12 +6,15 @@
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.valuation.Valuation; 11import tools.refinery.store.query.valuation.Valuation;
11 12
12import java.util.Objects; 13import java.util.Objects;
13import java.util.Set; 14import java.util.Set;
14 15
16// {@link Object#equals(Object)} is implemented by {@link AbstractTerm}.
17@SuppressWarnings("squid:S2160")
15public final class ConstantTerm<T> extends AbstractTerm<T> { 18public final class ConstantTerm<T> extends AbstractTerm<T> {
16 private final T value; 19 private final T value;
17 20
@@ -47,6 +50,11 @@ public final class ConstantTerm<T> extends AbstractTerm<T> {
47 } 50 }
48 51
49 @Override 52 @Override
53 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
54 return Objects.hash(super.hashCodeWithSubstitution(helper), Objects.hash(value));
55 }
56
57 @Override
50 public Set<AnyDataVariable> getInputVariables() { 58 public Set<AnyDataVariable> getInputVariables() {
51 return Set.of(); 59 return Set.of();
52 } 60 }
@@ -55,17 +63,4 @@ public final class ConstantTerm<T> extends AbstractTerm<T> {
55 public String toString() { 63 public String toString() {
56 return value.toString(); 64 return value.toString();
57 } 65 }
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} 66}
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..e71c69ac 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
@@ -7,6 +7,7 @@ package tools.refinery.store.query.term;
7 7
8import org.jetbrains.annotations.Nullable; 8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.literal.Literal; 11import tools.refinery.store.query.literal.Literal;
11import tools.refinery.store.query.substitution.Substitution; 12import tools.refinery.store.query.substitution.Substitution;
12import tools.refinery.store.query.valuation.Valuation; 13import tools.refinery.store.query.valuation.Valuation;
@@ -62,6 +63,16 @@ public final class DataVariable<T> extends AnyDataVariable implements Term<T> {
62 return other instanceof DataVariable<?> dataVariable && helper.variableEqual(this, dataVariable); 63 return other instanceof DataVariable<?> dataVariable && helper.variableEqual(this, dataVariable);
63 } 64 }
64 65
66 @Override
67 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
68 return helper.getVariableHashCode(this);
69 }
70
71 @Override
72 public int hashCodeWithSubstitution(int sequenceNumber) {
73 return Objects.hash(type, sequenceNumber);
74 }
75
65 public Literal assign(AssignedValue<T> value) { 76 public Literal assign(AssignedValue<T> value) {
66 return value.toLiteral(this); 77 return value.toLiteral(this);
67 } 78 }
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..d679908a 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
@@ -46,6 +46,11 @@ public final class NodeVariable extends Variable {
46 throw new IllegalStateException("%s is a node variable".formatted(this)); 46 throw new IllegalStateException("%s is a node variable".formatted(this));
47 } 47 }
48 48
49 @Override
50 public int hashCodeWithSubstitution(int sequenceNumber) {
51 return sequenceNumber;
52 }
53
49 public ConstantLiteral isConstant(int value) { 54 public ConstantLiteral isConstant(int value) {
50 return new ConstantLiteral(this, value); 55 return new ConstantLiteral(this, value);
51 } 56 }
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..6451ea00 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
@@ -6,12 +6,15 @@
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.valuation.Valuation; 11import tools.refinery.store.query.valuation.Valuation;
11 12
12import java.util.Objects; 13import java.util.Objects;
13import java.util.Set; 14import java.util.Set;
14 15
16// {@link Object#equals(Object)} is implemented by {@link AbstractTerm}.
17@SuppressWarnings("squid:S2160")
15public abstract class UnaryTerm<R, T> extends AbstractTerm<R> { 18public abstract class UnaryTerm<R, T> extends AbstractTerm<R> {
16 private final Class<T> bodyType; 19 private final Class<T> bodyType;
17 private final Term<T> body; 20 private final Term<T> body;
@@ -52,6 +55,11 @@ public abstract class UnaryTerm<R, T> extends AbstractTerm<R> {
52 } 55 }
53 56
54 @Override 57 @Override
58 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
59 return Objects.hash(super.hashCodeWithSubstitution(helper), bodyType, body.hashCodeWithSubstitution(helper));
60 }
61
62 @Override
55 public Term<R> substitute(Substitution substitution) { 63 public Term<R> substitute(Substitution substitution) {
56 return doSubstitute(substitution, body.substitute(substitution)); 64 return doSubstitute(substitution, body.substitute(substitution));
57 } 65 }
@@ -62,18 +70,4 @@ public abstract class UnaryTerm<R, T> extends AbstractTerm<R> {
62 public Set<AnyDataVariable> getInputVariables() { 70 public Set<AnyDataVariable> getInputVariables() {
63 return body.getInputVariables(); 71 return body.getInputVariables();
64 } 72 }
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} 73}
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..74384df3 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
@@ -48,6 +48,8 @@ public abstract sealed class Variable permits AnyDataVariable, NodeVariable {
48 48
49 public abstract <T> DataVariable<T> asDataVariable(Class<T> type); 49 public abstract <T> DataVariable<T> asDataVariable(Class<T> type);
50 50
51 public abstract int hashCodeWithSubstitution(int sequenceNumber);
52
51 @Override 53 @Override
52 public String toString() { 54 public String toString() {
53 return getName(); 55 return getName();