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 1142401 2011-07-03 08:30:12Z bayard $
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        // Basics
375        //--------------------------------------------------------------------
376    
377        /**
378         * <p>Compares this range to another object to test if they are equal.</p>.
379         *
380         * <p>To be equal, the minimum and maximum values must be equal, which
381         * ignores any differences in the comparator.</p>
382         *
383         * @param obj the reference object with which to compare
384         * @return true if this object is equal
385         */
386        @Override
387        public boolean equals(Object obj) {
388            if (obj == this) {
389                return true;
390            } else if (obj == null || obj.getClass() != getClass()) {
391                return false;
392            } else {
393                @SuppressWarnings("unchecked") // OK because we checked the class above
394                Range<T> range = (Range<T>) obj;
395                return minimum.equals(range.minimum) &&
396                       maximum.equals(range.maximum);
397            }
398        }
399    
400        /**
401         * <p>Gets a suitable hash code for the range.</p>
402         *
403         * @return a hash code value for this object
404         */
405        @Override
406        public int hashCode() {
407            int result = hashCode;
408            if (hashCode == 0) {
409                result = 17;
410                result = 37 * result + getClass().hashCode();
411                result = 37 * result + minimum.hashCode();
412                result = 37 * result + maximum.hashCode();
413                hashCode = result;
414            }
415            return result;
416        }
417    
418        /**
419         * <p>Gets the range as a {@code String}.</p>
420         *
421         * <p>The format of the String is '[<i>min</i>..<i>max</i>]'.</p>
422         *
423         * @return the {@code String} representation of this range
424         */
425        @Override
426        public String toString() {
427            String result = toString;
428            if (result == null) {
429                StringBuilder buf = new StringBuilder(32);
430                buf.append('[');
431                buf.append(minimum);
432                buf.append("..");
433                buf.append(maximum);
434                buf.append(']');
435                result = buf.toString();
436                toString = result;
437            }
438            return result;
439        }
440    
441        /**
442         * <p>Formats the receiver using the given format.</p>
443         * 
444         * <p>This uses {@link java.util.Formattable} to perform the formatting. Three variables may
445         * be used to embed the minimum, maximum and comparator.
446         * Use {@code %1$s} for the minimum element, {@code %2$s} for the maximum element
447         * and {@code %3$s} for the comparator.
448         * The default format used by {@code toString()} is {@code [%1$s..%2$s]}.</p>
449         * 
450         * @param format  the format string, optionally containing {@code %1$s}, {@code %2$s} and  {@code %3$s}, not null
451         * @return the formatted string, not null
452         */
453        public String toString(String format) {
454            return String.format(format, minimum, maximum, comparator);
455        }
456    
457        //-----------------------------------------------------------------------
458        @SuppressWarnings({"rawtypes", "unchecked"})
459        private enum ComparableComparator implements Comparator {
460            INSTANCE;
461            /**
462             * Comparable based compare implementation. 
463             *
464             * @param obj1 left hand side of comparison
465             * @param obj2 right hand side of comparison
466             * @return negative, 0, positive comparison value
467             */
468            public int compare(Object obj1, Object obj2) {
469                return ((Comparable) obj1).compareTo(obj2);
470            }
471        }
472    
473    }