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;
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     * @param element the element to check for, not null
332     * @return the minimum, the element, or the maximum depending on the element's location relative to the range
333     * @throws NullPointerException if {@code element} is {@code null}
334     * @since 3.10
335     */
336    public T fit(final T element) {
337        // Comparable API says throw NPE on null
338        Objects.requireNonNull(element, "element");
339        if (isAfter(element)) {
340            return minimum;
341        }
342        if (isBefore(element)) {
343            return maximum;
344        }
345        return element;
346    }
347
348    /**
349     * Gets the comparator being used to determine if objects are within the range.
350     *
351     * <p>Natural ordering uses an internal comparator implementation, thus this
352     * method never returns null. See {@link #isNaturalOrdering()}.</p>
353     *
354     * @return the comparator being used, not null
355     */
356    public Comparator<T> getComparator() {
357        return comparator;
358    }
359
360    /**
361     * Gets the maximum value in this range.
362     *
363     * @return the maximum value in this range, not null
364     */
365    public T getMaximum() {
366        return maximum;
367    }
368
369    /**
370     * Gets the minimum value in this range.
371     *
372     * @return the minimum value in this range, not null
373     */
374    public T getMinimum() {
375        return minimum;
376    }
377
378    /**
379     * Gets a suitable hash code for the range.
380     *
381     * @return a hash code value for this object
382     */
383    @Override
384    public int hashCode() {
385        int result = hashCode;
386        if (hashCode == 0) {
387            result = 17;
388            result = 37 * result + getClass().hashCode();
389            result = 37 * result + minimum.hashCode();
390            result = 37 * result + maximum.hashCode();
391            hashCode = result;
392        }
393        return result;
394    }
395
396    /**
397     * Calculate the intersection of {@code this} and an overlapping Range.
398     * @param other overlapping Range
399     * @return range representing the intersection of {@code this} and {@code other} ({@code this} if equal)
400     * @throws IllegalArgumentException if {@code other} does not overlap {@code this}
401     * @since 3.0.1
402     */
403    public Range<T> intersectionWith(final Range<T> other) {
404        if (!this.isOverlappedBy(other)) {
405            throw new IllegalArgumentException(String.format(
406                "Cannot calculate intersection with non-overlapping range %s", other));
407        }
408        if (this.equals(other)) {
409            return this;
410        }
411        final T min = getComparator().compare(minimum, other.minimum) < 0 ? other.minimum : minimum;
412        final T max = getComparator().compare(maximum, other.maximum) < 0 ? maximum : other.maximum;
413        return of(min, max, getComparator());
414    }
415
416    /**
417     * Checks whether this range is after the specified element.
418     *
419     * @param element  the element to check for, null returns false
420     * @return true if this range is entirely after the specified element
421     */
422    public boolean isAfter(final T element) {
423        if (element == null) {
424            return false;
425        }
426        return comparator.compare(element, minimum) < 0;
427    }
428
429    /**
430     * Checks whether this range is completely after the specified range.
431     *
432     * <p>This method may fail if the ranges have two different comparators or element types.</p>
433     *
434     * @param otherRange  the range to check, null returns false
435     * @return true if this range is completely after the specified range
436     * @throws RuntimeException if ranges cannot be compared
437     */
438    public boolean isAfterRange(final Range<T> otherRange) {
439        if (otherRange == null) {
440            return false;
441        }
442        return isAfter(otherRange.maximum);
443    }
444
445    /**
446     * Checks whether this range is before the specified element.
447     *
448     * @param element  the element to check for, null returns false
449     * @return true if this range is entirely before the specified element
450     */
451    public boolean isBefore(final T element) {
452        if (element == null) {
453            return false;
454        }
455        return comparator.compare(element, maximum) > 0;
456    }
457
458    /**
459     * Checks whether this range is completely before the specified range.
460     *
461     * <p>This method may fail if the ranges have two different comparators or element types.</p>
462     *
463     * @param otherRange  the range to check, null returns false
464     * @return true if this range is completely before the specified range
465     * @throws RuntimeException if ranges cannot be compared
466     */
467    public boolean isBeforeRange(final Range<T> otherRange) {
468        if (otherRange == null) {
469            return false;
470        }
471        return isBefore(otherRange.minimum);
472    }
473
474    /**
475     * Checks whether this range ends with the specified element.
476     *
477     * @param element  the element to check for, null returns false
478     * @return true if the specified element occurs within this range
479     */
480    public boolean isEndedBy(final T element) {
481        if (element == null) {
482            return false;
483        }
484        return comparator.compare(element, maximum) == 0;
485    }
486
487    /**
488     * Whether or not the Range is using the natural ordering of the elements.
489     *
490     * <p>Natural ordering uses an internal comparator implementation, thus this
491     * method is the only way to check if a null comparator was specified.</p>
492     *
493     * @return true if using natural ordering
494     */
495    public boolean isNaturalOrdering() {
496        return comparator == ComparableComparator.INSTANCE;
497    }
498
499    /**
500     * Checks whether this range is overlapped by the specified range.
501     *
502     * <p>Two ranges overlap if there is at least one element in common.</p>
503     *
504     * <p>This method may fail if the ranges have two different comparators or element types.</p>
505     *
506     * @param otherRange  the range to test, null returns false
507     * @return true if the specified range overlaps with this
508     *  range; otherwise, {@code false}
509     * @throws RuntimeException if ranges cannot be compared
510     */
511    public boolean isOverlappedBy(final Range<T> otherRange) {
512        if (otherRange == null) {
513            return false;
514        }
515        return otherRange.contains(minimum)
516            || otherRange.contains(maximum)
517            || contains(otherRange.minimum);
518    }
519
520    /**
521     * Checks whether this range starts with the specified element.
522     *
523     * @param element  the element to check for, null returns false
524     * @return true if the specified element occurs within this range
525     */
526    public boolean isStartedBy(final T element) {
527        if (element == null) {
528            return false;
529        }
530        return comparator.compare(element, minimum) == 0;
531    }
532
533    /**
534     * Gets the range as a {@link String}.
535     *
536     * <p>The format of the String is '[<em>min</em>..<em>max</em>]'.</p>
537     *
538     * @return the {@link String} representation of this range
539     */
540    @Override
541    public String toString() {
542        if (toString == null) {
543            toString = "[" + minimum + ".." + maximum + "]";
544        }
545        return toString;
546    }
547
548    /**
549     * Formats the receiver using the given format.
550     *
551     * <p>This uses {@link java.util.Formattable} to perform the formatting. Three variables may
552     * be used to embed the minimum, maximum and comparator.
553     * Use {@code %1$s} for the minimum element, {@code %2$s} for the maximum element
554     * and {@code %3$s} for the comparator.
555     * The default format used by {@code toString()} is {@code [%1$s..%2$s]}.</p>
556     *
557     * @param format  the format string, optionally containing {@code %1$s}, {@code %2$s} and  {@code %3$s}, not null
558     * @return the formatted string, not null
559     */
560    public String toString(final String format) {
561        return String.format(format, minimum, maximum, comparator);
562    }
563
564}