001 package edu.nrao.sss.measure; 002 003 import static java.math.BigDecimal.ONE; 004 005 import java.math.BigDecimal; 006 import java.math.RoundingMode; 007 import java.util.ArrayList; 008 import java.util.List; 009 import java.util.SortedMap; 010 import java.util.TreeMap; 011 012 import edu.nrao.sss.util.EnumerationUtility; 013 import edu.nrao.sss.util.Symbolic; 014 015 import static edu.nrao.sss.math.MathUtil.MC_FINAL_CALC; 016 import static edu.nrao.sss.math.MathUtil.MC_INTERM_CALCS; 017 import static edu.nrao.sss.measure.Wave.LIGHT_SPEED_VACUUM_KM_PER_SEC; 018 019 /** 020 * Units of linear velocity. 021 * <p> 022 * <b>Version Info:</b> 023 * <table style="margin-left:2em"> 024 * <tr><td>$Revision: 1586 $</td></tr> 025 * <tr><td>$Date: 2008-10-01 10:38:49 -0600 (Wed, 01 Oct 2008) $</td></tr> 026 * <tr><td>$Author: dharland $ (last person to modify)</td></tr> 027 * </table></p> 028 * 029 * @author David M. Harland 030 * @since 2006-03-31 031 */ 032 public class LinearVelocityUnits 033 implements Symbolic 034 { 035 private static final int PRECISION = MC_FINAL_CALC.getPrecision(); 036 037 private static final boolean IS_CASE_SENSITIVE; 038 static 039 { 040 IS_CASE_SENSITIVE = 041 DistanceUnits.getDefault().symbolsAreCaseSensitive() || 042 TimeUnits.getDefault().symbolsAreCaseSensitive(); 043 } 044 045 private static final SortedMap<String, LinearVelocityUnits> UNITS = 046 new TreeMap<String, LinearVelocityUnits>(); 047 048 /** 049 * Returns linear velocity units for the given distance and time units. 050 * Successive calls to this method with the same parameters will return 051 * the same object, not new equal objects. 052 * 053 * @param distanceUnits 054 * units of distance, the numerator for the returned velocity units. 055 * 056 * @param timeUnits 057 * units of time, the denominator for the returned velocity units. 058 * 059 * @return 060 * linear velocity units of <tt>distanceUnits</tt> per <tt>timeUnits</tt>. 061 */ 062 public static LinearVelocityUnits from(DistanceUnits distanceUnits, 063 TimeUnits timeUnits) 064 { 065 String key = makeSymbol(distanceUnits, timeUnits); 066 067 LinearVelocityUnits units = UNITS.get(key); 068 069 if (units == null) 070 { 071 units = new LinearVelocityUnits(distanceUnits, timeUnits); 072 UNITS.put(key, units); 073 } 074 075 return units; 076 } 077 078 /** 079 * Returns a default unit of linear velocity. 080 * @return a default unit of linear velocity. 081 */ 082 public static LinearVelocityUnits getDefault() 083 { 084 return KILOMETERS_PER_SECOND; 085 } 086 087 /** 088 * This non-public method is here only to support unit testing and should 089 * not be employed for other purposes. Calling this method erases this 090 * class's memory of which units have been created. If called after clients 091 * have made velocity units, it will be possible for one client to have 092 * one m/s instance, and new clients to have a different m/s instance. 093 * Not a crisis, but wasteful, and in violation of comments in 094 * "from" method. 095 */ 096 static void clearCache() { UNITS.clear(); } 097 098 //============================================================================ 099 // FREQUENTLY USED UNITS 100 //============================================================================ 101 102 /** Meters per second. */ 103 public static final LinearVelocityUnits METERS_PER_SECOND = 104 from(DistanceUnits.METER, TimeUnits.SECOND); 105 106 /** Kilometers per second. */ 107 public static final LinearVelocityUnits KILOMETERS_PER_SECOND = 108 from(DistanceUnits.KILOMETER, TimeUnits.SECOND); 109 110 /** 111 * A redshift measurement related to velocity. 112 * <p> 113 * For more information on the relationship of <i>z</i> 114 * to velocity, see these references: 115 * <ul> 116 * <li><a href="http://www.astro.ucla.edu/~wright/doppler.htm"> 117 * http://www.astro.ucla.edu/~wright/doppler.htm</a></li> 118 * <li><a href="http://iram.fr/IRAMFR/ARN/may95/node4.html"> 119 * http://iram.fr/IRAMFR/ARN/may95/node4.html</a></li> 120 * </ul</p> 121 */ 122 public static final LinearVelocityUnits Z = new LVU_Z(); 123 124 /** 125 * Returns a list of frequently used velocity units. 126 * This is a small subset of the total set of units that could 127 * be produced by all combinations of {@link DistanceUnits} 128 * and {@link TimeUnits}. 129 * 130 * @return a list of frequently used velocity units. 131 */ 132 public static List<LinearVelocityUnits> getFrequentlyUsedUnits() 133 { 134 List<LinearVelocityUnits> list = new ArrayList<LinearVelocityUnits>(); 135 136 list.add(METERS_PER_SECOND); 137 list.add(KILOMETERS_PER_SECOND); 138 list.add(Z); 139 140 return list; 141 } 142 143 //============================================================================ 144 // INSTANCE VARIABLES, CONSTRUCTORS, & SIMPLE GETTERS 145 //============================================================================ 146 147 private DistanceUnits distUnits; 148 private TimeUnits timeUnits; 149 private BigDecimal multiplier; //introduced in order to support "Z" 150 151 //This no-arg constructor is here for mechanisms such as JAXB & Hibernate 152 private LinearVelocityUnits() 153 { 154 this(DistanceUnits.getDefault(), TimeUnits.getDefault(), BigDecimal.ONE); 155 } 156 157 private LinearVelocityUnits(DistanceUnits distanceUnits, TimeUnits timeUnits) 158 { 159 this(distanceUnits, timeUnits, BigDecimal.ONE); 160 } 161 162 private LinearVelocityUnits(DistanceUnits du, TimeUnits tu, BigDecimal mult) 163 { 164 distUnits = du; 165 timeUnits = tu; 166 multiplier = mult.setScale(PRECISION, RoundingMode.HALF_UP); 167 } 168 169 /** 170 * Returns <i>true</i> if either the {@link DistanceUnits} or {@link TimeUnits} 171 * class has case-sensitive symbols. 172 */ 173 public boolean symbolsAreCaseSensitive() 174 { 175 return IS_CASE_SENSITIVE; 176 } 177 178 /** 179 * Returns the units of distance used by this unit of velocity. 180 * @return the units of distance used by this unit of velocity. 181 */ 182 public DistanceUnits getDistanceUnits() 183 { 184 return distUnits; 185 } 186 187 /** 188 * Returns the units of time used by this unit of velocity. 189 * @return the units of time used by this unit of velocity. 190 */ 191 public TimeUnits getTimeUnits() 192 { 193 return timeUnits; 194 } 195 196 //============================================================================ 197 // CONVERSIONS TO OTHER UNITS 198 //============================================================================ 199 200 /** 201 * Returns a factor for converting from this unit to {@code otherUnits}. 202 * 203 * @param otherUnits the unit to which conversion is desired. 204 * 205 * @return a factor for converting from this unit to {@code otherUnits}. 206 */ 207 public BigDecimal toUnits(LinearVelocityUnits otherUnits) 208 { 209 return convertTo(otherUnits, ONE); 210 } 211 212 /** 213 * Non-public method for use by this and other classes in pkg. 214 * Does the actual conversion; keeps answer in BigDecimal form. 215 */ 216 BigDecimal convertTo(LinearVelocityUnits otherUnits, BigDecimal value) 217 { 218 BigDecimal answer = value; 219 220 if (!otherUnits.equals(this)) 221 { 222 try //to use BigDecimal, for better accuracy, but... 223 { 224 if (value.scale() < PRECISION) 225 value = value.setScale(PRECISION); 226 227 //Strategy: use conversion methods of DistanceUnits and TimeUnits and 228 //then divide. 229 BigDecimal time = this.timeUnits.convertTo(otherUnits.timeUnits, ONE); 230 BigDecimal dist = this.distUnits.convertTo(otherUnits.distUnits, value); 231 232 dist = dist.multiply(this.multiplier); 233 dist = dist.divide(otherUnits.multiplier, MC_INTERM_CALCS); 234 235 answer = dist.divide(time, MC_FINAL_CALC); 236 } 237 catch (Exception ex) 238 { 239 //...if it fails, use java primitives 240 double distance = 241 this.distUnits.convertTo(otherUnits.distUnits, value).doubleValue(); 242 243 double time = 244 this.timeUnits.convertTo(otherUnits.timeUnits, ONE).doubleValue(); 245 246 double mult = this.multiplier.doubleValue() / 247 otherUnits.multiplier.doubleValue(); 248 249 answer = BigDecimal.valueOf(mult * distance / time); 250 } 251 } 252 253 return answer.round(MC_FINAL_CALC); 254 } 255 256 //============================================================================ 257 // TO / FROM TEXT 258 //============================================================================ 259 260 private static String makeSymbol(DistanceUnits du, TimeUnits tu) 261 { 262 return du.getSymbol() + '/' + tu.getSymbol(); 263 } 264 265 /** 266 * Returns the symbol for this unit. 267 * @return the symbol for this unit. 268 */ 269 public String getSymbol() 270 { 271 return makeSymbol(distUnits, timeUnits); 272 } 273 274 /** 275 * Returns the name of this unit. 276 * This method is here for historical reasons; it mimics the behavior 277 * this method had when this class had been an <tt>enum</tt> class. 278 * The {@link #toString()} is a better alternative. 279 * 280 * @return the name of this unit. 281 */ 282 public String name() 283 { 284 return distUnits.name() + "_PER_" + timeUnits.name(); 285 } 286 287 /** 288 * Returns a text representation of this unit of linear velocity. 289 * @return a text representation of this unit of linear velocity. 290 */ 291 @Override 292 public String toString() 293 { 294 EnumerationUtility eu = EnumerationUtility.getSharedInstance(); 295 296 return eu.enumToString(distUnits) + " Per " + eu.enumToString(timeUnits); 297 } 298 299 /** 300 * Returns the linear velocity units represented by {@code text}. 301 * <p> 302 * Two basic formats for <tt>text</tt> are employed, both based on the 303 * text representations of the component distance and time units. 304 * The first is of the form 305 * <tt><i>distUnits.symbol</i> / <i>timeUnits.symbol</i></tt>. 306 * The amount of space surrounding the "/" separator is not material; 307 * zero or more spaces are allowed on either side of the separator. 308 * The second is of the form 309 * <tt><i>distUnits.name</i> per <i>timeUnits.name</i></tt>. 310 * The case of the separator "per" is not material. The same comment about 311 * whitespace given above applies here as well. 312 * The rules governing the format of the symbol or name of the component 313 * units are given in 314 * {@link EnumerationUtility#enumFromString(Class, String)}.</p> 315 * 316 * @param text a text representation of a unit of linear velocity. 317 * 318 * @return the linear velocity units represented by {@code text}. 319 * 320 * @throws IllegalArgumentException 321 * if <tt>text</tt> cannot be parsed to a valid units. 322 */ 323 public static LinearVelocityUnits fromString(String text) 324 { 325 text = text.trim(); 326 327 LinearVelocityUnits units = null; 328 329 //Special case: "Z" 330 if (text.toUpperCase().equals("Z")) 331 { 332 units = Z; 333 } 334 //See if we get lucky 335 else if (UNITS.containsKey(text)) 336 { 337 units = UNITS.get(text); 338 } 339 //Parse text to create distance and time units 340 else 341 { 342 String separator = "/"; 343 344 if (!text.contains(separator)) //then try separator "per" 345 { 346 String upperText = text.toUpperCase(); 347 int sepIndex = upperText.indexOf(" PER "); 348 if (sepIndex > -1) 349 { 350 separator = text.substring(sepIndex, sepIndex+" PER ".length()); 351 } 352 else 353 { 354 sepIndex = upperText.indexOf("_PER_"); 355 if (sepIndex > -1) 356 { 357 separator = text.substring(sepIndex, sepIndex+" PER ".length()); 358 } 359 else 360 { 361 separator = null; 362 } 363 } 364 } 365 366 if (separator != null) 367 { 368 String[] tokens = text.split(separator); 369 370 EnumerationUtility eu = EnumerationUtility.getSharedInstance(); 371 372 TimeUnits tu = eu.enumFromString( TimeUnits.class, tokens[1]); 373 DistanceUnits du = eu.enumFromString(DistanceUnits.class, tokens[0]); 374 375 if (du == null) 376 { 377 //Distance units c/b plural; make singular 378 int indexLast = tokens[0].length() - 1; 379 if (tokens[0].toUpperCase().charAt(indexLast) == 'S') 380 { 381 tokens[0] = tokens[0].substring(0, indexLast); 382 du = eu.enumFromString(DistanceUnits.class, tokens[0]); 383 } 384 } 385 386 if (du == null || tu == null) 387 throwBadUnitsText(text, du, tu); 388 389 units = from(du, tu); 390 } 391 else //no separator 392 { 393 throw new IllegalArgumentException("Text '" + text + 394 "' could not be recognized as a unit of velocity."); 395 } 396 } 397 398 return units; 399 } 400 401 public static void throwBadUnitsText(String text, DistanceUnits du, TimeUnits tu) 402 throws IllegalArgumentException 403 { 404 StringBuilder msg = new StringBuilder(); 405 406 msg.append("Text '").append(text); 407 msg.append("' could not be recognized as a unit of velocity. "); 408 if (du != null) 409 { 410 msg.append("The distance units were recognized as '").append(du.name()); 411 if (tu == null) 412 { 413 msg.append("', but the time units were not recognized."); 414 } 415 else //tu not null, du not null 416 { 417 msg.append("', and the time units were recognized as '").append(tu.name()); 418 msg.append("', but the combination was not recognized. "); 419 msg.append("This could be a PROGRAMMER ERROR."); 420 } 421 } 422 else //du is null 423 { 424 if (tu != null) 425 { 426 msg.append("The time units were recognized as '").append(tu.name()); 427 msg.append("', but the distance units were not recognized."); 428 } 429 else //both null 430 { 431 msg.append("Neither the distance units nor the time units were recognized."); 432 } 433 } 434 435 throw new IllegalArgumentException(msg.toString()); 436 } 437 438 //============================================================================ 439 // 440 //============================================================================ 441 442 /** Returns <i>true</i> if <tt>o</tt> is equal to this units. */ 443 @Override 444 public boolean equals(Object o) 445 { 446 //Quick exit if o is this 447 if (o == this) 448 return true; 449 450 //Quick exit if o is null 451 if (o == null) 452 return false; 453 454 //Quick exit if classes are different 455 if (!o.getClass().equals(this.getClass())) 456 return false; 457 458 LinearVelocityUnits other = (LinearVelocityUnits)o; 459 460 return other.getSymbol().equals(this.getSymbol()); 461 } 462 463 /** Returns a hash code for this units. */ 464 @Override 465 public int hashCode() 466 { 467 return getSymbol().hashCode(); 468 } 469 470 //============================================================================ 471 // 472 //============================================================================ 473 474 /** Special subclass for "Z"; overrides a few methods. */ 475 private static class LVU_Z extends LinearVelocityUnits 476 { 477 private LVU_Z() 478 { 479 super(DistanceUnits.KILOMETER, TimeUnits.SECOND, LIGHT_SPEED_VACUUM_KM_PER_SEC); 480 } 481 482 @Override public String getSymbol() { return "Z"; } 483 @Override public String toString() { return "Z"; } 484 } 485 486 //============================================================================ 487 // 488 //============================================================================ 489 /* 490 public static void main(String... args) throws Exception 491 { 492 LinearVelocityUnits lvu; 493 494 //Build from good symbols 495 for (DistanceUnits du : DistanceUnits.values()) 496 { 497 for (TimeUnits tu : TimeUnits.values()) 498 { 499 String symbol = makeSymbol(du, tu); 500 lvu = fromString(symbol); 501 System.out.println(lvu.getSymbol() + ", " + lvu.toString()); 502 } 503 } 504 lvu = fromString("Z"); 505 System.out.println(lvu.getSymbol() + ", " + lvu.toString()); 506 507 //Clear memory of constructed units 508 LinearVelocityUnits.UNITS.clear(); 509 System.out.println("--------------------------------------------------------"); 510 511 //Build from good names 512 for (DistanceUnits du : DistanceUnits.values()) 513 { 514 for (TimeUnits tu : TimeUnits.values()) 515 { 516 String name = du.toString() + " per " + tu.toString(); 517 lvu = fromString(name); 518 System.out.println(lvu.getSymbol() + ", " + lvu.toString()); 519 } 520 } 521 } 522 */ 523 }