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 * http://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; 021 022/** 023 * <p>An immutable range of objects from a minimum to maximum point inclusive.</p> 024 * 025 * <p>The objects need to either be implementations of {@code Comparable} 026 * or you need to supply a {@code Comparator}. </p> 027 * 028 * <p>#ThreadSafe# if the objects and comparator are thread-safe</p> 029 * 030 * @param <T> The type of range values. 031 * @since 3.0 032 */ 033public final class Range<T> implements Serializable { 034 035 //----------------------------------------------------------------------- 036 @SuppressWarnings({"rawtypes", "unchecked"}) 037 private enum ComparableComparator implements Comparator { 038 INSTANCE; 039 /** 040 * Comparable based compare implementation. 041 * 042 * @param obj1 left hand side of comparison 043 * @param obj2 right hand side of comparison 044 * @return negative, 0, positive comparison value 045 */ 046 @Override 047 public int compare(final Object obj1, final Object obj2) { 048 return ((Comparable) obj1).compareTo(obj2); 049 } 050 } 051 052 /** 053 * Serialization version. 054 * @see java.io.Serializable 055 */ 056 private static final long serialVersionUID = 1L; 057 /** 058 * <p>Obtains a range with the specified minimum and maximum values (both inclusive).</p> 059 * 060 * <p>The range uses the natural ordering of the elements to determine where 061 * values lie in the range.</p> 062 * 063 * <p>The arguments may be passed in the order (min,max) or (max,min). 064 * The getMinimum and getMaximum methods will return the correct values.</p> 065 * 066 * @param <T> the type of the elements in this range 067 * @param fromInclusive the first value that defines the edge of the range, inclusive 068 * @param toInclusive the second value that defines the edge of the range, inclusive 069 * @return the range object, not null 070 * @throws IllegalArgumentException if either element is null 071 * @throws ClassCastException if the elements are not {@code Comparable} 072 */ 073 public static <T extends Comparable<T>> Range<T> between(final T fromInclusive, final T toInclusive) { 074 return between(fromInclusive, toInclusive, null); 075 } 076 /** 077 * <p>Obtains a range with the specified minimum and maximum values (both inclusive).</p> 078 * 079 * <p>The range uses the specified {@code Comparator} to determine where 080 * values lie in the range.</p> 081 * 082 * <p>The arguments may be passed in the order (min,max) or (max,min). 083 * The getMinimum and getMaximum methods will return the correct values.</p> 084 * 085 * @param <T> the type of the elements in this range 086 * @param fromInclusive the first value that defines the edge of the range, inclusive 087 * @param toInclusive the second value that defines the edge of the range, inclusive 088 * @param comparator the comparator to be used, null for natural ordering 089 * @return the range object, not null 090 * @throws IllegalArgumentException if either element is null 091 * @throws ClassCastException if using natural ordering and the elements are not {@code Comparable} 092 */ 093 public static <T> Range<T> between(final T fromInclusive, final T toInclusive, final Comparator<T> comparator) { 094 return new Range<>(fromInclusive, toInclusive, comparator); 095 } 096 /** 097 * <p>Obtains a range using the specified element as both the minimum 098 * and maximum in this range.</p> 099 * 100 * <p>The range uses the natural ordering of the elements to determine where 101 * values lie in the range.</p> 102 * 103 * @param <T> the type of the elements in this range 104 * @param element the value to use for this range, not null 105 * @return the range object, not null 106 * @throws IllegalArgumentException if the element is null 107 * @throws ClassCastException if the element is not {@code Comparable} 108 */ 109 public static <T extends Comparable<T>> Range<T> is(final T element) { 110 return between(element, element, null); 111 } 112 /** 113 * <p>Obtains a range using the specified element as both the minimum 114 * and maximum in this range.</p> 115 * 116 * <p>The range uses the specified {@code Comparator} to determine where 117 * values lie in the range.</p> 118 * 119 * @param <T> the type of the elements in this range 120 * @param element the value to use for this range, must not be {@code null} 121 * @param comparator the comparator to be used, null for natural ordering 122 * @return the range object, not null 123 * @throws IllegalArgumentException if the element is null 124 * @throws ClassCastException if using natural ordering and the elements are not {@code Comparable} 125 */ 126 public static <T> Range<T> is(final T element, final Comparator<T> comparator) { 127 return between(element, element, comparator); 128 } 129 130 /** 131 * The ordering scheme used in this range. 132 */ 133 private final Comparator<T> comparator; 134 135 /** 136 * Cached output hashCode (class is immutable). 137 */ 138 private transient int hashCode; 139 140 /** 141 * The maximum value in this range (inclusive). 142 */ 143 private final T maximum; 144 145 /** 146 * The minimum value in this range (inclusive). 147 */ 148 private final T minimum; 149 150 /** 151 * Cached output toString (class is immutable). 152 */ 153 private transient String toString; 154 155 // Accessors 156 //-------------------------------------------------------------------- 157 158 /** 159 * Creates an instance. 160 * 161 * @param element1 the first element, not null 162 * @param element2 the second element, not null 163 * @param comp the comparator to be used, null for natural ordering 164 */ 165 @SuppressWarnings("unchecked") 166 private Range(final T element1, final T element2, final Comparator<T> comp) { 167 if (element1 == null || element2 == null) { 168 throw new IllegalArgumentException("Elements in a range must not be null: element1=" + 169 element1 + ", element2=" + element2); 170 } 171 if (comp == null) { 172 this.comparator = ComparableComparator.INSTANCE; 173 } else { 174 this.comparator = comp; 175 } 176 if (this.comparator.compare(element1, element2) < 1) { 177 this.minimum = element1; 178 this.maximum = element2; 179 } else { 180 this.minimum = element2; 181 this.maximum = element1; 182 } 183 } 184 185 /** 186 * <p>Checks whether the specified element occurs within this range.</p> 187 * 188 * @param element the element to check for, null returns false 189 * @return true if the specified element occurs within this range 190 */ 191 public boolean contains(final T element) { 192 if (element == null) { 193 return false; 194 } 195 return comparator.compare(element, minimum) > -1 && comparator.compare(element, maximum) < 1; 196 } 197 198 /** 199 * <p>Checks whether this range contains all the elements of the specified range.</p> 200 * 201 * <p>This method may fail if the ranges have two different comparators or element types.</p> 202 * 203 * @param otherRange the range to check, null returns false 204 * @return true if this range contains the specified range 205 * @throws RuntimeException if ranges cannot be compared 206 */ 207 public boolean containsRange(final Range<T> otherRange) { 208 if (otherRange == null) { 209 return false; 210 } 211 return contains(otherRange.minimum) 212 && contains(otherRange.maximum); 213 } 214 215 /** 216 * <p>Checks where the specified element occurs relative to this range.</p> 217 * 218 * <p>The API is reminiscent of the Comparable interface returning {@code -1} if 219 * the element is before the range, {@code 0} if contained within the range and 220 * {@code 1} if the element is after the range. </p> 221 * 222 * @param element the element to check for, not null 223 * @return -1, 0 or +1 depending on the element's location relative to the range 224 */ 225 public int elementCompareTo(final T element) { 226 // Comparable API says throw NPE on null 227 Validate.notNull(element, "Element is null"); 228 if (isAfter(element)) { 229 return -1; 230 } else if (isBefore(element)) { 231 return 1; 232 } else { 233 return 0; 234 } 235 } 236 237 // Element tests 238 //-------------------------------------------------------------------- 239 240 /** 241 * <p>Compares this range to another object to test if they are equal.</p>. 242 * 243 * <p>To be equal, the minimum and maximum values must be equal, which 244 * ignores any differences in the comparator.</p> 245 * 246 * @param obj the reference object with which to compare 247 * @return true if this object is equal 248 */ 249 @Override 250 public boolean equals(final Object obj) { 251 if (obj == this) { 252 return true; 253 } else if (obj == null || obj.getClass() != getClass()) { 254 return false; 255 } else { 256 @SuppressWarnings("unchecked") // OK because we checked the class above 257 final 258 Range<T> range = (Range<T>) obj; 259 return minimum.equals(range.minimum) && 260 maximum.equals(range.maximum); 261 } 262 } 263 264 /** 265 * <p>Gets the comparator being used to determine if objects are within the range.</p> 266 * 267 * <p>Natural ordering uses an internal comparator implementation, thus this 268 * method never returns null. See {@link #isNaturalOrdering()}.</p> 269 * 270 * @return the comparator being used, not null 271 */ 272 public Comparator<T> getComparator() { 273 return comparator; 274 } 275 276 /** 277 * <p>Gets the maximum value in this range.</p> 278 * 279 * @return the maximum value in this range, not null 280 */ 281 public T getMaximum() { 282 return maximum; 283 } 284 285 /** 286 * <p>Gets the minimum value in this range.</p> 287 * 288 * @return the minimum value in this range, not null 289 */ 290 public T getMinimum() { 291 return minimum; 292 } 293 294 /** 295 * <p>Gets a suitable hash code for the range.</p> 296 * 297 * @return a hash code value for this object 298 */ 299 @Override 300 public int hashCode() { 301 int result = hashCode; 302 if (hashCode == 0) { 303 result = 17; 304 result = 37 * result + getClass().hashCode(); 305 result = 37 * result + minimum.hashCode(); 306 result = 37 * result + maximum.hashCode(); 307 hashCode = result; 308 } 309 return result; 310 } 311 312 /** 313 * Calculate the intersection of {@code this} and an overlapping Range. 314 * @param other overlapping Range 315 * @return range representing the intersection of {@code this} and {@code other} ({@code this} if equal) 316 * @throws IllegalArgumentException if {@code other} does not overlap {@code this} 317 * @since 3.0.1 318 */ 319 public Range<T> intersectionWith(final Range<T> other) { 320 if (!this.isOverlappedBy(other)) { 321 throw new IllegalArgumentException(String.format( 322 "Cannot calculate intersection with non-overlapping range %s", other)); 323 } 324 if (this.equals(other)) { 325 return this; 326 } 327 final T min = getComparator().compare(minimum, other.minimum) < 0 ? other.minimum : minimum; 328 final T max = getComparator().compare(maximum, other.maximum) < 0 ? maximum : other.maximum; 329 return between(min, max, getComparator()); 330 } 331 332 // Range tests 333 //-------------------------------------------------------------------- 334 335 /** 336 * <p>Checks whether this range is after the specified element.</p> 337 * 338 * @param element the element to check for, null returns false 339 * @return true if this range is entirely after the specified element 340 */ 341 public boolean isAfter(final T element) { 342 if (element == null) { 343 return false; 344 } 345 return comparator.compare(element, minimum) < 0; 346 } 347 348 /** 349 * <p>Checks whether this range is completely after the specified range.</p> 350 * 351 * <p>This method may fail if the ranges have two different comparators or element types.</p> 352 * 353 * @param otherRange the range to check, null returns false 354 * @return true if this range is completely after the specified range 355 * @throws RuntimeException if ranges cannot be compared 356 */ 357 public boolean isAfterRange(final Range<T> otherRange) { 358 if (otherRange == null) { 359 return false; 360 } 361 return isAfter(otherRange.maximum); 362 } 363 364 /** 365 * <p>Checks whether this range is before the specified element.</p> 366 * 367 * @param element the element to check for, null returns false 368 * @return true if this range is entirely before the specified element 369 */ 370 public boolean isBefore(final T element) { 371 if (element == null) { 372 return false; 373 } 374 return comparator.compare(element, maximum) > 0; 375 } 376 377 /** 378 * <p>Checks whether this range is completely before the specified range.</p> 379 * 380 * <p>This method may fail if the ranges have two different comparators or element types.</p> 381 * 382 * @param otherRange the range to check, null returns false 383 * @return true if this range is completely before the specified range 384 * @throws RuntimeException if ranges cannot be compared 385 */ 386 public boolean isBeforeRange(final Range<T> otherRange) { 387 if (otherRange == null) { 388 return false; 389 } 390 return isBefore(otherRange.minimum); 391 } 392 393 /** 394 * <p>Checks whether this range ends with the specified element.</p> 395 * 396 * @param element the element to check for, null returns false 397 * @return true if the specified element occurs within this range 398 */ 399 public boolean isEndedBy(final T element) { 400 if (element == null) { 401 return false; 402 } 403 return comparator.compare(element, maximum) == 0; 404 } 405 406 // Basics 407 //-------------------------------------------------------------------- 408 409 /** 410 * <p>Whether or not the Range is using the natural ordering of the elements.</p> 411 * 412 * <p>Natural ordering uses an internal comparator implementation, thus this 413 * method is the only way to check if a null comparator was specified.</p> 414 * 415 * @return true if using natural ordering 416 */ 417 public boolean isNaturalOrdering() { 418 return comparator == ComparableComparator.INSTANCE; 419 } 420 421 /** 422 * <p>Checks whether this range is overlapped by the specified range.</p> 423 * 424 * <p>Two ranges overlap if there is at least one element in common.</p> 425 * 426 * <p>This method may fail if the ranges have two different comparators or element types.</p> 427 * 428 * @param otherRange the range to test, null returns false 429 * @return true if the specified range overlaps with this 430 * range; otherwise, {@code false} 431 * @throws RuntimeException if ranges cannot be compared 432 */ 433 public boolean isOverlappedBy(final Range<T> otherRange) { 434 if (otherRange == null) { 435 return false; 436 } 437 return otherRange.contains(minimum) 438 || otherRange.contains(maximum) 439 || contains(otherRange.minimum); 440 } 441 442 /** 443 * <p>Checks whether this range starts with the specified element.</p> 444 * 445 * @param element the element to check for, null returns false 446 * @return true if the specified element occurs within this range 447 */ 448 public boolean isStartedBy(final T element) { 449 if (element == null) { 450 return false; 451 } 452 return comparator.compare(element, minimum) == 0; 453 } 454 455 /** 456 * <p> 457 * Fits the given element into this range by returning the given element or, if out of bounds, the range minimum if 458 * below, or the range maximum if above. 459 * </p> 460 * <pre> 461 * Range<Integer> range = Range.between(16, 64); 462 * range.fit(-9) --> 16 463 * range.fit(0) --> 16 464 * range.fit(15) --> 16 465 * range.fit(16) --> 16 466 * range.fit(17) --> 17 467 * ... 468 * range.fit(63) --> 63 469 * range.fit(64) --> 64 470 * range.fit(99) --> 64 471 * </pre> 472 * @param element the element to check for, not null 473 * @return the minimum, the element, or the maximum depending on the element's location relative to the range 474 * @since 3.10 475 */ 476 public T fit(final T element) { 477 // Comparable API says throw NPE on null 478 Validate.notNull(element, "element"); 479 if (isAfter(element)) { 480 return minimum; 481 } else if (isBefore(element)) { 482 return maximum; 483 } else { 484 return element; 485 } 486 } 487 488 /** 489 * <p>Gets the range as a {@code String}.</p> 490 * 491 * <p>The format of the String is '[<i>min</i>..<i>max</i>]'.</p> 492 * 493 * @return the {@code String} representation of this range 494 */ 495 @Override 496 public String toString() { 497 if (toString == null) { 498 toString = "[" + minimum + ".." + maximum + "]"; 499 } 500 return toString; 501 } 502 503 /** 504 * <p>Formats the receiver using the given format.</p> 505 * 506 * <p>This uses {@link java.util.Formattable} to perform the formatting. Three variables may 507 * be used to embed the minimum, maximum and comparator. 508 * Use {@code %1$s} for the minimum element, {@code %2$s} for the maximum element 509 * and {@code %3$s} for the comparator. 510 * The default format used by {@code toString()} is {@code [%1$s..%2$s]}.</p> 511 * 512 * @param format the format string, optionally containing {@code %1$s}, {@code %2$s} and {@code %3$s}, not null 513 * @return the formatted string, not null 514 */ 515 public String toString(final String format) { 516 return String.format(format, minimum, maximum, comparator); 517 } 518 519}