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    }