Skip to content

Commit cdcd21a

Browse files
committed
Allow functional mapping of Range and Boundary.
We now support functional mapping of Range values by accepting a mapping function. Closes #2692
1 parent b451185 commit cdcd21a

File tree

2 files changed

+70
-5
lines changed

2 files changed

+70
-5
lines changed

src/main/java/org/springframework/data/domain/Range.java

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.Comparator;
1919
import java.util.Optional;
20+
import java.util.function.Function;
2021

2122
import org.springframework.util.Assert;
2223
import org.springframework.util.ObjectUtils;
@@ -30,7 +31,7 @@
3031
*/
3132
public final class Range<T> {
3233

33-
private final static Range<?> UNBOUNDED = Range.of(Bound.unbounded(), Bound.UNBOUNDED);
34+
private final static Range<?> UNBOUNDED = Range.of(Bound.unbounded(), Bound.unbounded());
3435

3536
/**
3637
* The lower bound of the range.
@@ -217,6 +218,22 @@ public boolean contains(T value, Comparator<T> comparator) {
217218
return greaterThanLowerBound && lessThanUpperBound;
218219
}
219220

221+
/**
222+
* Apply a mapping {@link Function} to the lower and upper boundary values.
223+
*
224+
* @param mapper must not be {@literal null}. If the mapper returns {@code null}, then the corresponding boundary
225+
* value represents an {@link Bound#unbounded()} boundary.
226+
* @return a new {@link Range} after applying the value to the mapper.
227+
* @param <R>
228+
* @since 3.0
229+
*/
230+
public <R> Range<R> map(Function<? super T, ? extends R> mapper) {
231+
232+
Assert.notNull(mapper, "Mapping function must not be null");
233+
234+
return Range.of(lowerBound.map(mapper), upperBound.map(mapper));
235+
}
236+
220237
@Override
221238
public String toString() {
222239
return String.format("%s-%s", lowerBound.toPrefixString(), upperBound.toSuffixString());
@@ -265,8 +282,7 @@ public int hashCode() {
265282
*/
266283
public static final class Bound<T> {
267284

268-
@SuppressWarnings({ "rawtypes", "unchecked" }) //
269-
private static final Bound<?> UNBOUNDED = new Bound(Optional.empty(), true);
285+
private static final Bound<?> UNBOUNDED = new Bound<>(Optional.empty(), true);
270286

271287
private final Optional<T> value;
272288
private final boolean inclusive;
@@ -302,7 +318,7 @@ public boolean isBounded() {
302318
public static <T> Bound<T> inclusive(T value) {
303319

304320
Assert.notNull(value, "Value must not be null");
305-
return new Bound<>(Optional.of(value), true);
321+
return Bound.of(Optional.of(value), true);
306322
}
307323

308324
/**
@@ -354,7 +370,7 @@ public static Bound<Double> inclusive(double value) {
354370
public static <T> Bound<T> exclusive(T value) {
355371

356372
Assert.notNull(value, "Value must not be null");
357-
return new Bound<>(Optional.of(value), false);
373+
return Bound.of(Optional.of(value), false);
358374
}
359375

360376
/**
@@ -437,6 +453,10 @@ public boolean equals(Object o) {
437453
return false;
438454
}
439455

456+
if (!value.isPresent() && !bound.value.isPresent()) {
457+
return true;
458+
}
459+
440460
if (inclusive != bound.inclusive)
441461
return false;
442462

@@ -445,10 +465,41 @@ public boolean equals(Object o) {
445465

446466
@Override
447467
public int hashCode() {
468+
469+
if (!value.isPresent()) {
470+
return ObjectUtils.nullSafeHashCode(value);
471+
}
472+
448473
int result = ObjectUtils.nullSafeHashCode(value);
449474
result = 31 * result + (inclusive ? 1 : 0);
450475
return result;
451476
}
477+
478+
/**
479+
* Apply a mapping {@link Function} to the boundary value.
480+
*
481+
* @param mapper must not be {@literal null}. If the mapper returns {@code null}, then the boundary value
482+
* corresponds with {@link Bound#unbounded()}.
483+
* @return a new {@link Bound} after applying the value to the mapper.
484+
* @param <R>
485+
* @since 3.0
486+
*/
487+
public <R> Bound<R> map(Function<? super T, ? extends R> mapper) {
488+
489+
Assert.notNull(mapper, "Mapping function must not be null");
490+
491+
return Bound.of(value.map(mapper), inclusive);
492+
}
493+
494+
private static <R> Bound<R> of(Optional<R> value, boolean inclusive) {
495+
496+
if (value.isPresent()) {
497+
return new Bound<>(value, inclusive);
498+
}
499+
500+
return unbounded();
501+
}
502+
452503
}
453504

454505
/**

src/test/java/org/springframework/data/domain/RangeUnitTests.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,20 @@ void createsRightOpenRange() {
253253
assertThat(range.contains(10L, Long::compareTo)).isFalse();
254254
}
255255

256+
@Test // GH-2692
257+
void mapsBoundaryValues() {
258+
259+
var range = Range.leftOpen(5L, 10L).map(it -> it * 10);
260+
261+
assertThat(range.getLowerBound()).isEqualTo(Bound.exclusive(50L));
262+
assertThat(range.getUpperBound()).isEqualTo(Bound.inclusive(100L));
263+
264+
range = Range.leftOpen(5L, 10L).map(it -> null);
265+
266+
assertThat(range.getLowerBound()).isEqualTo(Bound.unbounded());
267+
assertThat(range.getUpperBound()).isEqualTo(Bound.unbounded());
268+
}
269+
256270
@Test // DATACMNS-1499
257271
void createsLeftUnboundedRange() {
258272
assertThat(Range.leftUnbounded(Bound.inclusive(10L)).contains(-10000L)).isTrue();

0 commit comments

Comments
 (0)