aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java97
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java (renamed from subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryTest.java)2
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java (renamed from subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryTransactionTest.java)2
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorStreamTest.java50
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorTest.java87
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/CardinalityInterval.java21
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervals.java46
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/EmptyCardinalityInterval.java59
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/FiniteUpperCardinality.java55
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/NonEmptyCardinalityInterval.java74
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UnboundedUpperCardinality.java42
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UpperCardinalities.java33
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UpperCardinality.java22
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervalTest.java122
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervalsTest.java21
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/EmptyCardinalityIntervalTest.java14
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/FiniteCardinalityIntervalTest.java20
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/FiniteUpperCardinalityTest.java12
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/UpperCardinalitiesTest.java25
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/UpperCardinalityTest.java110
20 files changed, 912 insertions, 2 deletions
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java
new file mode 100644
index 00000000..ffd5f6de
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java
@@ -0,0 +1,97 @@
1package tools.refinery.store.query.viatra.internal.cardinality;
2
3import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.BoundAggregator;
4import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
5import tools.refinery.store.model.representation.cardinality.FiniteUpperCardinality;
6import tools.refinery.store.model.representation.cardinality.UnboundedUpperCardinality;
7import tools.refinery.store.model.representation.cardinality.UpperCardinalities;
8import tools.refinery.store.model.representation.cardinality.UpperCardinality;
9
10import java.util.stream.Stream;
11
12public class UpperCardinalitySumAggregationOperator implements IMultisetAggregationOperator<UpperCardinality,
13 UpperCardinalitySumAggregationOperator.Accumulator, UpperCardinality> {
14 public static final UpperCardinalitySumAggregationOperator INSTANCE = new UpperCardinalitySumAggregationOperator();
15
16 public static final BoundAggregator BOUND_AGGREGATOR = new BoundAggregator(INSTANCE, UpperCardinality.class,
17 UpperCardinality.class);
18
19 private UpperCardinalitySumAggregationOperator() {
20 // Singleton constructor.
21 }
22
23 @Override
24 public String getName() {
25 return "sum<UpperCardinality>";
26 }
27
28 @Override
29 public String getShortDescription() {
30 return "%s computes the sum of finite or unbounded upper cardinalities".formatted(getName());
31 }
32
33 @Override
34 public Accumulator createNeutral() {
35 return new Accumulator();
36 }
37
38 @Override
39 public boolean isNeutral(Accumulator result) {
40 return result.sumFiniteUpperBounds == 0 && result.countUnbounded == 0;
41 }
42
43 @Override
44 public Accumulator update(Accumulator oldResult, UpperCardinality updateValue, boolean isInsertion) {
45 if (updateValue instanceof FiniteUpperCardinality finiteUpperCardinality) {
46 int finiteUpperBound = finiteUpperCardinality.finiteUpperBound();
47 if (isInsertion) {
48 oldResult.sumFiniteUpperBounds += finiteUpperBound;
49 } else {
50 oldResult.sumFiniteUpperBounds -= finiteUpperBound;
51 }
52 } else if (updateValue instanceof UnboundedUpperCardinality) {
53 if (isInsertion) {
54 oldResult.countUnbounded += 1;
55 } else {
56 oldResult.countUnbounded -= 1;
57 }
58 } else {
59 throw new IllegalArgumentException("Unknown UpperCardinality: " + updateValue);
60 }
61 return oldResult;
62 }
63
64 @Override
65 public UpperCardinality getAggregate(Accumulator result) {
66 return result.countUnbounded > 0 ? UpperCardinalities.UNBOUNDED :
67 UpperCardinalities.valueOf(result.sumFiniteUpperBounds);
68 }
69
70 @Override
71 public UpperCardinality aggregateStream(Stream<UpperCardinality> stream) {
72 var result = stream.collect(this::createNeutral, (accumulator, value) -> update(accumulator, value, true),
73 (left, right) -> new Accumulator(left.sumFiniteUpperBounds + right.sumFiniteUpperBounds,
74 left.countUnbounded + right.countUnbounded));
75 return getAggregate(result);
76 }
77
78 @Override
79 public Accumulator clone(Accumulator original) {
80 return new Accumulator(original.sumFiniteUpperBounds, original.countUnbounded);
81 }
82
83 public static class Accumulator {
84 private int sumFiniteUpperBounds;
85
86 private int countUnbounded;
87
88 private Accumulator(int sumFiniteUpperBounds, int countUnbounded) {
89 this.sumFiniteUpperBounds = sumFiniteUpperBounds;
90 this.countUnbounded = countUnbounded;
91 }
92
93 private Accumulator() {
94 this(0, 0);
95 }
96 }
97}
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java
index a7e09023..d18187e5 100644
--- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryTest.java
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java
@@ -1,4 +1,4 @@
1package tools.refinery.store.query.viatra.tests; 1package tools.refinery.store.query.viatra;
2 2
3import org.junit.jupiter.api.Test; 3import org.junit.jupiter.api.Test;
4import tools.refinery.store.model.representation.Relation; 4import tools.refinery.store.model.representation.Relation;
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryTransactionTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java
index 117edd3e..e8fa6ed1 100644
--- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryTransactionTest.java
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java
@@ -1,4 +1,4 @@
1package tools.refinery.store.query.viatra.tests; 1package tools.refinery.store.query.viatra;
2 2
3import org.junit.jupiter.api.Test; 3import org.junit.jupiter.api.Test;
4import tools.refinery.store.model.representation.Relation; 4import tools.refinery.store.model.representation.Relation;
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorStreamTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorStreamTest.java
new file mode 100644
index 00000000..07869050
--- /dev/null
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorStreamTest.java
@@ -0,0 +1,50 @@
1package tools.refinery.store.query.viatra.internal.cardinality;
2
3import org.junit.jupiter.params.ParameterizedTest;
4import org.junit.jupiter.params.provider.Arguments;
5import org.junit.jupiter.params.provider.MethodSource;
6import tools.refinery.store.model.representation.cardinality.UpperCardinalities;
7import tools.refinery.store.model.representation.cardinality.UpperCardinality;
8
9import java.util.stream.Stream;
10
11import static org.hamcrest.MatcherAssert.assertThat;
12import static org.hamcrest.Matchers.equalTo;
13
14class UpperCardinalitySumAggregationOperatorStreamTest {
15 @ParameterizedTest
16 @MethodSource
17 void testStream(Stream<UpperCardinality> stream, UpperCardinality expected) {
18 var result = UpperCardinalitySumAggregationOperator.INSTANCE.aggregateStream(stream);
19 assertThat(result, equalTo(expected));
20 }
21
22 static Stream<Arguments> testStream() {
23 return Stream.of(
24 Arguments.of(Stream.of(), UpperCardinalities.ZERO),
25 Arguments.of(Stream.of(UpperCardinality.of(3)), UpperCardinality.of(3)),
26 Arguments.of(
27 Stream.of(
28 UpperCardinality.of(2),
29 UpperCardinality.of(3)
30 ),
31 UpperCardinality.of(5)
32 ),
33 Arguments.of(Stream.of(UpperCardinalities.UNBOUNDED), UpperCardinalities.UNBOUNDED),
34 Arguments.of(
35 Stream.of(
36 UpperCardinalities.UNBOUNDED,
37 UpperCardinalities.UNBOUNDED
38 ),
39 UpperCardinalities.UNBOUNDED
40 ),
41 Arguments.of(
42 Stream.of(
43 UpperCardinalities.UNBOUNDED,
44 UpperCardinality.of(3)
45 ),
46 UpperCardinalities.UNBOUNDED
47 )
48 );
49 }
50}
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorTest.java
new file mode 100644
index 00000000..afc4a2f3
--- /dev/null
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorTest.java
@@ -0,0 +1,87 @@
1package tools.refinery.store.query.viatra.internal.cardinality;
2
3import org.junit.jupiter.api.BeforeEach;
4import org.junit.jupiter.api.Test;
5import tools.refinery.store.model.representation.cardinality.UpperCardinalities;
6import tools.refinery.store.model.representation.cardinality.UpperCardinality;
7
8import static org.hamcrest.MatcherAssert.assertThat;
9import static org.hamcrest.Matchers.equalTo;
10
11class UpperCardinalitySumAggregationOperatorTest {
12 private UpperCardinalitySumAggregationOperator.Accumulator accumulator;
13
14 @BeforeEach
15 void beforeEach() {
16 accumulator = UpperCardinalitySumAggregationOperator.INSTANCE.createNeutral();
17 }
18
19 @Test
20 void emptyAggregationTest() {
21 assertResult(UpperCardinality.of(0));
22 }
23
24 @Test
25 void singleBoundedTest() {
26 insert(UpperCardinality.of(3));
27 assertResult(UpperCardinality.of(3));
28 }
29
30 @Test
31 void multipleBoundedTest() {
32 insert(UpperCardinality.of(2));
33 insert(UpperCardinality.of(3));
34 assertResult(UpperCardinality.of(5));
35 }
36
37 @Test
38 void singleUnboundedTest() {
39 insert(UpperCardinalities.UNBOUNDED);
40 assertResult(UpperCardinalities.UNBOUNDED);
41 }
42
43 @Test
44 void multipleUnboundedTest() {
45 insert(UpperCardinalities.UNBOUNDED);
46 insert(UpperCardinalities.UNBOUNDED);
47 assertResult(UpperCardinalities.UNBOUNDED);
48 }
49
50 @Test
51 void removeBoundedTest() {
52 insert(UpperCardinality.of(2));
53 insert(UpperCardinality.of(3));
54 remove(UpperCardinality.of(2));
55 assertResult(UpperCardinality.of(3));
56 }
57
58 @Test
59 void removeAllUnboundedTest() {
60 insert(UpperCardinalities.UNBOUNDED);
61 insert(UpperCardinality.of(3));
62 remove(UpperCardinalities.UNBOUNDED);
63 assertResult(UpperCardinality.of(3));
64 }
65
66 @Test
67 void removeSomeUnboundedTest() {
68 insert(UpperCardinalities.UNBOUNDED);
69 insert(UpperCardinalities.UNBOUNDED);
70 insert(UpperCardinality.of(3));
71 remove(UpperCardinalities.UNBOUNDED);
72 assertResult(UpperCardinalities.UNBOUNDED);
73 }
74
75 private void insert(UpperCardinality value) {
76 accumulator = UpperCardinalitySumAggregationOperator.INSTANCE.update(accumulator, value, true);
77 }
78
79 private void remove(UpperCardinality value) {
80 accumulator = UpperCardinalitySumAggregationOperator.INSTANCE.update(accumulator, value, false);
81 }
82
83 private void assertResult(UpperCardinality expected) {
84 var result = UpperCardinalitySumAggregationOperator.INSTANCE.getAggregate(accumulator);
85 assertThat(result, equalTo(expected));
86 }
87}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/CardinalityInterval.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/CardinalityInterval.java
new file mode 100644
index 00000000..1f252b4c
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/CardinalityInterval.java
@@ -0,0 +1,21 @@
1package tools.refinery.store.model.representation.cardinality;
2
3public sealed interface CardinalityInterval permits NonEmptyCardinalityInterval, EmptyCardinalityInterval {
4 int lowerBound();
5
6 UpperCardinality upperBound();
7
8 boolean isEmpty();
9
10 CardinalityInterval min(CardinalityInterval other);
11
12 CardinalityInterval max(CardinalityInterval other);
13
14 CardinalityInterval add(CardinalityInterval other);
15
16 CardinalityInterval multiply(CardinalityInterval other);
17
18 CardinalityInterval meet(CardinalityInterval other);
19
20 CardinalityInterval join(CardinalityInterval other);
21}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervals.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervals.java
new file mode 100644
index 00000000..b7c52bd1
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervals.java
@@ -0,0 +1,46 @@
1package tools.refinery.store.model.representation.cardinality;
2
3public final class CardinalityIntervals {
4 public static final CardinalityInterval NONE = exactly(0);
5
6 public static final CardinalityInterval ONE = exactly(1);
7
8 public static final CardinalityInterval LONE = atMost(1);
9
10 public static final CardinalityInterval SET = atLeast(0);
11
12 public static final CardinalityInterval SOME = atLeast(1);
13
14 public static final CardinalityInterval ERROR = EmptyCardinalityInterval.INSTANCE;
15
16 private CardinalityIntervals() {
17 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
18 }
19
20 public static CardinalityInterval between(int lowerBound, UpperCardinality upperBound) {
21 if (upperBound.compareToInt(lowerBound) < 0) {
22 return ERROR;
23 }
24 return new NonEmptyCardinalityInterval(lowerBound, upperBound);
25 }
26
27 public static CardinalityInterval between(int lowerBound, int upperBound) {
28 return between(lowerBound, UpperCardinalities.valueOf(upperBound));
29 }
30
31 public static CardinalityInterval atMost(UpperCardinality upperBound) {
32 return new NonEmptyCardinalityInterval(0, upperBound);
33 }
34
35 public static CardinalityInterval atMost(int upperBound) {
36 return atMost(UpperCardinalities.valueOf(upperBound));
37 }
38
39 public static CardinalityInterval atLeast(int lowerBound) {
40 return new NonEmptyCardinalityInterval(lowerBound, UpperCardinalities.UNBOUNDED);
41 }
42
43 public static CardinalityInterval exactly(int lowerBound) {
44 return new NonEmptyCardinalityInterval(lowerBound, UpperCardinalities.valueOf(lowerBound));
45 }
46}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/EmptyCardinalityInterval.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/EmptyCardinalityInterval.java
new file mode 100644
index 00000000..127651df
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/EmptyCardinalityInterval.java
@@ -0,0 +1,59 @@
1package tools.refinery.store.model.representation.cardinality;
2
3public final class EmptyCardinalityInterval implements CardinalityInterval {
4 static final EmptyCardinalityInterval INSTANCE = new EmptyCardinalityInterval();
5
6 private EmptyCardinalityInterval() {
7 // Singleton constructor.
8 }
9
10 @Override
11 public int lowerBound() {
12 return 1;
13 }
14
15 @Override
16 public boolean isEmpty() {
17 return true;
18 }
19
20 @Override
21 public UpperCardinality upperBound() {
22 return UpperCardinalities.ZERO;
23 }
24
25 @Override
26 public CardinalityInterval min(CardinalityInterval other) {
27 return this;
28 }
29
30 @Override
31 public CardinalityInterval max(CardinalityInterval other) {
32 return this;
33 }
34
35 @Override
36 public CardinalityInterval add(CardinalityInterval other) {
37 return this;
38 }
39
40 @Override
41 public CardinalityInterval multiply(CardinalityInterval other) {
42 return this;
43 }
44
45 @Override
46 public CardinalityInterval meet(CardinalityInterval other) {
47 return this;
48 }
49
50 @Override
51 public CardinalityInterval join(CardinalityInterval other) {
52 return other;
53 }
54
55 @Override
56 public String toString() {
57 return "error";
58 }
59}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/FiniteUpperCardinality.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/FiniteUpperCardinality.java
new file mode 100644
index 00000000..ec643a97
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/FiniteUpperCardinality.java
@@ -0,0 +1,55 @@
1package tools.refinery.store.model.representation.cardinality;
2
3import org.jetbrains.annotations.NotNull;
4
5import java.util.function.IntBinaryOperator;
6
7public record FiniteUpperCardinality(int finiteUpperBound) implements UpperCardinality {
8 public FiniteUpperCardinality {
9 if (finiteUpperBound < 0) {
10 throw new IllegalArgumentException("finiteUpperBound must not be negative");
11 }
12 }
13
14 @Override
15 public UpperCardinality add(UpperCardinality other) {
16 return lift(other, Integer::sum);
17 }
18
19 @Override
20 public UpperCardinality multiply(UpperCardinality other) {
21 return lift(other, (a, b) -> a * b);
22 }
23
24 @Override
25 public int compareTo(@NotNull UpperCardinality upperCardinality) {
26 if (upperCardinality instanceof FiniteUpperCardinality finiteUpperCardinality) {
27 return compareToInt(finiteUpperCardinality.finiteUpperBound);
28 }
29 if (upperCardinality instanceof UnboundedUpperCardinality) {
30 return -1;
31 }
32 throw new IllegalArgumentException("Unknown UpperCardinality: " + upperCardinality);
33 }
34
35 @Override
36 public int compareToInt(int value) {
37 return Integer.compare(finiteUpperBound, value);
38 }
39
40 @Override
41 public String toString() {
42 return Integer.toString(finiteUpperBound);
43 }
44
45 private UpperCardinality lift(@NotNull UpperCardinality other, IntBinaryOperator operator) {
46 if (other instanceof FiniteUpperCardinality finiteUpperCardinality) {
47 return UpperCardinalities.valueOf(operator.applyAsInt(finiteUpperBound,
48 finiteUpperCardinality.finiteUpperBound));
49 }
50 if (other instanceof UnboundedUpperCardinality) {
51 return UpperCardinalities.UNBOUNDED;
52 }
53 throw new IllegalArgumentException("Unknown UpperCardinality: " + other);
54 }
55}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/NonEmptyCardinalityInterval.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/NonEmptyCardinalityInterval.java
new file mode 100644
index 00000000..b534f2e3
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/NonEmptyCardinalityInterval.java
@@ -0,0 +1,74 @@
1package tools.refinery.store.model.representation.cardinality;
2
3import java.util.function.BinaryOperator;
4import java.util.function.IntBinaryOperator;
5
6public record NonEmptyCardinalityInterval(int lowerBound, UpperCardinality upperBound) implements CardinalityInterval {
7 public NonEmptyCardinalityInterval {
8 if (lowerBound < 0) {
9 throw new IllegalArgumentException("lowerBound must not be negative");
10 }
11 if (upperBound.compareToInt(lowerBound) < 0) {
12 throw new IllegalArgumentException("lowerBound must not be larger than upperBound");
13 }
14 }
15
16 @Override
17 public boolean isEmpty() {
18 return false;
19 }
20
21 @Override
22 public CardinalityInterval min(CardinalityInterval other) {
23 return lift(other, Math::min, UpperCardinality::min);
24 }
25
26 @Override
27 public CardinalityInterval max(CardinalityInterval other) {
28 return lift(other, Math::max, UpperCardinality::max);
29 }
30
31 @Override
32 public CardinalityInterval add(CardinalityInterval other) {
33 return lift(other, Integer::sum, UpperCardinality::add);
34 }
35
36 @Override
37 public CardinalityInterval multiply(CardinalityInterval other) {
38 return lift(other, (a, b) -> a * b, UpperCardinality::multiply);
39 }
40
41 @Override
42 public CardinalityInterval meet(CardinalityInterval other) {
43 return lift(other, Math::max, UpperCardinality::min);
44 }
45
46 @Override
47 public CardinalityInterval join(CardinalityInterval other) {
48 return lift(other, Math::min, UpperCardinality::max, this);
49 }
50
51 private CardinalityInterval lift(CardinalityInterval other, IntBinaryOperator lowerOperator,
52 BinaryOperator<UpperCardinality> upperOperator,
53 CardinalityInterval whenEmpty) {
54 if (other instanceof NonEmptyCardinalityInterval nonEmptyOther) {
55 return CardinalityIntervals.between(lowerOperator.applyAsInt(lowerBound, nonEmptyOther.lowerBound),
56 upperOperator.apply(upperBound, nonEmptyOther.upperBound));
57 }
58 if (other instanceof EmptyCardinalityInterval) {
59 return whenEmpty;
60 }
61 throw new IllegalArgumentException("Unknown CardinalityInterval: " + other);
62 }
63
64 private CardinalityInterval lift(CardinalityInterval other, IntBinaryOperator lowerOperator,
65 BinaryOperator<UpperCardinality> upperOperator) {
66 return lift(other, lowerOperator, upperOperator, CardinalityIntervals.ERROR);
67 }
68
69 @Override
70 public String toString() {
71 var closeBracket = upperBound instanceof UnboundedUpperCardinality ? ")" : "]";
72 return "[%d..%s%s".formatted(lowerBound, upperBound, closeBracket);
73 }
74}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UnboundedUpperCardinality.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UnboundedUpperCardinality.java
new file mode 100644
index 00000000..4199d44f
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UnboundedUpperCardinality.java
@@ -0,0 +1,42 @@
1package tools.refinery.store.model.representation.cardinality;
2
3import org.jetbrains.annotations.NotNull;
4
5public final class UnboundedUpperCardinality implements UpperCardinality {
6 static final UnboundedUpperCardinality INSTANCE = new UnboundedUpperCardinality();
7
8 private UnboundedUpperCardinality() {
9 // Singleton constructor.
10 }
11
12 @Override
13 public UpperCardinality add(UpperCardinality other) {
14 return this;
15 }
16
17 @Override
18 public UpperCardinality multiply(UpperCardinality other) {
19 return this;
20 }
21
22 @Override
23 public int compareTo(@NotNull UpperCardinality upperCardinality) {
24 if (upperCardinality instanceof FiniteUpperCardinality) {
25 return 1;
26 }
27 if (upperCardinality instanceof UnboundedUpperCardinality) {
28 return 0;
29 }
30 throw new IllegalArgumentException("Unknown UpperCardinality: " + upperCardinality);
31 }
32
33 @Override
34 public int compareToInt(int value) {
35 return 1;
36 }
37
38 @Override
39 public String toString() {
40 return "*";
41 }
42}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UpperCardinalities.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UpperCardinalities.java
new file mode 100644
index 00000000..b433cda2
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UpperCardinalities.java
@@ -0,0 +1,33 @@
1package tools.refinery.store.model.representation.cardinality;
2
3public final class UpperCardinalities {
4 public static final UpperCardinality UNBOUNDED = UnboundedUpperCardinality.INSTANCE;
5
6 public static final UpperCardinality ZERO;
7
8 public static final UpperCardinality ONE;
9
10 private static final FiniteUpperCardinality[] cache = new FiniteUpperCardinality[256];
11
12 static {
13 for (int i = 0; i < cache.length; i++) {
14 cache[i] = new FiniteUpperCardinality(i);
15 }
16 ZERO = cache[0];
17 ONE = cache[1];
18 }
19
20 private UpperCardinalities() {
21 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
22 }
23
24 public static UpperCardinality valueOf(int upperBound) {
25 if (upperBound < 0) {
26 return UNBOUNDED;
27 }
28 if (upperBound < cache.length) {
29 return cache[upperBound];
30 }
31 return new FiniteUpperCardinality(upperBound);
32 }
33}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UpperCardinality.java b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UpperCardinality.java
new file mode 100644
index 00000000..91dd40b0
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/representation/cardinality/UpperCardinality.java
@@ -0,0 +1,22 @@
1package tools.refinery.store.model.representation.cardinality;
2
3public sealed interface UpperCardinality extends Comparable<UpperCardinality> permits FiniteUpperCardinality,
4 UnboundedUpperCardinality {
5 default UpperCardinality min(UpperCardinality other) {
6 return this.compareTo(other) <= 0 ? this : other;
7 }
8
9 default UpperCardinality max(UpperCardinality other) {
10 return this.compareTo(other) >= 0 ? this : other;
11 }
12
13 UpperCardinality add(UpperCardinality other);
14
15 UpperCardinality multiply(UpperCardinality other);
16
17 int compareToInt(int value);
18
19 static UpperCardinality of(int upperBound) {
20 return UpperCardinalities.valueOf(upperBound);
21 }
22}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervalTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervalTest.java
new file mode 100644
index 00000000..8a39b9b5
--- /dev/null
+++ b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervalTest.java
@@ -0,0 +1,122 @@
1package tools.refinery.store.model.representation.cardinality;
2
3import org.junit.jupiter.params.ParameterizedTest;
4import org.junit.jupiter.params.provider.Arguments;
5import org.junit.jupiter.params.provider.MethodSource;
6
7import java.util.stream.Stream;
8
9import static org.hamcrest.MatcherAssert.assertThat;
10import static org.hamcrest.Matchers.equalTo;
11import static tools.refinery.store.model.representation.cardinality.CardinalityIntervals.*;
12
13class CardinalityIntervalTest {
14 @ParameterizedTest(name = "min({0}, {1}) == {2}")
15 @MethodSource
16 void minTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) {
17 assertThat(a.min(b), equalTo(expected));
18 }
19
20 static Stream<Arguments> minTest() {
21 return Stream.of(
22 Arguments.of(atMost(1), atMost(1), atMost(1)),
23 Arguments.of(atMost(1), between(2, 3), atMost(1)),
24 Arguments.of(atMost(1), atLeast(2), atMost(1)),
25 Arguments.of(atMost(1), ERROR, ERROR),
26 Arguments.of(atLeast(1), atLeast(2), atLeast(1)),
27 Arguments.of(atLeast(1), ERROR, ERROR),
28 Arguments.of(ERROR, atLeast(2), ERROR),
29 Arguments.of(ERROR, ERROR, ERROR)
30 );
31 }
32
33 @ParameterizedTest(name = "max({0}, {1}) == {2}")
34 @MethodSource
35 void maxTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) {
36 assertThat(a.max(b), equalTo(expected));
37 }
38
39 static Stream<Arguments> maxTest() {
40 return Stream.of(
41 Arguments.of(atMost(1), atMost(1), atMost(1)),
42 Arguments.of(atMost(1), between(2, 3), between(2, 3)),
43 Arguments.of(atMost(1), atLeast(2), atLeast(2)),
44 Arguments.of(atMost(1), ERROR, ERROR),
45 Arguments.of(atLeast(1), atLeast(2), atLeast(2)),
46 Arguments.of(atLeast(1), ERROR, ERROR),
47 Arguments.of(ERROR, atLeast(2), ERROR),
48 Arguments.of(ERROR, ERROR, ERROR)
49 );
50 }
51
52 @ParameterizedTest(name = "{0} + {1} == {2}")
53 @MethodSource
54 void addTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) {
55 assertThat(a.add(b), equalTo(expected));
56 }
57
58 static Stream<Arguments> addTest() {
59 return Stream.of(
60 Arguments.of(atMost(1), atMost(1), atMost(2)),
61 Arguments.of(atMost(1), between(2, 3), between(2, 4)),
62 Arguments.of(atMost(1), atLeast(2), atLeast(2)),
63 Arguments.of(atMost(1), ERROR, ERROR),
64 Arguments.of(atLeast(1), atLeast(2), atLeast(3)),
65 Arguments.of(atLeast(1), ERROR, ERROR),
66 Arguments.of(ERROR, atLeast(2), ERROR),
67 Arguments.of(ERROR, ERROR, ERROR)
68 );
69 }
70
71 @ParameterizedTest(name = "{0} * {1} == {2}")
72 @MethodSource
73 void multiplyTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) {
74 assertThat(a.multiply(b), equalTo(expected));
75 }
76
77 static Stream<Arguments> multiplyTest() {
78 return Stream.of(
79 Arguments.of(between(2, 3), between(4, 5), between(8, 15)),
80 Arguments.of(atLeast(2), between(4, 5), atLeast(8)),
81 Arguments.of(between(2, 3), atLeast(4), atLeast(8)),
82 Arguments.of(between(2, 3), ERROR, ERROR),
83 Arguments.of(ERROR, between(4, 5), ERROR),
84 Arguments.of(ERROR, ERROR, ERROR)
85 );
86 }
87
88 @ParameterizedTest(name = "{0} /\\ {1} == {2}")
89 @MethodSource
90 void meetTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) {
91 assertThat(a.meet(b), equalTo(expected));
92 }
93
94 static Stream<Arguments> meetTest() {
95 return Stream.of(
96 Arguments.of(atMost(1), atMost(2), atMost(1)),
97 Arguments.of(atMost(2), between(1, 3), between(1, 2)),
98 Arguments.of(atMost(1), between(1, 3), exactly(1)),
99 Arguments.of(atMost(1), between(2, 3), ERROR),
100 Arguments.of(atMost(1), ERROR, ERROR),
101 Arguments.of(ERROR, atMost(1), ERROR),
102 Arguments.of(ERROR, ERROR, ERROR)
103 );
104 }
105
106 @ParameterizedTest(name = "{0} \\/ {1} == {2}")
107 @MethodSource
108 void joinTest(CardinalityInterval a, CardinalityInterval b, CardinalityInterval expected) {
109 assertThat(a.join(b), equalTo(expected));
110 }
111
112 static Stream<Arguments> joinTest() {
113 return Stream.of(
114 Arguments.of(atMost(1), atMost(2), atMost(2)),
115 Arguments.of(atMost(2), between(1, 3), atMost(3)),
116 Arguments.of(atMost(1), between(2, 3), atMost(3)),
117 Arguments.of(atMost(1), ERROR, atMost(1)),
118 Arguments.of(ERROR, atMost(1), atMost(1)),
119 Arguments.of(ERROR, ERROR, ERROR)
120 );
121 }
122}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervalsTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervalsTest.java
new file mode 100644
index 00000000..ef68a607
--- /dev/null
+++ b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/CardinalityIntervalsTest.java
@@ -0,0 +1,21 @@
1package tools.refinery.store.model.representation.cardinality;
2
3import org.junit.jupiter.api.Test;
4
5import static org.hamcrest.MatcherAssert.assertThat;
6import static org.hamcrest.Matchers.*;
7
8class CardinalityIntervalsTest {
9 @Test
10 void betweenEmptyTest() {
11 var interval = CardinalityIntervals.between(2, 1);
12 assertThat(interval.isEmpty(), equalTo(true));
13 }
14
15 @Test
16 void betweenNegativeUpperBoundTest() {
17 var interval = CardinalityIntervals.between(0, -1);
18 assertThat(interval.upperBound(), equalTo(UpperCardinalities.UNBOUNDED));
19 assertThat(interval.isEmpty(), equalTo(false));
20 }
21}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/EmptyCardinalityIntervalTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/EmptyCardinalityIntervalTest.java
new file mode 100644
index 00000000..41c884ec
--- /dev/null
+++ b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/EmptyCardinalityIntervalTest.java
@@ -0,0 +1,14 @@
1package tools.refinery.store.model.representation.cardinality;
2
3import org.junit.jupiter.api.Test;
4
5import static org.hamcrest.MatcherAssert.assertThat;
6import static org.hamcrest.Matchers.lessThan;
7
8class EmptyCardinalityIntervalTest {
9 @Test
10 void inconsistentBoundsTest() {
11 assertThat(CardinalityIntervals.ERROR.upperBound().compareToInt(CardinalityIntervals.ERROR.lowerBound()),
12 lessThan(0));
13 }
14}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/FiniteCardinalityIntervalTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/FiniteCardinalityIntervalTest.java
new file mode 100644
index 00000000..dfb37786
--- /dev/null
+++ b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/FiniteCardinalityIntervalTest.java
@@ -0,0 +1,20 @@
1package tools.refinery.store.model.representation.cardinality;
2
3import org.junit.jupiter.api.Test;
4
5import static org.junit.jupiter.api.Assertions.assertThrows;
6
7class FiniteCardinalityIntervalTest {
8 @Test
9 void invalidLowerBoundConstructorTest() {
10 assertThrows(IllegalArgumentException.class, () -> new NonEmptyCardinalityInterval(-1,
11 UpperCardinalities.UNBOUNDED));
12 }
13
14 @Test
15 void invalidUpperBoundConstructorTest() {
16 var upperCardinality = UpperCardinality.of(1);
17 assertThrows(IllegalArgumentException.class, () -> new NonEmptyCardinalityInterval(2,
18 upperCardinality));
19 }
20}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/FiniteUpperCardinalityTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/FiniteUpperCardinalityTest.java
new file mode 100644
index 00000000..3f0f7a4a
--- /dev/null
+++ b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/FiniteUpperCardinalityTest.java
@@ -0,0 +1,12 @@
1package tools.refinery.store.model.representation.cardinality;
2
3import org.junit.jupiter.api.Test;
4
5import static org.junit.jupiter.api.Assertions.assertThrows;
6
7class FiniteUpperCardinalityTest {
8 @Test
9 void invalidConstructorTest() {
10 assertThrows(IllegalArgumentException.class, () -> new FiniteUpperCardinality(-1));
11 }
12}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/UpperCardinalitiesTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/UpperCardinalitiesTest.java
new file mode 100644
index 00000000..13171ae5
--- /dev/null
+++ b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/UpperCardinalitiesTest.java
@@ -0,0 +1,25 @@
1package tools.refinery.store.model.representation.cardinality;
2
3import org.junit.jupiter.api.Test;
4import org.junit.jupiter.params.ParameterizedTest;
5import org.junit.jupiter.params.provider.ValueSource;
6
7import static org.hamcrest.MatcherAssert.assertThat;
8import static org.hamcrest.Matchers.equalTo;
9import static org.hamcrest.Matchers.instanceOf;
10
11class UpperCardinalitiesTest {
12 @ParameterizedTest
13 @ValueSource(ints = {0, 1, 255, 256, 1000, Integer.MAX_VALUE})
14 void valueOfBoundedTest(int value) {
15 var upperCardinality = UpperCardinalities.valueOf(value);
16 assertThat(upperCardinality, instanceOf(FiniteUpperCardinality.class));
17 assertThat(((FiniteUpperCardinality) upperCardinality).finiteUpperBound(), equalTo(value));
18 }
19
20 @Test
21 void valueOfUnboundedTest() {
22 var upperCardinality = UpperCardinalities.valueOf(-1);
23 assertThat(upperCardinality, instanceOf(UnboundedUpperCardinality.class));
24 }
25}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/UpperCardinalityTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/UpperCardinalityTest.java
new file mode 100644
index 00000000..f5763c2d
--- /dev/null
+++ b/subprojects/store/src/test/java/tools/refinery/store/model/representation/cardinality/UpperCardinalityTest.java
@@ -0,0 +1,110 @@
1package tools.refinery.store.model.representation.cardinality;
2
3import org.junit.jupiter.params.ParameterizedTest;
4import org.junit.jupiter.params.provider.Arguments;
5import org.junit.jupiter.params.provider.MethodSource;
6
7import java.util.stream.Stream;
8
9import static org.hamcrest.MatcherAssert.assertThat;
10import static org.hamcrest.Matchers.equalTo;
11
12class UpperCardinalityTest {
13 @ParameterizedTest(name = "min({0}, {1}) == {2}")
14 @MethodSource
15 void minTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) {
16 assertThat(a.min(b), equalTo(expected));
17 }
18
19 static Stream<Arguments> minTest() {
20 return Stream.of(
21 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)),
22 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(0)),
23 Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(0)),
24 Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinality.of(0)),
25 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinality.of(0)),
26 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED)
27 );
28 }
29
30 @ParameterizedTest(name = "max({0}, {1}) == {2}")
31 @MethodSource
32 void maxTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) {
33 assertThat(a.max(b), equalTo(expected));
34 }
35
36 static Stream<Arguments> maxTest() {
37 return Stream.of(
38 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)),
39 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(1)),
40 Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(1)),
41 Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
42 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinalities.UNBOUNDED),
43 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED)
44 );
45 }
46
47 @ParameterizedTest(name = "{0} + {1} == {2}")
48 @MethodSource
49 void addTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) {
50 assertThat(a.add(b), equalTo(expected));
51 }
52
53 static Stream<Arguments> addTest() {
54 return Stream.of(
55 Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(5)),
56 Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
57 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED),
58 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED)
59 );
60 }
61
62 @ParameterizedTest(name = "{0} * {1} == {2}")
63 @MethodSource
64 void multiplyTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) {
65 assertThat(a.multiply(b), equalTo(expected));
66 }
67
68 static Stream<Arguments> multiplyTest() {
69 return Stream.of(
70 Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(6)),
71 Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED),
72 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED),
73 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED)
74 );
75 }
76
77 @ParameterizedTest(name = "{0}.compareTo({1}) == {2}")
78 @MethodSource
79 void compareToTest(UpperCardinality a, UpperCardinality b, int expected) {
80 assertThat(a.compareTo(b), equalTo(expected));
81 }
82
83 static Stream<Arguments> compareToTest() {
84 return Stream.of(
85 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), 0),
86 Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), -1),
87 Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), 1),
88 Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, -1),
89 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), 1),
90 Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, 0)
91 );
92 }
93
94 @ParameterizedTest(name = "{0}.compareToInt({1}) == {2}")
95 @MethodSource
96 void compareToIntTest(UpperCardinality a, int b, int expected) {
97 assertThat(a.compareToInt(b), equalTo(expected));
98 }
99
100 static Stream<Arguments> compareToIntTest() {
101 return Stream.of(
102 Arguments.of(UpperCardinality.of(3), -1, 1),
103 Arguments.of(UpperCardinality.of(3), 2, 1),
104 Arguments.of(UpperCardinality.of(3), 3, 0),
105 Arguments.of(UpperCardinality.of(3), 4, -1),
106 Arguments.of(UpperCardinalities.UNBOUNDED, -1, 1),
107 Arguments.of(UpperCardinalities.UNBOUNDED, 3, 1)
108 );
109 }
110}