aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store-query/src/main/java/tools/refinery/store/query/literal
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/store-query/src/main/java/tools/refinery/store/query/literal')
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java60
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCountLiteral.java107
-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.java36
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java49
-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.java28
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java4
-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)50
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Connectivity.java18
-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.java80
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java61
-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/literal/RepresentativeElectionLiteral.java119
16 files changed, 487 insertions, 210 deletions
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 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.substitution.Substitution; 12import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.ParameterDirection; 13import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable; 14import tools.refinery.store.query.term.Variable;
13 15
14import java.util.*; 16import java.util.*;
15 17
16public abstract class AbstractCallLiteral implements Literal { 18// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
19@SuppressWarnings("squid:S2160")
20public 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 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
10import tools.refinery.store.query.equality.LiteralEqualityHelper;
11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
12import tools.refinery.store.query.term.ConstantTerm;
13import tools.refinery.store.query.term.DataVariable;
14import tools.refinery.store.query.term.Variable;
15
16import java.util.List;
17import java.util.Objects;
18import java.util.Set;
19
20// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
21@SuppressWarnings("squid:S2160")
22public 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 */
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..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 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.substitution.Substitution; 12import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.*; 13import tools.refinery.store.query.term.*;
12 14
@@ -14,6 +16,8 @@ import java.util.List;
14import java.util.Objects; 16import java.util.Objects;
15import java.util.Set; 17import java.util.Set;
16 18
19// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
20@SuppressWarnings("squid:S2160")
17public class AggregationLiteral<R, T> extends AbstractCallLiteral { 21public 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 */
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.DataVariable; 12import tools.refinery.store.query.term.DataVariable;
11import tools.refinery.store.query.term.Term; 13import tools.refinery.store.query.term.Term;
@@ -15,17 +17,32 @@ import java.util.Collections;
15import java.util.Objects; 17import java.util.Objects;
16import java.util.Set; 18import java.util.Set;
17 19
18public 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")
22public 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 @@
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..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 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.substitution.Substitution; 12import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.ParameterDirection; 13import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable; 14import tools.refinery.store.query.term.Variable;
13 15
14import java.util.*; 16import java.util.*;
15 17
18// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
19@SuppressWarnings("squid:S2160")
16public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> { 20public 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 */
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.InvalidQueryException;
9
8public enum CallPolarity { 10public 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 */
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.ConstantTerm; 12import tools.refinery.store.query.term.ConstantTerm;
11import tools.refinery.store.query.term.Term; 13import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.Variable; 14import tools.refinery.store.query.term.Variable;
15import tools.refinery.store.query.term.bool.BoolNotTerm;
16import tools.refinery.store.query.term.bool.BoolTerms;
13 17
14import java.util.Collections; 18import java.util.Collections;
15import java.util.Objects; 19import java.util.Objects;
16import java.util.Set; 20import java.util.Set;
17 21
18public record AssumeLiteral(Term<Boolean> term) implements Literal { 22// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
19 public AssumeLiteral { 23@SuppressWarnings("squid:S2160")
24public 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 */
6package tools.refinery.store.query.literal;
7
8import java.util.Locale;
9
10public 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 @@
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..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 @@
6package tools.refinery.store.query.literal; 6package 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;
10import tools.refinery.store.query.substitution.Substitution; 9import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.DataVariable; 10import tools.refinery.store.query.term.DataVariable;
12import tools.refinery.store.query.term.Variable; 11import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.query.term.int_.IntTerms;
14 12
15import java.util.List; 13import java.util.List;
16import java.util.Objects;
17import java.util.Set;
18
19public class CountLiteral extends AbstractCallLiteral {
20 private final DataVariable<Integer> resultVariable;
21 14
15public 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 */
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.Variable; 12import tools.refinery.store.query.term.Variable;
12 13
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) 17// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
17 implements CanNegate<EquivalenceLiteral> { 18@SuppressWarnings("squid:S2160")
19public 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 @@
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/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 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
10import tools.refinery.store.query.equality.LiteralEqualityHelper;
11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
12import tools.refinery.store.query.substitution.Substitution;
13import tools.refinery.store.query.term.NodeVariable;
14import tools.refinery.store.query.term.ParameterDirection;
15import tools.refinery.store.query.term.Variable;
16
17import java.util.List;
18import java.util.Set;
19
20// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
21@SuppressWarnings("squid:S2160")
22public 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}