1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.lang3;
18
19 import java.io.Serializable;
20 import java.util.Comparator;
21 import java.util.Objects;
22
23 /**
24 * An immutable range of objects from a minimum to maximum point inclusive.
25 *
26 * <p>The objects need to either be implementations of {@link Comparable}
27 * or you need to supply a {@link Comparator}.</p>
28 *
29 * <p>#ThreadSafe# if the objects and comparator are thread-safe.</p>
30 *
31 * @param <T> The type of range values.
32 * @since 3.0
33 * @since 3.21.0 {@code serialVersionUID} changed from {@code 1L} to {@code 2L}.
34 */
35 public class Range<T> implements Serializable {
36
37 @SuppressWarnings({"rawtypes", "unchecked"})
38 private enum ComparableComparator implements Comparator {
39 INSTANCE;
40
41 /**
42 * Comparable based compare implementation.
43 *
44 * @param obj1 left-hand side side of comparison.
45 * @param obj2 right-hand side side of comparison.
46 * @return negative, 0, positive comparison value.
47 */
48 @Override
49 public int compare(final Object obj1, final Object obj2) {
50 return ((Comparable) obj1).compareTo(obj2);
51 }
52 }
53
54 /**
55 * Serialization version.
56 *
57 * @see java.io.Serializable
58 * @since 3.21.0 {@code serialVersionUID} changed from {@code 1L} to {@value}.
59 */
60 private static final long serialVersionUID = 2L;
61
62 /**
63 * Creates a range with the specified minimum and maximum values (both inclusive).
64 *
65 * <p>The range uses the natural ordering of the elements to determine where
66 * values lie in the range.</p>
67 *
68 * <p>The arguments may be passed in the order (min, max) or (max, min).
69 * The getMinimum and getMaximum methods will return the correct values.</p>
70 *
71 * @param <T> the type of the elements in this range.
72 * @param fromInclusive the first value that defines the edge of the range, inclusive.
73 * @param toInclusive the second value that defines the edge of the range, inclusive.
74 * @return the range object, not null.
75 * @throws NullPointerException when fromInclusive is null.
76 * @throws NullPointerException when toInclusive is null.
77 * @throws ClassCastException if the elements are not {@link Comparable}.
78 * @deprecated Use {@link #of(Comparable, Comparable)}.
79 */
80 @Deprecated
81 public static <T extends Comparable<? super T>> Range<T> between(final T fromInclusive, final T toInclusive) {
82 return of(fromInclusive, toInclusive, null);
83 }
84
85 /**
86 * Creates a range with the specified minimum and maximum values (both inclusive).
87 *
88 * <p>The range uses the specified {@link Comparator} to determine where
89 * values lie in the range.</p>
90 *
91 * <p>The arguments may be passed in the order (min, max) or (max, min).
92 * The getMinimum and getMaximum methods will return the correct values.</p>
93 *
94 * @param <T> the type of the elements in this range.
95 * @param fromInclusive the first value that defines the edge of the range, inclusive.
96 * @param toInclusive the second value that defines the edge of the range, inclusive.
97 * @param comparator the comparator to be used, null for natural ordering.
98 * @return the range object, not null.
99 * @throws NullPointerException when fromInclusive is null.
100 * @throws NullPointerException when toInclusive is null.
101 * @throws ClassCastException if using natural ordering and the elements are not {@link Comparable}.
102 * @deprecated Use {@link #of(Object, Object, Comparator)}.
103 */
104 @Deprecated
105 public static <T> Range<T> between(final T fromInclusive, final T toInclusive, final Comparator<T> comparator) {
106 return new Range<>(fromInclusive, toInclusive, comparator);
107 }
108
109 /**
110 * Creates a range using the specified element as both the minimum
111 * and maximum in this range.
112 *
113 * <p>The range uses the natural ordering of the elements to determine where
114 * values lie in the range.</p>
115 *
116 * @param <T> the type of the elements in this range.
117 * @param element the value to use for this range, not null.
118 * @return the range object, not null.
119 * @throws NullPointerException if the element is null.
120 * @throws ClassCastException if the element is not {@link Comparable}.
121 */
122 public static <T extends Comparable<? super T>> Range<T> is(final T element) {
123 return of(element, element, null);
124 }
125
126 /**
127 * Creates a range using the specified element as both the minimum
128 * and maximum in this range.
129 *
130 * <p>The range uses the specified {@link Comparator} to determine where
131 * values lie in the range.</p>
132 *
133 * @param <T> the type of the elements in this range.
134 * @param element the value to use for this range, must not be {@code null}.
135 * @param comparator the comparator to be used, null for natural ordering.
136 * @return the range object, not null.
137 * @throws NullPointerException if the element is null.
138 * @throws ClassCastException if using natural ordering and the elements are not {@link Comparable}.
139 */
140 public static <T> Range<T> is(final T element, final Comparator<T> comparator) {
141 return of(element, element, comparator);
142 }
143
144 /**
145 * Creates a range with the specified minimum and maximum values (both inclusive).
146 *
147 * <p>The range uses the natural ordering of the elements to determine where
148 * values lie in the range.</p>
149 *
150 * <p>The arguments may be passed in the order (min, max) or (max, min).
151 * The getMinimum and getMaximum methods will return the correct values.</p>
152 *
153 * @param <T> the type of the elements in this range.
154 * @param fromInclusive the first value that defines the edge of the range, inclusive.
155 * @param toInclusive the second value that defines the edge of the range, inclusive.
156 * @return the range object, not null.
157 * @throws NullPointerException if either element is null.
158 * @throws ClassCastException if the elements are not {@link Comparable}.
159 * @since 3.13.0
160 */
161 public static <T extends Comparable<? super T>> Range<T> of(final T fromInclusive, final T toInclusive) {
162 return of(fromInclusive, toInclusive, null);
163 }
164
165 /**
166 * Creates a range with the specified minimum and maximum values (both inclusive).
167 *
168 * <p>The range uses the specified {@link Comparator} to determine where
169 * values lie in the range.</p>
170 *
171 * <p>The arguments may be passed in the order (min, max) or (max, min).
172 * The getMinimum and getMaximum methods will return the correct values.</p>
173 *
174 * @param <T> the type of the elements in this range.
175 * @param fromInclusive the first value that defines the edge of the range, inclusive.
176 * @param toInclusive the second value that defines the edge of the range, inclusive.
177 * @param comparator the comparator to be used, null for natural ordering.
178 * @return the range object, not null.
179 * @throws NullPointerException when fromInclusive is null.
180 * @throws NullPointerException when toInclusive is null.
181 * @throws ClassCastException if using natural ordering and the elements are not {@link Comparable}.
182 * @since 3.13.0
183 */
184 public static <T> Range<T> of(final T fromInclusive, final T toInclusive, final Comparator<T> comparator) {
185 return new Range<>(fromInclusive, toInclusive, comparator);
186 }
187
188 /**
189 * The ordering scheme used in this range.
190 */
191 private final Comparator<T> comparator;
192
193 /**
194 * Cached output hashCode (class is immutable).
195 */
196 private final int hashCode;
197
198 /**
199 * The maximum value in this range (inclusive).
200 */
201 private final T maximum;
202
203 /**
204 * The minimum value in this range (inclusive).
205 */
206 private final T minimum;
207
208 /**
209 * Cached output toString (class is immutable).
210 */
211 private transient String toString;
212
213 /**
214 * Creates an instance.
215 *
216 * @param element1 the first element, not null.
217 * @param element2 the second element, not null
218 * @param comp the comparator to be used, null for natural ordering.
219 * @throws NullPointerException when element1 is null.
220 * @throws NullPointerException when element2 is null.
221 */
222 @SuppressWarnings("unchecked")
223 Range(final T element1, final T element2, final Comparator<T> comp) {
224 Objects.requireNonNull(element1, "element1");
225 Objects.requireNonNull(element2, "element2");
226 if (comp == null) {
227 this.comparator = ComparableComparator.INSTANCE;
228 } else {
229 this.comparator = comp;
230 }
231 if (this.comparator.compare(element1, element2) < 1) {
232 this.minimum = element1;
233 this.maximum = element2;
234 } else {
235 this.minimum = element2;
236 this.maximum = element1;
237 }
238 this.hashCode = Objects.hash(minimum, maximum);
239 }
240
241 /**
242 * Checks whether the specified element occurs within this range.
243 *
244 * @param element the element to check for, null returns false.
245 * @return true if the specified element occurs within this range.
246 */
247 public boolean contains(final T element) {
248 if (element == null) {
249 return false;
250 }
251 return comparator.compare(element, minimum) > -1 && comparator.compare(element, maximum) < 1;
252 }
253
254 /**
255 * Checks whether this range contains all the elements of the specified range.
256 *
257 * <p>This method may fail if the ranges have two different comparators or element types.</p>
258 *
259 * @param otherRange the range to check, null returns false.
260 * @return true if this range contains the specified range.
261 * @throws RuntimeException if ranges cannot be compared.
262 */
263 public boolean containsRange(final Range<T> otherRange) {
264 if (otherRange == null) {
265 return false;
266 }
267 return contains(otherRange.minimum)
268 && contains(otherRange.maximum);
269 }
270
271 /**
272 * Checks where the specified element occurs relative to this range.
273 *
274 * <p>The API is reminiscent of the Comparable interface returning {@code -1} if
275 * the element is before the range, {@code 0} if contained within the range and
276 * {@code 1} if the element is after the range.</p>
277 *
278 * @param element the element to check for, not null.
279 * @return -1, 0 or +1 depending on the element's location relative to the range.
280 * @throws NullPointerException if {@code element} is {@code null}.
281 */
282 public int elementCompareTo(final T element) {
283 // Comparable API says throw NPE on null
284 Objects.requireNonNull(element, "element");
285 if (isAfter(element)) {
286 return -1;
287 }
288 if (isBefore(element)) {
289 return 1;
290 }
291 return 0;
292 }
293
294 /**
295 * Compares this range to another object to test if they are equal.
296 *
297 * <p>To be equal, the minimum and maximum values must be equal, which
298 * ignores any differences in the comparator.</p>
299 *
300 * @param obj the reference object with which to compare.
301 * @return true if this object is equal.
302 */
303 @Override
304 public boolean equals(final Object obj) {
305 if (obj == this) {
306 return true;
307 }
308 if (obj == null || obj.getClass() != getClass()) {
309 return false;
310 }
311 @SuppressWarnings("unchecked") // OK because we checked the class above
312 final
313 Range<T> range = (Range<T>) obj;
314 return minimum.equals(range.minimum) &&
315 maximum.equals(range.maximum);
316 }
317
318 /**
319 * Fits the given element into this range by returning the given element or, if out of bounds, the range minimum if
320 * below, or the range maximum if above.
321 *
322 * <pre>{@code
323 * Range<Integer> range = Range.between(16, 64);
324 * range.fit(-9) --> 16
325 * range.fit(0) --> 16
326 * range.fit(15) --> 16
327 * range.fit(16) --> 16
328 * range.fit(17) --> 17
329 * ...
330 * range.fit(63) --> 63
331 * range.fit(64) --> 64
332 * range.fit(99) --> 64
333 * }</pre>
334 *
335 * @param element the element to check for, not null.
336 * @return the minimum, the element, or the maximum depending on the element's location relative to the range.
337 * @throws NullPointerException if {@code element} is {@code null}.
338 * @since 3.10
339 */
340 public T fit(final T element) {
341 // Comparable API says throw NPE on null
342 Objects.requireNonNull(element, "element");
343 if (isAfter(element)) {
344 return minimum;
345 }
346 if (isBefore(element)) {
347 return maximum;
348 }
349 return element;
350 }
351
352 /**
353 * Gets the comparator being used to determine if objects are within the range.
354 *
355 * <p>Natural ordering uses an internal comparator implementation, thus this
356 * method never returns null. See {@link #isNaturalOrdering()}.</p>
357 *
358 * @return the comparator being used, not null.
359 */
360 public Comparator<T> getComparator() {
361 return comparator;
362 }
363
364 /**
365 * Gets the maximum value in this range.
366 *
367 * @return the maximum value in this range, not null.
368 */
369 public T getMaximum() {
370 return maximum;
371 }
372
373 /**
374 * Gets the minimum value in this range.
375 *
376 * @return the minimum value in this range, not null.
377 */
378 public T getMinimum() {
379 return minimum;
380 }
381
382 /**
383 * Gets a suitable hash code for the range.
384 *
385 * @return a hash code value for this object.
386 */
387 @Override
388 public int hashCode() {
389 return hashCode;
390 }
391
392 /**
393 * Calculate the intersection of {@code this} and an overlapping Range.
394 *
395 * @param other overlapping Range.
396 * @return range representing the intersection of {@code this} and {@code other} ({@code this} if equal).
397 * @throws IllegalArgumentException if {@code other} does not overlap {@code this}.
398 * @since 3.0.1
399 */
400 public Range<T> intersectionWith(final Range<T> other) {
401 if (!this.isOverlappedBy(other)) {
402 throw new IllegalArgumentException(String.format(
403 "Cannot calculate intersection with non-overlapping range %s", other));
404 }
405 if (this.equals(other)) {
406 return this;
407 }
408 final T min = getComparator().compare(minimum, other.minimum) < 0 ? other.minimum : minimum;
409 final T max = getComparator().compare(maximum, other.maximum) < 0 ? maximum : other.maximum;
410 return of(min, max, getComparator());
411 }
412
413 /**
414 * Checks whether this range is after the specified element.
415 *
416 * @param element the element to check for, null returns false.
417 * @return true if this range is entirely after the specified element.
418 */
419 public boolean isAfter(final T element) {
420 if (element == null) {
421 return false;
422 }
423 return comparator.compare(element, minimum) < 0;
424 }
425
426 /**
427 * Checks whether this range is completely after the specified range.
428 *
429 * <p>This method may fail if the ranges have two different comparators or element types.</p>
430 *
431 * @param otherRange the range to check, null returns false.
432 * @return true if this range is completely after the specified range.
433 * @throws RuntimeException if ranges cannot be compared.
434 */
435 public boolean isAfterRange(final Range<T> otherRange) {
436 if (otherRange == null) {
437 return false;
438 }
439 return isAfter(otherRange.maximum);
440 }
441
442 /**
443 * Checks whether this range is before the specified element.
444 *
445 * @param element the element to check for, null returns false.
446 * @return true if this range is entirely before the specified element.
447 */
448 public boolean isBefore(final T element) {
449 if (element == null) {
450 return false;
451 }
452 return comparator.compare(element, maximum) > 0;
453 }
454
455 /**
456 * Checks whether this range is completely before the specified range.
457 *
458 * <p>This method may fail if the ranges have two different comparators or element types.</p>
459 *
460 * @param otherRange the range to check, null returns false.
461 * @return true if this range is completely before the specified range.
462 * @throws RuntimeException if ranges cannot be compared.
463 */
464 public boolean isBeforeRange(final Range<T> otherRange) {
465 if (otherRange == null) {
466 return false;
467 }
468 return isBefore(otherRange.minimum);
469 }
470
471 /**
472 * Checks whether this range ends with the specified element.
473 *
474 * @param element the element to check for, null returns false.
475 * @return true if the specified element occurs within this range.
476 */
477 public boolean isEndedBy(final T element) {
478 if (element == null) {
479 return false;
480 }
481 return comparator.compare(element, maximum) == 0;
482 }
483
484 /**
485 * Tests whether or not the Range is using the natural ordering of the elements.
486 *
487 * <p>Natural ordering uses an internal comparator implementation, thus this
488 * method is the only way to check if a null comparator was specified.</p>
489 *
490 * @return true if using natural ordering.
491 */
492 public boolean isNaturalOrdering() {
493 return comparator == ComparableComparator.INSTANCE;
494 }
495
496 /**
497 * Tests whether this range is overlapped by the specified range.
498 *
499 * <p>Two ranges overlap if there is at least one element in common.</p>
500 *
501 * <p>This method may fail if the ranges have two different comparators or element types.</p>
502 *
503 * @param otherRange the range to test, null returns false.
504 * @return true if the specified range overlaps with this
505 * range; otherwise, {@code false}.
506 * @throws RuntimeException if ranges cannot be compared.
507 */
508 public boolean isOverlappedBy(final Range<T> otherRange) {
509 if (otherRange == null) {
510 return false;
511 }
512 return otherRange.contains(minimum)
513 || otherRange.contains(maximum)
514 || contains(otherRange.minimum);
515 }
516
517 /**
518 * Tests whether this range starts with the specified element.
519 *
520 * @param element the element to check for, null returns false.
521 * @return true if the specified element occurs within this range.
522 */
523 public boolean isStartedBy(final T element) {
524 if (element == null) {
525 return false;
526 }
527 return comparator.compare(element, minimum) == 0;
528 }
529
530 /**
531 * Gets the range as a {@link String}.
532 *
533 * <p>The format of the String is '[<em>min</em>..<em>max</em>]'.</p>
534 *
535 * @return the {@link String} representation of this range.
536 */
537 @Override
538 public String toString() {
539 if (toString == null) {
540 toString = "[" + minimum + ".." + maximum + "]";
541 }
542 return toString;
543 }
544
545 /**
546 * Formats the receiver using the given format.
547 *
548 * <p>This uses {@link java.util.Formattable} to perform the formatting. Three variables may
549 * be used to embed the minimum, maximum and comparator.
550 * Use {@code %1$s} for the minimum element, {@code %2$s} for the maximum element
551 * and {@code %3$s} for the comparator.
552 * The default format used by {@code toString()} is {@code [%1$s..%2$s]}.</p>
553 *
554 * @param format the format string, optionally containing {@code %1$s}, {@code %2$s} and {@code %3$s}, not null.
555 * @return the formatted string, not null.
556 */
557 public String toString(final String format) {
558 return String.format(format, minimum, maximum, comparator);
559 }
560
561 }