diff options
Diffstat (limited to 'subprojects/store-query/src/main/java')
30 files changed, 1050 insertions, 0 deletions
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/Dnf.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/Dnf.java new file mode 100644 index 00000000..760b264b --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/Dnf.java | |||
@@ -0,0 +1,112 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import tools.refinery.store.query.literal.CallPolarity; | ||
4 | import tools.refinery.store.query.literal.DnfCallLiteral; | ||
5 | import tools.refinery.store.query.literal.LiteralReduction; | ||
6 | |||
7 | import java.util.*; | ||
8 | |||
9 | public final class Dnf implements RelationLike { | ||
10 | private final String name; | ||
11 | |||
12 | private final String uniqueName; | ||
13 | |||
14 | private final List<Variable> parameters; | ||
15 | |||
16 | private final List<FunctionalDependency<Variable>> functionalDependencies; | ||
17 | |||
18 | private final List<DnfClause> clauses; | ||
19 | |||
20 | Dnf(String name, List<Variable> parameters, List<FunctionalDependency<Variable>> functionalDependencies, | ||
21 | List<DnfClause> clauses) { | ||
22 | validateFunctionalDependencies(parameters, functionalDependencies); | ||
23 | this.name = name; | ||
24 | this.uniqueName = DnfUtils.generateUniqueName(name); | ||
25 | this.parameters = parameters; | ||
26 | this.functionalDependencies = functionalDependencies; | ||
27 | this.clauses = clauses; | ||
28 | } | ||
29 | |||
30 | private static void validateFunctionalDependencies( | ||
31 | Collection<Variable> parameters, Collection<FunctionalDependency<Variable>> functionalDependencies) { | ||
32 | var parameterSet = new HashSet<>(parameters); | ||
33 | for (var functionalDependency : functionalDependencies) { | ||
34 | validateParameters(parameters, parameterSet, functionalDependency.forEach(), functionalDependency); | ||
35 | validateParameters(parameters, parameterSet, functionalDependency.unique(), functionalDependency); | ||
36 | } | ||
37 | } | ||
38 | |||
39 | private static void validateParameters(Collection<Variable> parameters, Set<Variable> parameterSet, | ||
40 | Collection<Variable> toValidate, | ||
41 | FunctionalDependency<Variable> functionalDependency) { | ||
42 | for (var variable : toValidate) { | ||
43 | if (!parameterSet.contains(variable)) { | ||
44 | throw new IllegalArgumentException( | ||
45 | "Variable %s of functional dependency %s does not appear in the parameter list %s" | ||
46 | .formatted(variable, functionalDependency, parameters)); | ||
47 | } | ||
48 | } | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | public String name() { | ||
53 | return name; | ||
54 | } | ||
55 | |||
56 | public String getUniqueName() { | ||
57 | return uniqueName; | ||
58 | } | ||
59 | |||
60 | public List<Variable> getParameters() { | ||
61 | return parameters; | ||
62 | } | ||
63 | |||
64 | public List<FunctionalDependency<Variable>> getFunctionalDependencies() { | ||
65 | return functionalDependencies; | ||
66 | } | ||
67 | |||
68 | @Override | ||
69 | public int arity() { | ||
70 | return parameters.size(); | ||
71 | } | ||
72 | |||
73 | public List<DnfClause> getClauses() { | ||
74 | return clauses; | ||
75 | } | ||
76 | |||
77 | public LiteralReduction getReduction() { | ||
78 | if (clauses.isEmpty()) { | ||
79 | return LiteralReduction.ALWAYS_FALSE; | ||
80 | } | ||
81 | for (var clause : clauses) { | ||
82 | if (clause.literals().isEmpty()) { | ||
83 | return LiteralReduction.ALWAYS_TRUE; | ||
84 | } | ||
85 | } | ||
86 | return LiteralReduction.NOT_REDUCIBLE; | ||
87 | } | ||
88 | |||
89 | public DnfCallLiteral call(CallPolarity polarity, List<Variable> arguments) { | ||
90 | return new DnfCallLiteral(polarity, this, arguments); | ||
91 | } | ||
92 | |||
93 | public DnfCallLiteral call(CallPolarity polarity, Variable... arguments) { | ||
94 | return call(polarity, List.of(arguments)); | ||
95 | } | ||
96 | |||
97 | public DnfCallLiteral call(Variable... arguments) { | ||
98 | return call(CallPolarity.POSITIVE, arguments); | ||
99 | } | ||
100 | |||
101 | public DnfCallLiteral callTransitive(Variable left, Variable right) { | ||
102 | return call(CallPolarity.TRANSITIVE, List.of(left, right)); | ||
103 | } | ||
104 | |||
105 | public static DnfBuilder builder() { | ||
106 | return builder(null); | ||
107 | } | ||
108 | |||
109 | public static DnfBuilder builder(String name) { | ||
110 | return new DnfBuilder(name); | ||
111 | } | ||
112 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfBuilder.java new file mode 100644 index 00000000..b18b5177 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfBuilder.java | |||
@@ -0,0 +1,108 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import tools.refinery.store.query.literal.Literal; | ||
4 | |||
5 | import java.util.*; | ||
6 | |||
7 | @SuppressWarnings("UnusedReturnValue") | ||
8 | public class DnfBuilder { | ||
9 | private final String name; | ||
10 | |||
11 | private final List<Variable> parameters = new ArrayList<>(); | ||
12 | |||
13 | private final List<FunctionalDependency<Variable>> functionalDependencies = new ArrayList<>(); | ||
14 | |||
15 | private final List<List<Literal>> clauses = new ArrayList<>(); | ||
16 | |||
17 | DnfBuilder(String name) { | ||
18 | this.name = name; | ||
19 | } | ||
20 | |||
21 | public DnfBuilder parameter(Variable variable) { | ||
22 | parameters.add(variable); | ||
23 | return this; | ||
24 | } | ||
25 | |||
26 | public DnfBuilder parameters(Variable... variables) { | ||
27 | return parameters(List.of(variables)); | ||
28 | } | ||
29 | |||
30 | public DnfBuilder parameters(Collection<Variable> variables) { | ||
31 | parameters.addAll(variables); | ||
32 | return this; | ||
33 | } | ||
34 | |||
35 | public DnfBuilder functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) { | ||
36 | this.functionalDependencies.addAll(functionalDependencies); | ||
37 | return this; | ||
38 | } | ||
39 | |||
40 | public DnfBuilder functionalDependency(FunctionalDependency<Variable> functionalDependency) { | ||
41 | functionalDependencies.add(functionalDependency); | ||
42 | return this; | ||
43 | } | ||
44 | |||
45 | public DnfBuilder functionalDependency(Set<Variable> forEach, Set<Variable> unique) { | ||
46 | return functionalDependency(new FunctionalDependency<>(forEach, unique)); | ||
47 | } | ||
48 | |||
49 | public DnfBuilder clause(Literal... literals) { | ||
50 | clause(List.of(literals)); | ||
51 | return this; | ||
52 | } | ||
53 | |||
54 | public DnfBuilder clause(Collection<Literal> literals) { | ||
55 | var filteredLiterals = new ArrayList<Literal>(literals.size()); | ||
56 | for (var literal : literals) { | ||
57 | var reduction = literal.getReduction(); | ||
58 | switch (reduction) { | ||
59 | case NOT_REDUCIBLE -> filteredLiterals.add(literal); | ||
60 | case ALWAYS_TRUE -> { | ||
61 | // Literals reducible to {@code true} can be omitted, because the model is always assumed to have at | ||
62 | // least on object. | ||
63 | } | ||
64 | case ALWAYS_FALSE -> { | ||
65 | // Clauses with {@code false} literals can be omitted entirely. | ||
66 | return this; | ||
67 | } | ||
68 | default -> throw new IllegalStateException("Invalid reduction %s".formatted(reduction)); | ||
69 | } | ||
70 | } | ||
71 | clauses.add(Collections.unmodifiableList(filteredLiterals)); | ||
72 | return this; | ||
73 | } | ||
74 | |||
75 | public DnfBuilder clause(DnfClause clause) { | ||
76 | return clause(clause.literals()); | ||
77 | } | ||
78 | |||
79 | public DnfBuilder clauses(DnfClause... clauses) { | ||
80 | for (var clause : clauses) { | ||
81 | this.clause(clause); | ||
82 | } | ||
83 | return this; | ||
84 | } | ||
85 | |||
86 | public DnfBuilder clauses(Collection<DnfClause> clauses) { | ||
87 | for (var clause : clauses) { | ||
88 | this.clause(clause); | ||
89 | } | ||
90 | return this; | ||
91 | } | ||
92 | |||
93 | public Dnf build() { | ||
94 | var postProcessedClauses = new ArrayList<DnfClause>(clauses.size()); | ||
95 | for (var constraints : clauses) { | ||
96 | var variables = new HashSet<Variable>(); | ||
97 | for (var constraint : constraints) { | ||
98 | constraint.collectAllVariables(variables); | ||
99 | } | ||
100 | parameters.forEach(variables::remove); | ||
101 | postProcessedClauses.add(new DnfClause(Collections.unmodifiableSet(variables), | ||
102 | Collections.unmodifiableList(constraints))); | ||
103 | } | ||
104 | return new Dnf(name, Collections.unmodifiableList(parameters), | ||
105 | Collections.unmodifiableList(functionalDependencies), | ||
106 | Collections.unmodifiableList(postProcessedClauses)); | ||
107 | } | ||
108 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfClause.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfClause.java new file mode 100644 index 00000000..2ba6becc --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfClause.java | |||
@@ -0,0 +1,9 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import tools.refinery.store.query.literal.Literal; | ||
4 | |||
5 | import java.util.List; | ||
6 | import java.util.Set; | ||
7 | |||
8 | public record DnfClause(Set<Variable> quantifiedVariables, List<Literal> literals) { | ||
9 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfUtils.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfUtils.java new file mode 100644 index 00000000..17564d43 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfUtils.java | |||
@@ -0,0 +1,24 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import java.util.Map; | ||
4 | import java.util.UUID; | ||
5 | |||
6 | public final class DnfUtils { | ||
7 | private DnfUtils() { | ||
8 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
9 | } | ||
10 | |||
11 | public static String generateUniqueName(String originalName) { | ||
12 | UUID uuid = UUID.randomUUID(); | ||
13 | String uniqueString = "_" + uuid.toString().replace('-', '_'); | ||
14 | if (originalName == null) { | ||
15 | return uniqueString; | ||
16 | } else { | ||
17 | return originalName + uniqueString; | ||
18 | } | ||
19 | } | ||
20 | |||
21 | public static Variable maybeSubstitute(Variable variable, Map<Variable, Variable> substitution) { | ||
22 | return substitution.getOrDefault(variable, variable); | ||
23 | } | ||
24 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/FunctionalDependency.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/FunctionalDependency.java new file mode 100644 index 00000000..63a81713 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/FunctionalDependency.java | |||
@@ -0,0 +1,15 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import java.util.HashSet; | ||
4 | import java.util.Set; | ||
5 | |||
6 | public record FunctionalDependency<T>(Set<T> forEach, Set<T> unique) { | ||
7 | public FunctionalDependency { | ||
8 | var uniqueForEach = new HashSet<>(unique); | ||
9 | uniqueForEach.retainAll(forEach); | ||
10 | if (!uniqueForEach.isEmpty()) { | ||
11 | throw new IllegalArgumentException("Variables %s appear on both sides of the functional dependency" | ||
12 | .formatted(uniqueForEach)); | ||
13 | } | ||
14 | } | ||
15 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQuery.java new file mode 100644 index 00000000..6a1aeabb --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQuery.java | |||
@@ -0,0 +1,11 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import tools.refinery.store.adapter.ModelAdapterType; | ||
4 | |||
5 | public final class ModelQuery extends ModelAdapterType<ModelQueryAdapter, ModelQueryStoreAdapter, ModelQueryBuilder> { | ||
6 | public static final ModelQuery ADAPTER = new ModelQuery(); | ||
7 | |||
8 | private ModelQuery() { | ||
9 | super(ModelQueryAdapter.class, ModelQueryStoreAdapter.class, ModelQueryBuilder.class); | ||
10 | } | ||
11 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java new file mode 100644 index 00000000..f7762444 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java | |||
@@ -0,0 +1,13 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import tools.refinery.store.adapter.ModelAdapter; | ||
4 | |||
5 | public interface ModelQueryAdapter extends ModelAdapter { | ||
6 | ModelQueryStoreAdapter getStoreAdapter(); | ||
7 | |||
8 | ResultSet getResultSet(Dnf query); | ||
9 | |||
10 | boolean hasPendingChanges(); | ||
11 | |||
12 | void flushChanges(); | ||
13 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java new file mode 100644 index 00000000..b3cfb4b4 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java | |||
@@ -0,0 +1,23 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import tools.refinery.store.adapter.ModelAdapterBuilder; | ||
4 | import tools.refinery.store.model.ModelStore; | ||
5 | |||
6 | import java.util.Collection; | ||
7 | import java.util.List; | ||
8 | |||
9 | public interface ModelQueryBuilder extends ModelAdapterBuilder { | ||
10 | default ModelQueryBuilder queries(Dnf... queries) { | ||
11 | return queries(List.of(queries)); | ||
12 | } | ||
13 | |||
14 | default ModelQueryBuilder queries(Collection<Dnf> queries) { | ||
15 | queries.forEach(this::query); | ||
16 | return this; | ||
17 | } | ||
18 | |||
19 | ModelQueryBuilder query(Dnf query); | ||
20 | |||
21 | @Override | ||
22 | ModelQueryStoreAdapter createStoreAdapter(ModelStore store); | ||
23 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java new file mode 100644 index 00000000..091d6d06 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java | |||
@@ -0,0 +1,16 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import tools.refinery.store.query.view.AnyRelationView; | ||
4 | import tools.refinery.store.adapter.ModelStoreAdapter; | ||
5 | import tools.refinery.store.model.Model; | ||
6 | |||
7 | import java.util.Collection; | ||
8 | |||
9 | public interface ModelQueryStoreAdapter extends ModelStoreAdapter { | ||
10 | Collection<AnyRelationView> getRelationViews(); | ||
11 | |||
12 | Collection<Dnf> getQueries(); | ||
13 | |||
14 | @Override | ||
15 | ModelQueryAdapter createModelAdapter(Model model); | ||
16 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/RelationLike.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/RelationLike.java new file mode 100644 index 00000000..8c784d8b --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/RelationLike.java | |||
@@ -0,0 +1,11 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | public interface RelationLike { | ||
4 | String name(); | ||
5 | |||
6 | int arity(); | ||
7 | |||
8 | default boolean invalidIndex(int i) { | ||
9 | return i < 0 || i >= arity(); | ||
10 | } | ||
11 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java new file mode 100644 index 00000000..3542e252 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java | |||
@@ -0,0 +1,25 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import tools.refinery.store.tuple.Tuple; | ||
4 | import tools.refinery.store.tuple.TupleLike; | ||
5 | |||
6 | import java.util.Optional; | ||
7 | import java.util.stream.Stream; | ||
8 | |||
9 | public interface ResultSet { | ||
10 | boolean hasResult(); | ||
11 | |||
12 | boolean hasResult(Tuple parameters); | ||
13 | |||
14 | Optional<TupleLike> oneResult(); | ||
15 | |||
16 | Optional<TupleLike> oneResult(Tuple parameters); | ||
17 | |||
18 | Stream<TupleLike> allResults(); | ||
19 | |||
20 | Stream<TupleLike> allResults(Tuple parameters); | ||
21 | |||
22 | int countResults(); | ||
23 | |||
24 | int countResults(Tuple parameters); | ||
25 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/Variable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/Variable.java new file mode 100644 index 00000000..2eb87649 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/Variable.java | |||
@@ -0,0 +1,58 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import tools.refinery.store.query.literal.ConstantLiteral; | ||
4 | import tools.refinery.store.query.literal.EquivalenceLiteral; | ||
5 | |||
6 | import java.util.Objects; | ||
7 | |||
8 | public class Variable { | ||
9 | private final String name; | ||
10 | private final String uniqueName; | ||
11 | |||
12 | public Variable() { | ||
13 | this(null); | ||
14 | } | ||
15 | |||
16 | public Variable(String name) { | ||
17 | super(); | ||
18 | this.name = name; | ||
19 | this.uniqueName = DnfUtils.generateUniqueName(name); | ||
20 | |||
21 | } | ||
22 | public String getName() { | ||
23 | return name; | ||
24 | } | ||
25 | |||
26 | public String getUniqueName() { | ||
27 | return uniqueName; | ||
28 | } | ||
29 | |||
30 | public boolean isNamed() { | ||
31 | return name != null; | ||
32 | } | ||
33 | |||
34 | public ConstantLiteral isConstant(int value) { | ||
35 | return new ConstantLiteral(this, value); | ||
36 | } | ||
37 | |||
38 | public EquivalenceLiteral isEquivalent(Variable other) { | ||
39 | return new EquivalenceLiteral(true, this, other); | ||
40 | } | ||
41 | |||
42 | public EquivalenceLiteral notEquivalent(Variable other) { | ||
43 | return new EquivalenceLiteral(false, this, other); | ||
44 | } | ||
45 | |||
46 | @Override | ||
47 | public boolean equals(Object o) { | ||
48 | if (this == o) return true; | ||
49 | if (o == null || getClass() != o.getClass()) return false; | ||
50 | Variable variable = (Variable) o; | ||
51 | return Objects.equals(uniqueName, variable.uniqueName); | ||
52 | } | ||
53 | |||
54 | @Override | ||
55 | public int hashCode() { | ||
56 | return Objects.hash(uniqueName); | ||
57 | } | ||
58 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java new file mode 100644 index 00000000..fd2f1eec --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java | |||
@@ -0,0 +1,37 @@ | |||
1 | package tools.refinery.store.query.literal; | ||
2 | |||
3 | import tools.refinery.store.query.Variable; | ||
4 | |||
5 | import java.util.Map; | ||
6 | import java.util.Set; | ||
7 | |||
8 | public class BooleanLiteral implements Literal { | ||
9 | public static final BooleanLiteral TRUE = new BooleanLiteral(LiteralReduction.ALWAYS_TRUE); | ||
10 | public static final BooleanLiteral FALSE = new BooleanLiteral(LiteralReduction.ALWAYS_FALSE); | ||
11 | |||
12 | private final LiteralReduction reduction; | ||
13 | |||
14 | private BooleanLiteral(LiteralReduction reduction) { | ||
15 | this.reduction = reduction; | ||
16 | } | ||
17 | |||
18 | @Override | ||
19 | public void collectAllVariables(Set<Variable> variables) { | ||
20 | // No variables to collect. | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | public Literal substitute(Map<Variable, Variable> substitution) { | ||
25 | // No variables to substitute. | ||
26 | return this; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public LiteralReduction getReduction() { | ||
31 | return reduction; | ||
32 | } | ||
33 | |||
34 | public static BooleanLiteral fromBoolean(boolean value) { | ||
35 | return value ? TRUE : FALSE; | ||
36 | } | ||
37 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java new file mode 100644 index 00000000..5e1ae94d --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java | |||
@@ -0,0 +1,66 @@ | |||
1 | package tools.refinery.store.query.literal; | ||
2 | |||
3 | import tools.refinery.store.query.DnfUtils; | ||
4 | import tools.refinery.store.query.RelationLike; | ||
5 | import tools.refinery.store.query.Variable; | ||
6 | |||
7 | import java.util.List; | ||
8 | import java.util.Map; | ||
9 | import java.util.Objects; | ||
10 | import java.util.Set; | ||
11 | |||
12 | public abstract class CallLiteral<T extends RelationLike> implements Literal { | ||
13 | private final CallPolarity polarity; | ||
14 | private final T target; | ||
15 | private final List<Variable> arguments; | ||
16 | |||
17 | protected CallLiteral(CallPolarity polarity, T target, List<Variable> arguments) { | ||
18 | if (arguments.size() != target.arity()) { | ||
19 | throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.name(), | ||
20 | target.arity(), arguments.size())); | ||
21 | } | ||
22 | if (polarity.isTransitive() && target.arity() != 2) { | ||
23 | throw new IllegalArgumentException("Transitive closures can only take binary relations"); | ||
24 | } | ||
25 | this.polarity = polarity; | ||
26 | this.target = target; | ||
27 | this.arguments = arguments; | ||
28 | } | ||
29 | |||
30 | public CallPolarity getPolarity() { | ||
31 | return polarity; | ||
32 | } | ||
33 | |||
34 | public T getTarget() { | ||
35 | return target; | ||
36 | } | ||
37 | |||
38 | public List<Variable> getArguments() { | ||
39 | return arguments; | ||
40 | } | ||
41 | |||
42 | @Override | ||
43 | public void collectAllVariables(Set<Variable> variables) { | ||
44 | if (polarity.isPositive()) { | ||
45 | variables.addAll(arguments); | ||
46 | } | ||
47 | } | ||
48 | |||
49 | protected List<Variable> substituteArguments(Map<Variable, Variable> substitution) { | ||
50 | return arguments.stream().map(variable -> DnfUtils.maybeSubstitute(variable, substitution)).toList(); | ||
51 | } | ||
52 | |||
53 | @Override | ||
54 | public boolean equals(Object o) { | ||
55 | if (this == o) return true; | ||
56 | if (o == null || getClass() != o.getClass()) return false; | ||
57 | CallLiteral<?> callAtom = (CallLiteral<?>) o; | ||
58 | return polarity == callAtom.polarity && Objects.equals(target, callAtom.target) && | ||
59 | Objects.equals(arguments, callAtom.arguments); | ||
60 | } | ||
61 | |||
62 | @Override | ||
63 | public int hashCode() { | ||
64 | return Objects.hash(polarity, target, arguments); | ||
65 | } | ||
66 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java new file mode 100644 index 00000000..84b4b771 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java | |||
@@ -0,0 +1,32 @@ | |||
1 | package tools.refinery.store.query.literal; | ||
2 | |||
3 | public enum CallPolarity { | ||
4 | POSITIVE(true, false), | ||
5 | NEGATIVE(false, false), | ||
6 | TRANSITIVE(true, true); | ||
7 | |||
8 | private final boolean positive; | ||
9 | |||
10 | private final boolean transitive; | ||
11 | |||
12 | CallPolarity(boolean positive, boolean transitive) { | ||
13 | this.positive = positive; | ||
14 | this.transitive = transitive; | ||
15 | } | ||
16 | |||
17 | public boolean isPositive() { | ||
18 | return positive; | ||
19 | } | ||
20 | |||
21 | public boolean isTransitive() { | ||
22 | return transitive; | ||
23 | } | ||
24 | |||
25 | public CallPolarity negate() { | ||
26 | return switch (this) { | ||
27 | case POSITIVE -> NEGATIVE; | ||
28 | case NEGATIVE -> POSITIVE; | ||
29 | case TRANSITIVE -> throw new IllegalArgumentException("Transitive polarity cannot be negated"); | ||
30 | }; | ||
31 | } | ||
32 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java new file mode 100644 index 00000000..746d23af --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java | |||
@@ -0,0 +1,19 @@ | |||
1 | package tools.refinery.store.query.literal; | ||
2 | |||
3 | import tools.refinery.store.query.DnfUtils; | ||
4 | import tools.refinery.store.query.Variable; | ||
5 | |||
6 | import java.util.Map; | ||
7 | import java.util.Set; | ||
8 | |||
9 | public record ConstantLiteral(Variable variable, int nodeId) implements Literal { | ||
10 | @Override | ||
11 | public void collectAllVariables(Set<Variable> variables) { | ||
12 | variables.add(variable); | ||
13 | } | ||
14 | |||
15 | @Override | ||
16 | public ConstantLiteral substitute(Map<Variable, Variable> substitution) { | ||
17 | return new ConstantLiteral(DnfUtils.maybeSubstitute(variable, substitution), nodeId); | ||
18 | } | ||
19 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/DnfCallLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/DnfCallLiteral.java new file mode 100644 index 00000000..de6c6005 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/DnfCallLiteral.java | |||
@@ -0,0 +1,29 @@ | |||
1 | package tools.refinery.store.query.literal; | ||
2 | |||
3 | import tools.refinery.store.query.Dnf; | ||
4 | import tools.refinery.store.query.Variable; | ||
5 | |||
6 | import java.util.List; | ||
7 | import java.util.Map; | ||
8 | |||
9 | public final class DnfCallLiteral extends CallLiteral<Dnf> implements PolarLiteral<DnfCallLiteral> { | ||
10 | public DnfCallLiteral(CallPolarity polarity, Dnf target, List<Variable> arguments) { | ||
11 | super(polarity, target, arguments); | ||
12 | } | ||
13 | |||
14 | @Override | ||
15 | public DnfCallLiteral substitute(Map<Variable, Variable> substitution) { | ||
16 | return new DnfCallLiteral(getPolarity(), getTarget(), substituteArguments(substitution)); | ||
17 | } | ||
18 | |||
19 | @Override | ||
20 | public DnfCallLiteral negate() { | ||
21 | return new DnfCallLiteral(getPolarity().negate(), getTarget(), getArguments()); | ||
22 | } | ||
23 | |||
24 | @Override | ||
25 | public LiteralReduction getReduction() { | ||
26 | var dnfReduction = getTarget().getReduction(); | ||
27 | return getPolarity().isPositive() ? dnfReduction : dnfReduction.negate(); | ||
28 | } | ||
29 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java new file mode 100644 index 00000000..f30179b2 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java | |||
@@ -0,0 +1,35 @@ | |||
1 | package tools.refinery.store.query.literal; | ||
2 | |||
3 | import tools.refinery.store.query.DnfUtils; | ||
4 | import tools.refinery.store.query.Variable; | ||
5 | |||
6 | import java.util.Map; | ||
7 | import java.util.Set; | ||
8 | |||
9 | public record EquivalenceLiteral(boolean positive, Variable left, Variable right) | ||
10 | implements PolarLiteral<EquivalenceLiteral> { | ||
11 | @Override | ||
12 | public void collectAllVariables(Set<Variable> variables) { | ||
13 | variables.add(left); | ||
14 | variables.add(right); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | public EquivalenceLiteral negate() { | ||
19 | return new EquivalenceLiteral(!positive, left, right); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public EquivalenceLiteral substitute(Map<Variable, Variable> substitution) { | ||
24 | return new EquivalenceLiteral(positive, DnfUtils.maybeSubstitute(left, substitution), | ||
25 | DnfUtils.maybeSubstitute(right, substitution)); | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public LiteralReduction getReduction() { | ||
30 | if (left.equals(right)) { | ||
31 | return positive ? LiteralReduction.ALWAYS_TRUE : LiteralReduction.ALWAYS_FALSE; | ||
32 | } | ||
33 | return LiteralReduction.NOT_REDUCIBLE; | ||
34 | } | ||
35 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java new file mode 100644 index 00000000..a6893acf --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java | |||
@@ -0,0 +1,16 @@ | |||
1 | package tools.refinery.store.query.literal; | ||
2 | |||
3 | import tools.refinery.store.query.Variable; | ||
4 | |||
5 | import java.util.Map; | ||
6 | import java.util.Set; | ||
7 | |||
8 | public interface Literal { | ||
9 | void collectAllVariables(Set<Variable> variables); | ||
10 | |||
11 | Literal substitute(Map<Variable, Variable> substitution); | ||
12 | |||
13 | default LiteralReduction getReduction() { | ||
14 | return LiteralReduction.NOT_REDUCIBLE; | ||
15 | } | ||
16 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LiteralReduction.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LiteralReduction.java new file mode 100644 index 00000000..146089f6 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LiteralReduction.java | |||
@@ -0,0 +1,26 @@ | |||
1 | package tools.refinery.store.query.literal; | ||
2 | |||
3 | public enum LiteralReduction { | ||
4 | /** | ||
5 | * Signifies that a literal should be preserved in the clause. | ||
6 | */ | ||
7 | NOT_REDUCIBLE, | ||
8 | |||
9 | /** | ||
10 | * Signifies that the literal may be omitted from the cause (if the model being queried is nonempty). | ||
11 | */ | ||
12 | ALWAYS_TRUE, | ||
13 | |||
14 | /** | ||
15 | * Signifies that the clause with the literal may be omitted entirely. | ||
16 | */ | ||
17 | ALWAYS_FALSE; | ||
18 | |||
19 | public LiteralReduction negate() { | ||
20 | return switch (this) { | ||
21 | case NOT_REDUCIBLE -> NOT_REDUCIBLE; | ||
22 | case ALWAYS_TRUE -> ALWAYS_FALSE; | ||
23 | case ALWAYS_FALSE -> ALWAYS_TRUE; | ||
24 | }; | ||
25 | } | ||
26 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java new file mode 100644 index 00000000..2c7e893f --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java | |||
@@ -0,0 +1,11 @@ | |||
1 | package tools.refinery.store.query.literal; | ||
2 | |||
3 | public final class Literals { | ||
4 | private Literals() { | ||
5 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
6 | } | ||
7 | |||
8 | public static <T extends PolarLiteral<T>> T not(PolarLiteral<T> literal) { | ||
9 | return literal.negate(); | ||
10 | } | ||
11 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/PolarLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/PolarLiteral.java new file mode 100644 index 00000000..32523675 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/PolarLiteral.java | |||
@@ -0,0 +1,5 @@ | |||
1 | package tools.refinery.store.query.literal; | ||
2 | |||
3 | public interface PolarLiteral<T extends PolarLiteral<T>> extends Literal { | ||
4 | T negate(); | ||
5 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RelationViewLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RelationViewLiteral.java new file mode 100644 index 00000000..4718b550 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RelationViewLiteral.java | |||
@@ -0,0 +1,24 @@ | |||
1 | package tools.refinery.store.query.literal; | ||
2 | |||
3 | import tools.refinery.store.query.Variable; | ||
4 | import tools.refinery.store.query.view.AnyRelationView; | ||
5 | |||
6 | import java.util.List; | ||
7 | import java.util.Map; | ||
8 | |||
9 | public final class RelationViewLiteral extends CallLiteral<AnyRelationView> | ||
10 | implements PolarLiteral<RelationViewLiteral> { | ||
11 | public RelationViewLiteral(CallPolarity polarity, AnyRelationView target, List<Variable> arguments) { | ||
12 | super(polarity, target, arguments); | ||
13 | } | ||
14 | |||
15 | @Override | ||
16 | public RelationViewLiteral substitute(Map<Variable, Variable> substitution) { | ||
17 | return new RelationViewLiteral(getPolarity(), getTarget(), substituteArguments(substitution)); | ||
18 | } | ||
19 | |||
20 | @Override | ||
21 | public RelationViewLiteral negate() { | ||
22 | return new RelationViewLiteral(getPolarity().negate(), getTarget(), getArguments()); | ||
23 | } | ||
24 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnyRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnyRelationView.java new file mode 100644 index 00000000..328cde3a --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnyRelationView.java | |||
@@ -0,0 +1,24 @@ | |||
1 | package tools.refinery.store.query.view; | ||
2 | |||
3 | import tools.refinery.store.model.Model; | ||
4 | import tools.refinery.store.query.FunctionalDependency; | ||
5 | import tools.refinery.store.representation.AnySymbol; | ||
6 | import tools.refinery.store.query.RelationLike; | ||
7 | |||
8 | import java.util.Set; | ||
9 | |||
10 | public sealed interface AnyRelationView extends RelationLike permits RelationView { | ||
11 | AnySymbol getSymbol(); | ||
12 | |||
13 | default Set<FunctionalDependency<Integer>> getFunctionalDependencies() { | ||
14 | return Set.of(); | ||
15 | } | ||
16 | |||
17 | default Set<RelationViewImplication> getImpliedRelationViews() { | ||
18 | return Set.of(); | ||
19 | } | ||
20 | |||
21 | boolean get(Model model, Object[] tuple); | ||
22 | |||
23 | Iterable<Object[]> getAll(Model model); | ||
24 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java new file mode 100644 index 00000000..64c601bb --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java | |||
@@ -0,0 +1,49 @@ | |||
1 | package tools.refinery.store.query.view; | ||
2 | |||
3 | import tools.refinery.store.tuple.Tuple; | ||
4 | import tools.refinery.store.representation.Symbol; | ||
5 | |||
6 | import java.util.Objects; | ||
7 | import java.util.function.BiPredicate; | ||
8 | import java.util.function.Predicate; | ||
9 | |||
10 | public class FilteredRelationView<T> extends TuplePreservingRelationView<T> { | ||
11 | private final BiPredicate<Tuple, T> predicate; | ||
12 | |||
13 | public FilteredRelationView(Symbol<T> symbol, String name, BiPredicate<Tuple, T> predicate) { | ||
14 | super(symbol, name); | ||
15 | this.predicate = predicate; | ||
16 | } | ||
17 | |||
18 | public FilteredRelationView(Symbol<T> symbol, BiPredicate<Tuple, T> predicate) { | ||
19 | super(symbol); | ||
20 | this.predicate = predicate; | ||
21 | } | ||
22 | |||
23 | public FilteredRelationView(Symbol<T> symbol, String name, Predicate<T> predicate) { | ||
24 | this(symbol, name, (k, v) -> predicate.test(v)); | ||
25 | } | ||
26 | |||
27 | public FilteredRelationView(Symbol<T> symbol, Predicate<T> predicate) { | ||
28 | this(symbol, (k, v) -> predicate.test(v)); | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public boolean filter(Tuple key, T value) { | ||
33 | return this.predicate.test(key, value); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public boolean equals(Object o) { | ||
38 | if (this == o) return true; | ||
39 | if (o == null || getClass() != o.getClass()) return false; | ||
40 | if (!super.equals(o)) return false; | ||
41 | FilteredRelationView<?> that = (FilteredRelationView<?>) o; | ||
42 | return Objects.equals(predicate, that.predicate); | ||
43 | } | ||
44 | |||
45 | @Override | ||
46 | public int hashCode() { | ||
47 | return Objects.hash(super.hashCode(), predicate); | ||
48 | } | ||
49 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java new file mode 100644 index 00000000..3d278a8b --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java | |||
@@ -0,0 +1,71 @@ | |||
1 | package tools.refinery.store.query.view; | ||
2 | |||
3 | import tools.refinery.store.model.Model; | ||
4 | import tools.refinery.store.query.FunctionalDependency; | ||
5 | import tools.refinery.store.representation.Symbol; | ||
6 | import tools.refinery.store.tuple.Tuple; | ||
7 | import tools.refinery.store.tuple.Tuple1; | ||
8 | |||
9 | import java.util.Set; | ||
10 | import java.util.stream.Collectors; | ||
11 | import java.util.stream.IntStream; | ||
12 | |||
13 | public final class FunctionalRelationView<T> extends RelationView<T> { | ||
14 | public FunctionalRelationView(Symbol<T> symbol, String name) { | ||
15 | super(symbol, name); | ||
16 | } | ||
17 | |||
18 | public FunctionalRelationView(Symbol<T> symbol) { | ||
19 | super(symbol); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public Set<FunctionalDependency<Integer>> getFunctionalDependencies() { | ||
24 | var arity = getSymbol().arity(); | ||
25 | var forEach = IntStream.range(0, arity).boxed().collect(Collectors.toUnmodifiableSet()); | ||
26 | var unique = Set.of(arity); | ||
27 | return Set.of(new FunctionalDependency<>(forEach, unique)); | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public Set<RelationViewImplication> getImpliedRelationViews() { | ||
32 | var symbol = getSymbol(); | ||
33 | var impliedIndices = IntStream.range(0, symbol.arity()).boxed().toList(); | ||
34 | var keyOnlyRelationView = new KeyOnlyRelationView<>(symbol); | ||
35 | return Set.of(new RelationViewImplication(this, keyOnlyRelationView, impliedIndices)); | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public boolean filter(Tuple key, T value) { | ||
40 | return true; | ||
41 | } | ||
42 | |||
43 | @Override | ||
44 | public Object[] forwardMap(Tuple key, T value) { | ||
45 | int size = key.getSize(); | ||
46 | Object[] result = new Object[size + 1]; | ||
47 | for (int i = 0; i < size; i++) { | ||
48 | result[i] = Tuple.of(key.get(i)); | ||
49 | } | ||
50 | result[key.getSize()] = value; | ||
51 | return result; | ||
52 | } | ||
53 | |||
54 | @Override | ||
55 | public boolean get(Model model, Object[] tuple) { | ||
56 | int[] content = new int[tuple.length - 1]; | ||
57 | for (int i = 0; i < tuple.length - 1; i++) { | ||
58 | content[i] = ((Tuple1) tuple[i]).value0(); | ||
59 | } | ||
60 | Tuple key = Tuple.of(content); | ||
61 | @SuppressWarnings("unchecked") | ||
62 | T valueInTuple = (T) tuple[tuple.length - 1]; | ||
63 | T valueInMap = model.getInterpretation(getSymbol()).get(key); | ||
64 | return valueInTuple.equals(valueInMap); | ||
65 | } | ||
66 | |||
67 | @Override | ||
68 | public int arity() { | ||
69 | return getSymbol().arity() + 1; | ||
70 | } | ||
71 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java new file mode 100644 index 00000000..e1b2e45b --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java | |||
@@ -0,0 +1,36 @@ | |||
1 | package tools.refinery.store.query.view; | ||
2 | |||
3 | import tools.refinery.store.representation.Symbol; | ||
4 | import tools.refinery.store.tuple.Tuple; | ||
5 | |||
6 | import java.util.Objects; | ||
7 | |||
8 | public final class KeyOnlyRelationView<T> extends TuplePreservingRelationView<T> { | ||
9 | public static final String VIEW_NAME = "key"; | ||
10 | |||
11 | private final T defaultValue; | ||
12 | |||
13 | public KeyOnlyRelationView(Symbol<T> symbol) { | ||
14 | super(symbol, VIEW_NAME); | ||
15 | defaultValue = symbol.defaultValue(); | ||
16 | } | ||
17 | |||
18 | @Override | ||
19 | public boolean filter(Tuple key, T value) { | ||
20 | return !Objects.equals(value, defaultValue); | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | public boolean equals(Object o) { | ||
25 | if (this == o) return true; | ||
26 | if (o == null || getClass() != o.getClass()) return false; | ||
27 | if (!super.equals(o)) return false; | ||
28 | KeyOnlyRelationView<?> that = (KeyOnlyRelationView<?>) o; | ||
29 | return Objects.equals(defaultValue, that.defaultValue); | ||
30 | } | ||
31 | |||
32 | @Override | ||
33 | public int hashCode() { | ||
34 | return Objects.hash(super.hashCode(), defaultValue); | ||
35 | } | ||
36 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java new file mode 100644 index 00000000..2714a8c5 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java | |||
@@ -0,0 +1,82 @@ | |||
1 | package tools.refinery.store.query.view; | ||
2 | |||
3 | import tools.refinery.store.query.Variable; | ||
4 | import tools.refinery.store.map.CursorAsIterator; | ||
5 | import tools.refinery.store.model.Model; | ||
6 | import tools.refinery.store.query.literal.CallPolarity; | ||
7 | import tools.refinery.store.query.literal.RelationViewLiteral; | ||
8 | import tools.refinery.store.representation.Symbol; | ||
9 | import tools.refinery.store.tuple.Tuple; | ||
10 | |||
11 | import java.util.List; | ||
12 | import java.util.Objects; | ||
13 | import java.util.UUID; | ||
14 | |||
15 | /** | ||
16 | * Represents a view of a {@link Symbol} that can be queried. | ||
17 | * | ||
18 | * @param <T> | ||
19 | * @author Oszkar Semerath | ||
20 | */ | ||
21 | public abstract non-sealed class RelationView<T> implements AnyRelationView { | ||
22 | private final Symbol<T> symbol; | ||
23 | |||
24 | private final String name; | ||
25 | |||
26 | protected RelationView(Symbol<T> symbol, String name) { | ||
27 | this.symbol = symbol; | ||
28 | this.name = name; | ||
29 | } | ||
30 | |||
31 | protected RelationView(Symbol<T> representation) { | ||
32 | this(representation, UUID.randomUUID().toString()); | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public Symbol<T> getSymbol() { | ||
37 | return symbol; | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public String name() { | ||
42 | return symbol.name() + "#" + name; | ||
43 | } | ||
44 | |||
45 | public abstract boolean filter(Tuple key, T value); | ||
46 | |||
47 | public abstract Object[] forwardMap(Tuple key, T value); | ||
48 | |||
49 | @Override | ||
50 | public Iterable<Object[]> getAll(Model model) { | ||
51 | return (() -> new CursorAsIterator<>(model.getInterpretation(symbol).getAll(), this::forwardMap, this::filter)); | ||
52 | } | ||
53 | |||
54 | public RelationViewLiteral call(CallPolarity polarity, List<Variable> arguments) { | ||
55 | return new RelationViewLiteral(polarity, this, arguments); | ||
56 | } | ||
57 | |||
58 | public RelationViewLiteral call(CallPolarity polarity, Variable... arguments) { | ||
59 | return call(polarity, List.of(arguments)); | ||
60 | } | ||
61 | |||
62 | public RelationViewLiteral call(Variable... arguments) { | ||
63 | return call(CallPolarity.POSITIVE, arguments); | ||
64 | } | ||
65 | |||
66 | public RelationViewLiteral callTransitive(Variable left, Variable right) { | ||
67 | return call(CallPolarity.TRANSITIVE, List.of(left, right)); | ||
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 | RelationView<?> that = (RelationView<?>) o; | ||
75 | return Objects.equals(symbol, that.symbol) && Objects.equals(name, that.name); | ||
76 | } | ||
77 | |||
78 | @Override | ||
79 | public int hashCode() { | ||
80 | return Objects.hash(symbol, name); | ||
81 | } | ||
82 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java new file mode 100644 index 00000000..2ba1fcc4 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java | |||
@@ -0,0 +1,19 @@ | |||
1 | package tools.refinery.store.query.view; | ||
2 | |||
3 | import java.util.List; | ||
4 | |||
5 | public record RelationViewImplication(AnyRelationView implyingRelationView, AnyRelationView impliedRelationView, | ||
6 | List<Integer> impliedIndices) { | ||
7 | public RelationViewImplication { | ||
8 | if (impliedIndices.size() != impliedRelationView.arity()) { | ||
9 | throw new IllegalArgumentException("Expected %d implied indices for %s, but %d are provided" | ||
10 | .formatted(impliedRelationView.arity(), impliedRelationView, impliedIndices.size())); | ||
11 | } | ||
12 | for (var index : impliedIndices) { | ||
13 | if (impliedRelationView.invalidIndex(index)) { | ||
14 | throw new IllegalArgumentException("%d is not a valid index for %s".formatted(index, | ||
15 | implyingRelationView)); | ||
16 | } | ||
17 | } | ||
18 | } | ||
19 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java new file mode 100644 index 00000000..8cc4986e --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java | |||
@@ -0,0 +1,44 @@ | |||
1 | package tools.refinery.store.query.view; | ||
2 | |||
3 | import tools.refinery.store.model.Model; | ||
4 | import tools.refinery.store.tuple.Tuple; | ||
5 | import tools.refinery.store.tuple.Tuple1; | ||
6 | import tools.refinery.store.representation.Symbol; | ||
7 | |||
8 | public abstract class TuplePreservingRelationView<T> extends RelationView<T> { | ||
9 | protected TuplePreservingRelationView(Symbol<T> symbol, String name) { | ||
10 | super(symbol, name); | ||
11 | } | ||
12 | |||
13 | protected TuplePreservingRelationView(Symbol<T> symbol) { | ||
14 | super(symbol); | ||
15 | } | ||
16 | |||
17 | public Object[] forwardMap(Tuple key) { | ||
18 | Object[] result = new Object[key.getSize()]; | ||
19 | for (int i = 0; i < key.getSize(); i++) { | ||
20 | result[i] = Tuple.of(key.get(i)); | ||
21 | } | ||
22 | return result; | ||
23 | } | ||
24 | |||
25 | @Override | ||
26 | public Object[] forwardMap(Tuple key, T value) { | ||
27 | return forwardMap(key); | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public boolean get(Model model, Object[] tuple) { | ||
32 | int[] content = new int[tuple.length]; | ||
33 | for (int i = 0; i < tuple.length; i++) { | ||
34 | content[i] = ((Tuple1) tuple[i]).value0(); | ||
35 | } | ||
36 | Tuple key = Tuple.of(content); | ||
37 | T value = model.getInterpretation(getSymbol()).get(key); | ||
38 | return filter(key, value); | ||
39 | } | ||
40 | |||
41 | public int arity() { | ||
42 | return this.getSymbol().arity(); | ||
43 | } | ||
44 | } | ||