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    }