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 }