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&lt;Integer&gt; range = Range.between(16, 64);
462     * range.fit(-9) --&gt;  16
463     * range.fit(0)  --&gt;  16
464     * range.fit(15) --&gt;  16
465     * range.fit(16) --&gt;  16
466     * range.fit(17) --&gt;  17
467     * ...
468     * range.fit(63) --&gt;  63
469     * range.fit(64) --&gt;  64
470     * range.fit(99) --&gt;  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}