    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.lang.math;
    018
    019    import java.io.Serializable;
    020
    021    /**
    022     * <p><code>NumberRange</code> represents an inclusive range of
    023     * {@link java.lang.Number} objects of the same type.</p>
    024     *
    025     * @author Apache Software Foundation
    026     * @author <a href="mailto:chrise@esha.com">Christopher Elkins</a>
    027     * @since 2.0 (previously in org.apache.commons.lang)
    028     * @version $Id: NumberRange.java 905636 2010-02-02 14:03:32Z niallp $
    029     */
    030    public final class NumberRange extends Range implements Serializable {
    031
    032        /**
    033         * Required for serialization support.
    034         *
    035         * @see java.io.Serializable
    036         */
    037        private static final long serialVersionUID = 71849363892710L;
    038
    039        /**
    040         * The minimum number in this range.
    041         */
    042        private final Number min;
    043        /**
    044         * The maximum number in this range.
    045         */
    046        private final Number max;
    047
    048        /**
    049         * Cached output hashCode (class is immutable).
    050         */
    051        private transient int hashCode = 0;
    052        /**
    053         * Cached output toString (class is immutable).
    054         */
    055        private transient String toString = null;
    056
    057        /**
    058         * <p>Constructs a new <code>NumberRange</code> using the specified
    059         * number as both the minimum and maximum in this range.</p>
    060         *
    061         * @param num the number to use for this range
    062         * @throws IllegalArgumentException if the number is <code>null</code>
    063         * @throws IllegalArgumentException if the number doesn't implement <code>Comparable</code>
    064         * @throws IllegalArgumentException if the number is <code>Double.NaN</code> or <code>Float.NaN</code>
    065         */
    066        public NumberRange(Number num) {
    067            if (num == null) {
    068                throw new IllegalArgumentException("The number must not be null");
    069            }
    070            if (num instanceof Comparable == false) {
    071                throw new IllegalArgumentException("The number must implement Comparable");
    072            }
    073            if (num instanceof Double && ((Double) num).isNaN()) {
    074                throw new IllegalArgumentException("The number must not be NaN");
    075            }
    076            if (num instanceof Float && ((Float) num).isNaN()) {
    077                throw new IllegalArgumentException("The number must not be NaN");
    078            }
    079
    080            this.min = num;
    081            this.max = num;
    082        }
    083
    084        /**
    085         * <p>Constructs a new <code>NumberRange</code> with the specified
    086         * minimum and maximum numbers (both inclusive).</p>
    087         *
    088         * <p>The arguments may be passed in the order (min,max) or (max,min). The
    089         * {@link #getMinimumNumber()} and {@link #getMaximumNumber()} methods will return the
    090         * correct value.</p>
    091         *
    092         * <p>This constructor is designed to be used with two <code>Number</code>
    093         * objects of the same type. If two objects of different types are passed in,
    094         * an exception is thrown.</p>
    095         *
    096         * @param num1  first number that defines the edge of the range, inclusive
    097         * @param num2  second number that defines the edge of the range, inclusive
    098         * @throws IllegalArgumentException if either number is <code>null</code>
    099         * @throws IllegalArgumentException if the numbers are of different types
    100         * @throws IllegalArgumentException if the numbers don't implement <code>Comparable</code>
    101         */
    102        public NumberRange(Number num1, Number num2) {
    103            if (num1 == null || num2 == null) {
    104                throw new IllegalArgumentException("The numbers must not be null");
    105            }
    106            if (num1.getClass() != num2.getClass()) {
    107                throw new IllegalArgumentException("The numbers must be of the same type");
    108            }
    109            if (num1 instanceof Comparable == false) {
    110                throw new IllegalArgumentException("The numbers must implement Comparable");
    111            }
    112            if (num1 instanceof Double) {
    113                if (((Double) num1).isNaN() || ((Double) num2).isNaN()) {
    114                    throw new IllegalArgumentException("The number must not be NaN");
    115                }
    116            } else if (num1 instanceof Float) {
    117                if (((Float) num1).isNaN() || ((Float) num2).isNaN()) {
    118                    throw new IllegalArgumentException("The number must not be NaN");
    119                }
    120            }
    121
    122            int compare = ((Comparable) num1).compareTo(num2);
    123            if (compare == 0) {
    124                this.min = num1;
    125                this.max = num1;
    126            } else if (compare > 0) {
    127                this.min = num2;
    128                this.max = num1;
    129            } else {
    130                this.min = num1;
    131                this.max = num2;
    132            }
    133        }
    134
    135        // Accessors
    136        //--------------------------------------------------------------------
    137
    138        /**
    139         * <p>Returns the minimum number in this range.</p>
    140         *
    141         * @return the minimum number in this range
    142         */
    143        public Number getMinimumNumber() {
    144            return min;
    145        }
    146
    147        /**
    148         * <p>Returns the maximum number in this range.</p>
    149         *
    150         * @return the maximum number in this range
    151         */
    152        public Number getMaximumNumber() {
    153            return max;
    154        }
    155
    156        // Tests
    157        //--------------------------------------------------------------------
    158
    159        /**
    160         * <p>Tests whether the specified <code>number</code> occurs within
    161         * this range.</p>
    162         *
    163         * <p><code>null</code> is handled and returns <code>false</code>.</p>
    164         *
    165         * @param number  the number to test, may be <code>null</code>
    166         * @return <code>true</code> if the specified number occurs within this range
    167         * @throws IllegalArgumentException if the number is of a different type to the range
    168         */
    169        public boolean containsNumber(Number number) {
    170            if (number == null) {
    171                return false;
    172            }
    173            if (number.getClass() != min.getClass()) {
    174                throw new IllegalArgumentException("The number must be of the same type as the range numbers");
    175            }
    176            int compareMin = ((Comparable) min).compareTo(number);
    177            int compareMax = ((Comparable) max).compareTo(number);
    178            return compareMin <= 0 && compareMax >= 0;
    179        }
    180
    181        // Range tests
    182        //--------------------------------------------------------------------
    183        // use Range implementations
    184
    185        // Basics
    186        //--------------------------------------------------------------------
    187
    188        /**
    189         * <p>Compares this range to another object to test if they are equal.</p>.
    190         *
    191         * <p>To be equal, the class, minimum and maximum must be equal.</p>
    192         *
    193         * @param obj the reference object with which to compare
    194         * @return <code>true</code> if this object is equal
    195         */
    196        public boolean equals(Object obj) {
    197            if (obj == this) {
    198                return true;
    199            }
    200            if (obj instanceof NumberRange == false) {
    201                return false;
    202            }
    203            NumberRange range = (NumberRange) obj;
    204            return min.equals(range.min) && max.equals(range.max);
    205        }
    206
    207        /**
    208         * <p>Gets a hashCode for the range.</p>
    209         *
    210         * @return a hash code value for this object
    211         */
    212        public int hashCode() {
    213            if (hashCode == 0) {
    214                hashCode = 17;
    215                hashCode = 37 * hashCode + getClass().hashCode();
    216                hashCode = 37 * hashCode + min.hashCode();
    217                hashCode = 37 * hashCode + max.hashCode();
    218            }
    219            return hashCode;
    220        }
    221
    222        /**
    223         * <p>Gets the range as a <code>String</code>.</p>
    224         *
    225         * <p>The format of the String is 'Range[<i>min</i>,<i>max</i>]'.</p>
    226         *
    227         * @return the <code>String</code> representation of this range
    228         */
    229        public String toString() {
    230            if (toString == null) {
    231                StringBuffer buf = new StringBuffer(32);
    232                buf.append("Range[");
    233                buf.append(min);
    234                buf.append(',');
    235                buf.append(max);
    236                buf.append(']');
    237                toString = buf.toString();
    238            }
    239            return toString;
    240        }
    241
    242    }