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 *      https://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.IOException;
020import java.io.InvalidObjectException;
021import java.io.ObjectInputStream;
022import java.io.Serializable;
023import java.util.Comparator;
024import java.util.Objects;
025
026/**
027 * An immutable range of objects from a minimum to maximum point inclusive.
028 *
029 * <p>
030 * The objects need to either be implementations of {@link Comparable}
031 * or you need to supply a {@link Comparator}.
032 * </p>
033 *
034 * <p>
035 * #ThreadSafe# if the objects and comparator are thread-safe.
036 * </p>
037 *
038 * @param <T> The type of range values.
039 * @since 3.0
040 */
041public class Range<T> implements Serializable {
042
043    @SuppressWarnings({"rawtypes", "unchecked"})
044    private enum ComparableComparator implements Comparator {
045        INSTANCE;
046
047        /**
048         * Comparable based compare implementation.
049         *
050         * @param obj1 left-hand side of comparison.
051         * @param obj2 right-hand side of comparison.
052         * @return negative, 0, positive comparison value.
053         */
054        @Override
055        public int compare(final Object obj1, final Object obj2) {
056            return ((Comparable) obj1).compareTo(obj2);
057        }
058    }
059
060    /**
061     * Serialization version.
062     *
063     * @see java.io.Serializable
064     */
065    private static final long serialVersionUID = 1L;
066
067    /**
068     * Creates a range with the specified minimum and maximum values (both inclusive).
069     *
070     * <p>
071     * The range uses the natural ordering of the elements to determine where
072     * values lie in the range.
073     * </p>
074     *
075     * <p>
076     * The arguments may be passed in the order (min, max) or (max, min).
077     * The getMinimum and getMaximum methods will return the correct values.
078     * </p>
079     *
080     * @param <T> The type of the elements in this range.
081     * @param fromInclusive  The first value that defines the edge of the range, inclusive.
082     * @param toInclusive  The second value that defines the edge of the range, inclusive.
083     * @return The range object, not null.
084     * @throws NullPointerException Thrown when fromInclusive is null.
085     * @throws NullPointerException Thrown when toInclusive is null.
086     * @throws ClassCastException Thrown if the elements are not {@link Comparable}.
087     * @throws IllegalArgumentException Thrown if either element is a floating-point NaN.
088     * @deprecated Use {@link #of(Comparable, Comparable)}.
089     */
090    @Deprecated
091    public static <T extends Comparable<? super T>> Range<T> between(final T fromInclusive, final T toInclusive) {
092        return of(fromInclusive, toInclusive, null);
093    }
094
095    /**
096     * Creates a range with the specified minimum and maximum values (both inclusive).
097     *
098     * <p>
099     * The range uses the specified {@link Comparator} to determine where
100     * values lie in the range.
101     * </p>
102     *
103     * <p>
104     * The arguments may be passed in the order (min, max) or (max, min).
105     * The getMinimum and getMaximum methods will return the correct values.
106     * </p>
107     *
108     * @param <T> The type of the elements in this range.
109     * @param fromInclusive  The first value that defines the edge of the range, inclusive.
110     * @param toInclusive  The second value that defines the edge of the range, inclusive.
111     * @param comparator  The comparator to be used, null for natural ordering.
112     * @return The range object, not null.
113     * @throws NullPointerException Thrown when fromInclusive is null.
114     * @throws NullPointerException Thrown when toInclusive is null.
115     * @throws ClassCastException Thrown if using natural ordering and the elements are not {@link Comparable}.
116     * @throws IllegalArgumentException Thrown if either element is a floating-point NaN.
117     * @deprecated Use {@link #of(Object, Object, Comparator)}.
118     */
119    @Deprecated
120    public static <T> Range<T> between(final T fromInclusive, final T toInclusive, final Comparator<T> comparator) {
121        return new Range<>(fromInclusive, toInclusive, comparator);
122    }
123
124    private static int hash(final Object value1, final Object value2) {
125        return Objects.hash(value1, value2);
126    }
127
128    /**
129     * Creates a range using the specified element as both the minimum
130     * and maximum in this range.
131     *
132     * <p>
133     * The range uses the natural ordering of the elements to determine where
134     * values lie in the range.
135     * </p>
136     *
137     * @param <T> The type of the elements in this range.
138     * @param element  The value to use for this range, not null.
139     * @return The range object, not null.
140     * @throws NullPointerException Thrown if the element is null.
141     * @throws ClassCastException Thrown if the element is not {@link Comparable}.
142     * @throws IllegalArgumentException Thrown if the element is a floating-point NaN.
143     */
144    public static <T extends Comparable<? super T>> Range<T> is(final T element) {
145        return of(element, element, null);
146    }
147
148    /**
149     * Creates a range using the specified element as both the minimum
150     * and maximum in this range.
151     *
152     * <p>
153     * The range uses the specified {@link Comparator} to determine where
154     * values lie in the range.
155     * </p>
156     *
157     * @param <T> The type of the elements in this range.
158     * @param element  The value to use for this range, must not be {@code null}.
159     * @param comparator  The comparator to be used, null for natural ordering.
160     * @return The range object, not null.
161     * @throws NullPointerException Thrown if the element is null.
162     * @throws ClassCastException Thrown if using natural ordering and the elements are not {@link Comparable}.
163     * @throws IllegalArgumentException Thrown if the element is a floating-point NaN.
164     */
165    public static <T> Range<T> is(final T element, final Comparator<T> comparator) {
166        return of(element, element, comparator);
167    }
168
169    /**
170     * Tests whether the element is a floating-point NaN. A NaN endpoint sorts above every value under the natural
171     * total order ({@link Double#compareTo(Double)} / {@link Float#compareTo(Float)}), silently producing a
172     * half-unbounded range whose {@code contains}/{@code fit} accept every value above the minimum.
173     *
174     * @param element The element to test, may be null.
175     * @return Whether the element is a floating-point NaN.
176     */
177    private static boolean isNaN(final Object element) {
178        return element instanceof Double && ((Double) element).isNaN()
179                || element instanceof Float && ((Float) element).isNaN();
180    }
181
182    /**
183     * Creates a range with the specified minimum and maximum values (both inclusive).
184     *
185     * <p>
186     * The range uses the natural ordering of the elements to determine where
187     * values lie in the range.
188     * </p>
189     *
190     * <p>
191     * The arguments may be passed in the order (min, max) or (max, min).
192     * The getMinimum and getMaximum methods will return the correct values.
193     * </p>
194     *
195     * @param <T> The type of the elements in this range.
196     * @param fromInclusive  The first value that defines the edge of the range, inclusive.
197     * @param toInclusive  The second value that defines the edge of the range, inclusive.
198     * @return The range object, not null.
199     * @throws NullPointerException Thrown if either element is null.
200     * @throws ClassCastException Thrown if the elements are not {@link Comparable}.
201     * @throws IllegalArgumentException Thrown if either element is a floating-point NaN.
202     * @since 3.13.0
203     */
204    public static <T extends Comparable<? super T>> Range<T> of(final T fromInclusive, final T toInclusive) {
205        return of(fromInclusive, toInclusive, null);
206    }
207
208    /**
209     * Creates a range with the specified minimum and maximum values (both inclusive).
210     *
211     * <p>
212     * The range uses the specified {@link Comparator} to determine where
213     * values lie in the range.
214     * </p>
215     *
216     * <p>
217     * The arguments may be passed in the order (min, max) or (max, min).
218     * The getMinimum and getMaximum methods will return the correct values.
219     * </p>
220     *
221     * @param <T> The type of the elements in this range.
222     * @param fromInclusive  The first value that defines the edge of the range, inclusive.
223     * @param toInclusive  The second value that defines the edge of the range, inclusive.
224     * @param comparator  The comparator to be used, null for natural ordering.
225     * @return The range object, not null.
226     * @throws NullPointerException Thrown when fromInclusive is null.
227     * @throws NullPointerException Thrown when toInclusive is null.
228     * @throws ClassCastException Thrown if using natural ordering and the elements are not {@link Comparable}.
229     * @throws IllegalArgumentException Thrown if either element is a floating-point NaN.
230     * @since 3.13.0
231     */
232    public static <T> Range<T> of(final T fromInclusive, final T toInclusive, final Comparator<T> comparator) {
233        return new Range<>(fromInclusive, toInclusive, comparator);
234    }
235
236    /**
237     * Validates that a floating-point endpoint is not NaN, mirroring the fail-closed posture of
238     * {@link Validate#notNaN(double, String, Object...)}.
239     *
240     * @param element The endpoint to validate.
241     * @param name The parameter name for the exception message.
242     * @throws IllegalArgumentException Thrown if the endpoint is a floating-point NaN.
243     */
244    private static void requireNotNaN(final Object element, final String name) {
245        if (isNaN(element)) {
246            throw new IllegalArgumentException(name + " must not be NaN");
247        }
248    }
249
250    /**
251     * The ordering scheme used in this range.
252     */
253    private final Comparator<T> comparator;
254
255    /**
256     * Cached output hashCode (class is immutable).
257     */
258    private transient int hashCode;
259
260    /**
261     * The maximum value in this range (inclusive).
262     */
263    private final T maximum;
264
265    /**
266     * The minimum value in this range (inclusive).
267     */
268    private final T minimum;
269
270    /**
271     * Cached output toString (class is immutable).
272     */
273    private transient String toString;
274
275    /**
276     * Creates an instance.
277     *
278     * @param element1  The first element, not null.
279     * @param element2  The second element, not null
280     * @param comp  The comparator to be used, null for natural ordering.
281     * @throws NullPointerException Thrown when element1 is null.
282     * @throws NullPointerException Thrown when element2 is null.
283     * @throws IllegalArgumentException Thrown when element1 or element2 is a floating-point NaN.
284     */
285    @SuppressWarnings("unchecked")
286    Range(final T element1, final T element2, final Comparator<T> comp) {
287        Objects.requireNonNull(element1, "element1");
288        Objects.requireNonNull(element2, "element2");
289        requireNotNaN(element1, "element1");
290        requireNotNaN(element2, "element2");
291        if (comp == null) {
292            this.comparator = ComparableComparator.INSTANCE;
293        } else {
294            this.comparator = comp;
295        }
296        if (this.comparator.compare(element1, element2) < 1) {
297            this.minimum = element1;
298            this.maximum = element2;
299        } else {
300            this.minimum = element2;
301            this.maximum = element1;
302        }
303        this.hashCode = hash(minimum, maximum);
304    }
305
306    /**
307     * Checks whether the specified element occurs within this range.
308     *
309     * @param element  The element to check for, null returns false.
310     * @return true if the specified element occurs within this range.
311     */
312    public boolean contains(final T element) {
313        if (element == null) {
314            return false;
315        }
316        return comparator.compare(element, minimum) > -1 && comparator.compare(element, maximum) < 1;
317    }
318
319    /**
320     * Checks whether this range contains all the elements of the specified range.
321     *
322     * <p>
323     * This method may fail if the ranges have two different comparators or element types.
324     * </p>
325     *
326     * @param otherRange  The range to check, null returns false.
327     * @return true if this range contains the specified range.
328     * @throws RuntimeException Thrown if ranges cannot be compared.
329     */
330    public boolean containsRange(final Range<T> otherRange) {
331        if (otherRange == null) {
332            return false;
333        }
334        return contains(otherRange.minimum)
335            && contains(otherRange.maximum);
336    }
337
338    /**
339     * Checks where the specified element occurs relative to this range.
340     *
341     * <p>
342     * The API is reminiscent of the Comparable interface returning {@code -1} if
343     * the element is before the range, {@code 0} if contained within the range and
344     * {@code 1} if the element is after the range.
345     * </p>
346     *
347     * @param element  The element to check for, not null.
348     * @return -1, 0 or +1 depending on the element's location relative to the range.
349     * @throws NullPointerException Thrown if {@code element} is {@code null}.
350     */
351    public int elementCompareTo(final T element) {
352        // Comparable API says throw NPE on null
353        Objects.requireNonNull(element, "element");
354        if (isAfter(element)) {
355            return -1;
356        }
357        if (isBefore(element)) {
358            return 1;
359        }
360        return 0;
361    }
362
363    /**
364     * Compares this range to another object to test if they are equal.
365     *
366     * <p>
367     * To be equal, the minimum and maximum values must be equal, which
368     * ignores any differences in the comparator.
369     * </p>
370     *
371     * @param obj The reference object with which to compare.
372     * @return true if this object is equal.
373     */
374    @Override
375    public boolean equals(final Object obj) {
376        if (obj == this) {
377            return true;
378        }
379        if (obj == null || obj.getClass() != getClass()) {
380            return false;
381        }
382        @SuppressWarnings("unchecked") // OK because we checked the class above
383        final
384        Range<T> range = (Range<T>) obj;
385        return minimum.equals(range.minimum) &&
386               maximum.equals(range.maximum);
387    }
388
389    /**
390     * Fits the given element into this range by returning the given element or, if out of bounds, the range minimum if
391     * below, or the range maximum if above.
392     *
393     * <pre>{@code
394     * Range<Integer> range = Range.between(16, 64);
395     * range.fit(-9) -->  16
396     * range.fit(0)  -->  16
397     * range.fit(15) -->  16
398     * range.fit(16) -->  16
399     * range.fit(17) -->  17
400     * ...
401     * range.fit(63) -->  63
402     * range.fit(64) -->  64
403     * range.fit(99) -->  64
404     * }</pre>
405     *
406     * @param element The element to check for, not null.
407     * @return The minimum, the element, or the maximum depending on the element's location relative to the range.
408     * @throws NullPointerException Thrown if {@code element} is {@code null}.
409     * @since 3.10
410     */
411    public T fit(final T element) {
412        // Comparable API says throw NPE on null
413        Objects.requireNonNull(element, "element");
414        if (isAfter(element)) {
415            return minimum;
416        }
417        if (isBefore(element)) {
418            return maximum;
419        }
420        return element;
421    }
422
423    /**
424     * Gets the comparator being used to determine if objects are within the range.
425     *
426     * <p>
427     * Natural ordering uses an internal comparator implementation, thus this
428     * method never returns null. See {@link #isNaturalOrdering()}.
429     * </p>
430     *
431     * @return The comparator being used, not null.
432     */
433    public Comparator<T> getComparator() {
434        return comparator;
435    }
436
437    /**
438     * Gets the maximum value in this range.
439     *
440     * @return The maximum value in this range, not null.
441     */
442    public T getMaximum() {
443        return maximum;
444    }
445
446    /**
447     * Gets the minimum value in this range.
448     *
449     * @return The minimum value in this range, not null.
450     */
451    public T getMinimum() {
452        return minimum;
453    }
454
455    /**
456     * Gets a suitable hash code for the range.
457     *
458     * @return A hash code value for this object.
459     */
460    @Override
461    public int hashCode() {
462        return hashCode;
463    }
464
465    /**
466     * Calculate the intersection of {@code this} and an overlapping Range.
467     *
468     * @param other overlapping Range.
469     * @return range representing the intersection of {@code this} and {@code other} ({@code this} if equal).
470     * @throws IllegalArgumentException Thrown if {@code other} does not overlap {@code this}.
471     * @since 3.0.1
472     */
473    public Range<T> intersectionWith(final Range<T> other) {
474        if (!this.isOverlappedBy(other)) {
475            throw new IllegalArgumentException(String.format(
476                "Cannot calculate intersection with non-overlapping range %s", other));
477        }
478        if (this.equals(other)) {
479            return this;
480        }
481        final T min = getComparator().compare(minimum, other.minimum) < 0 ? other.minimum : minimum;
482        final T max = getComparator().compare(maximum, other.maximum) < 0 ? maximum : other.maximum;
483        return of(min, max, getComparator());
484    }
485
486    /**
487     * Tests whether this range is after the specified element.
488     *
489     * @param element  The element to check for, null returns false.
490     * @return true if this range is entirely after the specified element.
491     */
492    public boolean isAfter(final T element) {
493        if (element == null) {
494            return false;
495        }
496        return comparator.compare(element, minimum) < 0;
497    }
498
499    /**
500     * Tests whether this range is completely after the specified range.
501     *
502     * <p>
503     * This method may fail if the ranges have two different comparators or element types.
504     * </p>
505     *
506     * @param otherRange  The range to check, null returns false.
507     * @return true if this range is completely after the specified range.
508     * @throws RuntimeException Thrown if ranges cannot be compared.
509     */
510    public boolean isAfterRange(final Range<T> otherRange) {
511        if (otherRange == null) {
512            return false;
513        }
514        return isAfter(otherRange.maximum);
515    }
516
517    /**
518     * Tests whether this range is before the specified element.
519     *
520     * @param element  The element to check for, null returns false.
521     * @return true if this range is entirely before the specified element.
522     */
523    public boolean isBefore(final T element) {
524        if (element == null) {
525            return false;
526        }
527        return comparator.compare(element, maximum) > 0;
528    }
529
530    /**
531     * Tests whether this range is completely before the specified range.
532     *
533     * <p>
534     * This method may fail if the ranges have two different comparators or element types.
535     * </p>
536     *
537     * @param otherRange  The range to check, null returns false.
538     * @return true if this range is completely before the specified range.
539     * @throws RuntimeException Thrown if ranges cannot be compared.
540     */
541    public boolean isBeforeRange(final Range<T> otherRange) {
542        if (otherRange == null) {
543            return false;
544        }
545        return isBefore(otherRange.minimum);
546    }
547
548    /**
549     * Tests whether this range ends with the specified element.
550     *
551     * @param element  The element to check for, null returns false.
552     * @return true if the specified element occurs within this range.
553     */
554    public boolean isEndedBy(final T element) {
555        if (element == null) {
556            return false;
557        }
558        return comparator.compare(element, maximum) == 0;
559    }
560
561    /**
562     * Tests whether or not the Range is using the natural ordering of the elements.
563     *
564     * <p>
565     * Natural ordering uses an internal comparator implementation, thus this
566     * method is the only way to check if a null comparator was specified.
567     * </p>
568     *
569     * @return true if using natural ordering.
570     */
571    public boolean isNaturalOrdering() {
572        return comparator == ComparableComparator.INSTANCE;
573    }
574
575    /**
576     * Tests whether this range is overlapped by the specified range.
577     *
578     * <p>
579     * Two ranges overlap if there is at least one element in common.
580     * </p>
581     *
582     * <p>
583     * This method may fail if the ranges have two different comparators or element types.
584     * </p>
585     *
586     * @param otherRange  The range to test, null returns false.
587     * @return true if the specified range overlaps with this
588     *  range; otherwise, {@code false}.
589     * @throws RuntimeException Thrown if ranges cannot be compared.
590     */
591    public boolean isOverlappedBy(final Range<T> otherRange) {
592        if (otherRange == null) {
593            return false;
594        }
595        return otherRange.contains(minimum)
596            || otherRange.contains(maximum)
597            || contains(otherRange.minimum);
598    }
599
600    /**
601     * Tests whether this range starts with the specified element.
602     *
603     * @param element  The element to check for, null returns false.
604     * @return true if the specified element occurs within this range.
605     */
606    public boolean isStartedBy(final T element) {
607        if (element == null) {
608            return false;
609        }
610        return comparator.compare(element, minimum) == 0;
611    }
612
613    /**
614     * Validates the endpoints and comparator and recomputes the cached hash code after deserialization.
615     *
616     * @param in See {@link Serializable}.
617     * @throws IOException Thrown as described in {@link Serializable}.
618     * @throws ClassNotFoundException Thrown as described in {@link Serializable}.
619     * @throws InvalidObjectException Thrown if the endpoints or comparator violate the range invariants.
620     */
621    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
622        in.defaultReadObject();
623        SerializationUtils.requireNonNull(maximum, "maximum null");
624        SerializationUtils.requireNonNull(minimum, "minimum null");
625        SerializationUtils.requireNonNull(comparator, "comparator null");
626        // Mirror the constructor's NaN endpoint rejection: a crafted stream cannot smuggle in the degenerate
627        // half-unbounded range that construction refuses.
628        if (isNaN(minimum) || isNaN(maximum)) {
629            throw new InvalidObjectException("Range minimum/maximum must not be NaN.");
630        }
631        if (comparator.compare(minimum, maximum) > 0) {
632            throw new InvalidObjectException("Range minimum is greater than maximum under the comparator.");
633        }
634        hashCode = hash(minimum, maximum);
635    }
636
637    /**
638     * Gets the range as a {@link String}.
639     *
640     * <p>
641     * The format of the String is '[<em>min</em>..<em>max</em>]'.
642     * </p>
643     *
644     * @return The {@link String} representation of this range.
645     */
646    @Override
647    public String toString() {
648        if (toString == null) {
649            toString = "[" + minimum + ".." + maximum + "]";
650        }
651        return toString;
652    }
653
654    /**
655     * Formats the receiver using the given format.
656     *
657     * <p>
658     * This uses {@link java.util.Formattable} to perform the formatting. Three variables may
659     * be used to embed the minimum, maximum and comparator.
660     * Use {@code %1$s} for the minimum element, {@code %2$s} for the maximum element
661     * and {@code %3$s} for the comparator.
662     * The default format used by {@code toString()} is {@code [%1$s..%2$s]}.
663     * </p>
664     *
665     * @param format  The format string, optionally containing {@code %1$s}, {@code %2$s} and  {@code %3$s}, not null.
666     * @return The formatted string, not null.
667     */
668    public String toString(final String format) {
669        return String.format(format, minimum, maximum, comparator);
670    }
671
672}