001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.lang3; 018 019import java.io.Serializable; 020import java.util.Comparator; 021import java.util.Objects; 022 023/** 024 * An immutable range of objects from a minimum to maximum point inclusive. 025 * 026 * <p>The objects need to either be implementations of {@link Comparable} 027 * or you need to supply a {@link Comparator}.</p> 028 * 029 * <p>#ThreadSafe# if the objects and comparator are thread-safe.</p> 030 * 031 * @param <T> The type of range values. 032 * @since 3.0 033 * @since 3.21.0 {@code serialVersionUID} changed from {@code 1L} to {@code 2L}. 034 */ 035public class Range<T> implements Serializable { 036 037 @SuppressWarnings({"rawtypes", "unchecked"}) 038 private enum ComparableComparator implements Comparator { 039 INSTANCE; 040 041 /** 042 * Comparable based compare implementation. 043 * 044 * @param obj1 left-hand side side of comparison. 045 * @param obj2 right-hand side side of comparison. 046 * @return negative, 0, positive comparison value. 047 */ 048 @Override 049 public int compare(final Object obj1, final Object obj2) { 050 return ((Comparable) obj1).compareTo(obj2); 051 } 052 } 053 054 /** 055 * Serialization version. 056 * 057 * @see java.io.Serializable 058 * @since 3.21.0 {@code serialVersionUID} changed from {@code 1L} to {@value}. 059 */ 060 private static final long serialVersionUID = 2L; 061 062 /** 063 * Creates a range with the specified minimum and maximum values (both inclusive). 064 * 065 * <p>The range uses the natural ordering of the elements to determine where 066 * values lie in the range.</p> 067 * 068 * <p>The arguments may be passed in the order (min, max) or (max, min). 069 * The getMinimum and getMaximum methods will return the correct values.</p> 070 * 071 * @param <T> the type of the elements in this range. 072 * @param fromInclusive the first value that defines the edge of the range, inclusive. 073 * @param toInclusive the second value that defines the edge of the range, inclusive. 074 * @return the range object, not null. 075 * @throws NullPointerException when fromInclusive is null. 076 * @throws NullPointerException when toInclusive is null. 077 * @throws ClassCastException if the elements are not {@link Comparable}. 078 * @deprecated Use {@link #of(Comparable, Comparable)}. 079 */ 080 @Deprecated 081 public static <T extends Comparable<? super T>> Range<T> between(final T fromInclusive, final T toInclusive) { 082 return of(fromInclusive, toInclusive, null); 083 } 084 085 /** 086 * Creates a range with the specified minimum and maximum values (both inclusive). 087 * 088 * <p>The range uses the specified {@link Comparator} to determine where 089 * values lie in the range.</p> 090 * 091 * <p>The arguments may be passed in the order (min, max) or (max, min). 092 * The getMinimum and getMaximum methods will return the correct values.</p> 093 * 094 * @param <T> the type of the elements in this range. 095 * @param fromInclusive the first value that defines the edge of the range, inclusive. 096 * @param toInclusive the second value that defines the edge of the range, inclusive. 097 * @param comparator the comparator to be used, null for natural ordering. 098 * @return the range object, not null. 099 * @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}