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