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..513d6d99c 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)")); } -} \ No newline at end of file + + + @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()); + } +}