diff options
Diffstat (limited to 'subprojects/store-query/src/main/java/tools/refinery/store/query/literal')
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 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | 12 | import tools.refinery.store.query.substitution.Substitution; |
11 | import tools.refinery.store.query.term.ParameterDirection; | 13 | import tools.refinery.store.query.term.ParameterDirection; |
12 | import tools.refinery.store.query.term.Variable; | 14 | import tools.refinery.store.query.term.Variable; |
13 | 15 | ||
14 | import java.util.*; | 16 | import java.util.*; |
15 | 17 | ||
16 | public abstract class AbstractCallLiteral implements Literal { | 18 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. |
19 | @SuppressWarnings("squid:S2160") | ||
20 | public abstract class AbstractCallLiteral extends AbstractLiteral { | ||
17 | private final Constraint target; | 21 | private final Constraint target; |
18 | private final List<Variable> arguments; | 22 | private final List<Variable> arguments; |
19 | private final Set<Variable> inArguments; | 23 | private final Set<Variable> inArguments; |
@@ -24,7 +28,7 @@ public abstract class AbstractCallLiteral implements Literal { | |||
24 | protected AbstractCallLiteral(Constraint target, List<Variable> arguments) { | 28 | protected AbstractCallLiteral(Constraint target, List<Variable> arguments) { |
25 | int arity = target.arity(); | 29 | int arity = target.arity(); |
26 | if (arguments.size() != arity) { | 30 | if (arguments.size() != arity) { |
27 | throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.name(), | 31 | throw new InvalidQueryException("%s needs %d arguments, but got %s".formatted(target.name(), |
28 | target.arity(), arguments.size())); | 32 | target.arity(), arguments.size())); |
29 | } | 33 | } |
30 | this.target = target; | 34 | this.target = target; |
@@ -36,21 +40,17 @@ public abstract class AbstractCallLiteral implements Literal { | |||
36 | var argument = arguments.get(i); | 40 | var argument = arguments.get(i); |
37 | var parameter = parameters.get(i); | 41 | var parameter = parameters.get(i); |
38 | if (!parameter.isAssignable(argument)) { | 42 | if (!parameter.isAssignable(argument)) { |
39 | throw new IllegalArgumentException("Argument %d of %s is not assignable to parameter %s" | 43 | throw new InvalidQueryException("Argument %d of %s is not assignable to parameter %s" |
40 | .formatted(i, target, parameter)); | 44 | .formatted(i, target, parameter)); |
41 | } | 45 | } |
42 | switch (parameter.getDirection()) { | 46 | switch (parameter.getDirection()) { |
43 | case IN -> { | 47 | case IN -> { |
44 | if (mutableOutArguments.remove(argument)) { | 48 | mutableOutArguments.remove(argument); |
45 | checkInOutUnifiable(argument); | ||
46 | } | ||
47 | mutableInArguments.add(argument); | 49 | mutableInArguments.add(argument); |
48 | } | 50 | } |
49 | case OUT -> { | 51 | case OUT -> { |
50 | if (mutableInArguments.contains(argument)) { | 52 | if (!mutableInArguments.contains(argument)) { |
51 | checkInOutUnifiable(argument); | 53 | mutableOutArguments.add(argument); |
52 | } else if (!mutableOutArguments.add(argument)) { | ||
53 | checkDuplicateOutUnifiable(argument); | ||
54 | } | 54 | } |
55 | } | 55 | } |
56 | } | 56 | } |
@@ -59,19 +59,6 @@ public abstract class AbstractCallLiteral implements Literal { | |||
59 | outArguments = Collections.unmodifiableSet(mutableOutArguments); | 59 | outArguments = Collections.unmodifiableSet(mutableOutArguments); |
60 | } | 60 | } |
61 | 61 | ||
62 | private static void checkInOutUnifiable(Variable argument) { | ||
63 | if (!argument.isUnifiable()) { | ||
64 | throw new IllegalArgumentException("Argument %s cannot appear with both %s and %s direction" | ||
65 | .formatted(argument, ParameterDirection.IN, ParameterDirection.OUT)); | ||
66 | } | ||
67 | } | ||
68 | |||
69 | private static void checkDuplicateOutUnifiable(Variable argument) { | ||
70 | if (!argument.isUnifiable()) { | ||
71 | throw new IllegalArgumentException("Argument %s cannot be bound multiple times".formatted(argument)); | ||
72 | } | ||
73 | } | ||
74 | |||
75 | public Constraint getTarget() { | 62 | public Constraint getTarget() { |
76 | return target; | 63 | return target; |
77 | } | 64 | } |
@@ -110,9 +97,18 @@ public abstract class AbstractCallLiteral implements Literal { | |||
110 | 97 | ||
111 | protected abstract Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments); | 98 | protected abstract Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments); |
112 | 99 | ||
100 | public AbstractCallLiteral withTarget(Constraint newTarget) { | ||
101 | if (Objects.equals(target, newTarget)) { | ||
102 | return this; | ||
103 | } | ||
104 | return withArguments(newTarget, arguments); | ||
105 | } | ||
106 | |||
107 | public abstract AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments); | ||
108 | |||
113 | @Override | 109 | @Override |
114 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | 110 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { |
115 | if (other == null || getClass() != other.getClass()) { | 111 | if (!super.equalsWithSubstitution(helper, other)) { |
116 | return false; | 112 | return false; |
117 | } | 113 | } |
118 | var otherCallLiteral = (AbstractCallLiteral) other; | 114 | var otherCallLiteral = (AbstractCallLiteral) other; |
@@ -129,15 +125,11 @@ public abstract class AbstractCallLiteral implements Literal { | |||
129 | } | 125 | } |
130 | 126 | ||
131 | @Override | 127 | @Override |
132 | public boolean equals(Object o) { | 128 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { |
133 | if (this == o) return true; | 129 | int result = super.hashCodeWithSubstitution(helper) * 31 + target.hashCode(); |
134 | if (o == null || getClass() != o.getClass()) return false; | 130 | for (var argument : arguments) { |
135 | AbstractCallLiteral that = (AbstractCallLiteral) o; | 131 | result = result * 31 + helper.getVariableHashCode(argument); |
136 | return target.equals(that.target) && arguments.equals(that.arguments); | 132 | } |
137 | } | 133 | return result; |
138 | |||
139 | @Override | ||
140 | public int hashCode() { | ||
141 | return Objects.hash(getClass(), target, arguments); | ||
142 | } | 134 | } |
143 | } | 135 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCountLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCountLiteral.java new file mode 100644 index 00000000..9bb572c0 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCountLiteral.java | |||
@@ -0,0 +1,107 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.InvalidQueryException; | ||
10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
12 | import tools.refinery.store.query.term.ConstantTerm; | ||
13 | import tools.refinery.store.query.term.DataVariable; | ||
14 | import tools.refinery.store.query.term.Variable; | ||
15 | |||
16 | import java.util.List; | ||
17 | import java.util.Objects; | ||
18 | import java.util.Set; | ||
19 | |||
20 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. | ||
21 | @SuppressWarnings("squid:S2160") | ||
22 | public abstract class AbstractCountLiteral<T> extends AbstractCallLiteral { | ||
23 | private final Class<T> resultType; | ||
24 | private final DataVariable<T> resultVariable; | ||
25 | |||
26 | protected AbstractCountLiteral(Class<T> resultType, DataVariable<T> resultVariable, Constraint target, | ||
27 | List<Variable> arguments) { | ||
28 | super(target, arguments); | ||
29 | if (!resultVariable.getType().equals(resultType)) { | ||
30 | throw new InvalidQueryException("Count result variable %s must be of type %s, got %s instead".formatted( | ||
31 | resultVariable, resultType, resultVariable.getType().getName())); | ||
32 | } | ||
33 | if (arguments.contains(resultVariable)) { | ||
34 | throw new InvalidQueryException("Count result variable %s must not appear in the argument list" | ||
35 | .formatted(resultVariable)); | ||
36 | } | ||
37 | this.resultType = resultType; | ||
38 | this.resultVariable = resultVariable; | ||
39 | } | ||
40 | |||
41 | public Class<T> getResultType() { | ||
42 | return resultType; | ||
43 | } | ||
44 | |||
45 | public DataVariable<T> getResultVariable() { | ||
46 | return resultVariable; | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public Set<Variable> getOutputVariables() { | ||
51 | return Set.of(resultVariable); | ||
52 | } | ||
53 | |||
54 | protected abstract T zero(); | ||
55 | |||
56 | protected abstract T one(); | ||
57 | |||
58 | @Override | ||
59 | public Literal reduce() { | ||
60 | var reduction = getTarget().getReduction(); | ||
61 | return switch (reduction) { | ||
62 | case ALWAYS_FALSE -> getResultVariable().assign(new ConstantTerm<>(resultType, zero())); | ||
63 | // The only way a constant {@code true} predicate can be called in a negative position is to have all of | ||
64 | // its arguments bound as input variables. Thus, there will only be a single match. | ||
65 | case ALWAYS_TRUE -> getResultVariable().assign(new ConstantTerm<>(resultType, one())); | ||
66 | case NOT_REDUCIBLE -> this; | ||
67 | }; | ||
68 | } | ||
69 | |||
70 | @Override | ||
71 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | ||
72 | if (!super.equalsWithSubstitution(helper, other)) { | ||
73 | return false; | ||
74 | } | ||
75 | var otherCountLiteral = (AbstractCountLiteral<?>) other; | ||
76 | return Objects.equals(resultType, otherCountLiteral.resultType) && | ||
77 | helper.variableEqual(resultVariable, otherCountLiteral.resultVariable); | ||
78 | } | ||
79 | |||
80 | @Override | ||
81 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { | ||
82 | return Objects.hash(super.hashCodeWithSubstitution(helper), resultType, | ||
83 | helper.getVariableHashCode(resultVariable)); | ||
84 | } | ||
85 | |||
86 | protected abstract String operatorName(); | ||
87 | |||
88 | @Override | ||
89 | public String toString() { | ||
90 | var builder = new StringBuilder(); | ||
91 | builder.append(resultVariable); | ||
92 | builder.append(" is "); | ||
93 | builder.append(operatorName()); | ||
94 | builder.append(' '); | ||
95 | builder.append(getTarget().toReferenceString()); | ||
96 | builder.append('('); | ||
97 | var argumentIterator = getArguments().iterator(); | ||
98 | if (argumentIterator.hasNext()) { | ||
99 | builder.append(argumentIterator.next()); | ||
100 | while (argumentIterator.hasNext()) { | ||
101 | builder.append(", ").append(argumentIterator.next()); | ||
102 | } | ||
103 | } | ||
104 | builder.append(')'); | ||
105 | return builder.toString(); | ||
106 | } | ||
107 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractLiteral.java new file mode 100644 index 00000000..7d3cabd7 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractLiteral.java | |||
@@ -0,0 +1,34 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
10 | |||
11 | public abstract class AbstractLiteral implements Literal { | ||
12 | @Override | ||
13 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | ||
14 | return other != null && getClass() == other.getClass(); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { | ||
19 | return getClass().hashCode(); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public boolean equals(Object o) { | ||
24 | if (this == o) return true; | ||
25 | if (o == null || getClass() != o.getClass()) return false; | ||
26 | AbstractLiteral that = (AbstractLiteral) o; | ||
27 | return equalsWithSubstitution(LiteralEqualityHelper.DEFAULT, that); | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public int hashCode() { | ||
32 | return hashCodeWithSubstitution(LiteralHashCodeHelper.DEFAULT); | ||
33 | } | ||
34 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java index 3a5eb5c7..e3acfacc 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java | |||
@@ -6,7 +6,9 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | 12 | import tools.refinery.store.query.substitution.Substitution; |
11 | import tools.refinery.store.query.term.*; | 13 | import tools.refinery.store.query.term.*; |
12 | 14 | ||
@@ -14,6 +16,8 @@ import java.util.List; | |||
14 | import java.util.Objects; | 16 | import java.util.Objects; |
15 | import java.util.Set; | 17 | import java.util.Set; |
16 | 18 | ||
19 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. | ||
20 | @SuppressWarnings("squid:S2160") | ||
17 | public class AggregationLiteral<R, T> extends AbstractCallLiteral { | 21 | public class AggregationLiteral<R, T> extends AbstractCallLiteral { |
18 | private final DataVariable<R> resultVariable; | 22 | private final DataVariable<R> resultVariable; |
19 | private final DataVariable<T> inputVariable; | 23 | private final DataVariable<T> inputVariable; |
@@ -23,19 +27,19 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral { | |||
23 | DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) { | 27 | DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) { |
24 | super(target, arguments); | 28 | super(target, arguments); |
25 | if (!inputVariable.getType().equals(aggregator.getInputType())) { | 29 | if (!inputVariable.getType().equals(aggregator.getInputType())) { |
26 | throw new IllegalArgumentException("Input variable %s must of type %s, got %s instead".formatted( | 30 | throw new InvalidQueryException("Input variable %s must of type %s, got %s instead".formatted( |
27 | inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName())); | 31 | inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName())); |
28 | } | 32 | } |
29 | if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(inputVariable)) { | 33 | if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(inputVariable)) { |
30 | throw new IllegalArgumentException("Input variable %s must be bound with direction %s in the argument list" | 34 | throw new InvalidQueryException("Input variable %s must be bound with direction %s in the argument list" |
31 | .formatted(inputVariable, ParameterDirection.OUT)); | 35 | .formatted(inputVariable, ParameterDirection.OUT)); |
32 | } | 36 | } |
33 | if (!resultVariable.getType().equals(aggregator.getResultType())) { | 37 | if (!resultVariable.getType().equals(aggregator.getResultType())) { |
34 | throw new IllegalArgumentException("Result variable %s must of type %s, got %s instead".formatted( | 38 | throw new InvalidQueryException("Result variable %s must of type %s, got %s instead".formatted( |
35 | resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName())); | 39 | resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName())); |
36 | } | 40 | } |
37 | if (arguments.contains(resultVariable)) { | 41 | if (arguments.contains(resultVariable)) { |
38 | throw new IllegalArgumentException("Result variable %s must not appear in the argument list".formatted( | 42 | throw new InvalidQueryException("Result variable %s must not appear in the argument list".formatted( |
39 | resultVariable)); | 43 | resultVariable)); |
40 | } | 44 | } |
41 | this.resultVariable = resultVariable; | 45 | this.resultVariable = resultVariable; |
@@ -63,7 +67,7 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral { | |||
63 | @Override | 67 | @Override |
64 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | 68 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { |
65 | if (positiveVariablesInClause.contains(inputVariable)) { | 69 | if (positiveVariablesInClause.contains(inputVariable)) { |
66 | throw new IllegalArgumentException("Aggregation variable %s must not be bound".formatted(inputVariable)); | 70 | throw new InvalidQueryException("Aggregation variable %s must not be bound".formatted(inputVariable)); |
67 | } | 71 | } |
68 | return super.getInputVariables(positiveVariablesInClause); | 72 | return super.getInputVariables(positiveVariablesInClause); |
69 | } | 73 | } |
@@ -77,7 +81,7 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral { | |||
77 | yield emptyValue == null ? BooleanLiteral.FALSE : | 81 | yield emptyValue == null ? BooleanLiteral.FALSE : |
78 | resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), emptyValue)); | 82 | resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), emptyValue)); |
79 | } | 83 | } |
80 | case ALWAYS_TRUE -> throw new IllegalArgumentException("Trying to aggregate over an infinite set"); | 84 | case ALWAYS_TRUE -> throw new InvalidQueryException("Trying to aggregate over an infinite set"); |
81 | case NOT_REDUCIBLE -> this; | 85 | case NOT_REDUCIBLE -> this; |
82 | }; | 86 | }; |
83 | } | 87 | } |
@@ -89,6 +93,11 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral { | |||
89 | } | 93 | } |
90 | 94 | ||
91 | @Override | 95 | @Override |
96 | public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) { | ||
97 | return new AggregationLiteral<>(resultVariable, aggregator, inputVariable, newTarget, newArguments); | ||
98 | } | ||
99 | |||
100 | @Override | ||
92 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | 101 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { |
93 | if (!super.equalsWithSubstitution(helper, other)) { | 102 | if (!super.equalsWithSubstitution(helper, other)) { |
94 | return false; | 103 | return false; |
@@ -100,18 +109,9 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral { | |||
100 | } | 109 | } |
101 | 110 | ||
102 | @Override | 111 | @Override |
103 | public boolean equals(Object o) { | 112 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { |
104 | if (this == o) return true; | 113 | return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(resultVariable), |
105 | if (o == null || getClass() != o.getClass()) return false; | 114 | helper.getVariableHashCode(inputVariable), aggregator); |
106 | if (!super.equals(o)) return false; | ||
107 | AggregationLiteral<?, ?> that = (AggregationLiteral<?, ?>) o; | ||
108 | return resultVariable.equals(that.resultVariable) && inputVariable.equals(that.inputVariable) && | ||
109 | aggregator.equals(that.aggregator); | ||
110 | } | ||
111 | |||
112 | @Override | ||
113 | public int hashCode() { | ||
114 | return Objects.hash(super.hashCode(), resultVariable, inputVariable, aggregator); | ||
115 | } | 115 | } |
116 | 116 | ||
117 | @Override | 117 | @Override |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java index dbf999a2..dadf487f 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java | |||
@@ -5,7 +5,9 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | 11 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.DataVariable; | 12 | import tools.refinery.store.query.term.DataVariable; |
11 | import tools.refinery.store.query.term.Term; | 13 | import tools.refinery.store.query.term.Term; |
@@ -15,17 +17,32 @@ import java.util.Collections; | |||
15 | import java.util.Objects; | 17 | import java.util.Objects; |
16 | import java.util.Set; | 18 | import java.util.Set; |
17 | 19 | ||
18 | public record AssignLiteral<T>(DataVariable<T> variable, Term<T> term) implements Literal { | 20 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. |
19 | public AssignLiteral { | 21 | @SuppressWarnings("squid:S2160") |
22 | public class AssignLiteral<T> extends AbstractLiteral { | ||
23 | private final DataVariable<T> variable; | ||
24 | private final Term<T> term; | ||
25 | |||
26 | public AssignLiteral(DataVariable<T> variable, Term<T> term) { | ||
20 | if (!term.getType().equals(variable.getType())) { | 27 | if (!term.getType().equals(variable.getType())) { |
21 | throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( | 28 | throw new InvalidQueryException("Term %s must be of type %s, got %s instead".formatted( |
22 | term, variable.getType().getName(), term.getType().getName())); | 29 | term, variable.getType().getName(), term.getType().getName())); |
23 | } | 30 | } |
24 | var inputVariables = term.getInputVariables(); | 31 | var inputVariables = term.getInputVariables(); |
25 | if (inputVariables.contains(variable)) { | 32 | if (inputVariables.contains(variable)) { |
26 | throw new IllegalArgumentException("Result variable %s must not appear in the term %s".formatted( | 33 | throw new InvalidQueryException("Result variable %s must not appear in the term %s".formatted( |
27 | variable, term)); | 34 | variable, term)); |
28 | } | 35 | } |
36 | this.variable = variable; | ||
37 | this.term = term; | ||
38 | } | ||
39 | |||
40 | public DataVariable<T> getVariable() { | ||
41 | return variable; | ||
42 | } | ||
43 | |||
44 | public Term<T> getTerm() { | ||
45 | return term; | ||
29 | } | 46 | } |
30 | 47 | ||
31 | @Override | 48 | @Override |
@@ -53,27 +70,19 @@ public record AssignLiteral<T>(DataVariable<T> variable, Term<T> term) implement | |||
53 | if (other == null || getClass() != other.getClass()) { | 70 | if (other == null || getClass() != other.getClass()) { |
54 | return false; | 71 | return false; |
55 | } | 72 | } |
56 | var otherLetLiteral = (AssignLiteral<?>) other; | 73 | var otherAssignLiteral = (AssignLiteral<?>) other; |
57 | return helper.variableEqual(variable, otherLetLiteral.variable) && term.equalsWithSubstitution(helper, | 74 | return helper.variableEqual(variable, otherAssignLiteral.variable) && |
58 | otherLetLiteral.term); | 75 | term.equalsWithSubstitution(helper, otherAssignLiteral.term); |
59 | } | ||
60 | |||
61 | @Override | ||
62 | public String toString() { | ||
63 | return "%s is (%s)".formatted(variable, term); | ||
64 | } | 76 | } |
65 | 77 | ||
66 | @Override | 78 | @Override |
67 | public boolean equals(Object obj) { | 79 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { |
68 | if (obj == this) return true; | 80 | return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(variable), |
69 | if (obj == null || obj.getClass() != this.getClass()) return false; | 81 | term.hashCodeWithSubstitution(helper)); |
70 | var that = (AssignLiteral<?>) obj; | ||
71 | return Objects.equals(this.variable, that.variable) && | ||
72 | Objects.equals(this.term, that.term); | ||
73 | } | 82 | } |
74 | 83 | ||
75 | @Override | 84 | @Override |
76 | public int hashCode() { | 85 | public String toString() { |
77 | return Objects.hash(getClass(), variable, term); | 86 | return "%s is (%s)".formatted(variable, term); |
78 | } | 87 | } |
79 | } | 88 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java index f312d202..6cd320da 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java | |||
@@ -6,6 +6,7 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | 10 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.Variable; | 11 | import tools.refinery.store.query.term.Variable; |
11 | 12 | ||
@@ -53,6 +54,11 @@ public enum BooleanLiteral implements CanNegate<BooleanLiteral> { | |||
53 | } | 54 | } |
54 | 55 | ||
55 | @Override | 56 | @Override |
57 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { | ||
58 | return hashCode(); | ||
59 | } | ||
60 | |||
61 | @Override | ||
56 | public String toString() { | 62 | public String toString() { |
57 | return Boolean.toString(value); | 63 | return Boolean.toString(value); |
58 | } | 64 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java index 29772aee..2d0e4e97 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java | |||
@@ -6,13 +6,17 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | 12 | import tools.refinery.store.query.substitution.Substitution; |
11 | import tools.refinery.store.query.term.ParameterDirection; | 13 | import tools.refinery.store.query.term.ParameterDirection; |
12 | import tools.refinery.store.query.term.Variable; | 14 | import tools.refinery.store.query.term.Variable; |
13 | 15 | ||
14 | import java.util.*; | 16 | import java.util.*; |
15 | 17 | ||
18 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. | ||
19 | @SuppressWarnings("squid:S2160") | ||
16 | public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> { | 20 | public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> { |
17 | private final CallPolarity polarity; | 21 | private final CallPolarity polarity; |
18 | 22 | ||
@@ -22,10 +26,14 @@ public final class CallLiteral extends AbstractCallLiteral implements CanNegate< | |||
22 | int arity = target.arity(); | 26 | int arity = target.arity(); |
23 | if (polarity.isTransitive()) { | 27 | if (polarity.isTransitive()) { |
24 | if (arity != 2) { | 28 | if (arity != 2) { |
25 | throw new IllegalArgumentException("Transitive closures can only take binary relations"); | 29 | throw new InvalidQueryException("Transitive closures can only take binary relations"); |
26 | } | 30 | } |
27 | if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) { | 31 | if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) { |
28 | throw new IllegalArgumentException("Transitive closures can only be computed over nodes"); | 32 | throw new InvalidQueryException("Transitive closures can only be computed over nodes"); |
33 | } | ||
34 | if (parameters.get(0).getDirection() != ParameterDirection.OUT || | ||
35 | parameters.get(1).getDirection() != ParameterDirection.OUT) { | ||
36 | throw new InvalidQueryException("Transitive closures cannot take input parameters"); | ||
29 | } | 37 | } |
30 | } | 38 | } |
31 | this.polarity = polarity; | 39 | this.polarity = polarity; |
@@ -85,22 +93,18 @@ public final class CallLiteral extends AbstractCallLiteral implements CanNegate< | |||
85 | } | 93 | } |
86 | 94 | ||
87 | @Override | 95 | @Override |
88 | public CallLiteral negate() { | 96 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { |
89 | return new CallLiteral(polarity.negate(), getTarget(), getArguments()); | 97 | return Objects.hash(super.hashCodeWithSubstitution(helper), polarity); |
90 | } | 98 | } |
91 | 99 | ||
92 | @Override | 100 | @Override |
93 | public boolean equals(Object o) { | 101 | public CallLiteral negate() { |
94 | if (this == o) return true; | 102 | return new CallLiteral(polarity.negate(), getTarget(), getArguments()); |
95 | if (o == null || getClass() != o.getClass()) return false; | ||
96 | if (!super.equals(o)) return false; | ||
97 | CallLiteral that = (CallLiteral) o; | ||
98 | return polarity == that.polarity; | ||
99 | } | 103 | } |
100 | 104 | ||
101 | @Override | 105 | @Override |
102 | public int hashCode() { | 106 | public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) { |
103 | return Objects.hash(super.hashCode(), polarity); | 107 | return new CallLiteral(polarity, newTarget, newArguments); |
104 | } | 108 | } |
105 | 109 | ||
106 | @Override | 110 | @Override |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java index ca70b0fd..716c7109 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java | |||
@@ -5,6 +5,8 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | |||
8 | public enum CallPolarity { | 10 | public enum CallPolarity { |
9 | POSITIVE(true, false), | 11 | POSITIVE(true, false), |
10 | NEGATIVE(false, false), | 12 | NEGATIVE(false, false), |
@@ -31,7 +33,7 @@ public enum CallPolarity { | |||
31 | return switch (this) { | 33 | return switch (this) { |
32 | case POSITIVE -> NEGATIVE; | 34 | case POSITIVE -> NEGATIVE; |
33 | case NEGATIVE -> POSITIVE; | 35 | case NEGATIVE -> POSITIVE; |
34 | case TRANSITIVE -> throw new IllegalArgumentException("Transitive polarity cannot be negated"); | 36 | case TRANSITIVE -> throw new InvalidQueryException("Transitive polarity cannot be negated"); |
35 | }; | 37 | }; |
36 | } | 38 | } |
37 | } | 39 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CheckLiteral.java index 1ca04c77..dfedd2cb 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CheckLiteral.java | |||
@@ -5,22 +5,35 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | 11 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.ConstantTerm; | 12 | import tools.refinery.store.query.term.ConstantTerm; |
11 | import tools.refinery.store.query.term.Term; | 13 | import tools.refinery.store.query.term.Term; |
12 | import tools.refinery.store.query.term.Variable; | 14 | import tools.refinery.store.query.term.Variable; |
15 | import tools.refinery.store.query.term.bool.BoolNotTerm; | ||
16 | import tools.refinery.store.query.term.bool.BoolTerms; | ||
13 | 17 | ||
14 | import java.util.Collections; | 18 | import java.util.Collections; |
15 | import java.util.Objects; | 19 | import java.util.Objects; |
16 | import java.util.Set; | 20 | import java.util.Set; |
17 | 21 | ||
18 | public record AssumeLiteral(Term<Boolean> term) implements Literal { | 22 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. |
19 | public AssumeLiteral { | 23 | @SuppressWarnings("squid:S2160") |
24 | public class CheckLiteral extends AbstractLiteral implements CanNegate<CheckLiteral> { | ||
25 | private final Term<Boolean> term; | ||
26 | |||
27 | public CheckLiteral(Term<Boolean> term) { | ||
20 | if (!term.getType().equals(Boolean.class)) { | 28 | if (!term.getType().equals(Boolean.class)) { |
21 | throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( | 29 | throw new InvalidQueryException("Term %s must be of type %s, got %s instead".formatted( |
22 | term, Boolean.class.getName(), term.getType().getName())); | 30 | term, Boolean.class.getName(), term.getType().getName())); |
23 | } | 31 | } |
32 | this.term = term; | ||
33 | } | ||
34 | |||
35 | public Term<Boolean> getTerm() { | ||
36 | return term; | ||
24 | } | 37 | } |
25 | 38 | ||
26 | @Override | 39 | @Override |
@@ -38,10 +51,17 @@ public record AssumeLiteral(Term<Boolean> term) implements Literal { | |||
38 | return Set.of(); | 51 | return Set.of(); |
39 | } | 52 | } |
40 | 53 | ||
41 | |||
42 | @Override | 54 | @Override |
43 | public Literal substitute(Substitution substitution) { | 55 | public Literal substitute(Substitution substitution) { |
44 | return new AssumeLiteral(term.substitute(substitution)); | 56 | return new CheckLiteral(term.substitute(substitution)); |
57 | } | ||
58 | |||
59 | @Override | ||
60 | public CheckLiteral negate() { | ||
61 | if (term instanceof BoolNotTerm notTerm) { | ||
62 | return new CheckLiteral(notTerm.getBody()); | ||
63 | } | ||
64 | return new CheckLiteral(BoolTerms.not(term)); | ||
45 | } | 65 | } |
46 | 66 | ||
47 | @Override | 67 | @Override |
@@ -49,11 +69,16 @@ public record AssumeLiteral(Term<Boolean> term) implements Literal { | |||
49 | if (other == null || getClass() != other.getClass()) { | 69 | if (other == null || getClass() != other.getClass()) { |
50 | return false; | 70 | return false; |
51 | } | 71 | } |
52 | var otherAssumeLiteral = (AssumeLiteral) other; | 72 | var otherAssumeLiteral = (CheckLiteral) other; |
53 | return term.equalsWithSubstitution(helper, otherAssumeLiteral.term); | 73 | return term.equalsWithSubstitution(helper, otherAssumeLiteral.term); |
54 | } | 74 | } |
55 | 75 | ||
56 | @Override | 76 | @Override |
77 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { | ||
78 | return Objects.hash(super.hashCodeWithSubstitution(helper), term.hashCodeWithSubstitution(helper)); | ||
79 | } | ||
80 | |||
81 | @Override | ||
57 | public Literal reduce() { | 82 | public Literal reduce() { |
58 | if (term instanceof ConstantTerm<Boolean> constantTerm) { | 83 | if (term instanceof ConstantTerm<Boolean> constantTerm) { |
59 | // Return {@link BooleanLiteral#FALSE} for {@code false} or {@code null} literals. | 84 | // Return {@link BooleanLiteral#FALSE} for {@code false} or {@code null} literals. |
@@ -67,17 +92,4 @@ public record AssumeLiteral(Term<Boolean> term) implements Literal { | |||
67 | public String toString() { | 92 | public String toString() { |
68 | return "(%s)".formatted(term); | 93 | return "(%s)".formatted(term); |
69 | } | 94 | } |
70 | |||
71 | @Override | ||
72 | public boolean equals(Object obj) { | ||
73 | if (obj == this) return true; | ||
74 | if (obj == null || obj.getClass() != this.getClass()) return false; | ||
75 | var that = (AssumeLiteral) obj; | ||
76 | return Objects.equals(this.term, that.term); | ||
77 | } | ||
78 | |||
79 | @Override | ||
80 | public int hashCode() { | ||
81 | return Objects.hash(getClass(), term); | ||
82 | } | ||
83 | } | 95 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Connectivity.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Connectivity.java new file mode 100644 index 00000000..a058094d --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Connectivity.java | |||
@@ -0,0 +1,18 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import java.util.Locale; | ||
9 | |||
10 | public enum Connectivity { | ||
11 | WEAK, | ||
12 | STRONG; | ||
13 | |||
14 | @Override | ||
15 | public String toString() { | ||
16 | return name().toLowerCase(Locale.ROOT); | ||
17 | } | ||
18 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java index 73545620..d83bd584 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java | |||
@@ -6,6 +6,7 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | 10 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.NodeVariable; | 11 | import tools.refinery.store.query.term.NodeVariable; |
11 | import tools.refinery.store.query.term.Variable; | 12 | import tools.refinery.store.query.term.Variable; |
@@ -13,7 +14,24 @@ import tools.refinery.store.query.term.Variable; | |||
13 | import java.util.Objects; | 14 | import java.util.Objects; |
14 | import java.util.Set; | 15 | import java.util.Set; |
15 | 16 | ||
16 | public record ConstantLiteral(NodeVariable variable, int nodeId) implements Literal { | 17 | public class ConstantLiteral extends AbstractLiteral { |
18 | private final NodeVariable variable; | ||
19 | private final int nodeId; | ||
20 | |||
21 | public ConstantLiteral(NodeVariable variable, int nodeId) { | ||
22 | this.variable = variable; | ||
23 | this.nodeId = nodeId; | ||
24 | } | ||
25 | |||
26 | public NodeVariable getVariable() { | ||
27 | return variable; | ||
28 | } | ||
29 | |||
30 | public int getNodeId() { | ||
31 | return nodeId; | ||
32 | } | ||
33 | |||
34 | |||
17 | @Override | 35 | @Override |
18 | public Set<Variable> getOutputVariables() { | 36 | public Set<Variable> getOutputVariables() { |
19 | return Set.of(variable); | 37 | return Set.of(variable); |
@@ -43,23 +61,13 @@ public record ConstantLiteral(NodeVariable variable, int nodeId) implements Lite | |||
43 | return helper.variableEqual(variable, otherConstantLiteral.variable) && nodeId == otherConstantLiteral.nodeId; | 61 | return helper.variableEqual(variable, otherConstantLiteral.variable) && nodeId == otherConstantLiteral.nodeId; |
44 | } | 62 | } |
45 | 63 | ||
46 | |||
47 | @Override | ||
48 | public String toString() { | ||
49 | return "%s === @Constant %d".formatted(variable, nodeId); | ||
50 | } | ||
51 | |||
52 | @Override | 64 | @Override |
53 | public boolean equals(Object obj) { | 65 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { |
54 | if (obj == this) return true; | 66 | return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(variable), nodeId); |
55 | if (obj == null || obj.getClass() != this.getClass()) return false; | ||
56 | var that = (ConstantLiteral) obj; | ||
57 | return Objects.equals(this.variable, that.variable) && | ||
58 | this.nodeId == that.nodeId; | ||
59 | } | 67 | } |
60 | 68 | ||
61 | @Override | 69 | @Override |
62 | public int hashCode() { | 70 | public String toString() { |
63 | return Objects.hash(getClass(), variable, nodeId); | 71 | return "%s === @Constant %d".formatted(variable, nodeId); |
64 | } | 72 | } |
65 | } | 73 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java index 4d4749c8..3d078d89 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java | |||
@@ -6,96 +6,40 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | 9 | import tools.refinery.store.query.substitution.Substitution; |
11 | import tools.refinery.store.query.term.DataVariable; | 10 | import tools.refinery.store.query.term.DataVariable; |
12 | import tools.refinery.store.query.term.Variable; | 11 | import tools.refinery.store.query.term.Variable; |
13 | import tools.refinery.store.query.term.int_.IntTerms; | ||
14 | 12 | ||
15 | import java.util.List; | 13 | import java.util.List; |
16 | import java.util.Objects; | ||
17 | import java.util.Set; | ||
18 | |||
19 | public class CountLiteral extends AbstractCallLiteral { | ||
20 | private final DataVariable<Integer> resultVariable; | ||
21 | 14 | ||
15 | public class CountLiteral extends AbstractCountLiteral<Integer> { | ||
22 | public CountLiteral(DataVariable<Integer> resultVariable, Constraint target, List<Variable> arguments) { | 16 | public CountLiteral(DataVariable<Integer> resultVariable, Constraint target, List<Variable> arguments) { |
23 | super(target, arguments); | 17 | super(Integer.class, resultVariable, target, arguments); |
24 | if (!resultVariable.getType().equals(Integer.class)) { | ||
25 | throw new IllegalArgumentException("Count result variable %s must be of type %s, got %s instead".formatted( | ||
26 | resultVariable, Integer.class.getName(), resultVariable.getType().getName())); | ||
27 | } | ||
28 | if (arguments.contains(resultVariable)) { | ||
29 | throw new IllegalArgumentException("Count result variable %s must not appear in the argument list" | ||
30 | .formatted(resultVariable)); | ||
31 | } | ||
32 | this.resultVariable = resultVariable; | ||
33 | } | ||
34 | |||
35 | public DataVariable<Integer> getResultVariable() { | ||
36 | return resultVariable; | ||
37 | } | 18 | } |
38 | 19 | ||
39 | @Override | 20 | @Override |
40 | public Set<Variable> getOutputVariables() { | 21 | protected Integer zero() { |
41 | return Set.of(resultVariable); | 22 | return 0; |
42 | } | 23 | } |
43 | 24 | ||
44 | @Override | 25 | @Override |
45 | public Literal reduce() { | 26 | protected Integer one() { |
46 | var reduction = getTarget().getReduction(); | 27 | return 1; |
47 | return switch (reduction) { | ||
48 | case ALWAYS_FALSE -> getResultVariable().assign(IntTerms.constant(0)); | ||
49 | // The only way a constant {@code true} predicate can be called in a negative position is to have all of | ||
50 | // its arguments bound as input variables. Thus, there will only be a single match. | ||
51 | case ALWAYS_TRUE -> getResultVariable().assign(IntTerms.constant(1)); | ||
52 | case NOT_REDUCIBLE -> this; | ||
53 | }; | ||
54 | } | 28 | } |
55 | 29 | ||
56 | @Override | 30 | @Override |
57 | protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) { | 31 | protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) { |
58 | return new CountLiteral(substitution.getTypeSafeSubstitute(resultVariable), getTarget(), substitutedArguments); | 32 | return new CountLiteral(substitution.getTypeSafeSubstitute(getResultVariable()), getTarget(), |
59 | } | 33 | substitutedArguments); |
60 | |||
61 | @Override | ||
62 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | ||
63 | if (!super.equalsWithSubstitution(helper, other)) { | ||
64 | return false; | ||
65 | } | ||
66 | var otherCountLiteral = (CountLiteral) other; | ||
67 | return helper.variableEqual(resultVariable, otherCountLiteral.resultVariable); | ||
68 | } | ||
69 | |||
70 | @Override | ||
71 | public boolean equals(Object o) { | ||
72 | if (this == o) return true; | ||
73 | if (o == null || getClass() != o.getClass()) return false; | ||
74 | if (!super.equals(o)) return false; | ||
75 | CountLiteral that = (CountLiteral) o; | ||
76 | return resultVariable.equals(that.resultVariable); | ||
77 | } | 34 | } |
78 | 35 | ||
79 | @Override | 36 | @Override |
80 | public int hashCode() { | 37 | public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) { |
81 | return Objects.hash(super.hashCode(), resultVariable); | 38 | return new CountLiteral(getResultVariable(), newTarget, newArguments); |
82 | } | 39 | } |
83 | 40 | ||
84 | @Override | 41 | @Override |
85 | public String toString() { | 42 | protected String operatorName() { |
86 | var builder = new StringBuilder(); | 43 | return "count"; |
87 | builder.append(resultVariable); | ||
88 | builder.append(" is count "); | ||
89 | builder.append(getTarget().toReferenceString()); | ||
90 | builder.append("("); | ||
91 | var argumentIterator = getArguments().iterator(); | ||
92 | if (argumentIterator.hasNext()) { | ||
93 | builder.append(argumentIterator.next()); | ||
94 | while (argumentIterator.hasNext()) { | ||
95 | builder.append(", ").append(argumentIterator.next()); | ||
96 | } | ||
97 | } | ||
98 | builder.append(")"); | ||
99 | return builder.toString(); | ||
100 | } | 44 | } |
101 | } | 45 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java index 28ba7625..7343f709 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java | |||
@@ -5,16 +5,44 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | 11 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | import tools.refinery.store.query.term.Variable; | 12 | import tools.refinery.store.query.term.Variable; |
12 | 13 | ||
13 | import java.util.Objects; | 14 | import java.util.Objects; |
14 | import java.util.Set; | 15 | import java.util.Set; |
15 | 16 | ||
16 | public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariable right) | 17 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. |
17 | implements CanNegate<EquivalenceLiteral> { | 18 | @SuppressWarnings("squid:S2160") |
19 | public final class EquivalenceLiteral extends AbstractLiteral implements CanNegate<EquivalenceLiteral> { | ||
20 | private final boolean positive; | ||
21 | private final Variable left; | ||
22 | private final Variable right; | ||
23 | |||
24 | public EquivalenceLiteral(boolean positive, Variable left, Variable right) { | ||
25 | if (!left.tryGetType().equals(right.tryGetType())) { | ||
26 | throw new InvalidQueryException("Variables %s and %s of different type cannot be equivalent" | ||
27 | .formatted(left, right)); | ||
28 | } | ||
29 | this.positive = positive; | ||
30 | this.left = left; | ||
31 | this.right = right; | ||
32 | } | ||
33 | |||
34 | public boolean isPositive() { | ||
35 | return positive; | ||
36 | } | ||
37 | |||
38 | public Variable getLeft() { | ||
39 | return left; | ||
40 | } | ||
41 | |||
42 | public Variable getRight() { | ||
43 | return right; | ||
44 | } | ||
45 | |||
18 | @Override | 46 | @Override |
19 | public Set<Variable> getOutputVariables() { | 47 | public Set<Variable> getOutputVariables() { |
20 | return Set.of(left); | 48 | return Set.of(left); |
@@ -37,8 +65,8 @@ public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariab | |||
37 | 65 | ||
38 | @Override | 66 | @Override |
39 | public EquivalenceLiteral substitute(Substitution substitution) { | 67 | public EquivalenceLiteral substitute(Substitution substitution) { |
40 | return new EquivalenceLiteral(positive, substitution.getTypeSafeSubstitute(left), | 68 | return new EquivalenceLiteral(positive, substitution.getSubstitute(left), |
41 | substitution.getTypeSafeSubstitute(right)); | 69 | substitution.getSubstitute(right)); |
42 | } | 70 | } |
43 | 71 | ||
44 | @Override | 72 | @Override |
@@ -55,27 +83,18 @@ public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariab | |||
55 | return false; | 83 | return false; |
56 | } | 84 | } |
57 | var otherEquivalenceLiteral = (EquivalenceLiteral) other; | 85 | var otherEquivalenceLiteral = (EquivalenceLiteral) other; |
58 | return helper.variableEqual(left, otherEquivalenceLiteral.left) && helper.variableEqual(right, | 86 | return helper.variableEqual(left, otherEquivalenceLiteral.left) && |
59 | otherEquivalenceLiteral.right); | 87 | helper.variableEqual(right, otherEquivalenceLiteral.right); |
60 | } | ||
61 | |||
62 | @Override | ||
63 | public String toString() { | ||
64 | return "%s %s %s".formatted(left, positive ? "===" : "!==", right); | ||
65 | } | 88 | } |
66 | 89 | ||
67 | @Override | 90 | @Override |
68 | public boolean equals(Object obj) { | 91 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { |
69 | if (obj == this) return true; | 92 | return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(left), |
70 | if (obj == null || obj.getClass() != this.getClass()) return false; | 93 | helper.getVariableHashCode(right)); |
71 | var that = (EquivalenceLiteral) obj; | ||
72 | return this.positive == that.positive && | ||
73 | Objects.equals(this.left, that.left) && | ||
74 | Objects.equals(this.right, that.right); | ||
75 | } | 94 | } |
76 | 95 | ||
77 | @Override | 96 | @Override |
78 | public int hashCode() { | 97 | public String toString() { |
79 | return Objects.hash(getClass(), positive, left, right); | 98 | return "%s %s %s".formatted(left, positive ? "===" : "!==", right); |
80 | } | 99 | } |
81 | } | 100 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java index ce6c11fe..cb16ab00 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java | |||
@@ -6,6 +6,7 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | 10 | import tools.refinery.store.query.substitution.Substitution; |
10 | import tools.refinery.store.query.term.Variable; | 11 | import tools.refinery.store.query.term.Variable; |
11 | 12 | ||
@@ -26,4 +27,6 @@ public interface Literal { | |||
26 | 27 | ||
27 | @SuppressWarnings("BooleanMethodIsAlwaysInverted") | 28 | @SuppressWarnings("BooleanMethodIsAlwaysInverted") |
28 | boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other); | 29 | boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other); |
30 | |||
31 | int hashCodeWithSubstitution(LiteralHashCodeHelper helper); | ||
29 | } | 32 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java index b3a87811..6056da45 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java | |||
@@ -16,7 +16,7 @@ public final class Literals { | |||
16 | return literal.negate(); | 16 | return literal.negate(); |
17 | } | 17 | } |
18 | 18 | ||
19 | public static AssumeLiteral assume(Term<Boolean> term) { | 19 | public static CheckLiteral check(Term<Boolean> term) { |
20 | return new AssumeLiteral(term); | 20 | return new CheckLiteral(term); |
21 | } | 21 | } |
22 | } | 22 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RepresentativeElectionLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RepresentativeElectionLiteral.java new file mode 100644 index 00000000..f7323947 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RepresentativeElectionLiteral.java | |||
@@ -0,0 +1,119 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.InvalidQueryException; | ||
10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | ||
12 | import tools.refinery.store.query.substitution.Substitution; | ||
13 | import tools.refinery.store.query.term.NodeVariable; | ||
14 | import tools.refinery.store.query.term.ParameterDirection; | ||
15 | import tools.refinery.store.query.term.Variable; | ||
16 | |||
17 | import java.util.List; | ||
18 | import java.util.Set; | ||
19 | |||
20 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. | ||
21 | @SuppressWarnings("squid:S2160") | ||
22 | public class RepresentativeElectionLiteral extends AbstractCallLiteral { | ||
23 | private final Connectivity connectivity; | ||
24 | |||
25 | public RepresentativeElectionLiteral(Connectivity connectivity, Constraint target, NodeVariable specific, | ||
26 | NodeVariable representative) { | ||
27 | this(connectivity, target, List.of(specific, representative)); | ||
28 | } | ||
29 | |||
30 | private RepresentativeElectionLiteral(Connectivity connectivity, Constraint target, List<Variable> arguments) { | ||
31 | super(target, arguments); | ||
32 | this.connectivity = connectivity; | ||
33 | var parameters = target.getParameters(); | ||
34 | int arity = target.arity(); | ||
35 | if (arity != 2) { | ||
36 | throw new InvalidQueryException("SCCs can only take binary relations"); | ||
37 | } | ||
38 | if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) { | ||
39 | throw new InvalidQueryException("SCCs can only be computed over nodes"); | ||
40 | } | ||
41 | if (parameters.get(0).getDirection() != ParameterDirection.OUT || | ||
42 | parameters.get(1).getDirection() != ParameterDirection.OUT) { | ||
43 | throw new InvalidQueryException("SCCs cannot take input parameters"); | ||
44 | } | ||
45 | } | ||
46 | |||
47 | public Connectivity getConnectivity() { | ||
48 | return connectivity; | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) { | ||
53 | return new RepresentativeElectionLiteral(connectivity, getTarget(), substitutedArguments); | ||
54 | } | ||
55 | |||
56 | @Override | ||
57 | public Set<Variable> getOutputVariables() { | ||
58 | return getArgumentsOfDirection(ParameterDirection.OUT); | ||
59 | } | ||
60 | |||
61 | @Override | ||
62 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
63 | return Set.of(); | ||
64 | } | ||
65 | |||
66 | @Override | ||
67 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
68 | return Set.of(); | ||
69 | } | ||
70 | |||
71 | @Override | ||
72 | public Literal reduce() { | ||
73 | var reduction = getTarget().getReduction(); | ||
74 | return switch (reduction) { | ||
75 | case ALWAYS_FALSE -> BooleanLiteral.FALSE; | ||
76 | case ALWAYS_TRUE -> throw new InvalidQueryException( | ||
77 | "Trying to elect representatives over an infinite set"); | ||
78 | case NOT_REDUCIBLE -> this; | ||
79 | }; | ||
80 | } | ||
81 | |||
82 | @Override | ||
83 | public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) { | ||
84 | return new RepresentativeElectionLiteral(connectivity, newTarget, newArguments); | ||
85 | } | ||
86 | |||
87 | @Override | ||
88 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | ||
89 | if (!super.equalsWithSubstitution(helper, other)) { | ||
90 | return false; | ||
91 | } | ||
92 | var otherRepresentativeElectionLiteral = (RepresentativeElectionLiteral) other; | ||
93 | return connectivity.equals(otherRepresentativeElectionLiteral.connectivity); | ||
94 | } | ||
95 | |||
96 | @Override | ||
97 | public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) { | ||
98 | return super.hashCodeWithSubstitution(helper) * 31 + connectivity.hashCode(); | ||
99 | } | ||
100 | |||
101 | @Override | ||
102 | public String toString() { | ||
103 | var builder = new StringBuilder(); | ||
104 | builder.append("@Representative(\""); | ||
105 | builder.append(connectivity); | ||
106 | builder.append("\") "); | ||
107 | builder.append(getTarget().toReferenceString()); | ||
108 | builder.append("("); | ||
109 | var argumentIterator = getArguments().iterator(); | ||
110 | if (argumentIterator.hasNext()) { | ||
111 | builder.append(argumentIterator.next()); | ||
112 | while (argumentIterator.hasNext()) { | ||
113 | builder.append(", ").append(argumentIterator.next()); | ||
114 | } | ||
115 | } | ||
116 | builder.append(")"); | ||
117 | return builder.toString(); | ||
118 | } | ||
119 | } | ||