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 */ 034public class Range<T> implements Serializable { 035 036 @SuppressWarnings({"rawtypes", "unchecked"}) 037 private enum ComparableComparator implements Comparator { 038 INSTANCE; 039 040 /** 041 * Comparable based compare implementation. 042 * 043 * @param obj1 left-hand side side of comparison. 044 * @param obj2 right-hand side side of comparison. 045 * @return negative, 0, positive comparison value. 046 */ 047 @Override 048 public int compare(final Object obj1, final Object obj2) { 049 return ((Comparable) obj1).compareTo(obj2); 050 } 051 } 052 053 /** 054 * Serialization version. 055 * 056 * @see java.io.Serializable 057 */ 058 private static final long serialVersionUID = 1L; 059 060 /** 061 * Creates a range with the specified minimum and maximum values (both inclusive). 062 * 063 * <p>The range uses the natural ordering of the elements to determine where 064 * values lie in the range.</p> 065 * 066 * <p>The arguments may be passed in the order (min,max) or (max,min). 067 * The getMinimum and getMaximum methods will return the correct values.</p> 068 * 069 * @param <T> the type of the elements in this range. 070 * @param fromInclusive the first value that defines the edge of the range, inclusive. 071 * @param toInclusive the second value that defines the edge of the range, inclusive. 072 * @return the range object, not null. 073 * @throws NullPointerException when fromInclusive is null. 074 * @throws NullPointerException when toInclusive is null. 075 * @throws ClassCastException if the elements are not {@link Comparable}. 076 * @deprecated Use {@link #of(Comparable, Comparable)}. 077 */ 078 @Deprecated 079 public static <T extends Comparable<? super T>> Range<T> between(final T fromInclusive, final T toInclusive) { 080 return of(fromInclusive, toInclusive, null); 081 } 082 083 /** 084 * Creates a range with the specified minimum and maximum values (both inclusive). 085 * 086 * <p>The range uses the specified {@link Comparator} to determine where 087 * values lie in the range.</p> 088 * 089 * <p>The arguments may be passed in the order (min,max) or (max,min). 090 * The getMinimum and getMaximum methods will return the correct values.</p> 091 * 092 * @param <T> the type of the elements in this range. 093 * @param fromInclusive the first value that defines the edge of the range, inclusive. 094 * @param toInclusive the second value that defines the edge of the range, inclusive. 095 * @param comparator the comparator to be used, null for natural ordering. 096 * @return the range object, not null. 097 * @throws NullPointerException when fromInclusive is null. 098 * @throws NullPointerException when toInclusive is null. 099 * @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}