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