1 /*
2 * Copyright 2015-2020 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.springframework.data.domain;
17
18 import lombok.AccessLevel;
19 import lombok.NonNull;
20 import lombok.RequiredArgsConstructor;
21 import lombok.Value;
22
23 import java.util.Optional;
24
25 import org.springframework.util.Assert;
26
27 /**
28 * Simple value object to work with ranges and boundaries.
29 *
30 * @author Oliver Gierke
31 * @author Mark Paluch
32 * @since 1.10
33 */
34 @Value
35 @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
36 public class Range<T extends Comparable<T>> {
37
38 private final static Range<?> UNBOUNDED = Range.of(Bound.unbounded(), Bound.UNBOUNDED);
39
40 /**
41 * The lower bound of the range.
42 */
43 private final @NonNull Bound<T> lowerBound;
44
45 /**
46 * The upper bound of the range.
47 */
48 private final @NonNull Bound<T> upperBound;
49
50 /**
51 * Returns an unbounded {@link Range}.
52 *
53 * @return
54 * @since 2.0
55 */
56 @SuppressWarnings("unchecked")
57 public static <T extends Comparable<T>> Range<T> unbounded() {
58 return (Range<T>) UNBOUNDED;
59 }
60
61 /**
62 * Creates a new {@link Range} with inclusive bounds for both values.
63 *
64 * @param <T>
65 * @param from must not be {@literal null}.
66 * @param to must not be {@literal null}.
67 * @return
68 * @since 2.2
69 */
70 public static <T extends Comparable<T>> Range<T> closed(T from, T to) {
71 return new Range<>(Bound.inclusive(from), Bound.inclusive(to));
72 }
73
74 /**
75 * Creates a new {@link Range} with exclusive bounds for both values.
76 *
77 * @param <T>
78 * @param from must not be {@literal null}.
79 * @param to must not be {@literal null}.
80 * @return
81 * @since 2.2
82 */
83 public static <T extends Comparable<T>> Range<T> open(T from, T to) {
84 return new Range<>(Bound.exclusive(from), Bound.exclusive(to));
85 }
86
87 /**
88 * Creates a new left-open {@link Range}, i.e. left exclusive, right inclusive.
89 *
90 * @param <T>
91 * @param from must not be {@literal null}.
92 * @param to must not be {@literal null}.
93 * @return
94 * @since 2.2
95 */
96 public static <T extends Comparable<T>> Range<T> leftOpen(T from, T to) {
97 return new Range<>(Bound.exclusive(from), Bound.inclusive(to));
98 }
99
100 /**
101 * Creates a new right-open {@link Range}, i.e. left inclusive, right exclusive.
102 *
103 * @param <T>
104 * @param from must not be {@literal null}.
105 * @param to must not be {@literal null}.
106 * @return
107 * @since 2.2
108 */
109 public static <T extends Comparable<T>> Range<T> rightOpen(T from, T to) {
110 return new Range<>(Bound.inclusive(from), Bound.exclusive(to));
111 }
112
113 /**
114 * Creates a left-unbounded {@link Range} (the left bound set to {@link Bound#unbounded()}) with the given right
115 * bound.
116 *
117 * @param <T>
118 * @param to the right {@link Bound}, must not be {@literal null}.
119 * @return
120 * @since 2.2
121 */
122 public static <T extends Comparable<T>> Range<T> leftUnbounded(Bound<T> to) {
123 return new Range<>(Bound.unbounded(), to);
124 }
125
126 /**
127 * Creates a right-unbounded {@link Range} (the right bound set to {@link Bound#unbounded()}) with the given left
128 * bound.
129 *
130 * @param <T>
131 * @param from the left {@link Bound}, must not be {@literal null}.
132 * @return
133 * @since 2.2
134 */
135 public static <T extends Comparable<T>> Range<T> rightUnbounded(Bound<T> from) {
136 return new Range<>(from, Bound.unbounded());
137 }
138
139 /**
140 * Create a {@link RangeBuilder} given the lower {@link Bound}.
141 *
142 * @param lower must not be {@literal null}.
143 * @return
144 * @since 2.0
145 */
146 public static <T extends Comparable<T>> RangeBuilder<T> from(Bound<T> lower) {
147
148 Assert.notNull(lower, "Lower bound must not be null!");
149 return new RangeBuilder<>(lower);
150 }
151
152 /**
153 * Creates a new {@link Range} with the given lower and upper bound. Prefer {@link #from(Bound)} for a more builder
154 * style API.
155 *
156 * @param lowerBound must not be {@literal null}.
157 * @param upperBound must not be {@literal null}.
158 * @since 2.0
159 * @see #from(Bound)
160 */
161 public static <T extends Comparable<T>> Range<T> of(Bound<T> lowerBound, Bound<T> upperBound) {
162 return new Range<>(lowerBound, upperBound);
163 }
164
165 /**
166 * Creates a new Range with the given value as sole member.
167 *
168 * @param <T>
169 * @param value must not be {@literal null}.
170 * @return
171 * @see Range#closed(Comparable, Comparable)
172 */
173 public static <T extends Comparable<T>> Range<T> just(T value) {
174 return Range.closed(value, value);
175 }
176
177 /**
178 * Returns whether the {@link Range} contains the given value.
179 *
180 * @param value must not be {@literal null}.
181 * @return
182 */
183 public boolean contains(T value) {
184
185 Assert.notNull(value, "Reference value must not be null!");
186
187 boolean greaterThanLowerBound = lowerBound.getValue() //
188 .map(it -> lowerBound.isInclusive() ? it.compareTo(value) <= 0 : it.compareTo(value) < 0) //
189 .orElse(true);
190
191 boolean lessThanUpperBound = upperBound.getValue() //
192 .map(it -> upperBound.isInclusive() ? it.compareTo(value) >= 0 : it.compareTo(value) > 0) //
193 .orElse(true);
194
195 return greaterThanLowerBound && lessThanUpperBound;
196 }
197
198 /*
199 * (non-Javadoc)
200 * @see java.lang.Object#toString()
201 */
202 @Override
203 public String toString() {
204 return String.format("%s-%s", lowerBound.toPrefixString(), upperBound.toSuffixString());
205 }
206
207 /**
208 * Value object representing a boundary. A boundary can either be {@link #unbounded() unbounded},
209 * {@link #inclusive(Comparable) including its value} or {@link #exclusive(Comparable) its value}.
210 *
211 * @author Mark Paluch
212 * @since 2.0
213 * @soundtrack Mohamed Ragab - Excelsior Sessions (March 2017)
214 */
215 @Value
216 @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
217 public static class Bound<T extends Comparable<T>> {
218
219 @SuppressWarnings({ "rawtypes", "unchecked" }) //
220 private static final Bound<?> UNBOUNDED = new Bound(Optional.empty(), true);
221
222 private final Optional<T> value;
223 private final boolean inclusive;
224
225 /**
226 * Creates an unbounded {@link Bound}.
227 */
228 @SuppressWarnings("unchecked")
229 public static <T extends Comparable<T>> Bound<T> unbounded() {
230 return (Bound<T>) UNBOUNDED;
231 }
232
233 /**
234 * Returns whether this boundary is bounded.
235 *
236 * @return
237 */
238 public boolean isBounded() {
239 return value.isPresent();
240 }
241
242 /**
243 * Creates a boundary including {@code value}.
244 *
245 * @param value must not be {@literal null}.
246 * @return
247 */
248 public static <T extends Comparable<T>> Bound<T> inclusive(T value) {
249
250 Assert.notNull(value, "Value must not be null!");
251 return new Bound<>(Optional.of(value), true);
252 }
253
254 /**
255 * Creates a boundary including {@code value}.
256 *
257 * @param value must not be {@literal null}.
258 * @return
259 */
260 public static Bound<Integer> inclusive(int value) {
261 return inclusive((Integer) value);
262 }
263
264 /**
265 * Creates a boundary including {@code value}.
266 *
267 * @param value must not be {@literal null}.
268 * @return
269 */
270 public static Bound<Long> inclusive(long value) {
271 return inclusive((Long) value);
272 }
273
274 /**
275 * Creates a boundary including {@code value}.
276 *
277 * @param value must not be {@literal null}.
278 * @return
279 */
280 public static Bound<Float> inclusive(float value) {
281 return inclusive((Float) value);
282 }
283
284 /**
285 * Creates a boundary including {@code value}.
286 *
287 * @param value must not be {@literal null}.
288 * @return
289 */
290 public static Bound<Double> inclusive(double value) {
291 return inclusive((Double) value);
292 }
293
294 /**
295 * Creates a boundary excluding {@code value}.
296 *
297 * @param value must not be {@literal null}.
298 * @return
299 */
300 public static <T extends Comparable<T>> Bound<T> exclusive(T value) {
301
302 Assert.notNull(value, "Value must not be null!");
303 return new Bound<>(Optional.of(value), false);
304 }
305
306 /**
307 * Creates a boundary excluding {@code value}.
308 *
309 * @param value must not be {@literal null}.
310 * @return
311 */
312 public static Bound<Integer> exclusive(int value) {
313 return exclusive((Integer) value);
314 }
315
316 /**
317 * Creates a boundary excluding {@code value}.
318 *
319 * @param value must not be {@literal null}.
320 * @return
321 */
322 public static Bound<Long> exclusive(long value) {
323 return exclusive((Long) value);
324 }
325
326 /**
327 * Creates a boundary excluding {@code value}.
328 *
329 * @param value must not be {@literal null}.
330 * @return
331 */
332 public static Bound<Float> exclusive(float value) {
333 return exclusive((Float) value);
334 }
335
336 /**
337 * Creates a boundary excluding {@code value}.
338 *
339 * @param value must not be {@literal null}.
340 * @return
341 */
342 public static Bound<Double> exclusive(double value) {
343 return exclusive((Double) value);
344 }
345
346 String toPrefixString() {
347
348 return getValue() //
349 .map(Object::toString) //
350 .map(it -> isInclusive() ? "[".concat(it) : "(".concat(it)) //
351 .orElse("unbounded");
352 }
353
354 String toSuffixString() {
355
356 return getValue() //
357 .map(Object::toString) //
358 .map(it -> isInclusive() ? it.concat("]") : it.concat(")")) //
359 .orElse("unbounded");
360 }
361
362 /*
363 * (non-Javadoc)
364 * @see java.lang.Object#toString()
365 */
366 @Override
367 public String toString() {
368 return value.map(Object::toString).orElse("unbounded");
369 }
370
371 }
372
373 /**
374 * Builder for {@link Range} allowing to specify the upper boundary.
375 *
376 * @author Mark Paluch
377 * @since 2.0
378 * @soundtrack Aly and Fila - Future Sound Of Egypt 493
379 */
380 public static class RangeBuilder<T extends Comparable<T>> {
381
382 private final Bound<T> lower;
383
384 RangeBuilder(Bound<T> lower) {
385 this.lower = lower;
386 }
387
388 /**
389 * Create a {@link Range} given the upper {@link Bound}.
390 *
391 * @param upper must not be {@literal null}.
392 * @return
393 */
394 public Range<T> to(Bound<T> upper) {
395
396 Assert.notNull(upper, "Upper bound must not be null!");
397 return new Range<>(lower, upper);
398 }
399 }
400 }
401