001 package edu.nrao.sss.math; 002 003 import static edu.nrao.sss.math.NumberSet.LookupMethod; 004 005 import java.math.BigDecimal; 006 import java.util.Set; 007 import java.util.SortedSet; 008 import java.util.TreeSet; 009 010 011 /** 012 * Creator of number sets. 013 * Clients may fetch a factory by using {@link NumberSet#factory}. 014 * <p> 015 * <b>Version Info:</b> 016 * <table style="margin-left:2em"> 017 * <tr><td>$Revision: 1707 $</td></tr> 018 * <tr><td>$Date: 2008-11-14 10:23:59 -0700 (Fri, 14 Nov 2008) $</td></tr> 019 * <tr><td>$Author: dharland $</td></tr> 020 * </table></p> 021 * 022 * @author David M. Harland 023 * @since 2007-03-01 024 */ 025 public class NumberSetFactory 026 { 027 /** The single instance of this factory. */ 028 static final NumberSetFactory instance = new NumberSetFactory(); 029 030 /** Enforce singleton pattern. */ 031 private NumberSetFactory() { } 032 033 /** 034 * Creates a continuous set of numbers. 035 * 036 * @param minValue the smallest value in this set. 037 * @param maxValue the largest value in this set. 038 * @param lookupMethod the manner in which a value is found in this set. 039 * 040 * @return a new continuous set of numbers. 041 */ 042 public NumberSet makeContinuousSet(Number minValue, 043 Number maxValue, LookupMethod lookupMethod) 044 { 045 assertValidMinMax(minValue, maxValue, false); 046 047 lookupMethod = makeValidLookupMethod(lookupMethod); 048 049 return new ContinuousNumberSet(minValue, maxValue, lookupMethod); 050 } 051 052 /** 053 * Creates a discrete set of numbers. 054 * 055 * @param discreteValues the numbers in this set. 056 * @param lookupMethod the manner in which a value is found in this set. 057 * 058 * @return a new discrete set of numbers. 059 */ 060 public NumberSet makeDiscreteSet(Set<? extends Number> discreteValues, 061 LookupMethod lookupMethod) 062 { 063 if (discreteValues == null) 064 discreteValues = new TreeSet<Number>(); 065 066 lookupMethod = makeValidLookupMethod(lookupMethod); 067 068 return new DiscreteNumberSet(discreteValues, lookupMethod); 069 } 070 071 /** 072 * Creates a discrete set of numbers. 073 * <p> 074 * The numeric parameters are used to construct the values held in the 075 * returned set. 076 * The smallest such value is <tt>minValue</tt>. 077 * The next smallest value is <tt>minValue</tt> plus <tt>stepSize</tt>, 078 * and so on until the largest value, which will be less than or equal 079 * to <tt>maxValue</tt>, is added.</p> 080 * 081 * @param minValue the smallest value in this set. 082 * @param maxValue the largest possible value in this set. Note that the 083 * largest value <i>actually</i> held in this set could be 084 * smaller. This happens when the distance from 085 * <tt>minValue</tt> to <tt>maxValue</tt> is not an 086 * integral multiple of <tt>stepSize</tt>. 087 * @param stepSize an increment used to create other values for this set. 088 * @param lookupMethod the manner in which a value is found in this set. 089 * 090 * @return a new discrete set of numbers. 091 */ 092 public NumberSet makeDiscreteSetAdditive(Number minValue, 093 Number maxValue, 094 Number stepSize, 095 LookupMethod lookupMethod) 096 { 097 assertValidMinMax(minValue, maxValue, true); 098 assertValidStepSize(stepSize); 099 100 lookupMethod = makeValidLookupMethod(lookupMethod); 101 102 TreeSet<BigDecimal> values = new TreeSet<BigDecimal>(); 103 104 BigDecimal currVal = new BigDecimal(minValue.toString()); 105 BigDecimal maxVal = new BigDecimal(maxValue.toString()); 106 BigDecimal stepVal = new BigDecimal(stepSize.toString()); 107 108 while (currVal.compareTo(maxVal) <= 0) 109 { 110 values.add(currVal); 111 112 currVal = currVal.add(stepVal); 113 } 114 115 return new DiscreteNumberSet(values, lookupMethod); 116 } 117 118 /** 119 * Creates a discrete set of numbers. 120 * <p> 121 * The numeric parameters are used to construct the values held in the 122 * returned set. 123 * The smallest such value is <tt>minValue</tt>. 124 * The next smallest value is <tt>minValue</tt> multiplied by 125 * <tt>stepSize</tt>, and so on until the largest value, which will be less 126 * than or equal to <tt>maxValue</tt>, is added.</p> 127 * 128 * @param minValue the smallest value in this set. 129 * @param maxValue the largest possible value in this set. Note that the 130 * largest value <i>actually</i> held in this set could be 131 * smaller. 132 * @param multiplier a value used to create other values for this set. 133 * @param lookupMethod the manner in which a value is found in this set. 134 * 135 * @return a new discrete set of numbers. 136 */ 137 public NumberSet makeDiscreteSetMultiplicative(Number minValue, 138 Number maxValue, 139 Number multiplier, 140 LookupMethod lookupMethod) 141 { 142 if (minValue.doubleValue() == 0.0) 143 throw new IllegalArgumentException("minValue may not be zero"); 144 145 assertValidMinMax(minValue, maxValue, true); 146 assertValidMultiplier(multiplier); 147 148 lookupMethod = makeValidLookupMethod(lookupMethod); 149 150 TreeSet<BigDecimal> values = new TreeSet<BigDecimal>(); 151 152 BigDecimal currVal = new BigDecimal(minValue.toString()); 153 BigDecimal maxVal = new BigDecimal(maxValue.toString()); 154 BigDecimal multVal = new BigDecimal(multiplier.toString()); 155 156 while (currVal.compareTo(maxVal) <= 0) 157 { 158 values.add(currVal); 159 160 currVal = currVal.multiply(multVal); 161 } 162 163 return new DiscreteNumberSet(values, lookupMethod); 164 } 165 166 /** Ensures valid minimum and maximum values. */ 167 private void assertValidMinMax(Number minValue, 168 Number maxValue, boolean discrete) 169 { 170 if (minValue.doubleValue() > maxValue.doubleValue()) 171 throw new IllegalArgumentException("May not have minValue > maxValue"); 172 173 if (Double.isNaN(minValue.doubleValue())) 174 throw new IllegalArgumentException("minValue may not be NaN"); 175 176 if (Double.isNaN(maxValue.doubleValue())) 177 throw new IllegalArgumentException("maxValue may not be NaN"); 178 179 if (discrete) 180 { 181 if (Double.isInfinite(minValue.doubleValue())) 182 throw new IllegalArgumentException("minValue may not be infinite"); 183 184 if (Double.isInfinite(maxValue.doubleValue())) 185 throw new IllegalArgumentException("maxValue may not be infinite"); 186 } 187 } 188 189 private void assertValidStepSize(Number stepSize) 190 { 191 if (stepSize.doubleValue() <= 0.0) 192 throw new IllegalArgumentException("stepSize must be greater than zero"); 193 } 194 195 private void assertValidMultiplier(Number multiplier) 196 { 197 if (multiplier.doubleValue() <= 1.0) 198 throw new IllegalArgumentException("multiplier must be greater than one"); 199 } 200 201 private LookupMethod makeValidLookupMethod(LookupMethod method) 202 { 203 return method == null ? LookupMethod.EQUAL : method; 204 } 205 206 //============================================================================ 207 // 208 //============================================================================ 209 210 /** A discrete set of numbers. */ 211 private class DiscreteNumberSet implements NumberSet 212 { 213 private LookupMethod method; 214 private SortedSet<BigDecimal> sortedValues; 215 216 DiscreteNumberSet(Set<? extends Number> values, LookupMethod lookupMethod) 217 { 218 method = lookupMethod; 219 sortedValues = new TreeSet<BigDecimal>(); 220 221 for (Number value : values) 222 sortedValues.add(new BigDecimal(value.toString())); 223 } 224 225 public DiscreteNumberSet(SortedSet<BigDecimal> values, 226 LookupMethod lookupMethod) 227 { 228 method = lookupMethod; 229 sortedValues = values; 230 } 231 232 public Number getBestMatchFor(Number myNumber) 233 { 234 Number answer; 235 236 BigDecimal myVal = new BigDecimal(myNumber.toString()); 237 238 switch (method) 239 { 240 case EQUAL: 241 answer = sortedValues.contains(myVal) ? myVal : null; 242 break; 243 244 case LARGEST_LESS_THAN_OR_EQUAL: 245 answer = findLteMatch(myNumber); 246 break; 247 248 case SMALLEST_GREATER_THAN_OR_EQUAL: 249 answer = findGteMatch(myNumber); 250 break; 251 252 default: 253 answer = null; 254 throw new 255 RuntimeException("PROGRAMMER ERROR: Unrecognized LookupMethod of " + 256 method.toString()); 257 } 258 259 return answer; 260 } 261 262 private Number findLteMatch(Number myNumber) 263 { 264 //Initialize match to last value in case we run off top of set 265 Number match = sortedValues.last(); 266 Number prevValue = null; 267 268 //The Number interface does not implement Comparable<Number>, so 269 //use BigDecimal. 270 BigDecimal myValue = new BigDecimal(myNumber.toString()); 271 272 for (BigDecimal value : sortedValues) 273 { 274 int comparison = myValue.compareTo(value); 275 276 if (comparison <= 0) 277 { 278 match = (comparison == 0) ? value : prevValue; 279 break; 280 } 281 282 prevValue = value; 283 } 284 285 return match; 286 } 287 288 private Number findGteMatch(Number myNumber) 289 { 290 //Reverse the sort so that greatest number is 1st 291 int valueCount = sortedValues.size(); 292 BigDecimal[] values = new BigDecimal[valueCount]; 293 294 int v = valueCount; 295 296 for (BigDecimal value : sortedValues) 297 values[--v] = value; 298 299 //Initialize match to last value in case we run off top of set 300 Number match = values[valueCount-1]; 301 Number prevValue = null; 302 303 //The Number interface does not implement Comparable<Number>, so 304 //use BigDecimal. 305 BigDecimal myValue = new BigDecimal(myNumber.toString()); 306 307 for (BigDecimal value : values) 308 { 309 int comparison = myValue.compareTo(value); 310 311 if (comparison >= 0) 312 { 313 match = (comparison == 0) ? value : prevValue; 314 break; 315 } 316 317 prevValue = value; 318 } 319 320 return match; 321 } 322 } 323 324 //============================================================================ 325 // 326 //============================================================================ 327 328 /** A continuous set of numbers. */ 329 private class ContinuousNumberSet implements NumberSet 330 { 331 private LookupMethod method; 332 private BigDecimal minVal, maxVal; 333 334 ContinuousNumberSet(Number minValue, 335 Number maxValue, LookupMethod lookupMethod) 336 { 337 minVal = new BigDecimal(minValue.toString()); 338 maxVal = new BigDecimal(maxValue.toString()); 339 340 method = lookupMethod; 341 } 342 343 public Number getBestMatchFor(Number myNumber) 344 { 345 Number answer; 346 347 BigDecimal myVal = new BigDecimal(myNumber.toString()); 348 349 //If number is greater than max, return max or null 350 if (myVal.compareTo(maxVal) > 0) 351 { 352 if (method.equals(LookupMethod.LARGEST_LESS_THAN_OR_EQUAL)) 353 answer = maxVal; 354 else 355 answer = null; 356 } 357 //Else if number is less than min, return min or null 358 else if (myVal.compareTo(minVal) < 0) 359 { 360 if (method.equals(LookupMethod.SMALLEST_GREATER_THAN_OR_EQUAL)) 361 answer = minVal; 362 else 363 answer = null; 364 } 365 //Otherwise, return their number 366 else 367 { 368 answer = myNumber; 369 } 370 371 return answer; 372 } 373 } 374 }