1717
1818import java .util .Comparator ;
1919import java .util .Optional ;
20+ import java .util .function .Function ;
2021
2122import org .springframework .util .Assert ;
2223import org .springframework .util .ObjectUtils ;
3031 */
3132public 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 /**
0 commit comments