001    package edu.nrao.sss.measure;
002    
003    import static java.math.BigDecimal.ONE;
004    
005    import java.math.BigDecimal;
006    import java.util.ArrayList;
007    import java.util.List;
008    import java.util.SortedMap;
009    import java.util.TreeMap;
010    
011    import static edu.nrao.sss.math.MathUtil.MC_FINAL_CALC;
012    
013    import edu.nrao.sss.util.EnumerationUtility;
014    import edu.nrao.sss.util.Symbolic;
015    
016    /**
017     * Units of angular velocity.
018     * <p>
019     * <b>Version Info:</b>
020     * <table style="margin-left:2em">
021     *   <tr><td>$Revision: 1586 $</td></tr>
022     *   <tr><td>$Date: 2008-10-01 10:38:49 -0600 (Wed, 01 Oct 2008) $</td></tr>
023     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
024     * </table></p>
025     * 
026     * @author David M. Harland
027     * @since 2006-05-30
028     */
029    public class AngularVelocityUnits
030      implements Symbolic
031    {
032      private static final int PRECISION = MC_FINAL_CALC.getPrecision(); 
033      
034      private static final boolean IS_CASE_SENSITIVE;
035      static
036      {
037        IS_CASE_SENSITIVE =
038          ArcUnits.getDefault().symbolsAreCaseSensitive() ||
039          TimeUnits.getDefault().symbolsAreCaseSensitive();
040      }
041    
042      private static final SortedMap<String, AngularVelocityUnits> UNITS =
043        new TreeMap<String, AngularVelocityUnits>();
044        
045      /**
046       * Returns angular velocity units for the given angle and time units.
047       * Successive calls to this method with the same parameters will return
048       * the same object, not new equal objects.
049       * 
050       * @param angleUnits
051       *   units of arc, the numerator for the returned velocity units.
052       *   
053       * @param timeUnits
054       *   units of time, the denominator for the returned velocity units.
055       *   
056       * @return
057       *   angular velocity units of <tt>angleUnits</tt> per <tt>timeUnits</tt>.
058       */
059      public static AngularVelocityUnits from(ArcUnits  angleUnits,
060                                                  TimeUnits timeUnits)
061      {
062        String key = makeSymbol(angleUnits, timeUnits);
063        
064        AngularVelocityUnits units = UNITS.get(key);
065        
066        if (units == null)
067        {
068          units = new AngularVelocityUnits(angleUnits, timeUnits);
069          UNITS.put(key, units);
070        }
071        
072        return units;
073      }
074      
075      /**
076       * Returns default units for angular velocity.
077       * @return default units for angular velocity.
078       */
079      public static AngularVelocityUnits getDefault()
080      {
081        return MILLI_ARC_SECONDS_PER_YEAR;
082      }
083    
084      /**
085       * This non-public method is here only to support unit testing and should
086       * not be employed for other purposes.  Calling this method erases this
087       * class's memory of which units have been created.  If called after clients
088       * have made velocity units, it will be possible for one client to have
089       * one m/s instance, and new clients to have a different m/s instance.
090       * Not a crisis, but wasteful, and in violation of comments in
091       * "from" method.
092       */
093      static void clearCache()  { UNITS.clear(); }
094        
095      //============================================================================
096      // FREQUENTLY USED UNITS
097      //============================================================================
098      
099      /** Arcseconds per day. */
100      public static final AngularVelocityUnits ARC_SECONDS_PER_DAY=
101        from(ArcUnits.ARC_SECOND, TimeUnits.DAY);
102      
103      /** Milliarcseconds per year. */
104      public static final AngularVelocityUnits MILLI_ARC_SECONDS_PER_YEAR =
105        from(ArcUnits.MILLI_ARC_SECOND, TimeUnits.YEAR);
106    
107      /**
108       * Returns a list of frequently used velocity units.
109       * This is a small subset of the total set of units that could
110       * be produced by all combinations of {@link ArcUnits}
111       * and {@link TimeUnits}.
112       * 
113       * @return a list of frequently used velocity units.
114       */
115      public static List<AngularVelocityUnits> getFrequentlyUsedUnits()
116      {
117        List<AngularVelocityUnits> list = new ArrayList<AngularVelocityUnits>();
118        
119        list.add(ARC_SECONDS_PER_DAY);
120        list.add(MILLI_ARC_SECONDS_PER_YEAR);
121        
122        return list;
123      }
124    
125      //============================================================================
126      // INSTANCE VARIABLES, CONSTRUCTORS, & SIMPLE GETTERS
127      //============================================================================
128    
129      private ArcUnits  arcUnits;
130      private TimeUnits timeUnits;
131    
132      //This no-arg constructor is here for mechanisms such as JAXB & Hibernate
133      private AngularVelocityUnits()
134      {
135        this(ArcUnits.getDefault(), TimeUnits.getDefault());
136      }
137      
138      private AngularVelocityUnits(ArcUnits au, TimeUnits tu)
139      {
140        arcUnits  = au;
141        timeUnits = tu;
142      }
143      
144      /**
145       * Returns <i>true</i> if either the {@link ArcUnits} or {@link TimeUnits}
146       * class has case-sensitive symbols.
147       */
148      public boolean symbolsAreCaseSensitive()
149      {
150        return IS_CASE_SENSITIVE;
151      }
152    
153      /**
154       * Returns the units of arc used by this unit of velocity.
155       * @return the units of arc used by this unit of velocity.
156       */
157      public ArcUnits getArcUnits()
158      {
159        return arcUnits;
160      }
161      
162      /**
163       * Returns the units of time used by this unit of velocity.
164       * @return the units of time used by this unit of velocity.
165       */
166      public TimeUnits getTimeUnits()
167      {
168        return timeUnits;
169      }
170    
171      //============================================================================
172      // CONVERSIONS TO OTHER UNITS
173      //============================================================================
174    
175      /**
176       * Returns a factor for converting from this unit to {@code otherUnits}.
177       * 
178       * @param otherUnits the unit to which conversion is desired.
179       * 
180       * @return a factor for converting from this unit to {@code otherUnits}.
181       */
182      public BigDecimal toUnits(AngularVelocityUnits otherUnits)
183      {
184        return convertTo(otherUnits, ONE);
185      }
186      
187      /**
188       * Non-public method for use by this and other classes in pkg.
189       * Does the actual conversion; keeps answer in BigDecimal form.
190       */
191      BigDecimal convertTo(AngularVelocityUnits otherUnits, BigDecimal value)
192      {
193        BigDecimal answer = value;
194    
195        if (!otherUnits.equals(this))
196        {
197          if (value.scale() < PRECISION)
198            value = value.setScale(PRECISION);
199          
200          //Strategy: use conversion methods of ArcUnits and TimeUnits then divide
201          BigDecimal angle = this.arcUnits.convertTo(otherUnits.arcUnits, value);
202          BigDecimal time  = this.timeUnits.convertTo(otherUnits.timeUnits, BigDecimal.ONE);
203    
204          try  //to use BigDecimal, for better accuracy, but...
205          {
206            answer = angle.divide(time, MC_FINAL_CALC);
207          }
208          catch (Exception ex)
209          {
210            //...if it fails, use java primitives
211            answer = BigDecimal.valueOf(angle.doubleValue() / time.doubleValue());
212          }
213        }
214        
215        return answer;
216      }
217    
218      //============================================================================
219      // TO / FROM TEXT
220      //============================================================================
221    
222      private static String makeSymbol(ArcUnits au, TimeUnits tu)
223      {
224        return au.getSymbol() + '/' + tu.getSymbol();
225      }
226    
227      /**
228       * Returns the symbol for this unit.
229       * @return the symbol for this unit.
230       */
231      public String getSymbol()
232      {
233        return makeSymbol(arcUnits, timeUnits);
234      }
235    
236      /**
237       * Returns the name of this unit.
238       * This method is here for historical reasons; it mimics the behavior
239       * this method had when this class had been an <tt>enum</tt> class.
240       * The {@link #toString()} is a better alternative.
241       * 
242       * @return the name of this unit.
243       */
244      public String name()
245      {
246        return arcUnits.name() + "_PER_" + timeUnits.name();
247      }
248      
249      /**
250       * Returns a text representation of this unit of angular velocity.
251       * @return a text representation of this unit of angular velocity.
252       */
253      @Override
254      public String toString()
255      {
256        EnumerationUtility eu = EnumerationUtility.getSharedInstance();
257        
258        return eu.enumToString(arcUnits) + " Per " + eu.enumToString(timeUnits);
259      }
260      
261      /**
262       * Returns the angular velocity units represented by {@code text}.
263       * <p>
264       * Two basic formats for <tt>text</tt> are employed, both based on the
265       * text representations of the component angle and time units.
266       * The first is of the form
267       * <tt><i>arcUnits.symbol</i> / <i>timeUnits.symbol</i></tt>.
268       * The amount of space surrounding the "/" separator is not material;
269       * zero or more spaces are allowed on either side of the separator.
270       * The second is of the form
271       * <tt><i>arcUnits.name</i> per <i>timeUnits.name</i></tt>.
272       * The case of the separator "per" is not material.  The same comment about
273       * whitespace given above applies here as well.
274       * The rules governing the format of the symbol or name of the component
275       * units are given in
276       * {@link EnumerationUtility#enumFromString(Class, String)}.</p>
277       * 
278       * @param text a text representation of a unit of linear velocity.
279       * 
280       * @return the linear velocity units represented by {@code text}.
281       */
282      public static AngularVelocityUnits fromString(String text)
283      {
284        text = text.trim();
285        
286        AngularVelocityUnits units = null;
287        
288        //See if we already made this unit
289        if (UNITS.containsKey(text))
290        {
291          units = UNITS.get(text);
292        }
293        //Parse text to create angle and time units
294        else
295        {
296          String separator = "/";
297    
298          if (!text.contains(separator)) //then try separator "per"
299          {
300            String upperText = text.toUpperCase();
301            int sepIndex = upperText.indexOf(" PER ");
302            if (sepIndex > -1)
303            {
304              separator = text.substring(sepIndex, sepIndex+" PER ".length());
305            }
306            else
307            {
308              sepIndex = upperText.indexOf("_PER_");
309              if (sepIndex > -1)
310              {
311                separator = text.substring(sepIndex, sepIndex+" PER ".length());
312              }
313              else
314              {
315                separator = null;
316              }
317            }
318          }
319          
320          if (separator != null)
321          {
322            String[] tokens = text.split(separator);
323    
324            EnumerationUtility eu = EnumerationUtility.getSharedInstance();
325    
326            TimeUnits tu = eu.enumFromString(TimeUnits.class, tokens[1]);
327            ArcUnits  au = eu.enumFromString( ArcUnits.class, tokens[0]);
328            
329            if (au == null)
330            {
331              //Arc units c/b plural; make singular
332              int indexLast = tokens[0].length() - 1;
333              if (tokens[0].toUpperCase().charAt(indexLast) == 'S')
334              {
335                tokens[0] = tokens[0].substring(0, indexLast);
336                au = eu.enumFromString(ArcUnits.class, tokens[0]);
337              }
338            }
339            
340            units = from(au, tu);
341          }
342          else //no separator
343          {
344            throw new IllegalArgumentException("Text '" + text +
345              "' could not be recognized as a unit of velocity.");
346          }
347        }
348        
349        return units;
350      }
351    
352      //============================================================================
353      // 
354      //============================================================================
355    
356      /** Returns <i>true</i> if <tt>o</tt> is equal to this units. */
357      @Override
358      public boolean equals(Object o)
359      {
360        //Quick exit if o is this
361        if (o == this)
362          return true;
363        
364        //Quick exit if o is null
365        if (o == null)
366          return false;
367        
368        //Quick exit if classes are different
369        if (!o.getClass().equals(this.getClass()))
370          return false;
371    
372        AngularVelocityUnits other = (AngularVelocityUnits)o;
373        
374        return other.getSymbol().equals(this.getSymbol());
375      }
376      
377      /** Returns a hash code for this units. */
378      @Override
379      public int hashCode()
380      {
381        return getSymbol().hashCode();
382      }
383      
384      //============================================================================
385      // 
386      //============================================================================
387      /*
388      public static void main(String... args) throws Exception
389      {
390        AngularVelocityUnits avu;
391    
392        //Build from good symbols
393        for (ArcUnits au : ArcUnits.values())
394        {
395          for (TimeUnits tu : TimeUnits.values())
396          {
397            String symbol = makeSymbol(au, tu);
398            avu = fromString(symbol);
399            System.out.println(avu.getSymbol() + ", " + avu.toString());
400          }
401        }
402          
403        //Clear memory of constructed units
404        AngularVelocityUnits.UNITS.clear();
405        System.out.println("--------------------------------------------------------");
406          
407        //Build from good names
408        for (ArcUnits au : ArcUnits.values())
409        {
410          for (TimeUnits tu : TimeUnits.values())
411          {
412            String name = au.toString() + " per " + tu.toString();
413            avu = fromString(name);
414            System.out.println(avu.getSymbol() + ", " + avu.toString());
415          }
416        }
417      }
418      */
419    }