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