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    }