diff --git a/pom.xml b/pom.xml
index c528bfe311..aa63dfde2a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-commons
- 3.0.0-SNAPSHOT
+ 3.0.0-GH-2571-SNAPSHOT
Spring Data Core
diff --git a/src/main/java/org/springframework/data/domain/Range.java b/src/main/java/org/springframework/data/domain/Range.java
index 2497f043f5..39120c79d8 100644
--- a/src/main/java/org/springframework/data/domain/Range.java
+++ b/src/main/java/org/springframework/data/domain/Range.java
@@ -15,6 +15,7 @@
*/
package org.springframework.data.domain;
+import java.util.Comparator;
import java.util.Optional;
import org.springframework.util.Assert;
@@ -27,7 +28,7 @@
* @author Mark Paluch
* @since 1.10
*/
-public final class Range> {
+public final class Range {
private final static Range> UNBOUNDED = Range.of(Bound.unbounded(), Bound.UNBOUNDED);
@@ -57,7 +58,7 @@ private Range(Bound lowerBound, Bound upperBound) {
* @since 2.0
*/
@SuppressWarnings("unchecked")
- public static > Range unbounded() {
+ public static Range unbounded() {
return (Range) UNBOUNDED;
}
@@ -70,7 +71,7 @@ public static > Range unbounded() {
* @return
* @since 2.2
*/
- public static > Range closed(T from, T to) {
+ public static Range closed(T from, T to) {
return new Range<>(Bound.inclusive(from), Bound.inclusive(to));
}
@@ -83,7 +84,7 @@ public static > Range closed(T from, T to) {
* @return
* @since 2.2
*/
- public static > Range open(T from, T to) {
+ public static Range open(T from, T to) {
return new Range<>(Bound.exclusive(from), Bound.exclusive(to));
}
@@ -96,7 +97,7 @@ public static > Range open(T from, T to) {
* @return
* @since 2.2
*/
- public static > Range leftOpen(T from, T to) {
+ public static Range leftOpen(T from, T to) {
return new Range<>(Bound.exclusive(from), Bound.inclusive(to));
}
@@ -109,7 +110,7 @@ public static > Range leftOpen(T from, T to) {
* @return
* @since 2.2
*/
- public static > Range rightOpen(T from, T to) {
+ public static Range rightOpen(T from, T to) {
return new Range<>(Bound.inclusive(from), Bound.exclusive(to));
}
@@ -122,7 +123,7 @@ public static > Range rightOpen(T from, T to) {
* @return
* @since 2.2
*/
- public static > Range leftUnbounded(Bound to) {
+ public static Range leftUnbounded(Bound to) {
return new Range<>(Bound.unbounded(), to);
}
@@ -135,7 +136,7 @@ public static > Range leftUnbounded(Bound to) {
* @return
* @since 2.2
*/
- public static > Range rightUnbounded(Bound from) {
+ public static Range rightUnbounded(Bound from) {
return new Range<>(from, Bound.unbounded());
}
@@ -146,7 +147,7 @@ public static > Range rightUnbounded(Bound from) {
* @return
* @since 2.0
*/
- public static > RangeBuilder from(Bound lower) {
+ public static RangeBuilder from(Bound lower) {
Assert.notNull(lower, "Lower bound must not be null!");
return new RangeBuilder<>(lower);
@@ -161,7 +162,7 @@ public static > RangeBuilder from(Bound lower) {
* @since 2.0
* @see #from(Bound)
*/
- public static > Range of(Bound lowerBound, Bound upperBound) {
+ public static Range of(Bound lowerBound, Bound upperBound) {
return new Range<>(lowerBound, upperBound);
}
@@ -171,9 +172,9 @@ public static > Range of(Bound lowerBound, Bound
* @param value must not be {@literal null}.
* @return
- * @see Range#closed(Comparable, Comparable)
+ * @see Range#closed(Object, Object)
*/
- public static > Range just(T value) {
+ public static Range just(T value) {
return Range.closed(value, value);
}
@@ -183,16 +184,34 @@ public static > Range just(T value) {
* @param value must not be {@literal null}.
* @return
*/
- public boolean contains(T value) {
+ @SuppressWarnings({ "unchecked" })
+ public boolean contains(Comparable value) {
+
+ return contains((T) value, (o1, o2) -> {
+
+ Assert.isInstanceOf(Comparable.class, o1,
+ "Range value must be an instance of Comparable to use contains(Comparable)");
+ return ((Comparable) o1).compareTo(o2);
+ });
+ }
+
+ /**
+ * Returns whether the {@link Range} contains the given value.
+ *
+ * @param value must not be {@literal null}.
+ * @return
+ * @since 3.0
+ */
+ public boolean contains(T value, Comparator comparator) {
Assert.notNull(value, "Reference value must not be null!");
boolean greaterThanLowerBound = lowerBound.getValue() //
- .map(it -> lowerBound.isInclusive() ? it.compareTo(value) <= 0 : it.compareTo(value) < 0) //
+ .map(it -> lowerBound.isInclusive() ? comparator.compare(it, value) <= 0 : comparator.compare(it, value) < 0) //
.orElse(true);
boolean lessThanUpperBound = upperBound.getValue() //
- .map(it -> upperBound.isInclusive() ? it.compareTo(value) >= 0 : it.compareTo(value) > 0) //
+ .map(it -> upperBound.isInclusive() ? comparator.compare(it, value) >= 0 : comparator.compare(it, value) > 0) //
.orElse(true);
return greaterThanLowerBound && lessThanUpperBound;
@@ -238,13 +257,13 @@ public int hashCode() {
/**
* Value object representing a boundary. A boundary can either be {@link #unbounded() unbounded},
- * {@link #inclusive(Comparable) including its value} or {@link #exclusive(Comparable) its value}.
+ * {@link #inclusive(Object) including its value} or {@link #exclusive(Object) its value}.
*
* @author Mark Paluch
* @since 2.0
* @soundtrack Mohamed Ragab - Excelsior Sessions (March 2017)
*/
- public static final class Bound> {
+ public static final class Bound {
@SuppressWarnings({ "rawtypes", "unchecked" }) //
private static final Bound> UNBOUNDED = new Bound(Optional.empty(), true);
@@ -261,7 +280,7 @@ private Bound(Optional value, boolean inclusive) {
* Creates an unbounded {@link Bound}.
*/
@SuppressWarnings("unchecked")
- public static > Bound unbounded() {
+ public static Bound unbounded() {
return (Bound) UNBOUNDED;
}
@@ -280,7 +299,7 @@ public boolean isBounded() {
* @param value must not be {@literal null}.
* @return
*/
- public static > Bound inclusive(T value) {
+ public static Bound inclusive(T value) {
Assert.notNull(value, "Value must not be null!");
return new Bound<>(Optional.of(value), true);
@@ -332,7 +351,7 @@ public static Bound inclusive(double value) {
* @param value must not be {@literal null}.
* @return
*/
- public static > Bound exclusive(T value) {
+ public static Bound exclusive(T value) {
Assert.notNull(value, "Value must not be null!");
return new Bound<>(Optional.of(value), false);
@@ -439,7 +458,7 @@ public int hashCode() {
* @since 2.0
* @soundtrack Aly and Fila - Future Sound Of Egypt 493
*/
- public static class RangeBuilder> {
+ public static class RangeBuilder {
private final Bound lower;
diff --git a/src/test/java/org/springframework/data/domain/RangeUnitTests.java b/src/test/java/org/springframework/data/domain/RangeUnitTests.java
index 43f131bf41..3146b26167 100755
--- a/src/test/java/org/springframework/data/domain/RangeUnitTests.java
+++ b/src/test/java/org/springframework/data/domain/RangeUnitTests.java
@@ -36,7 +36,7 @@ void rejectsNullReferenceValuesForContains() {
.isThrownBy(() -> Range.from(Bound.inclusive(10L)).to(Bound.inclusive(20L)).contains(null));
}
- @Test // DATACMNS-651
+ @Test // DATACMNS-651, GH-2571
void excludesLowerBoundIfConfigured() {
var range = Range.from(Bound.exclusive(10L)).to(Bound.inclusive(20L));
@@ -46,9 +46,15 @@ void excludesLowerBoundIfConfigured() {
assertThat(range.contains(15L)).isTrue();
assertThat(range.contains(5L)).isFalse();
assertThat(range.contains(25L)).isFalse();
+
+ assertThat(range.contains(10L, Long::compareTo)).isFalse();
+ assertThat(range.contains(20L, Long::compareTo)).isTrue();
+ assertThat(range.contains(15L, Long::compareTo)).isTrue();
+ assertThat(range.contains(5L, Long::compareTo)).isFalse();
+ assertThat(range.contains(25L, Long::compareTo)).isFalse();
}
- @Test // DATACMNS-651
+ @Test // DATACMNS-651, GH-2571
void excludesUpperBoundIfConfigured() {
var range = Range.of(Bound.inclusive(10L), Bound.exclusive(20L));
@@ -58,9 +64,15 @@ void excludesUpperBoundIfConfigured() {
assertThat(range.contains(15L)).isTrue();
assertThat(range.contains(5L)).isFalse();
assertThat(range.contains(25L)).isFalse();
+
+ assertThat(range.contains(10L, Long::compareTo)).isTrue();
+ assertThat(range.contains(20L, Long::compareTo)).isFalse();
+ assertThat(range.contains(15L, Long::compareTo)).isTrue();
+ assertThat(range.contains(5L, Long::compareTo)).isFalse();
+ assertThat(range.contains(25L, Long::compareTo)).isFalse();
}
- @Test // DATACMNS-651, DATACMNS-1050
+ @Test // DATACMNS-651, DATACMNS-1050, GH-2571
void handlesOpenUpperBoundCorrectly() {
var range = Range.of(Bound.inclusive(10L), Bound.unbounded());
@@ -70,12 +82,19 @@ void handlesOpenUpperBoundCorrectly() {
assertThat(range.contains(15L)).isTrue();
assertThat(range.contains(5L)).isFalse();
assertThat(range.contains(25L)).isTrue();
+
+ assertThat(range.contains(10L, Long::compareTo)).isTrue();
+ assertThat(range.contains(20L, Long::compareTo)).isTrue();
+ assertThat(range.contains(15L, Long::compareTo)).isTrue();
+ assertThat(range.contains(5L, Long::compareTo)).isFalse();
+ assertThat(range.contains(25L, Long::compareTo)).isTrue();
+
assertThat(range.getLowerBound().isBounded()).isTrue();
assertThat(range.getUpperBound().isBounded()).isFalse();
assertThat(range.toString()).isEqualTo("[10-unbounded");
}
- @Test // DATACMNS-651, DATACMNS-1050
+ @Test // DATACMNS-651, DATACMNS-1050, GH-2571
void handlesOpenLowerBoundCorrectly() {
var range = Range.of(Bound.unbounded(), Bound.inclusive(20L));
@@ -85,10 +104,17 @@ void handlesOpenLowerBoundCorrectly() {
assertThat(range.contains(15L)).isTrue();
assertThat(range.contains(5L)).isTrue();
assertThat(range.contains(25L)).isFalse();
+
+ assertThat(range.contains(10L, Long::compareTo)).isTrue();
+ assertThat(range.contains(20L, Long::compareTo)).isTrue();
+ assertThat(range.contains(15L, Long::compareTo)).isTrue();
+ assertThat(range.contains(5L, Long::compareTo)).isTrue();
+ assertThat(range.contains(25L, Long::compareTo)).isFalse();
assertThat(range.getLowerBound().isBounded()).isFalse();
assertThat(range.getUpperBound().isBounded()).isTrue();
}
+
@Test // DATACMNS-1050
void createsInclusiveBoundaryCorrectly() {
@@ -107,7 +133,7 @@ void createsExclusiveBoundaryCorrectly() {
assertThat(bound.getValue()).contains(10d);
}
- @Test // DATACMNS-1050
+ @Test // DATACMNS-1050, GH-2571
void createsRangeFromBoundariesCorrectly() {
var lower = Bound.inclusive(10L);
@@ -119,9 +145,14 @@ void createsRangeFromBoundariesCorrectly() {
assertThat(range.contains(10L)).isTrue();
assertThat(range.contains(20L)).isTrue();
assertThat(range.contains(21L)).isFalse();
+
+ assertThat(range.contains(9L, Long::compareTo)).isFalse();
+ assertThat(range.contains(10L, Long::compareTo)).isTrue();
+ assertThat(range.contains(20L, Long::compareTo)).isTrue();
+ assertThat(range.contains(21L, Long::compareTo)).isFalse();
}
- @Test // DATACMNS-1050
+ @Test // DATACMNS-1050, GH-2571
void shouldExclusiveBuildRangeLowerFirst() {
var range = Range.from(Bound.exclusive(10L)).to(Bound.exclusive(20L));
@@ -132,10 +163,17 @@ void shouldExclusiveBuildRangeLowerFirst() {
assertThat(range.contains(19L)).isTrue();
assertThat(range.contains(20L)).isFalse();
assertThat(range.contains(21L)).isFalse();
+
+ assertThat(range.contains(9L, Long::compareTo)).isFalse();
+ assertThat(range.contains(10L, Long::compareTo)).isFalse();
+ assertThat(range.contains(11L, Long::compareTo)).isTrue();
+ assertThat(range.contains(19L, Long::compareTo)).isTrue();
+ assertThat(range.contains(20L, Long::compareTo)).isFalse();
+ assertThat(range.contains(21L, Long::compareTo)).isFalse();
assertThat(range.toString()).isEqualTo("(10-20)");
}
- @Test // DATACMNS-1050
+ @Test // DATACMNS-1050, GH-2571
void shouldBuildRange() {
var range = Range.from(Bound.inclusive(10L)).to(Bound.inclusive(20L));
@@ -146,53 +184,73 @@ void shouldBuildRange() {
assertThat(range.contains(19L)).isTrue();
assertThat(range.contains(20L)).isTrue();
assertThat(range.contains(21L)).isFalse();
+
+ assertThat(range.contains(9L, Long::compareTo)).isFalse();
+ assertThat(range.contains(10L, Long::compareTo)).isTrue();
+ assertThat(range.contains(11L, Long::compareTo)).isTrue();
+ assertThat(range.contains(19L, Long::compareTo)).isTrue();
+ assertThat(range.contains(20L, Long::compareTo)).isTrue();
+ assertThat(range.contains(21L, Long::compareTo)).isFalse();
assertThat(range.toString()).isEqualTo("[10-20]");
}
- @Test // DATACMNS-1050
+ @Test // DATACMNS-1050, GH-2571
void createsUnboundedRange() {
Range range = Range.unbounded();
assertThat(range.contains(10L)).isTrue();
+ assertThat(range.contains(10L, Long::compareTo)).isTrue();
assertThat(range.getLowerBound().getValue()).isEmpty();
assertThat(range.getUpperBound().getValue()).isEmpty();
}
- @Test // DATACMNS-1499
+ @Test // DATACMNS-1499, GH-2571
void createsOpenRange() {
var range = Range.open(5L, 10L);
assertThat(range.contains(5L)).isFalse();
assertThat(range.contains(10L)).isFalse();
+
+ assertThat(range.contains(5L, Long::compareTo)).isFalse();
+ assertThat(range.contains(10L, Long::compareTo)).isFalse();
}
- @Test // DATACMNS-1499
+ @Test // DATACMNS-1499, GH-2571
void createsClosedRange() {
var range = Range.closed(5L, 10L);
assertThat(range.contains(5L)).isTrue();
assertThat(range.contains(10L)).isTrue();
+
+ assertThat(range.contains(5L, Long::compareTo)).isTrue();
+ assertThat(range.contains(10L, Long::compareTo)).isTrue();
}
- @Test // DATACMNS-1499
+ @Test // DATACMNS-1499, GH-2571
void createsLeftOpenRange() {
var range = Range.leftOpen(5L, 10L);
assertThat(range.contains(5L)).isFalse();
assertThat(range.contains(10L)).isTrue();
+
+ assertThat(range.contains(5L, Long::compareTo)).isFalse();
+ assertThat(range.contains(10L, Long::compareTo)).isTrue();
}
- @Test // DATACMNS-1499
+ @Test // DATACMNS-1499, GH-2571
void createsRightOpenRange() {
var range = Range.rightOpen(5L, 10L);
assertThat(range.contains(5L)).isTrue();
assertThat(range.contains(10L)).isFalse();
+
+ assertThat(range.contains(5L, Long::compareTo)).isTrue();
+ assertThat(range.contains(10L, Long::compareTo)).isFalse();
}
@Test // DATACMNS-1499