Skip to content

Commit

Permalink
Add support for the PostgreSQL 'empty' range that's distinct from the…
Browse files Browse the repository at this point in the history
… (,) infinite range #492
  • Loading branch information
nstdio authored and vladmihalcea committed Oct 17, 2022
1 parent 71b9696 commit 160407b
Show file tree
Hide file tree
Showing 12 changed files with 639 additions and 75 deletions.
Expand Up @@ -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.
*
* <p>
Expand Down Expand Up @@ -47,7 +47,7 @@ private Range(T lower, T upper, int mask, Class<T> 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!");
}
}
Expand Down Expand Up @@ -230,6 +230,28 @@ public static <T extends Comparable<? super T>> Range<T> infinite(Class<T> cls)
return new Range<T>(null, null, LOWER_INFINITE | UPPER_INFINITE, cls);
}

/**
* Creates the empty range. In other words the range that contains no points.
* <p>
* The mathematical equivalent will be:
* <pre>{@code
* (a, a) = ∅
* }</pre>
*
* @param cls The range class, never null.
* @param <R> The type of bounds.
*
* @return The empty range.
*/
public static <R extends Comparable<? super R>> Range<R> emptyRange(Class<R> cls) {
return new Range<R>(
null,
null,
LOWER_EXCLUSIVE | UPPER_EXCLUSIVE,
cls
);
}

@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> Range<T> ofString(String str, Function<String, T> converter, Class<T> clazz) {
if(str.equals(EMPTY)) {
Expand Down Expand Up @@ -446,6 +468,10 @@ public T upper() {
*/
@SuppressWarnings("unchecked")
public boolean contains(T point) {
if (isEmpty()) {
return false;
}

boolean l = hasLowerBound();
boolean u = hasUpperBound();

Expand Down Expand Up @@ -481,7 +507,30 @@ public boolean contains(T point) {
* @return Whether {@code range} in this range or not.
*/
public boolean contains(Range<T> 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.
* <p>
* For example:
* <pre>{@code
* assertFalse(integerRange("empty").contains(1))
* }</pre>
*
* @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() {
Expand Down Expand Up @@ -513,13 +562,4 @@ public interface Function<T, R> {

R apply(T t);
}

public static <R extends Comparable<? super R>> Range<R> emptyRange(Class<R> clazz) {
return new Range<R>(
null,
null,
LOWER_INFINITE|UPPER_INFINITE,
clazz
);
}
}
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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<Long> 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<Long> 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());
}
}
Expand Up @@ -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.
*
* <p>
Expand Down Expand Up @@ -47,7 +47,7 @@ private Range(T lower, T upper, int mask, Class<T> 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!");
}
}
Expand Down Expand Up @@ -230,6 +230,28 @@ public static <T extends Comparable<? super T>> Range<T> infinite(Class<T> cls)
return new Range<T>(null, null, LOWER_INFINITE | UPPER_INFINITE, cls);
}

/**
* Creates the empty range. In other words the range that contains no points.
* <p>
* The mathematical equivalent will be:
* <pre>{@code
* (a, a) = ∅
* }</pre>
*
* @param cls The range class, never null.
* @param <R> The type of bounds.
*
* @return The empty range.
*/
public static <R extends Comparable<? super R>> Range<R> emptyRange(Class<R> cls) {
return new Range<R>(
null,
null,
LOWER_EXCLUSIVE | UPPER_EXCLUSIVE,
cls
);
}

@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> Range<T> ofString(String str, Function<String, T> converter, Class<T> clazz) {
if(str.equals(EMPTY)) {
Expand Down Expand Up @@ -446,6 +468,10 @@ public T upper() {
*/
@SuppressWarnings("unchecked")
public boolean contains(T point) {
if (isEmpty()) {
return false;
}

boolean l = hasLowerBound();
boolean u = hasUpperBound();

Expand Down Expand Up @@ -481,7 +507,30 @@ public boolean contains(T point) {
* @return Whether {@code range} in this range or not.
*/
public boolean contains(Range<T> 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.
* <p>
* For example:
* <pre>{@code
* assertFalse(integerRange("empty").contains(1))
* }</pre>
*
* @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() {
Expand Down Expand Up @@ -513,13 +562,4 @@ public interface Function<T, R> {

R apply(T t);
}

public static <R extends Comparable<? super R>> Range<R> emptyRange(Class<R> clazz) {
return new Range<R>(
null,
null,
LOWER_INFINITE|UPPER_INFINITE,
clazz
);
}
}
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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<Long> 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<Long> 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());
}
}

0 comments on commit 160407b

Please sign in to comment.