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 }