001 package edu.nrao.sss.measure; 002 003 import static edu.nrao.sss.math.MathUtil.MC_FINAL_CALC; 004 005 import java.math.BigDecimal; 006 007 import javax.xml.bind.annotation.XmlAccessType; 008 import javax.xml.bind.annotation.XmlAccessorType; 009 import javax.xml.bind.annotation.XmlElement; 010 import javax.xml.bind.annotation.XmlType; 011 012 import edu.nrao.sss.math.MathUtil; 013 import edu.nrao.sss.util.StringUtil; 014 015 /** 016 * A measure of angular velocity. 017 * <p> 018 * <b>Version Info:</b> 019 * <table style="margin-left:2em"> 020 * <tr><td>$Revision: 1816 $</td></tr> 021 * <tr><td>$Date: 2008-12-23 10:21:00 -0700 (Tue, 23 Dec 2008) $</td></tr> 022 * <tr><td>$Author: dharland $</td></tr> 023 * </table></p> 024 * 025 * @author David M. Harland 026 * @since 2006-05-30 027 */ 028 @XmlAccessorType(XmlAccessType.NONE) 029 @XmlType(propOrder={"xmlValue","unitsSymbol"}) 030 public class AngularVelocity 031 implements Cloneable, Comparable<AngularVelocity> 032 { 033 private static final BigDecimal DEFAULT_VALUE = BigDecimal.ZERO; 034 private static final AngularVelocityUnits DEFAULT_UNITS = 035 AngularVelocityUnits.MILLI_ARC_SECONDS_PER_YEAR; 036 037 //Used by equals, hashCode, and compareTo methods 038 private static AngularVelocityUnits STD_UNITS = 039 AngularVelocityUnits.MILLI_ARC_SECONDS_PER_YEAR; 040 041 private static final int PRECISION = MC_FINAL_CALC.getPrecision(); 042 043 private BigDecimal value; 044 private AngularVelocityUnits units; 045 046 //=========================================================================== 047 // CONSTRUCTORS 048 //=========================================================================== 049 050 /** Creates a new angular velocity of zero milliarcseconds per year. */ 051 public AngularVelocity() 052 { 053 set(DEFAULT_VALUE, DEFAULT_UNITS); 054 } 055 056 /** 057 * Creates a new angular velocity of {@code masPerYear} milliarcseconds 058 * per year. 059 */ 060 public AngularVelocity(BigDecimal masPerYear) 061 { 062 set(masPerYear, AngularVelocityUnits.MILLI_ARC_SECONDS_PER_YEAR); 063 } 064 065 /** 066 * Creates a new angular velocity of {@code masPerYear} milliarcseconds 067 * per year. 068 */ 069 public AngularVelocity(String masPerYear) 070 { 071 set(masPerYear, AngularVelocityUnits.MILLI_ARC_SECONDS_PER_YEAR); 072 } 073 074 /** Creates a new angular velocity with the given magnitude and units. */ 075 public AngularVelocity(BigDecimal magnitude, AngularVelocityUnits units) 076 { 077 set(magnitude, units); 078 } 079 080 /** Creates a new angular velocity with the given magnitude and units. */ 081 public AngularVelocity(String magnitude, AngularVelocityUnits units) 082 { 083 set(magnitude, units); 084 } 085 086 /** 087 * Resets this angular velocity so that it is equal to an 088 * angular velocity created via the no-argument constructor. 089 */ 090 public void reset() 091 { 092 set(DEFAULT_VALUE, DEFAULT_UNITS); 093 } 094 095 //=========================================================================== 096 // GETTING & SETTING THE PROPERTIES 097 //=========================================================================== 098 099 /** 100 * Returns the magnitude of this angular velocity. 101 * @return the magnitude of this angular velocity. 102 */ 103 public BigDecimal getValue() 104 { 105 return value; 106 } 107 108 /** 109 * Returns the units of this angular velocity. 110 * @return the units of this angular velocity. 111 */ 112 public AngularVelocityUnits getUnits() 113 { 114 return units; 115 } 116 117 /** 118 * Sets the value and units of this velocity based on {@code velocityText}. 119 * See {@link #parse(String)} for the expected format of 120 * {@code velocityText}. 121 * <p> 122 * If the parsing fails, this velocity will be kept in its current 123 * state.</p> 124 * 125 * @param velocityText 126 * a string that will be converted into a velocity. 127 * 128 * @throws IllegalArgumentException 129 * if {@code velocityText} is not in the expected form. 130 * 131 * @since 2008-09-22 132 */ 133 public void set(String velocityText) 134 { 135 if (velocityText == null || velocityText.equals("")) 136 { 137 this.reset(); 138 } 139 else 140 { 141 AngularVelocityUnits oldUnits = units; 142 BigDecimal oldValue = value; 143 144 try { 145 this.parseVelocity(velocityText); 146 } 147 catch (Exception ex) { 148 set(oldValue, oldUnits); 149 throw new IllegalArgumentException("Could not parse " + velocityText, ex); 150 } 151 } 152 } 153 154 /** 155 * Sets the magnitude and units of this angular velocity. 156 * 157 * @param value the new magnitude for this angular velocity. 158 * @param units the new units for this angular velocity. 159 */ 160 public final void set(BigDecimal value, AngularVelocityUnits units) 161 { 162 setValue(value); 163 setUnits(units); 164 } 165 166 /** 167 * Sets the magnitude and units of this angular velocity. 168 * 169 * @param value the new magnitude for this angular velocity. 170 * @param units the new units for this angular velocity. 171 */ 172 public final void set(String value, AngularVelocityUnits units) 173 { 174 setValue(value); 175 setUnits(units); 176 } 177 178 /** 179 * Sets the magnitude of this angular velocity to {@code newValue}. 180 * <p> 181 * Note that the <tt>units</tt> of this angular velocity are unaffected by 182 * this method.</p> 183 * 184 * @param newValue the new magnitude for this angular velocity. 185 */ 186 public final void setValue(BigDecimal newValue) 187 { 188 if (newValue == null) 189 throw new NumberFormatException("newValue may not be null."); 190 191 setAndRescale(newValue); 192 } 193 194 private void setAndRescale(BigDecimal newValue) 195 { 196 int precision = newValue.precision(); 197 198 if (precision < PRECISION) 199 { 200 int newScale = 201 (newValue.signum() == 0) ? 1 : PRECISION - precision + newValue.scale(); 202 203 value = newValue.setScale(newScale); 204 } 205 else if (precision > PRECISION) 206 { 207 value = newValue.round(MC_FINAL_CALC); 208 } 209 else 210 { 211 value = newValue; 212 } 213 } 214 215 /** 216 * Sets the magnitude of this angular velocity to {@code newValue}. 217 * <p> 218 * Note that the <tt>units</tt> of this angular velocity are unaffected by 219 * this method.</p> 220 * 221 * @param newValue the new magnitude for this angular velocity. 222 * 223 * @throws NumberFormatException 224 * if {@code newValue} is <i>null</i>. 225 */ 226 public final void setValue(String newValue) 227 { 228 if (newValue == null) 229 throw new NumberFormatException("newValue may not be null."); 230 231 BigDecimal newBD; 232 233 newValue = newValue.trim().toLowerCase(); 234 235 if (newValue.equals("infinity") || 236 newValue.equals("+infinity") || newValue.equals("-infinity")) 237 { 238 newBD = MathUtil.getInfiniteValue(newValue.startsWith("-") ? -1 : +1); 239 } 240 else 241 { 242 newBD = new BigDecimal(newValue); 243 } 244 245 setAndRescale(newBD); 246 } 247 248 /** 249 * Sets the units of this angular velocity to {@code newUnits}. 250 * <p> 251 * Note that the <tt>value</tt> of this angular velocity is unaffected by 252 * this method. 253 * Contrast this with {@link #convertTo(AngularVelocityUnits)}.</p> 254 * 255 * @param newUnits the new units for this angular velocity. 256 * If {@code newUnits} is <i>null</i> it will be replaced 257 * with a non-null default type. 258 */ 259 public final void setUnits(AngularVelocityUnits newUnits) 260 { 261 units = (newUnits == null) ? AngularVelocityUnits.getDefault() : newUnits; 262 } 263 264 //=========================================================================== 265 // HELPERS FOR PERSISTENCE MECHANISMS 266 //=========================================================================== 267 268 //JAXB was having trouble with the overloaded setValue methods. 269 //These methods work around that trouble. 270 @XmlElement(name="value") 271 @SuppressWarnings("unused") 272 private BigDecimal getXmlValue() { return getValue().stripTrailingZeros(); } 273 @SuppressWarnings("unused") 274 private void setXmlValue(BigDecimal v) { setValue(v); } 275 276 //We'll use the shorter symbol for persistent storage 277 @XmlElement(name="units") 278 @SuppressWarnings("unused") 279 private String getUnitsSymbol() { return getUnits().getSymbol(); } 280 @SuppressWarnings("unused") 281 private void setUnitsSymbol(String u) { setUnits(AngularVelocityUnits.fromString(u)); } 282 283 //=========================================================================== 284 // DERIVED QUERIES 285 //=========================================================================== 286 287 /** 288 * Returns <i>true</i> if this angular velocity is in its default state, 289 * no matter how it got there. 290 * <p> 291 * An angular velocity is in its <i>default state</i> if both its value and 292 * its units are the same as those of an angular velocity newly created via 293 * the {@link #AngularVelocity() no-argument constructor}. 294 * An angular velocity whose most recent update came via the 295 * {@link #reset() reset} method is also in its default state.</p> 296 * 297 * @return <i>true</i> if this angular velocity is in its default state. 298 */ 299 public boolean isInDefaultState() 300 { 301 return (value == DEFAULT_VALUE) && units.equals(DEFAULT_UNITS); 302 } 303 304 /** 305 * Returns <i>true</i> if the magnitude of this frequency approaches infinity. 306 * @return <i>true</i> if the magnitude of this frequency approaches infinity. 307 */ 308 public boolean isInfinite() 309 { 310 return MathUtil.doubleValueIsInfinite(value); 311 } 312 313 //=========================================================================== 314 // CONVERSION TO, AND EXPRESSION IN, OTHER UNITS 315 //=========================================================================== 316 317 /** 318 * Converts this measure of angular velocity to the new units. 319 * <p> 320 * After this method is complete this angular velocity will have units of 321 * {@code newUnits} and its <tt>value</tt> will have been converted 322 * accordingly.</p> 323 * 324 * @param newUnits the new units for this angular velocity. 325 * If {@code newUnits} is <i>null</i> an 326 * {@code IllegalArgumentException} will be thrown. 327 * 328 * @return this angular velocity. The reason for this return type is to allow 329 * code of this nature: 330 * {@code double kilometers = 331 * myAngularVelocity.convertTo(AngularVelocityUnits.KILOMETERS).getValue();} 332 */ 333 public AngularVelocity convertTo(AngularVelocityUnits newUnits) 334 { 335 if (newUnits == null) 336 throw new IllegalArgumentException("May not convert to NULL units."); 337 338 if (!newUnits.equals(units)) 339 { 340 set(toUnits(newUnits), newUnits); 341 } 342 343 return this; 344 } 345 346 /** 347 * Returns the magnitude of this angular velocity in {@code otherUnits}. 348 * <p> 349 * Note that this method does not alter the state of this angular velocity. 350 * Contrast this with {@link #convertTo(AngularVelocityUnits)}.</p> 351 * 352 * @param otherUnits the units in which to express this angular velocity's magnitude. 353 * 354 * @return this angular velocity's value converted to {@code otherUnits}. 355 * 356 * @throws IllegalArgumentException if {@code otherUnits} is <i>null</i>. 357 */ 358 public BigDecimal toUnits(AngularVelocityUnits otherUnits) 359 { 360 if (otherUnits == null) 361 throw new IllegalArgumentException("May not convert to NULL units."); 362 363 BigDecimal answer = value; 364 365 //No conversion for zero, infinite, or if no change of units 366 if (!otherUnits.equals(units) && 367 value.compareTo(BigDecimal.ZERO) != 0.0 && !isInfinite()) 368 { 369 answer = units.convertTo(otherUnits, value); 370 } 371 372 return answer; 373 } 374 375 //=========================================================================== 376 // ARITHMETIC 377 //=========================================================================== 378 379 /** 380 * Adds {@code other} angular velocity to this one. 381 * 382 * @param other the angular velocity to be added to this angular velocity. 383 * 384 * @return this angular velocity, after the addition. 385 */ 386 public AngularVelocity add(AngularVelocity other) 387 { 388 setValue(value.add(other.toUnits(units))); 389 390 return this; 391 } 392 393 /** 394 * Subtracts {@code other} angular velocity from this one. 395 * 396 * @param other the angular velocity to be subtracted from this angular velocity. 397 * 398 * @return this angular velocity, after the subtraction. 399 */ 400 public AngularVelocity subtract(AngularVelocity other) 401 { 402 //TODO when we work on INFINITY, we need to consider other being infinite 403 // and the result being inf, too 404 if (!isInfinite()) 405 setValue(value.subtract(other.toUnits(units))); 406 407 return this; 408 } 409 410 //=========================================================================== 411 // PARSING 412 //=========================================================================== 413 414 /** 415 * Returns a new velocity based on {@code velocityString}. 416 * <p> 417 * <b><u>Valid Formats</u></b><br/> 418 * Let R be the text representation of a real number.<br/> 419 * Let w represent zero or more whitespace characters.<br/> 420 * Let S be a valid {@link AngularVelocityUnits units} symbol.<br/> 421 * <br/> 422 * <i>Format One</i>: <tt>wRw</tt>. The given number will be defined to be 423 * in units of {@link AngularVelocityUnits#MILLI_ARC_SECONDS_PER_YEAR mas/yr}.<br/> 424 * <br/> 425 * <i>Format Two</i>: <tt>wRwSw</tt>.</p> 426 * <p> 427 * If {@code velocityString} is <i>null</i> or the empty string (<tt>""</tt>), 428 * the returned velocity will be equal to one created via the no-argument 429 * constructor.</p> 430 * 431 * @param velocityString 432 * a string that will be converted into a velocity. 433 * 434 * @return a new velocity. 435 * 436 * @throws IllegalArgumentException if {@code velocityString} is not in 437 * the expected form. 438 */ 439 public static AngularVelocity parse(String velocityString) 440 { 441 AngularVelocity newVelocity = new AngularVelocity(); 442 443 //null & "" are permissible 444 if ((velocityString != null) && !velocityString.equals("")) 445 { 446 try 447 { 448 newVelocity.parseVelocity(velocityString); 449 } 450 catch (Exception ex) 451 { 452 throw new IllegalArgumentException("Could not parse " + velocityString + 453 ". " + ex.getMessage(), ex); 454 } 455 } 456 457 return newVelocity; 458 } 459 460 //Did not just look for alpha char in case we have wacky things 461 //like Greeks symbols (eg, micrometers). 462 //Want 1st char that is non-digit, non-space, and not in [+ - .]. 463 static final String VALUE_UNITS_SPLITTER = "[^\\+\\-\\.\\d\\s]"; 464 465 /** 466 * If parsing was successful, this velocity's units & value will have been 467 * valued. Otherwise an exception is thrown. 468 */ 469 private void parseVelocity(String velocityString) 470 { 471 //Quick exit if text represents infinity 472 if (parseInfiniteVelocity(velocityString)) 473 return; 474 475 String[] parts = velocityString.split(VALUE_UNITS_SPLITTER, 2); 476 477 if (parts.length == 1) 478 { 479 set(parts[0], AngularVelocityUnits.getDefault()); 480 } 481 else if (parts.length == 2) 482 { 483 String unitsText = velocityString.substring(parts[0].length()); 484 AngularVelocityUnits vu = AngularVelocityUnits.fromString(unitsText); 485 486 if (vu == null) 487 throw new IllegalArgumentException("Could not parse '" + velocityString + 488 "'. Software thinks your units are '" + unitsText + 489 "', but supports no such units."); 490 491 set(parts[0], vu); 492 } 493 else 494 { 495 throw new RuntimeException("PROGRAMMER ERROR: split velocityString '" + 496 velocityString + "' into " + parts.length + 497 " parts. Max expected is 2 parts."); 498 } 499 } 500 501 //TODO see if this can be generalized in EnumUtil, perhaps 502 // This code is also in LinearVelocity 503 /** Returns <i>true</i> if parsed velocity was infinite. */ 504 private boolean parseInfiniteVelocity(String velocText) 505 { 506 final String origText = velocText; //in case we need to throw exception 507 508 boolean isInfinite; 509 510 final String INF_TEXT = "infinity"; 511 512 char signChar = velocText.charAt(0); 513 boolean negate = (signChar == '-'); 514 boolean hasSignChar = negate || (signChar == '+'); 515 516 //Strip off "+" or "-" 517 if (hasSignChar) 518 velocText = velocText.substring(1); 519 520 int testLength = INF_TEXT.length(); 521 int actualLength = velocText.length(); 522 523 //Might have "infinity" with no units 524 if (actualLength == testLength) 525 { 526 isInfinite = velocText.equalsIgnoreCase(INF_TEXT); 527 528 if (isInfinite) 529 set(MathUtil.getInfiniteValue(negate ? -1 : +1), STD_UNITS); 530 } 531 //Might have "infinity" followed by units 532 else if (actualLength > testLength) 533 { 534 String testString = velocText.substring(0, testLength); 535 536 isInfinite = testString.equalsIgnoreCase(INF_TEXT); 537 538 if (isInfinite) 539 { 540 AngularVelocityUnits vu = 541 AngularVelocityUnits.fromString(velocText.substring(testLength, actualLength)); 542 543 if (vu == null) 544 throw new IllegalArgumentException("Could not parse '" + origText + 545 "'. This looked like an infinite velocity but units could not be determined."); 546 547 set(MathUtil.getInfiniteValue(negate ? -1 : +1), vu); 548 } 549 } 550 //String too short to hold "infinity" 551 else //actualLength < testLength 552 { 553 isInfinite = false; 554 } 555 556 return isInfinite; 557 } 558 559 //=========================================================================== 560 // UTILITY METHODS 561 //=========================================================================== 562 563 /** Returns a text representation of this angular velocity. */ 564 @Override 565 public String toString() 566 { 567 return StringUtil.getInstance().formatNoScientificNotation(getValue()) + 568 getUnits().getSymbol(); 569 } 570 571 /** Returns a angular velocity that is equal to this one. */ 572 @Override 573 public AngularVelocity clone() 574 { 575 //Since this class has only primitive (& immutable) attributes, 576 //the clone in Object is all we need. 577 try 578 { 579 return (AngularVelocity)super.clone(); 580 } 581 catch (CloneNotSupportedException ex) 582 { 583 //We'll never get here, but just in case... 584 throw new RuntimeException(ex); 585 } 586 } 587 588 /** Returns <i>true</i> if {@code o} is equal to this angular velocity. */ 589 @Override 590 public boolean equals(Object o) 591 { 592 //Quick exit if o is this 593 if (o == this) 594 return true; 595 596 //Quick exit if o is null 597 if (o == null) 598 return false; 599 600 //Quick exit if classes are different 601 if (!o.getClass().equals(this.getClass())) 602 return false; 603 604 AngularVelocity other = (AngularVelocity)o; 605 606 //Treat two infinite values of same sign as equal, 607 //regardless of actual BigDecimal values 608 if (isInfinite() && other.isInfinite()) 609 return value.signum() == other.value.signum(); 610 611 //Ignore stored units; equality is based purely on magnitude in std units 612 return compareTo(other) == 0; 613 } 614 615 /** Returns a hash code value for this angular velocity. */ 616 @Override 617 public int hashCode() 618 { 619 if (isInfinite()) 620 return value.signum() > 0 ? "+infinity".hashCode() : "-infinity".hashCode(); 621 622 String crude = value.toPlainString() + units.getSymbol(); 623 return crude.hashCode(); 624 } 625 626 /** Compares this angular velocity with the {@code otherDist} for order. */ 627 public int compareTo(AngularVelocity otherDist) 628 { 629 //Treat two infinite values of same sign as equal, 630 //regardless of actual BigDecimal values 631 if (isInfinite() && otherDist.isInfinite()) 632 return value.signum() - otherDist.value.signum(); 633 634 //Avoid doing two unit conversions 635 return getValue().compareTo(otherDist.toUnits(units)); 636 } 637 638 //This is here for quick & dirty testing 639 /* 640 public static void main(String args[]) 641 { 642 AngularVelocity a1 = new AngularVelocity(1000000.0, AngularVelocityUnits.MILLI_ARC_SECONDS_PER_YEAR); 643 System.out.println("a1 = " + a1); 644 for (AngularVelocityUnits units : AngularVelocityUnits.values()) 645 { 646 a1.convertTo(units); 647 System.out.println("a1 = " + a1); 648 } 649 650 System.out.println(); 651 652 AngularVelocity a2 = new AngularVelocity(0.000001, AngularVelocityUnits.RADIANS_PER_DAY); 653 System.out.println("a2 = " + a2); 654 for (AngularVelocityUnits units : AngularVelocityUnits.values()) 655 { 656 a2.convertTo(units); 657 System.out.println("a2 = " + a2); 658 } 659 } 660 */ 661 /* 662 public static void main(String... args) throws Exception 663 { 664 for (ArcUnits au : ArcUnits.values()) 665 for (TimeUnits tu : TimeUnits.values()) 666 { 667 AngularVelocityUnits vu = AngularVelocityUnits.from(au, tu); 668 System.out.println(" <xs:enumeration value=\""+vu.getSymbol()+"\"/>"); 669 } 670 } 671 */ 672 }