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 */
017 package org.apache.commons.lang3;
018
019 import java.io.Serializable;
020 import 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 * @since 3.0
031 * @version $Id: Range.java 1147537 2011-07-17 06:10:37Z mbenson $
032 */
033 public final class Range<T> implements Serializable {
034
035 /**
036 * Serialization version.
037 * @see java.io.Serializable
038 */
039 private static final long serialVersionUID = 1L;
040
041 /**
042 * The ordering scheme used in this range.
043 */
044 private final Comparator<T> comparator;
045 /**
046 * The minimum value in this range (inclusive).
047 */
048 private final T minimum;
049 /**
050 * The maximum value in this range (inclusive).
051 */
052 private final T maximum;
053 /**
054 * Cached output hashCode (class is immutable).
055 */
056 private transient int hashCode;
057 /**
058 * Cached output toString (class is immutable).
059 */
060 private transient String toString;
061
062 /**
063 * <p>Obtains a range using the specified element as both the minimum
064 * and maximum in this range.</p>
065 *
066 * <p>The range uses the natural ordering of the elements to determine where
067 * values lie in the range.</p>
068 *
069 * @param <T> the type of the elements in this range
070 * @param element the value to use for this range, not null
071 * @return the range object, not null
072 * @throws IllegalArgumentException if the element is null
073 * @throws ClassCastException if the element is not {@code Comparable}
074 */
075 public static <T extends Comparable<T>> Range<T> is(T element) {
076 return between(element, element, null);
077 }
078
079 /**
080 * <p>Obtains a range using the specified element as both the minimum
081 * and maximum in this range.</p>
082 *
083 * <p>The range uses the specified {@code Comparator} to determine where
084 * values lie in the range.</p>
085 *
086 * @param <T> the type of the elements in this range
087 * @param element the value to use for this range, must not be {@code null}
088 * @param comparator the comparator to be used, null for natural ordering
089 * @return the range object, not null
090 * @throws IllegalArgumentException if the element is null
091 * @throws ClassCastException if using natural ordering and the elements are not {@code Comparable}
092 */
093 public static <T> Range<T> is(T element, Comparator<T> comparator) {
094 return between(element, element, comparator);
095 }
096
097 /**
098 * <p>Obtains a range with the specified minimum and maximum values (both inclusive).</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 * <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(T fromInclusive, 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(T fromInclusive, T toInclusive, 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(T element1, 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(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(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(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(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(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(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(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(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(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(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(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 T min = getComparator().compare(minimum, other.minimum) < 0 ? other.minimum : minimum;
390 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(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 Range<T> range = (Range<T>) obj;
415 return minimum.equals(range.minimum) &&
416 maximum.equals(range.maximum);
417 }
418 }
419
420 /**
421 * <p>Gets a suitable hash code for the range.</p>
422 *
423 * @return a hash code value for this object
424 */
425 @Override
426 public int hashCode() {
427 int result = hashCode;
428 if (hashCode == 0) {
429 result = 17;
430 result = 37 * result + getClass().hashCode();
431 result = 37 * result + minimum.hashCode();
432 result = 37 * result + maximum.hashCode();
433 hashCode = result;
434 }
435 return result;
436 }
437
438 /**
439 * <p>Gets the range as a {@code String}.</p>
440 *
441 * <p>The format of the String is '[<i>min</i>..<i>max</i>]'.</p>
442 *
443 * @return the {@code String} representation of this range
444 */
445 @Override
446 public String toString() {
447 String result = toString;
448 if (result == null) {
449 StringBuilder buf = new StringBuilder(32);
450 buf.append('[');
451 buf.append(minimum);
452 buf.append("..");
453 buf.append(maximum);
454 buf.append(']');
455 result = buf.toString();
456 toString = result;
457 }
458 return result;
459 }
460
461 /**
462 * <p>Formats the receiver using the given format.</p>
463 *
464 * <p>This uses {@link java.util.Formattable} to perform the formatting. Three variables may
465 * be used to embed the minimum, maximum and comparator.
466 * Use {@code %1$s} for the minimum element, {@code %2$s} for the maximum element
467 * and {@code %3$s} for the comparator.
468 * The default format used by {@code toString()} is {@code [%1$s..%2$s]}.</p>
469 *
470 * @param format the format string, optionally containing {@code %1$s}, {@code %2$s} and {@code %3$s}, not null
471 * @return the formatted string, not null
472 */
473 public String toString(String format) {
474 return String.format(format, minimum, maximum, comparator);
475 }
476
477 //-----------------------------------------------------------------------
478 @SuppressWarnings({"rawtypes", "unchecked"})
479 private enum ComparableComparator implements Comparator {
480 INSTANCE;
481 /**
482 * Comparable based compare implementation.
483 *
484 * @param obj1 left hand side of comparison
485 * @param obj2 right hand side of comparison
486 * @return negative, 0, positive comparison value
487 */
488 public int compare(Object obj1, Object obj2) {
489 return ((Comparable) obj1).compareTo(obj2);
490 }
491 }
492
493 }