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