View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang.math;
18  
19  import java.io.Serializable;
20  
21  /**
22   * <p><code>NumberRange</code> represents an inclusive range of 
23   * {@link java.lang.Number} objects of the same type.</p>
24   *
25   * @author <a href="mailto:chrise@esha.com">Christopher Elkins</a>
26   * @author Stephen Colebourne
27   * @since 2.0 (previously in org.apache.commons.lang)
28   * @version $Id: NumberRange.java 437554 2006-08-28 06:21:41Z bayard $
29   */
30  public final class NumberRange extends Range implements Serializable {
31      
32      /**
33       * Required for serialization support.
34       * 
35       * @see java.io.Serializable
36       */
37      private static final long serialVersionUID = 71849363892710L;
38  
39      /**
40       * The minimum number in this range.
41       */
42      private final Number min;
43      /**
44       * The maximum number in this range.
45       */
46      private final Number max;
47      
48      /**
49       * Cached output hashCode (class is immutable).
50       */
51      private transient int hashCode = 0;
52      /**
53       * Cached output toString (class is immutable).
54       */
55      private transient String toString = null;
56  
57      /**
58       * <p>Constructs a new <code>NumberRange</code> using the specified
59       * number as both the minimum and maximum in this range.</p>
60       *
61       * @param num the number to use for this range
62       * @throws IllegalArgumentException if the number is <code>null</code>
63       * @throws IllegalArgumentException if the number doesn't implement <code>Comparable</code>
64       * @throws IllegalArgumentException if the number is <code>Double.NaN</code> or <code>Float.NaN</code>
65       */
66      public NumberRange(Number num) {
67          if (num == null) {
68              throw new IllegalArgumentException("The number must not be null");
69          }
70          if (num instanceof Comparable == false) {
71              throw new IllegalArgumentException("The number must implement Comparable");
72          }
73          if (num instanceof Double && ((Double) num).isNaN()) {
74              throw new IllegalArgumentException("The number must not be NaN");
75          }
76          if (num instanceof Float && ((Float) num).isNaN()) {
77              throw new IllegalArgumentException("The number must not be NaN");
78          }
79  
80          this.min = num;
81          this.max = num;
82      }
83  
84      /**
85       * <p>Constructs a new <code>NumberRange</code> with the specified
86       * minimum and maximum numbers (both inclusive).</p>
87       * 
88       * <p>The arguments may be passed in the order (min,max) or (max,min). The
89       * {@link #getMinimumNumber()} and {@link #getMaximumNumber()} methods will return the
90       * correct value.</p>
91       * 
92       * <p>This constructor is designed to be used with two <code>Number</code>
93       * objects of the same type. If two objects of different types are passed in,
94       * an exception is thrown.</p>
95       *
96       * @param num1  first number that defines the edge of the range, inclusive
97       * @param num2  second number that defines the edge of the range, inclusive
98       * @throws IllegalArgumentException if either number is <code>null</code>
99       * @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 }