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