From 160407b7e906d798e4001d8d1d8caedf1311cd9a Mon Sep 17 00:00:00 2001 From: Edgar Asatryan Date: Sun, 2 Oct 2022 17:31:32 +0400 Subject: [PATCH] Add support for the PostgreSQL 'empty' range that's distinct from the (,) infinite range #492 --- .../hibernate/type/range/Range.java | 64 +++++++++++++++---- .../hibernate/type/range/RangeTest.java | 52 ++++++++++++++- .../hibernate/type/range/Range.java | 64 +++++++++++++++---- .../hibernate/type/range/RangeTest.java | 52 ++++++++++++++- .../hibernate/type/range/Range.java | 64 +++++++++++++++---- .../hibernate/type/range/RangeTest.java | 52 ++++++++++++++- .../hibernate/type/range/Range.java | 64 +++++++++++++++---- .../hibernate/type/range/RangeTest.java | 58 +++++++++++++++++ .../hibernate/type/range/Range.java | 64 +++++++++++++++---- .../hibernate/type/range/RangeTest.java | 58 +++++++++++++++++ .../hibernate/type/range/Range.java | 64 +++++++++++++++---- .../hibernate/type/range/RangeTest.java | 58 +++++++++++++++++ 12 files changed, 639 insertions(+), 75 deletions(-) diff --git a/hibernate-types-4/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java b/hibernate-types-4/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java index 17f27a723..d685c8f9a 100644 --- a/hibernate-types-4/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java +++ b/hibernate-types-4/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java @@ -8,7 +8,7 @@ /** * Represents the range/interval with two bounds. Abstraction follows the semantics of the mathematical interval. The - * range can be unbounded or open from the left or/and unbounded from the right. The range supports half-open or closed + * range can be unbounded, empty or open from the left or/and unbounded from the right. The range supports half-open or closed * bounds on both sides. * *

@@ -47,7 +47,7 @@ private Range(T lower, T upper, int mask, Class clazz) { this.mask = mask; this.clazz = clazz; - if (isBounded() && lower.compareTo(upper) > 0) { + if (isBounded() && lower != null && upper != null && lower.compareTo(upper) > 0) { throw new IllegalArgumentException("The lower bound is greater then upper!"); } } @@ -230,6 +230,28 @@ public static > Range infinite(Class cls) return new Range(null, null, LOWER_INFINITE | UPPER_INFINITE, cls); } + /** + * Creates the empty range. In other words the range that contains no points. + *

+ * The mathematical equivalent will be: + *

{@code
+     *     (a, a) = ∅
+     * }
+ * + * @param cls The range class, never null. + * @param The type of bounds. + * + * @return The empty range. + */ + public static > Range emptyRange(Class cls) { + return new Range( + null, + null, + LOWER_EXCLUSIVE | UPPER_EXCLUSIVE, + cls + ); + } + @SuppressWarnings("unchecked") public static > Range ofString(String str, Function converter, Class clazz) { if(str.equals(EMPTY)) { @@ -446,6 +468,10 @@ public T upper() { */ @SuppressWarnings("unchecked") public boolean contains(T point) { + if (isEmpty()) { + return false; + } + boolean l = hasLowerBound(); boolean u = hasUpperBound(); @@ -481,7 +507,30 @@ public boolean contains(T point) { * @return Whether {@code range} in this range or not. */ public boolean contains(Range range) { - return (!range.hasLowerBound() || contains(range.lower)) && (!range.hasUpperBound() || contains(range.upper)); + return !isEmpty() && (!range.hasLowerBound() || contains(range.lower)) && (!range.hasUpperBound() || contains(range.upper)); + } + + /** + * Determines whether this range is empty or not. + *

+ * For example: + *

{@code
+     *     assertFalse(integerRange("empty").contains(1))
+     * }
+ * + * @return Whether {@code range} in this range or not. + */ + public boolean isEmpty() { + boolean boundedExclusive = isBounded() + && hasMask(LOWER_EXCLUSIVE) + && hasMask(UPPER_EXCLUSIVE); + + return boundedExclusive && hasEqualBounds(); + } + + private boolean hasEqualBounds() { + return lower == null && upper == null + || lower != null && upper != null && lower.compareTo(upper) == 0; } public String asString() { @@ -513,13 +562,4 @@ public interface Function { R apply(T t); } - - public static > Range emptyRange(Class clazz) { - return new Range( - null, - null, - LOWER_INFINITE|UPPER_INFINITE, - clazz - ); - } } diff --git a/hibernate-types-4/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java b/hibernate-types-4/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java index 3e77781a5..95f6e51f1 100644 --- a/hibernate-types-4/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java +++ b/hibernate-types-4/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java @@ -5,7 +5,8 @@ import static com.vladmihalcea.hibernate.type.range.Range.integerRange; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; /** * @author Edgar Asatryan @@ -35,6 +36,9 @@ public void ofStringTest() { assertThat(integerRange("(-5,5]").isUpperBoundClosed(), is(true)); assertThat(integerRange("(-5,5]").isLowerBoundClosed(), is(false)); + assertThat(integerRange("(,)").contains(integerRange("empty")), is(true)); + + assertThat(integerRange("empty").contains(integerRange("(,)")), is(false)); } @Test @@ -50,4 +54,50 @@ public void containsRange() { assertThat(integerRange("(,)").contains(integerRange("(6,)")), is(true)); assertThat(integerRange("(,)").contains(integerRange("(,6)")), is(true)); } + + @Test + public void emptyInfinityEquality() { + assertEquals(integerRange("empty"), integerRange("empty")); + assertEquals(integerRange("(infinity,infinity)"), integerRange("(infinity,infinity)")); + assertEquals(integerRange("(,)"), integerRange("(infinity,infinity)")); + assertEquals(integerRange("(infinity,infinity)"), integerRange("(,)")); + + assertNotEquals(integerRange("empty"), integerRange("(infinity,infinity)")); + assertNotEquals(integerRange("empty"), integerRange("(,)")); + assertNotEquals(integerRange("empty"), integerRange("(5,5)")); + } + + @Test + public void emptyRangeWithEmptyKeyword() { + Range empty = Range.longRange("empty"); + + assertTrue(empty.isEmpty()); + + assertFalse(empty.contains(Long.MIN_VALUE)); + assertFalse(empty.contains(Long.valueOf(0))); + assertFalse(empty.contains(Long.MAX_VALUE)); + + assertNull(empty.upper()); + assertNull(empty.lower()); + } + + @Test + public void emptyRangeWithValues() { + Range empty = Range.longRange("(1,1)"); + + assertTrue(empty.isEmpty()); + assertFalse(empty.contains(Long.MIN_VALUE)); + assertFalse(empty.contains(Long.valueOf(0))); + assertFalse(empty.contains(Long.MAX_VALUE)); + + assertTrue(integerRange("(5,5)").isEmpty()); + } + + @Test + public void notEmptyWithValues() { + assertFalse(integerRange("(5,)").isEmpty()); + assertFalse(integerRange("(5,5]").isEmpty()); + assertFalse(integerRange("(,5)").isEmpty()); + assertFalse(integerRange("(,)").isEmpty()); + } } \ No newline at end of file diff --git a/hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java b/hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java index f196e3fbf..055f31b51 100644 --- a/hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java +++ b/hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java @@ -8,7 +8,7 @@ /** * Represents the range/interval with two bounds. Abstraction follows the semantics of the mathematical interval. The - * range can be unbounded or open from the left or/and unbounded from the right. The range supports half-open or closed + * range can be unbounded, empty or open from the left or/and unbounded from the right. The range supports half-open or closed * bounds on both sides. * *

@@ -47,7 +47,7 @@ private Range(T lower, T upper, int mask, Class clazz) { this.mask = mask; this.clazz = clazz; - if (isBounded() && lower.compareTo(upper) > 0) { + if (isBounded() && lower != null && upper != null && lower.compareTo(upper) > 0) { throw new IllegalArgumentException("The lower bound is greater then upper!"); } } @@ -230,6 +230,28 @@ public static > Range infinite(Class cls) return new Range(null, null, LOWER_INFINITE | UPPER_INFINITE, cls); } + /** + * Creates the empty range. In other words the range that contains no points. + *

+ * The mathematical equivalent will be: + *

{@code
+     *     (a, a) = ∅
+     * }
+ * + * @param cls The range class, never null. + * @param The type of bounds. + * + * @return The empty range. + */ + public static > Range emptyRange(Class cls) { + return new Range( + null, + null, + LOWER_EXCLUSIVE | UPPER_EXCLUSIVE, + cls + ); + } + @SuppressWarnings("unchecked") public static > Range ofString(String str, Function converter, Class clazz) { if(str.equals(EMPTY)) { @@ -446,6 +468,10 @@ public T upper() { */ @SuppressWarnings("unchecked") public boolean contains(T point) { + if (isEmpty()) { + return false; + } + boolean l = hasLowerBound(); boolean u = hasUpperBound(); @@ -481,7 +507,30 @@ public boolean contains(T point) { * @return Whether {@code range} in this range or not. */ public boolean contains(Range range) { - return (!range.hasLowerBound() || contains(range.lower)) && (!range.hasUpperBound() || contains(range.upper)); + return !isEmpty() && (!range.hasLowerBound() || contains(range.lower)) && (!range.hasUpperBound() || contains(range.upper)); + } + + /** + * Determines whether this range is empty or not. + *

+ * For example: + *

{@code
+     *     assertFalse(integerRange("empty").contains(1))
+     * }
+ * + * @return Whether {@code range} in this range or not. + */ + public boolean isEmpty() { + boolean boundedExclusive = isBounded() + && hasMask(LOWER_EXCLUSIVE) + && hasMask(UPPER_EXCLUSIVE); + + return boundedExclusive && hasEqualBounds(); + } + + private boolean hasEqualBounds() { + return lower == null && upper == null + || lower != null && upper != null && lower.compareTo(upper) == 0; } public String asString() { @@ -513,13 +562,4 @@ public interface Function { R apply(T t); } - - public static > Range emptyRange(Class clazz) { - return new Range( - null, - null, - LOWER_INFINITE|UPPER_INFINITE, - clazz - ); - } } diff --git a/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java b/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java index 3e77781a5..95f6e51f1 100644 --- a/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java +++ b/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java @@ -5,7 +5,8 @@ import static com.vladmihalcea.hibernate.type.range.Range.integerRange; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; /** * @author Edgar Asatryan @@ -35,6 +36,9 @@ public void ofStringTest() { assertThat(integerRange("(-5,5]").isUpperBoundClosed(), is(true)); assertThat(integerRange("(-5,5]").isLowerBoundClosed(), is(false)); + assertThat(integerRange("(,)").contains(integerRange("empty")), is(true)); + + assertThat(integerRange("empty").contains(integerRange("(,)")), is(false)); } @Test @@ -50,4 +54,50 @@ public void containsRange() { assertThat(integerRange("(,)").contains(integerRange("(6,)")), is(true)); assertThat(integerRange("(,)").contains(integerRange("(,6)")), is(true)); } + + @Test + public void emptyInfinityEquality() { + assertEquals(integerRange("empty"), integerRange("empty")); + assertEquals(integerRange("(infinity,infinity)"), integerRange("(infinity,infinity)")); + assertEquals(integerRange("(,)"), integerRange("(infinity,infinity)")); + assertEquals(integerRange("(infinity,infinity)"), integerRange("(,)")); + + assertNotEquals(integerRange("empty"), integerRange("(infinity,infinity)")); + assertNotEquals(integerRange("empty"), integerRange("(,)")); + assertNotEquals(integerRange("empty"), integerRange("(5,5)")); + } + + @Test + public void emptyRangeWithEmptyKeyword() { + Range empty = Range.longRange("empty"); + + assertTrue(empty.isEmpty()); + + assertFalse(empty.contains(Long.MIN_VALUE)); + assertFalse(empty.contains(Long.valueOf(0))); + assertFalse(empty.contains(Long.MAX_VALUE)); + + assertNull(empty.upper()); + assertNull(empty.lower()); + } + + @Test + public void emptyRangeWithValues() { + Range empty = Range.longRange("(1,1)"); + + assertTrue(empty.isEmpty()); + assertFalse(empty.contains(Long.MIN_VALUE)); + assertFalse(empty.contains(Long.valueOf(0))); + assertFalse(empty.contains(Long.MAX_VALUE)); + + assertTrue(integerRange("(5,5)").isEmpty()); + } + + @Test + public void notEmptyWithValues() { + assertFalse(integerRange("(5,)").isEmpty()); + assertFalse(integerRange("(5,5]").isEmpty()); + assertFalse(integerRange("(,5)").isEmpty()); + assertFalse(integerRange("(,)").isEmpty()); + } } \ No newline at end of file diff --git a/hibernate-types-5/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java b/hibernate-types-5/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java index f196e3fbf..055f31b51 100644 --- a/hibernate-types-5/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java +++ b/hibernate-types-5/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java @@ -8,7 +8,7 @@ /** * Represents the range/interval with two bounds. Abstraction follows the semantics of the mathematical interval. The - * range can be unbounded or open from the left or/and unbounded from the right. The range supports half-open or closed + * range can be unbounded, empty or open from the left or/and unbounded from the right. The range supports half-open or closed * bounds on both sides. * *

@@ -47,7 +47,7 @@ private Range(T lower, T upper, int mask, Class clazz) { this.mask = mask; this.clazz = clazz; - if (isBounded() && lower.compareTo(upper) > 0) { + if (isBounded() && lower != null && upper != null && lower.compareTo(upper) > 0) { throw new IllegalArgumentException("The lower bound is greater then upper!"); } } @@ -230,6 +230,28 @@ public static > Range infinite(Class cls) return new Range(null, null, LOWER_INFINITE | UPPER_INFINITE, cls); } + /** + * Creates the empty range. In other words the range that contains no points. + *

+ * The mathematical equivalent will be: + *

{@code
+     *     (a, a) = ∅
+     * }
+ * + * @param cls The range class, never null. + * @param The type of bounds. + * + * @return The empty range. + */ + public static > Range emptyRange(Class cls) { + return new Range( + null, + null, + LOWER_EXCLUSIVE | UPPER_EXCLUSIVE, + cls + ); + } + @SuppressWarnings("unchecked") public static > Range ofString(String str, Function converter, Class clazz) { if(str.equals(EMPTY)) { @@ -446,6 +468,10 @@ public T upper() { */ @SuppressWarnings("unchecked") public boolean contains(T point) { + if (isEmpty()) { + return false; + } + boolean l = hasLowerBound(); boolean u = hasUpperBound(); @@ -481,7 +507,30 @@ public boolean contains(T point) { * @return Whether {@code range} in this range or not. */ public boolean contains(Range range) { - return (!range.hasLowerBound() || contains(range.lower)) && (!range.hasUpperBound() || contains(range.upper)); + return !isEmpty() && (!range.hasLowerBound() || contains(range.lower)) && (!range.hasUpperBound() || contains(range.upper)); + } + + /** + * Determines whether this range is empty or not. + *

+ * For example: + *

{@code
+     *     assertFalse(integerRange("empty").contains(1))
+     * }
+ * + * @return Whether {@code range} in this range or not. + */ + public boolean isEmpty() { + boolean boundedExclusive = isBounded() + && hasMask(LOWER_EXCLUSIVE) + && hasMask(UPPER_EXCLUSIVE); + + return boundedExclusive && hasEqualBounds(); + } + + private boolean hasEqualBounds() { + return lower == null && upper == null + || lower != null && upper != null && lower.compareTo(upper) == 0; } public String asString() { @@ -513,13 +562,4 @@ public interface Function { R apply(T t); } - - public static > Range emptyRange(Class clazz) { - return new Range( - null, - null, - LOWER_INFINITE|UPPER_INFINITE, - clazz - ); - } } diff --git a/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java b/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java index 3e77781a5..95f6e51f1 100644 --- a/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java +++ b/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java @@ -5,7 +5,8 @@ import static com.vladmihalcea.hibernate.type.range.Range.integerRange; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; /** * @author Edgar Asatryan @@ -35,6 +36,9 @@ public void ofStringTest() { assertThat(integerRange("(-5,5]").isUpperBoundClosed(), is(true)); assertThat(integerRange("(-5,5]").isLowerBoundClosed(), is(false)); + assertThat(integerRange("(,)").contains(integerRange("empty")), is(true)); + + assertThat(integerRange("empty").contains(integerRange("(,)")), is(false)); } @Test @@ -50,4 +54,50 @@ public void containsRange() { assertThat(integerRange("(,)").contains(integerRange("(6,)")), is(true)); assertThat(integerRange("(,)").contains(integerRange("(,6)")), is(true)); } + + @Test + public void emptyInfinityEquality() { + assertEquals(integerRange("empty"), integerRange("empty")); + assertEquals(integerRange("(infinity,infinity)"), integerRange("(infinity,infinity)")); + assertEquals(integerRange("(,)"), integerRange("(infinity,infinity)")); + assertEquals(integerRange("(infinity,infinity)"), integerRange("(,)")); + + assertNotEquals(integerRange("empty"), integerRange("(infinity,infinity)")); + assertNotEquals(integerRange("empty"), integerRange("(,)")); + assertNotEquals(integerRange("empty"), integerRange("(5,5)")); + } + + @Test + public void emptyRangeWithEmptyKeyword() { + Range empty = Range.longRange("empty"); + + assertTrue(empty.isEmpty()); + + assertFalse(empty.contains(Long.MIN_VALUE)); + assertFalse(empty.contains(Long.valueOf(0))); + assertFalse(empty.contains(Long.MAX_VALUE)); + + assertNull(empty.upper()); + assertNull(empty.lower()); + } + + @Test + public void emptyRangeWithValues() { + Range empty = Range.longRange("(1,1)"); + + assertTrue(empty.isEmpty()); + assertFalse(empty.contains(Long.MIN_VALUE)); + assertFalse(empty.contains(Long.valueOf(0))); + assertFalse(empty.contains(Long.MAX_VALUE)); + + assertTrue(integerRange("(5,5)").isEmpty()); + } + + @Test + public void notEmptyWithValues() { + assertFalse(integerRange("(5,)").isEmpty()); + assertFalse(integerRange("(5,5]").isEmpty()); + assertFalse(integerRange("(,5)").isEmpty()); + assertFalse(integerRange("(,)").isEmpty()); + } } \ No newline at end of file diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java index 976b8a3e7..7f486d2a7 100644 --- a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java @@ -12,7 +12,7 @@ /** * Represents the range/interval with two bounds. Abstraction follows the semantics of the mathematical interval. The - * range can be unbounded or open from the left or/and unbounded from the right. The range supports half-open or closed + * range can be unbounded, empty or open from the left or/and unbounded from the right. The range supports half-open or closed * bounds on both sides. * *

@@ -68,7 +68,7 @@ private Range(T lower, T upper, int mask, Class clazz) { this.mask = mask; this.clazz = clazz; - if (isBounded() && lower.compareTo(upper) > 0) { + if (isBounded() && lower != null && upper != null && lower.compareTo(upper) > 0) { throw new IllegalArgumentException("The lower bound is greater then upper!"); } } @@ -250,6 +250,28 @@ public static > Range infinite(Class cls) return new Range<>(null, null, LOWER_INFINITE | UPPER_INFINITE, cls); } + /** + * Creates the empty range. In other words the range that contains no points. + *

+ * The mathematical equivalent will be: + *

{@code
+     *     (a, a) = ∅
+     * }
+ * + * @param cls The range class, never null. + * @param The type of bounds. + * + * @return The empty range. + */ + public static > Range emptyRange(Class cls) { + return new Range<>( + null, + null, + LOWER_EXCLUSIVE | UPPER_EXCLUSIVE, + cls + ); + } + public static > Range ofString(String str, Function converter, Class clazz) { if(str.equals(EMPTY)) { return emptyRange(clazz); @@ -561,6 +583,10 @@ public T upper() { * @return Whether {@code point} in this range or not. */ public boolean contains(T point) { + if (isEmpty()) { + return false; + } + boolean l = hasLowerBound(); boolean u = hasUpperBound(); @@ -596,7 +622,30 @@ public boolean contains(T point) { * @return Whether {@code range} in this range or not. */ public boolean contains(Range range) { - return (!range.hasLowerBound() || contains(range.lower)) && (!range.hasUpperBound() || contains(range.upper)); + return !isEmpty() && (!range.hasLowerBound() || contains(range.lower)) && (!range.hasUpperBound() || contains(range.upper)); + } + + /** + * Determines whether this range is empty or not. + *

+ * For example: + *

{@code
+     *     assertFalse(integerRange("empty").contains(1))
+     * }
+ * + * @return Whether {@code range} in this range or not. + */ + public boolean isEmpty() { + boolean boundedExclusive = isBounded() + && hasMask(LOWER_EXCLUSIVE) + && hasMask(UPPER_EXCLUSIVE); + + return boundedExclusive && hasEqualBounds(); + } + + private boolean hasEqualBounds() { + return lower == null && upper == null + || lower != null && upper != null && lower.compareTo(upper) == 0; } public String asString() { @@ -624,13 +673,4 @@ private Function boundToString() { Class getClazz() { return clazz; } - - public static > Range emptyRange(Class clazz) { - return new Range<>( - null, - null, - LOWER_INFINITE|UPPER_INFINITE, - clazz - ); - } } diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java index 25f752bc2..eb7e520a8 100644 --- a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java @@ -5,8 +5,15 @@ import static com.vladmihalcea.hibernate.type.range.Range.integerRange; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.time.LocalDate; /** * @author Edgar Asatryan @@ -36,6 +43,9 @@ public void ofStringTest() { assertThat(integerRange("(-5,5]").isUpperBoundClosed(), is(true)); assertThat(integerRange("(-5,5]").isLowerBoundClosed(), is(false)); + assertThat(integerRange("(,)").contains(integerRange("empty")), is(true)); + + assertThat(integerRange("empty").contains(integerRange("(,)")), is(false)); } @Test @@ -74,4 +84,52 @@ public void zonedDateTimeTest() { assertNotNull(Range.zonedDateTimeRange("[2019-03-27 16:33:10.123456+05:30,)")); assertNotNull(Range.zonedDateTimeRange("[2019-03-27 16:33:10.123456-06,infinity)")); } + + + @Test + public void emptyInfinityEquality() { + assertEquals(integerRange("empty"), integerRange("empty")); + assertEquals(integerRange("(infinity,infinity)"), integerRange("(infinity,infinity)")); + assertEquals(integerRange("(,)"), integerRange("(infinity,infinity)")); + assertEquals(integerRange("(infinity,infinity)"), integerRange("(,)")); + + assertNotEquals(integerRange("empty"), integerRange("(infinity,infinity)")); + assertNotEquals(integerRange("empty"), integerRange("(,)")); + assertNotEquals(integerRange("empty"), integerRange("(5,5)")); + } + + @Test + public void emptyRangeWithEmptyKeyword() { + Range empty = Range.localDateRange("empty"); + + assertTrue(empty.isEmpty()); + + assertFalse(empty.contains(LocalDate.MIN)); + assertFalse(empty.contains((LocalDate) null)); + assertFalse(empty.contains(LocalDate.now())); + assertFalse(empty.contains(LocalDate.MAX)); + + assertNull(empty.upper()); + assertNull(empty.lower()); + } + + @Test + public void emptyRangeWithValues() { + Range empty = Range.localDateRange("(2019-03-27,2019-03-27)"); + + assertTrue(empty.isEmpty()); + assertFalse(empty.contains(LocalDate.MIN)); + assertFalse(empty.contains(LocalDate.now())); + assertFalse(empty.contains(LocalDate.MAX)); + + assertTrue(integerRange("(5,5)").isEmpty()); + } + + @Test + public void notEmptyWithValues() { + assertFalse(integerRange("(5,)").isEmpty()); + assertFalse(integerRange("(5,5]").isEmpty()); + assertFalse(integerRange("(,5)").isEmpty()); + assertFalse(integerRange("(,)").isEmpty()); + } } \ No newline at end of file diff --git a/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java b/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java index 976b8a3e7..7f486d2a7 100644 --- a/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java +++ b/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java @@ -12,7 +12,7 @@ /** * Represents the range/interval with two bounds. Abstraction follows the semantics of the mathematical interval. The - * range can be unbounded or open from the left or/and unbounded from the right. The range supports half-open or closed + * range can be unbounded, empty or open from the left or/and unbounded from the right. The range supports half-open or closed * bounds on both sides. * *

@@ -68,7 +68,7 @@ private Range(T lower, T upper, int mask, Class clazz) { this.mask = mask; this.clazz = clazz; - if (isBounded() && lower.compareTo(upper) > 0) { + if (isBounded() && lower != null && upper != null && lower.compareTo(upper) > 0) { throw new IllegalArgumentException("The lower bound is greater then upper!"); } } @@ -250,6 +250,28 @@ public static > Range infinite(Class cls) return new Range<>(null, null, LOWER_INFINITE | UPPER_INFINITE, cls); } + /** + * Creates the empty range. In other words the range that contains no points. + *

+ * The mathematical equivalent will be: + *

{@code
+     *     (a, a) = ∅
+     * }
+ * + * @param cls The range class, never null. + * @param The type of bounds. + * + * @return The empty range. + */ + public static > Range emptyRange(Class cls) { + return new Range<>( + null, + null, + LOWER_EXCLUSIVE | UPPER_EXCLUSIVE, + cls + ); + } + public static > Range ofString(String str, Function converter, Class clazz) { if(str.equals(EMPTY)) { return emptyRange(clazz); @@ -561,6 +583,10 @@ public T upper() { * @return Whether {@code point} in this range or not. */ public boolean contains(T point) { + if (isEmpty()) { + return false; + } + boolean l = hasLowerBound(); boolean u = hasUpperBound(); @@ -596,7 +622,30 @@ public boolean contains(T point) { * @return Whether {@code range} in this range or not. */ public boolean contains(Range range) { - return (!range.hasLowerBound() || contains(range.lower)) && (!range.hasUpperBound() || contains(range.upper)); + return !isEmpty() && (!range.hasLowerBound() || contains(range.lower)) && (!range.hasUpperBound() || contains(range.upper)); + } + + /** + * Determines whether this range is empty or not. + *

+ * For example: + *

{@code
+     *     assertFalse(integerRange("empty").contains(1))
+     * }
+ * + * @return Whether {@code range} in this range or not. + */ + public boolean isEmpty() { + boolean boundedExclusive = isBounded() + && hasMask(LOWER_EXCLUSIVE) + && hasMask(UPPER_EXCLUSIVE); + + return boundedExclusive && hasEqualBounds(); + } + + private boolean hasEqualBounds() { + return lower == null && upper == null + || lower != null && upper != null && lower.compareTo(upper) == 0; } public String asString() { @@ -624,13 +673,4 @@ private Function boundToString() { Class getClazz() { return clazz; } - - public static > Range emptyRange(Class clazz) { - return new Range<>( - null, - null, - LOWER_INFINITE|UPPER_INFINITE, - clazz - ); - } } diff --git a/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java b/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java index a9441b687..8029de669 100644 --- a/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java +++ b/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java @@ -5,8 +5,15 @@ import static com.vladmihalcea.hibernate.type.range.Range.integerRange; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.time.LocalDate; /** * @author Edgar Asatryan @@ -36,6 +43,9 @@ public void ofStringTest() { assertThat(integerRange("(-5,5]").isUpperBoundClosed(), is(true)); assertThat(integerRange("(-5,5]").isLowerBoundClosed(), is(false)); + assertThat(integerRange("(,)").contains(integerRange("empty")), is(true)); + + assertThat(integerRange("empty").contains(integerRange("(,)")), is(false)); } @Test @@ -74,4 +84,52 @@ public void zonedDateTimeTest() { assertNotNull(Range.zonedDateTimeRange("[2019-03-27 16:33:10.123456+05:30,)")); assertNotNull(Range.zonedDateTimeRange("[2019-03-27 16:33:10.123456-06,infinity)")); } + + + @Test + public void emptyInfinityEquality() { + assertEquals(integerRange("empty"), integerRange("empty")); + assertEquals(integerRange("(infinity,infinity)"), integerRange("(infinity,infinity)")); + assertEquals(integerRange("(,)"), integerRange("(infinity,infinity)")); + assertEquals(integerRange("(infinity,infinity)"), integerRange("(,)")); + + assertNotEquals(integerRange("empty"), integerRange("(infinity,infinity)")); + assertNotEquals(integerRange("empty"), integerRange("(,)")); + assertNotEquals(integerRange("empty"), integerRange("(5,5)")); + } + + @Test + public void emptyRangeWithEmptyKeyword() { + Range empty = Range.localDateRange("empty"); + + assertTrue(empty.isEmpty()); + + assertFalse(empty.contains(LocalDate.MIN)); + assertFalse(empty.contains((LocalDate) null)); + assertFalse(empty.contains(LocalDate.now())); + assertFalse(empty.contains(LocalDate.MAX)); + + assertNull(empty.upper()); + assertNull(empty.lower()); + } + + @Test + public void emptyRangeWithValues() { + Range empty = Range.localDateRange("(2019-03-27,2019-03-27)"); + + assertTrue(empty.isEmpty()); + assertFalse(empty.contains(LocalDate.MIN)); + assertFalse(empty.contains(LocalDate.now())); + assertFalse(empty.contains(LocalDate.MAX)); + + assertTrue(integerRange("(5,5)").isEmpty()); + } + + @Test + public void notEmptyWithValues() { + assertFalse(integerRange("(5,)").isEmpty()); + assertFalse(integerRange("(5,5]").isEmpty()); + assertFalse(integerRange("(,5)").isEmpty()); + assertFalse(integerRange("(,)").isEmpty()); + } } \ No newline at end of file diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java index 3311f8682..354ee024c 100644 --- a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java +++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java @@ -12,7 +12,7 @@ /** * Represents the range/interval with two bounds. Abstraction follows the semantics of the mathematical interval. The - * range can be unbounded or open from the left or/and unbounded from the right. The range supports half-open or closed + * range can be unbounded, empty or open from the left or/and unbounded from the right. The range supports half-open or closed * bounds on both sides. * *

@@ -68,7 +68,7 @@ private Range(T lower, T upper, int mask, Class clazz) { this.mask = mask; this.clazz = clazz; - if (isBounded() && lower.compareTo(upper) > 0) { + if (isBounded() && lower != null && upper != null && lower.compareTo(upper) > 0) { throw new IllegalArgumentException("The lower bound is greater then upper!"); } } @@ -250,6 +250,28 @@ public static > Range infinite(Class cls) return new Range<>(null, null, LOWER_INFINITE | UPPER_INFINITE, cls); } + /** + * Creates the empty range. In other words the range that contains no points. + *

+ * The mathematical equivalent will be: + *

{@code
+     *     (a, a) = ∅
+     * }
+ * + * @param cls The range class, never null. + * @param The type of bounds. + * + * @return The empty range. + */ + public static > Range emptyRange(Class cls) { + return new Range<>( + null, + null, + LOWER_EXCLUSIVE | UPPER_EXCLUSIVE, + cls + ); + } + public static > Range ofString(String str, Function converter, Class clazz) { if(str.equals(EMPTY)) { return emptyRange(clazz); @@ -561,6 +583,10 @@ public T upper() { * @return Whether {@code point} in this range or not. */ public boolean contains(T point) { + if (isEmpty()) { + return false; + } + boolean l = hasLowerBound(); boolean u = hasUpperBound(); @@ -596,7 +622,30 @@ public boolean contains(T point) { * @return Whether {@code range} in this range or not. */ public boolean contains(Range range) { - return (!range.hasLowerBound() || contains(range.lower)) && (!range.hasUpperBound() || contains(range.upper)); + return !isEmpty() && (!range.hasLowerBound() || contains(range.lower)) && (!range.hasUpperBound() || contains(range.upper)); + } + + /** + * Determines whether this range is empty or not. + *

+ * For example: + *

{@code
+     *     assertFalse(integerRange("empty").contains(1))
+     * }
+ * + * @return Whether {@code range} in this range or not. + */ + public boolean isEmpty() { + boolean boundedExclusive = isBounded() + && hasMask(LOWER_EXCLUSIVE) + && hasMask(UPPER_EXCLUSIVE); + + return boundedExclusive && hasEqualBounds(); + } + + private boolean hasEqualBounds() { + return lower == null && upper == null + || lower != null && upper != null && lower.compareTo(upper) == 0; } public String asString() { @@ -624,13 +673,4 @@ private Function boundToString() { Class getClazz() { return clazz; } - - public static > Range emptyRange(Class clazz) { - return new Range<>( - null, - null, - LOWER_INFINITE|UPPER_INFINITE, - clazz - ); - } } diff --git a/hibernate-types-60/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java b/hibernate-types-60/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java index a9441b687..8029de669 100644 --- a/hibernate-types-60/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java +++ b/hibernate-types-60/src/test/java/com/vladmihalcea/hibernate/type/range/RangeTest.java @@ -5,8 +5,15 @@ import static com.vladmihalcea.hibernate.type.range.Range.integerRange; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.time.LocalDate; /** * @author Edgar Asatryan @@ -36,6 +43,9 @@ public void ofStringTest() { assertThat(integerRange("(-5,5]").isUpperBoundClosed(), is(true)); assertThat(integerRange("(-5,5]").isLowerBoundClosed(), is(false)); + assertThat(integerRange("(,)").contains(integerRange("empty")), is(true)); + + assertThat(integerRange("empty").contains(integerRange("(,)")), is(false)); } @Test @@ -74,4 +84,52 @@ public void zonedDateTimeTest() { assertNotNull(Range.zonedDateTimeRange("[2019-03-27 16:33:10.123456+05:30,)")); assertNotNull(Range.zonedDateTimeRange("[2019-03-27 16:33:10.123456-06,infinity)")); } + + + @Test + public void emptyInfinityEquality() { + assertEquals(integerRange("empty"), integerRange("empty")); + assertEquals(integerRange("(infinity,infinity)"), integerRange("(infinity,infinity)")); + assertEquals(integerRange("(,)"), integerRange("(infinity,infinity)")); + assertEquals(integerRange("(infinity,infinity)"), integerRange("(,)")); + + assertNotEquals(integerRange("empty"), integerRange("(infinity,infinity)")); + assertNotEquals(integerRange("empty"), integerRange("(,)")); + assertNotEquals(integerRange("empty"), integerRange("(5,5)")); + } + + @Test + public void emptyRangeWithEmptyKeyword() { + Range empty = Range.localDateRange("empty"); + + assertTrue(empty.isEmpty()); + + assertFalse(empty.contains(LocalDate.MIN)); + assertFalse(empty.contains((LocalDate) null)); + assertFalse(empty.contains(LocalDate.now())); + assertFalse(empty.contains(LocalDate.MAX)); + + assertNull(empty.upper()); + assertNull(empty.lower()); + } + + @Test + public void emptyRangeWithValues() { + Range empty = Range.localDateRange("(2019-03-27,2019-03-27)"); + + assertTrue(empty.isEmpty()); + assertFalse(empty.contains(LocalDate.MIN)); + assertFalse(empty.contains(LocalDate.now())); + assertFalse(empty.contains(LocalDate.MAX)); + + assertTrue(integerRange("(5,5)").isEmpty()); + } + + @Test + public void notEmptyWithValues() { + assertFalse(integerRange("(5,)").isEmpty()); + assertFalse(integerRange("(5,5]").isEmpty()); + assertFalse(integerRange("(,5)").isEmpty()); + assertFalse(integerRange("(,)").isEmpty()); + } } \ No newline at end of file