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 }