001    package edu.nrao.sss.measure;
002    
003    import static edu.nrao.sss.math.MathUtil.MC_FINAL_CALC;
004    import static edu.nrao.sss.math.MathUtil.MC_INTERM_CALCS;
005    
006    import java.math.BigDecimal;
007    import java.util.Arrays;
008    import java.util.Comparator;
009    
010    import javax.xml.bind.annotation.XmlAccessType;
011    import javax.xml.bind.annotation.XmlAccessorType;
012    import javax.xml.bind.annotation.XmlElement;
013    import javax.xml.bind.annotation.XmlType;
014    
015    import edu.nrao.sss.math.MathUtil;
016    import edu.nrao.sss.util.StringUtil;
017    
018    //TODO work on infinity.  Make private static +/-INFINITY BigDecimal variables
019    //     Whenever a value becomes infinite, use the private statics
020    
021    //TODO the allow-negative-values was kludged in and has not been accounted for
022    //     in things like JAXB & maybe Hibernate.  We might be saving negative
023    //     values and then not allowing them to be reconstituted.
024    
025    /**
026     * A measure of frequency.
027     * <p>
028     * <b><u>Note About Accuracy</u></b><br/>
029     * This class originally used java's primitive <tt>double</tt> type
030     * for storage and calculation.  Certain transformations, though, 
031     * led to results that where not accurate enough for many purposes.
032     * Because of that, the internal references to <tt>double</tt>
033     * have been replaced with references to {@link BigDecimal}.</p>
034     * <p>
035     * <b>Version Info:</b>
036     * <table style="margin-left:2em">
037     *   <tr><td>$Revision: 1816 $</td></tr>
038     *   <tr><td>$Date: 2008-12-23 10:21:00 -0700 (Tue, 23 Dec 2008) $</td></tr>
039     *   <tr><td>$Author: dharland $</td></tr>
040     * </table></p>
041     *  
042     * @author David M. Harland
043     * @since 2006-05-03
044     */
045    @XmlAccessorType(XmlAccessType.NONE)
046    @XmlType(propOrder={"storedValue","units"})
047    public class Frequency
048      implements Cloneable, Comparable<Frequency>
049    {
050    
051      private static final BigDecimal     DEFAULT_VALUE = BigDecimal.ZERO;
052              static final FrequencyUnits DEFAULT_UNITS = FrequencyUnits.GIGAHERTZ;
053    
054      //Used by equals, hashCode, and compareTo methods
055      private static FrequencyUnits STD_UNITS = FrequencyUnits.HERTZ;
056    
057      private static final int PRECISION = MC_FINAL_CALC.getPrecision(); 
058    
059      private BigDecimal     value;
060      private FrequencyUnits units;
061      
062      private boolean allowNegativeValues;
063    
064      //===========================================================================
065      // CONSTRUCTORS
066      //===========================================================================
067    
068      /** Creates a new frequency of zero gigahertz. */
069      public Frequency()
070      {
071        allowNegativeValues = false;
072        set(DEFAULT_VALUE, DEFAULT_UNITS);
073      }
074      
075      /**
076       * Creates a new frequency of {@code gigahertz} gigahertz.
077       * See {@link #setValue(BigDecimal)} for information
078       * about valid parameter values and exceptions that might
079       * be thrown.
080       */
081      public Frequency(BigDecimal gigahertz)
082      {
083        allowNegativeValues = gigahertz.signum() < 0;
084        set(gigahertz, FrequencyUnits.GIGAHERTZ);
085      }
086      
087      /**
088       * Creates a new frequency of {@code gigahertz} gigahertz.
089       * See {@link #setValue(String)} for information
090       * about valid parameter values and exceptions that might
091       * be thrown.
092       */
093      public Frequency(String gigahertz)
094      {
095        allowNegativeValues = gigahertz.trim().startsWith("-");
096        set(gigahertz, FrequencyUnits.GIGAHERTZ);
097      }
098      
099      /**
100       * Creates a new frequency with the given magnitude and units.
101       * See {@link #set(BigDecimal, FrequencyUnits)} for information
102       * about valid parameter values and exceptions that might
103       * be thrown.
104       */
105      public Frequency(BigDecimal magnitude, FrequencyUnits units)
106      {
107        allowNegativeValues = magnitude.signum() < 0;
108        set(magnitude, units);
109      }
110      
111      /**
112       * Creates a new frequency with the given magnitude and units.
113       * See {@link #set(String, FrequencyUnits)} for information
114       * about valid parameter values and exceptions that might
115       * be thrown.
116       */
117      public Frequency(String magnitude, FrequencyUnits units)
118      {
119        allowNegativeValues = magnitude.trim().startsWith("-");
120        set(magnitude, units);
121      }
122    
123      /**
124       * Resets this frequency so that it is equal to a frequency created
125       * via the no-argument constructor.
126       */
127      public void reset()
128      {
129        allowNegativeValues = false;
130        set(DEFAULT_VALUE, DEFAULT_UNITS);
131      }
132    
133      //===========================================================================
134      // GETTING & SETTING THE PROPERTIES
135      //===========================================================================
136      
137      /**
138       * Returns the magnitude of this frequency.
139       * @return the magnitude of this frequency.
140       */
141      public BigDecimal getValue()
142      {
143        return value;
144      }
145      
146      /**
147       * Returns the units of this frequency.
148       * @return the units of this frequency.
149       */
150      @XmlElement
151      public FrequencyUnits getUnits()
152      {
153        return units;
154      }
155      
156      /**
157       * Sets the value and units of this frequency based on {@code frequencyText}.
158       * See {@link #parse(String)} for the expected format of
159       * {@code frequencyText}.
160       * <p>
161       * If the parsing fails, this frequency will be kept in its current
162       * state.</p>
163       *                                  
164       * @param frequencyText
165       *   a string that will be converted into a frequency.
166       * 
167       * @throws IllegalArgumentException
168       *   if {@code frequencyText} is not in the expected form.
169       */
170      public void set(String frequencyText)
171      {
172        if (frequencyText == null || frequencyText.equals(""))
173        {
174          this.reset();
175        }
176        else
177        {
178          FrequencyUnits oldUnits = units;
179          BigDecimal     oldValue = value;
180        
181          try {
182            this.parseFrequency(frequencyText);
183          }
184          catch (Exception ex) {
185            set(oldValue, oldUnits);
186            throw new IllegalArgumentException("Could not parse " + frequencyText, ex);
187          }
188        }
189      }
190    
191      /**
192       * Sets the magnitude and units of this frequency.
193       * <p>
194       * See {@link #setValue(BigDecimal)} for more information on legal
195       * values for <tt>value</tt>.</p>
196       * 
197       * @param value the new magnitude for this frequency.
198       * @param units the new units for this frequency.
199       */
200      public final void set(BigDecimal value, FrequencyUnits units)
201      {
202        setValue(value);
203        setUnits(units);
204      }
205      
206      /**
207       * Sets the magnitude and units of this frequency.
208       * <p>
209       * See {@link #setValue(String)} for more information on legal
210       * values for <tt>value</tt>.</p>
211       * 
212       * @param value the new magnitude for this frequency.
213       * @param units the new units for this frequency.
214       */
215      public final void set(String value, FrequencyUnits units)
216      {
217        setValue(value);
218        setUnits(units);
219      }
220      
221      /**
222       * Sets the magnitude of this frequency to {@code newValue}.
223       * <p>
224       * Note that the <tt>units</tt> of this frequency are unaffected by
225       * this method.</p>
226       * 
227       * @param newValue
228       *   the new magnitude for this frequency.  This value is normally be zero or
229       *   positive, but negative values will be accepted if the user has
230       *   specifically allowed negative values by calling
231       *   {@link #setAllowNegativeValues(boolean)}.  It is permissible for this
232       *   value to be infinite, but it may not be <i>null</i>.
233       * 
234       * @throws NumberFormatException
235       *   if {@code newValue} is <i>null</i>, or if it is negative and negative
236       *   values have not been expressly allowed.
237       */
238      public final void setValue(BigDecimal newValue)
239      {
240        if (newValue == null || (!allowNegativeValues && newValue.signum() < 0))
241          throw new NumberFormatException("newValue=" + newValue +
242          " is not a valid duration.  It must be non-null and non-negative.");
243        
244        setAndRescale(newValue);
245      }
246      
247      private void setAndRescale(BigDecimal newValue)
248      {
249        int precision = newValue.precision();
250        
251        if (precision < PRECISION)
252        {
253          int newScale =
254            (newValue.signum() == 0) ? 1 : PRECISION - precision + newValue.scale();
255          
256          value = newValue.setScale(newScale);
257        }
258        else if (precision > PRECISION)
259        {
260          value = newValue.round(MC_FINAL_CALC);
261        }
262        else
263        {
264          value = newValue;
265        }
266      }
267    
268      /**
269       * Sets the magnitude of this frequency to {@code newValue}.
270       * <p>
271       * Note that the <tt>units</tt> of this frequency are unaffected by
272       * this method.</p>
273       * 
274       * @param newValue
275       *   the new magnitude for this frequency.  This value is normally zero or
276       *   positive, but negative values will be accepted if the user has
277       *   specifically allowed negative values by calling
278       *   {@link #allowNegativeValues}.  It is permissible for this value to be
279       *   infinite, but it may not be <tt>NaN</tt>.  The allowable representations
280       *   of infinity are <tt>"infinity", "+infinity", </tt>and<tt>
281       *   "-infinity"</tt>; these values are not case sensitive.
282       *        
283       * @throws NumberFormatException
284       *   if {@code newValue} is <i>null</i>, <tt>NaN</tt>,
285       *   or if it is negative and negative values have not been expressly allowed.
286       */
287      public final void setValue(String newValue)
288      {
289        if (newValue == null)
290          throw new NumberFormatException("newValue may not be null.");
291        
292        BigDecimal newBD;
293        
294        newValue = newValue.trim().toLowerCase();
295        
296        if (newValue.equals("infinity") ||
297            newValue.equals("+infinity") || newValue.equals("-infinity"))
298        {
299          newBD = MathUtil.getInfiniteValue(newValue.startsWith("-") ? -1 : +1);
300        }
301        else
302        {
303          newBD = new BigDecimal(newValue);
304        }
305        
306        if (!allowNegativeValues && newBD.signum() < 0)
307          throw new NumberFormatException("newValue=" + newValue +
308          " is not a valid frequency.  It may not be negative.");
309        
310        setAndRescale(newBD);
311      }
312    
313      /**
314       * Sets the units of this frequency to {@code newUnits}.
315       * <p>
316       * Note that the <tt>value</tt> of this frequency is unaffected by
317       * this method.  Contrast this with {@link #convertTo(FrequencyUnits)}.</p>
318       * 
319       * @param newUnits
320       *   the new units for this frequency.  If {@code newUnits} is <i>null</i>
321       *   it will be treated as {@link FrequencyUnits#GIGAHERTZ}.
322       */
323      public final void setUnits(FrequencyUnits newUnits)
324      {
325        units = (newUnits == null) ? FrequencyUnits.GIGAHERTZ : newUnits;
326      }
327      
328      /**
329       * Configure this frequency so that it allows, or disallows, negative
330       * values.  By default negative values are <i>not</i> allowed.
331       *  
332       * @param allow
333       *   <i>true</i> if this frequency should be allowed to hold negative values.
334       *   
335       * @throws IllegalStateException
336       *   if <tt>allow</tt> is <i>false</i> and this frequency is currently
337       *   negative.
338       */
339      public void setAllowNegativeValues(boolean allow)
340      {
341        if (!allow && value.signum() < 0)
342          throw new IllegalStateException("This frequency is currently " +
343            toString() + ", so you may not disallow negative frequencies.");
344          
345        allowNegativeValues = allow;
346      }
347    
348      //===========================================================================
349      // HELPERS FOR PERSISTENCE MECHANISMS
350      //===========================================================================
351      
352      //JAXB was having trouble with the overloaded setValue methods.
353      //These methods work around that trouble.
354      //Also, the set method now allows negative values.
355      @XmlElement(name="value")
356      @SuppressWarnings("unused")
357      private BigDecimal getStoredValue()  { return getValue().stripTrailingZeros(); }
358      
359      @SuppressWarnings("unused")
360      private void setStoredValue(BigDecimal v)
361      {
362        allowNegativeValues = v.signum() < 0;
363        setValue(v);
364      }
365    
366      //===========================================================================
367      // DERIVED QUERIES
368      //===========================================================================
369    
370      /**
371       * Returns <i>true</i> if this frequency is in its default state,
372       * no matter how it got there.
373       * <p>
374       * A frequency is in its <i>default state</i> if both its value and
375       * its units are the same as those of a frequency newly created via
376       * the {@link #Frequency() no-argument constructor}.
377       * A frequency whose most recent update came via the
378       * {@link #reset() reset} method is also in its default state.</p>
379       * 
380       * @return <i>true</i> if this frequency is in its default state.
381       */
382      public boolean isInDefaultState()
383      {
384        return value.equals(DEFAULT_VALUE) &&
385               units.equals(DEFAULT_UNITS);
386      }
387    
388      /**
389       * Returns <i>true</i> if the magnitude of this frequency approaches infinity.
390       * @return <i>true</i> if the magnitude of this frequency approaches infinity.
391       */
392      public boolean isInfinite()
393      {
394        return MathUtil.doubleValueIsInfinite(value);
395      }
396      
397      /**
398       * Returns <i>true</i> if this frequency is equal to either the
399       * low or high frequency of {@code range}.
400       * 
401       * @param range
402       *   a frequency range that may or may not have a low or high frequency equal
403       *   to this one.
404       * 
405       * @return
406       *   <i>true</i> if this frequency is one of the endpoints of {@code range}.
407       * 
408       * @since 2008-10-14
409       */
410      public boolean isEndPointOf(FrequencyRange range)
411      {
412        return compareTo(range.getLowFrequency() ) == 0 ||
413               compareTo(range.getHighFrequency()) == 0;
414      }
415      
416      //===========================================================================
417      // CONVERSION TO, AND EXPRESSION IN, OTHER UNITS
418      //===========================================================================
419    
420      /**
421       * Converts this measure of frequency to the new units.
422       * <p>
423       * After this method is complete this frequency will have units of
424       * {@code units} and its <tt>value</tt> will have been converted
425       * accordingly.</p>
426       *  
427       * @param newUnits the new units for this frequency.
428       *                 If {@code newUnits} is <i>null</i> an
429       *                 {@code IllegalArgumentException} will be thrown.
430       * 
431       * @return this frequency.  The reason for this return type is to allow
432       *         code of this nature:
433       *         {@code BigDecimal gigahertz = 
434       *         myFrequency.convertTo(FrequencyUnits.GIGAHERTZ).getValue();}
435       */
436      public Frequency convertTo(FrequencyUnits newUnits)
437      {
438        if (newUnits == null)
439          throw new
440            IllegalArgumentException("NULL is not a valid value for newUnits.");
441      
442        if (!newUnits.equals(units))
443        {
444          set(toUnits(newUnits), newUnits);
445        }
446        
447        return this;
448      }
449      
450      /**
451       * Returns the magnitude of this frequency in {@code otherUnits}.
452       * <p>
453       * Note that this method does not alter the state of this frequency.
454       * Contrast this with {@link #convertTo(FrequencyUnits)}.</p>
455       * 
456       * @param otherUnits the units in which to express this frequency's magnitude.
457       *                   If {@code newUnits} is <i>null</i>, it will be treated as
458       *                   {@link FrequencyUnits#GIGAHERTZ}.
459       * 
460       * @return this frequency's value converted to {@code otherUnits}.
461       */
462      public BigDecimal toUnits(FrequencyUnits otherUnits)
463      {
464        BigDecimal answer = value;
465        
466        if (otherUnits == null)
467          otherUnits = FrequencyUnits.GIGAHERTZ;
468        
469        //No conversion for zero, infinite, or if no change of units
470        if (!otherUnits.equals(units) && 
471            value.compareTo(BigDecimal.ZERO) != 0.0 && !isInfinite())
472        {
473          answer = units.convertTo(otherUnits, value);
474        }
475    
476        return answer;
477      }
478      
479      /**
480       * Converts the value and units of this frequency so that the value
481       * is between one and one thousand.  The units will be <tt>10<sup>x</sup></tt>
482       * Hertz, where <tt>x</tt> is evenly divisible by three.
483       * The value might not fall in the range [1.0-1000.0) if the
484       * units are the smallest or largest available.
485       * <p>
486       * If this frequency is exactly zero, the default units, as opposed to the
487       * smallest units, will be used.</p>
488       * 
489       * @return this frequency, after normalization.
490       */
491      public Frequency normalize()
492      {
493        if (value.signum() == 0)
494        {
495          units = FrequencyUnits.getDefault();
496        }
497        else
498        {
499          int power = (int)Math.floor(
500                        Math.log10(toUnits(FrequencyUnits.HERTZ).doubleValue()));
501        
502          FrequencyUnits newUnits = FrequencyUnits.getForMultipleOfThreePower(power);
503        
504          convertTo(newUnits);
505        }
506        
507        return this;
508      }
509    
510      //===========================================================================
511      // ARITHMETIC
512      //===========================================================================
513    
514      /**
515       * Adds {@code other} frequency to this one.
516       * <p>
517       * If all of the items below are true:
518       * <ol>
519       *   <li>this frequency is positive</li>
520       *   <li><tt>other</tt> is negative</li>
521       *   <li>the magnitude of <tt>other</tt> is greater than the magnitude
522       *       of this frequency</li>
523       *   <li>this frequency does not allow negative values</li>
524       * </ol>
525       * the result of this operation will be a frequency of zero.
526       * Otherwise the result will be the sum of the two frequencies.</p>
527       * 
528       * @param other the frequency to be added to this frequency.
529       * 
530       * @return this frequency, after the addition.
531       */
532      public Frequency add(Frequency other)
533      {
534        //TODO when we work on INFINITY, we need to consider other being infinite
535        //     and the result being inf, too
536        if (!isInfinite())
537        {
538          BigDecimal sum = value.add(other.toUnits(this.units));
539        
540          if (!allowNegativeValues && sum.signum() < 0)
541            sum = BigDecimal.ZERO;
542          
543          setAndRescale(sum);
544        }
545        return this;
546      }
547      
548      /**
549       * Subtracts {@code other} frequency from this one.
550       * <p>
551       * If the result of the subtraction is negative, and if this frequency
552       * does not allow negative values, the result of this operation will
553       * be a frequency of zero.</p>
554       * 
555       * @param other the frequency to be subtracted from this frequency.
556       * 
557       * @return this frequency, after the subtraction.
558       */
559      public Frequency subtract(Frequency other)
560      {
561        //TODO when we work on INFINITY, we need to consider other being infinite
562        //     and the result being inf, too
563        if (!isInfinite())
564        {
565          BigDecimal diff = value.subtract(other.toUnits(this.units));
566          
567          if (!allowNegativeValues && diff.signum() < 0)
568            diff = BigDecimal.ZERO;
569    
570          setAndRescale(diff);
571        }
572        return this;
573      }
574      
575      /**
576       * Multiplies this frequency by {@code multiplier}.
577       * 
578       * @param multiplier
579       *   the number by which this frequency should be multiplied.
580       *   This value must not result in a frequency magnitude that is
581       *   negative or <tt>NaN</tt>.
582       *        
583       * @return this frequency, after the multiplication.
584       * 
585       * @throws ArithmeticException
586       *   if the multiplication results in a negative value and this frequency  
587       *   is not allowed to be negative.
588       *   
589       * @throws NumberFormatException
590       *   if <tt>multiplier</tt> is <tt>NaN</tt> or is
591       *   otherwise rejected by the <tt>BigDecimal</tt> class.
592       *   
593       * @throws NullPointerException
594       *   if <tt>multiplier</tt> is <i>null</i>.
595       */
596      public Frequency multiplyBy(String multiplier)
597      {
598        return multiplyBy(new BigDecimal(multiplier));
599      }
600    
601      /**
602       * Multiplies this frequency by {@code multiplier}.
603       * 
604       * @param multiplier
605       *   the number by which this frequency should be multiplied.
606       *   This value must not result in a frequency magnitude that is
607       *   negative.
608       *        
609       * @return this frequency, after the multiplication.
610       * 
611       * @throws ArithmeticException
612       *   if the multiplication results in a negative value and this frequency  
613       *   is not allowed to be negative.
614       *   
615       * @throws NullPointerException
616       *   if <tt>multiplier</tt> is <i>null</i>.
617       */
618      public Frequency multiplyBy(BigDecimal multiplier)
619      {
620        if (!isInfinite())
621        {
622          BigDecimal result = value.multiply(multiplier);
623        
624          verifyResult(result);
625    
626          setAndRescale(result);
627        }
628        return this;
629      }
630      
631      /**
632       * Divides this frequency by {@code divisor}.
633       * 
634       * @param divisor
635       *   the number by which this frequency should be divided.
636       *   This value must not result in a frequency magnitude that is
637       *   negative or <tt>NaN</tt>.
638       *        
639       * @return this frequency, after the division.
640       * 
641       * @throws ArithmeticException
642       *   if the division results in a negative value and this frequency  
643       *   is not allowed to be negative, or if the <tt>divisor</tt> is zero.
644       *   
645       * @throws NumberFormatException
646       *   if <tt>divisor</tt> is <i>null</i>, <tt>NaN</tt>, or is
647       *   otherwise rejected by the <tt>BigDecimal</tt> class.
648       * 
649       * @see #dividedBy(Frequency)
650       */
651      public Frequency divideBy(String divisor)
652      {
653        return divideBy(new BigDecimal(divisor));
654      }
655    
656      /**
657       * Divides this frequency by {@code divisor}.
658       * 
659       * @param divisor
660       *   the number by which this frequency should be divided.
661       *   This value must not result in a frequency magnitude that is
662       *   negative or <tt>NaN</tt>.
663       *        
664       * @return this frequency, after the division.
665       * 
666       * @throws ArithmeticException
667       *   if the division results in a negative value and this frequency  
668       *   is not allowed to be negative, or if the <tt>divisor</tt> is zero.
669       *   
670       * @throws NumberFormatException
671       *   if <tt>divisor</tt> is <i>null</i>.
672       */
673      public Frequency divideBy(BigDecimal divisor)
674      {
675        if (!isInfinite())
676        {
677          BigDecimal result = value.divide(divisor, MC_INTERM_CALCS);
678        
679          verifyResult(result);
680    
681          setAndRescale(result);
682        }
683        return this;
684      }
685    
686      /**
687       * Returns the result of dividing this frequency by {@code other},
688       * without altering this frequency.
689       * 
690       * @param other
691       *   the frequency by which this one is divided.  This parameter
692       *   is the denominator
693       *   in the equation <tt>quotient = dividend / divisor</tt>,
694       *   this frequency is the numerator, and the quotient is the
695       *   value returned.
696       * 
697       * @return the result of dividing this frequency by {@code other}.
698       * 
699       * @see #divideBy(BigDecimal)
700       * @see #divideBy(String)
701       */
702      public BigDecimal dividedBy(Frequency other)
703      {
704        if (isInfinite())
705          return value;
706        else
707          return this.toUnits(STD_UNITS)
708                     .divide(other.toUnits(STD_UNITS), MC_INTERM_CALCS);
709      }
710      
711      private static final String ARITH_ERR =
712        "Result of operation may not be negative.  This frequency was not changed.";
713      
714      private void verifyResult(BigDecimal arithmeticResult)
715      {
716        if (!allowNegativeValues && arithmeticResult.signum() < 0)
717          throw new ArithmeticException(ARITH_ERR);
718      }
719      
720      //===========================================================================
721      // ADVANCED QUERIES
722      //===========================================================================
723    
724      /**
725       * Returns the smallest positive difference between this frequency and
726       * {@code range}.
727       * 
728       * @param range the range to which a distance is computed.
729       * 
730       * @return the smallest positive difference between this frequency and
731       *         {@code range}. If {@code range} is <i>null</i>, <i>null</i>
732       *         is returned.  If {@code range} contains this frequency,
733       *         a frequency of zero hertz is returned.
734       */
735      public Frequency getAbsoluteDistanceFrom(FrequencyRange range)
736      {
737        //TODO need to think about INFINITY in here
738        
739        Frequency result;
740        
741        if (range == null)
742        {
743          result = null;
744        }
745        else if (range.contains(this))
746        {
747          result = new Frequency("0.0", STD_UNITS);
748        }
749        else
750        {
751          Frequency temp = range.getLowFrequency();
752          Frequency lowDiff =
753            (temp.compareTo(this) < 0) ? this.clone().subtract(temp)
754                                       : temp.clone().subtract(this);
755    
756          temp = range.getHighFrequency();  
757          Frequency highDiff =
758            (temp.compareTo(this) < 0) ? this.clone().subtract(temp)
759                                       : temp.clone().subtract(this);
760    
761          result = (lowDiff.compareTo(highDiff) < 0) ? lowDiff : highDiff;
762        }
763        
764        return result;
765      }
766      
767      //===========================================================================
768      // 
769      //===========================================================================
770      
771      private static final LinearVelocity C =
772        new LinearVelocity(Wave.LIGHT_SPEED_VACUUM_KM_PER_SEC,
773                           LinearVelocityUnits.KILOMETERS_PER_SECOND);
774      
775      /**
776       * Creates and returns a new wave with the speed of light and this frequency.
777       * @return a new wave with the speed of light and this frequency.
778       */
779      public Wave makeWave()
780      {
781        return makeWave(C);
782      }
783      
784      /**
785       * Creates and returns a new wave with the given speed and this frequency.
786       * @param speedOfWave the speed of the returned wave.
787       * @return a new wave with the given speed and this frequency.
788       */
789      public Wave makeWave(LinearVelocity speedOfWave)
790      {
791        Wave wave = new Wave(speedOfWave);
792        
793        wave.setFrequency(this);
794        
795        return wave;
796      }
797      
798      //===========================================================================
799      // PARSING
800      //===========================================================================
801    
802      /**
803       * Returns a new frequency based on {@code frequencyString}.
804       * <p>
805       * <b><u>Valid Formats</u></b><br/>
806       * Let R be the text representation of a real number.<br/>
807       * Let w represent zero or more whitespace characters.<br/>
808       * Let S be a valid {@link FrequencyUnits units} symbol.<br/>
809       * <br/>
810       * <i>Format One</i>: <tt>wRw</tt>.  The given number will be defined to be
811       * in units of {@link FrequencyUnits#HERTZ hertz}.<br/>
812       * <br/>
813       * <i>Format Two</i>: <tt>wRwSw</tt>.</p>
814       * 
815       * @param frequencyString
816       *   a string that will be converted into a frequency.
817       *   In addition to the valid formats listed above, this parameter may
818       *   use the special values <tt>"infinity"</tt>, <tt>"+infinity"</tt>,
819       *   and <tt>"-infinity"</tt>, with or without units, and without
820       *   regard to case.
821       * 
822       * @return a new frequency.
823       * 
824       * @throws IllegalArgumentException if {@code frequencyString} is not in
825       *                                  the expected form.
826       */
827      public static Frequency parse(String frequencyString)
828      {
829        Frequency newFrequency = new Frequency();
830        
831        if ((frequencyString != null) && !frequencyString.equals(""))
832        {
833          try
834          {
835            //Allow parsing of negative numbers, but if the parsed # is non-negative,
836            //go back to default of disallowing negatives.
837            newFrequency.allowNegativeValues = true;
838            newFrequency.parseFrequency(frequencyString);
839            newFrequency.allowNegativeValues = newFrequency.value.signum() < 0;
840          }
841          catch (Exception ex)
842          {
843            throw new IllegalArgumentException("Could not parse " + frequencyString, ex);
844          }
845        }
846        return newFrequency;
847      }
848      
849      /**
850       * If parsing was successful, this frequency's units & value will have been
851       * valued.  Otherwise an exception is thrown.
852       */
853      private void parseFrequency(String frequencyString)
854      {
855        //Quick exit if text represents infinity
856        if (parseInfiniteFrequency(frequencyString))
857          return;
858        
859        //Eliminate whitespace
860        frequencyString = frequencyString.replaceAll("\\s", "");
861    
862        //Assume we have a number followed (optionally) by a symbol
863        units = null;
864        
865        int unitsPos = -1;
866    
867        //Sort units by length of symbol, longer symbols before shorter.
868        //This helps w/ discovering which unit is contained in distanceString.
869        FrequencyUnits[] sortedUnits = FrequencyUnits.values();
870        Arrays.sort(sortedUnits,
871                    new Comparator<FrequencyUnits>() {
872                      public int compare(FrequencyUnits a, FrequencyUnits b) {
873                        return b.getSymbol().length() - a.getSymbol().length();
874                      }
875                    });
876    
877        //Figure out what kind of units we have
878        for (FrequencyUnits u : sortedUnits) 
879        {
880          if (frequencyString.endsWith(u.getSymbol()))
881          {
882            units = u;
883            unitsPos = frequencyString.lastIndexOf(u.getSymbol());
884            break;
885          }
886        }
887        
888        //If unitsPos < 0, we either have no units or garbage.
889        //The BigDecimal constructor will fail if it is garbage.
890        //If we survive that parsing, we will assume default units.
891        String numberString =
892          (unitsPos < 0) ? frequencyString : frequencyString.substring(0, unitsPos);
893        
894        setValue(new BigDecimal(numberString));
895    
896        //If we got this far, BigDecimal constructor was successful.
897        //If the units are still null, use kilometers.
898        if (units == null)
899          units = FrequencyUnits.HERTZ;
900      }
901      
902      //TODO see if this can be generalized in EnumUtil, perhaps
903      /** Returns <i>true</i> if parsed frequency was infinite. */
904      private boolean parseInfiniteFrequency(String freqText)
905      {
906        final String origText = freqText; //in case we need to throw exception
907        
908        boolean isInfinite;
909       
910        final String INF_TEXT = "infinity";
911        
912        char    signChar    = freqText.charAt(0);
913        boolean negate      = (signChar == '-');
914        boolean hasSignChar = negate || (signChar == '+');
915        
916        //Strip off "+" or "-"
917        if (hasSignChar)
918          freqText = freqText.substring(1);
919        
920        int testLength   = INF_TEXT.length();
921        int actualLength = freqText.length();
922     
923        //Might have "infinity" with no units
924        if (actualLength == testLength)
925        {
926          isInfinite = freqText.equalsIgnoreCase(INF_TEXT);
927          
928          if (isInfinite)
929            set(MathUtil.getInfiniteValue(negate ? -1 : +1), STD_UNITS);
930        }
931        //Might have "infinity" followed by units
932        else if (actualLength > testLength)
933        {
934          String testString = freqText.substring(0, testLength);
935    
936          isInfinite = testString.equalsIgnoreCase(INF_TEXT);
937          
938          if (isInfinite)
939          {
940            FrequencyUnits fu =
941              FrequencyUnits.fromString(freqText.substring(testLength, actualLength));
942            
943            if (fu == null)
944              throw new IllegalArgumentException("Could not parse '" + origText +
945                "'. This looked like an infinite frequency but units could not be determined.");
946    
947            set(MathUtil.getInfiniteValue(negate ? -1 : +1), fu);
948          }
949        }
950        //String too short to hold "infinity"
951        else //actualLength < testLength
952        {
953          isInfinite = false;
954        }
955        
956        return isInfinite;
957      }
958    
959      //===========================================================================
960      // UTILITY METHODS
961      //===========================================================================
962    
963      /** Returns a text representation of this frequency. */
964      @Override
965      public String toString()
966      {
967        return StringUtil.getInstance()
968                         .formatNoScientificNotation(getValue()) +
969                                                     getUnits().getSymbol();
970      }
971      
972      /**
973       * Returns a text representation of this frequency.
974       * 
975       * @param minFracDigits the minimum number of places after the decimal point.
976       *                      
977       * @param maxFracDigits the maximum number of places after the decimal point.
978       * 
979       * @return a text representation of this frequency.
980       */
981      public String toString(int minFracDigits, int maxFracDigits)
982      {
983        return StringUtil.getInstance().formatNoScientificNotation(value,
984                                                                   minFracDigits,
985                                                                   maxFracDigits) +
986               getUnits().getSymbol();
987      }
988      
989      /**
990       * Returns a text representation of a normalized representation of this
991       * frequency.  This method does not modify this frequency; specifically,
992       * it does not normalize this frequency.  (See {@link #normalize()} for
993       * the definition of "normalized".)
994       * 
995       * @param minFracDigits the minimum number of places after the decimal point.
996       *                      
997       * @param maxFracDigits the maximum number of places after the decimal point.
998       * 
999       * @return a text representation of this frequency.
1000       */
1001      public String toStringNormalized(int minFracDigits, int maxFracDigits)
1002      {
1003        Frequency clone = this.clone();
1004        clone.normalize();
1005        return clone.toString(minFracDigits, maxFracDigits);
1006      }
1007      
1008      /** Returns a frequency that is equal to this one. */
1009      @Override
1010      public Frequency clone()
1011      {
1012        //Since this class has only primitive (& immutable) attributes,
1013        //the clone in Object is all we need.
1014        try
1015        {
1016          return (Frequency)super.clone();
1017        }
1018        catch (CloneNotSupportedException ex)
1019        {
1020          //We'll never get here, but just in case...
1021          throw new RuntimeException(ex);
1022        }
1023      }
1024      
1025      /** Returns <i>true</i> if {@code o} is equal to this frequency. */
1026      @Override
1027      public boolean equals(Object o)
1028      {
1029        //Quick exit if o is this
1030        if (o == this)
1031          return true;
1032        
1033        //Quick exit if o is null
1034        if (o == null)
1035          return false;
1036        
1037        //Quick exit if classes are different
1038        if (!o.getClass().equals(this.getClass()))
1039          return false;
1040        
1041        Frequency other = (Frequency)o;
1042    
1043        //Treat two infinite values of same sign as equal,
1044        //regardless of actual BigDecimal values
1045        if (isInfinite() && other.isInfinite())
1046          return value.signum() == other.value.signum();
1047        
1048        //Ignore stored units; equality is based purely on magnitude in std units
1049        return compareTo(other) == 0;
1050      }
1051      
1052      /** Returns a hash code value for this frequency. */
1053      @Override
1054      public int hashCode()
1055      {
1056        if (isInfinite())
1057          return value.signum() > 0 ? "+infinity".hashCode() : "-infinity".hashCode();
1058          
1059        String crude = value.toPlainString() + units.getSymbol();
1060        return crude.hashCode();
1061      }
1062    
1063      /** Compares this frequency with the {@code otherFreq} for order. */
1064      public int compareTo(Frequency otherFreq)
1065      {
1066        //Treat two infinite values of same sign as equal,
1067        //regardless of actual BigDecimal values
1068        if (isInfinite() && otherFreq.isInfinite())
1069          return value.signum() - otherFreq.value.signum();
1070        
1071        //Avoid doing two unit conversions
1072        return value.compareTo(otherFreq.toUnits(units));
1073      }
1074      
1075      //This is here for quick & dirty testing
1076      /*public static void main(String args[])
1077      {
1078        Frequency f1 = new Frequency(1.2, FrequencyUnits.HERTZ);
1079        System.out.println("f1 = " + f1);
1080        for (FrequencyUnits fu : FrequencyUnits.values())
1081        {
1082          f1.convertTo(fu);
1083          System.out.println("f1 = " + f1);
1084        }
1085        
1086        System.out.println();
1087    
1088        Frequency f2 = new Frequency(12345.678, FrequencyUnits.GIGAHERTZ);
1089        System.out.println("f2 = " + f2);
1090        for (FrequencyUnits fu : FrequencyUnits.values())
1091        {
1092          f2.convertTo(fu);
1093          System.out.println("f2 = " + f2);
1094        }
1095      }*/
1096      /*
1097      public static void main(String[] args)
1098      {
1099        Frequency i  = new Frequency("infinity ");
1100        Frequency pi = new Frequency("+INFINITY");
1101        Frequency ni = Frequency.parse("-infinity Hz");
1102        
1103        System.out.println(i.isInfinite());
1104        System.out.println(pi.isInfinite());
1105        System.out.println(ni.isInfinite());
1106        System.out.println();
1107        
1108        System.out.println(i);
1109        System.out.println(pi);
1110        System.out.println(ni);
1111        System.out.println();
1112        
1113        BigDecimal bd1 = new BigDecimal(0.1);
1114        BigDecimal bd2 = new BigDecimal(Double.toString(0.1));
1115        BigDecimal bd3 = new BigDecimal("0.1");
1116    
1117        System.out.println(bd1.toPlainString());
1118        System.out.println(bd2.toPlainString());
1119        System.out.println(bd3.toPlainString());
1120        System.out.println();
1121        
1122        bd1 = new BigDecimal(28.976);
1123        bd2 = new BigDecimal(Double.toString(28.976));
1124        bd3 = new BigDecimal("28.976");
1125    
1126        System.out.println(bd1.toPlainString());
1127        System.out.println(bd2.toPlainString());
1128        System.out.println(bd3.toPlainString());
1129        System.out.println();
1130    
1131        System.out.println(bd1.scale() + ", " + bd1.precision());
1132        System.out.println(bd2.scale() + ", " + bd2.precision());
1133        System.out.println(bd3.scale() + ", " + bd3.precision());
1134        System.out.println();
1135        
1136        System.out.println(bd1.doubleValue());
1137        bd1 = bd1.setScale(3, RoundingMode.HALF_UP);
1138        System.out.println(bd1.toPlainString());
1139        System.out.println(bd1.doubleValue());
1140        System.out.println();
1141        
1142        bd1 = new BigDecimal(123.0);
1143        bd2 = new BigDecimal(Double.toString(123.0));
1144        bd3 = new BigDecimal("123.0");
1145    
1146        System.out.println(bd1.toPlainString());
1147        System.out.println(bd2.toPlainString());
1148        System.out.println(bd3.toPlainString());
1149        System.out.println();
1150    
1151        System.out.println(bd1.scale() + ", " + bd1.precision());
1152        System.out.println(bd2.scale() + ", " + bd2.precision());
1153        System.out.println(bd3.scale() + ", " + bd3.precision());
1154        System.out.println();
1155        
1156        bd1 = new BigDecimal("2.04800000000000000", MathContext.DECIMAL128);
1157        System.out.println(bd1.toPlainString());
1158        System.out.println(bd1.scale() + ", " + bd1.precision());
1159        System.out.println();
1160        
1161        bd1 = bd1.stripTrailingZeros();
1162        System.out.println(bd1.toPlainString());
1163        System.out.println(bd1.scale() + ", " + bd1.precision());
1164        System.out.println();
1165      }
1166      */
1167    }