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    }