001    package edu.nrao.sss.model.source;
002    
003    import java.net.MalformedURLException;
004    import java.net.URL;
005    import java.util.Date;
006    
007    import javax.xml.bind.annotation.XmlAttribute;
008    import javax.xml.bind.annotation.XmlElement;
009    import javax.xml.bind.annotation.XmlType;
010    
011    import edu.nrao.sss.astronomy.StokesParameter;
012    import edu.nrao.sss.measure.FluxDensity;
013    import edu.nrao.sss.measure.FrequencyRange;
014    import edu.nrao.sss.measure.TimeInterval;
015    import edu.nrao.sss.util.Identifiable;
016    
017    /**
018     * The brightness of an astronomical source.
019     * <p>
020     * <b>Version Info:</b>
021     * <table style="margin-left:2em">
022     *   <tr><td>$Revision: 1709 $</td></tr>
023     *   <tr><td>$Date: 2008-11-14 11:22:37 -0700 (Fri, 14 Nov 2008) $</td></tr>
024     *   <tr><td>$Author: dharland $</td></tr>
025     * </table></p>
026     * 
027     * @author David M. Harland
028     * @since 2006-04-04
029     */
030    @XmlType
031    public abstract class SourceBrightness
032      implements Identifiable, Cloneable, Comparable<SourceBrightness>
033    {
034      static URL DEFAULT_URL;
035      static
036      {
037        try { DEFAULT_URL = new URL("http://www.nrao.edu/brokenLink.html"); }
038        catch (MalformedURLException ex) { DEFAULT_URL = null; }
039      }
040    
041      //IDENTIFICATION
042      @XmlAttribute
043      private long id;
044    
045              BrightnessDistribution distributionType;
046              StokesParameter        polarization;
047      private TimeInterval           validTime;
048              FrequencyRange         validFrequency;
049              FluxDensity            peakFluxDensity;
050              FluxDensity            totalFluxDensity;
051              double                 majorAxisDiameter;
052              double                 minorAxisDiameter;
053              double                 positionAngle;
054              double                 limbDarkening;
055              URL                    brightnessFile;
056              
057      private VlaFluxObservation vlaObservation;
058      
059      /**
060       * Returns a source brightness instance for the distribution of the given type.
061       * 
062       * @param type the brightness distribution for which a source brightness
063       *             instance is desired. 
064       *             
065       * @return a new source brightness of the given type.
066       */
067      public static SourceBrightness createBrightness(BrightnessDistribution type)
068      {
069        switch(type)
070        {
071          case CLEAN_COMPONENTS_FILE: return new CleanFileBrightness();
072          case DISK:                  return new DiskBrightness();
073          case FITS_FILE:             return new FitsFileBrightness();
074          case GAUSSIAN:              return new GaussianBrightness();
075          case POINT:                 return new PointBrightness();
076          case UNKNOWN:               return new UnknownBrightness();
077          default:                    return new UnknownBrightness(); //SourceBrightness();
078        }
079      }
080      
081      /**
082       * Creates a pair of source brightnesses based on {@code fluxRec}.
083       * <p>
084       * The returned array will have exactly two brightnesses.  The element
085       * at index zero will hold a brightness for left circular polarization;
086       * the element at index one will hold a brightness for right circular
087       * polarization.</p>
088       * 
089       * @param fluxRec an observation of the flux of a source performed by the VLA.
090       * 
091       * @return a pair of source brightnesses based on {@code fluxRec}.
092       */
093      public static SourceBrightness[] createBrightnesses(VlaFluxObservation fluxRec)
094      {
095        SourceBrightness[] result = fluxRec.makeBrightnesses();
096        
097        for (SourceBrightness sb : result)
098          sb.vlaObservation = fluxRec;
099        
100        return result;
101      }
102    
103      /** Creates a new instance. */
104      SourceBrightness()
105      {
106        validTime        = new TimeInterval();
107        validFrequency   = new FrequencyRange();
108        peakFluxDensity  = new FluxDensity();
109        totalFluxDensity = new FluxDensity();
110    
111        initialize();
112      }
113      
114      /** Initializes the instance variables of this class.  */
115      private void initialize()
116      {
117        id = Identifiable.UNIDENTIFIED;
118         
119        distributionType  = BrightnessDistribution.UNKNOWN;
120        polarization      = StokesParameter.getDefault();
121        majorAxisDiameter = 0.0;
122        minorAxisDiameter = 0.0;
123        positionAngle     = 0.0;
124        limbDarkening     = 0.0;
125        brightnessFile    = DEFAULT_URL;
126      }
127    
128      /**
129       *  Resets this brightness to its initial state.  A reset brightness
130       *  has the same state as a new brightness. 
131       */
132      public void reset()
133      {
134        initialize();
135        
136        validTime.reset();
137        validFrequency.reset();
138        peakFluxDensity.reset();
139        totalFluxDensity.reset();
140        
141        vlaObservation = null;
142      }
143    
144      //============================================================================
145      // IDENTIFICATION
146      //============================================================================
147      
148      /* (non-Javadoc)
149       * @see edu.nrao.sss.model.util.Identifiable#getId()
150       */
151      public Long getId()
152      {
153        return id;
154      }
155      
156      @SuppressWarnings("unused")
157      private void setId(Long id)
158      {
159        this.id = id;
160      }
161    
162      /**
163       * Resets this brightness's ID
164       * to a value that represents the unidentified state. 
165       */
166      void clearId()
167      {
168        this.id = Identifiable.UNIDENTIFIED;
169      }
170    
171      /**
172       * Returns the distribution type of this brightness.
173       *
174       * @return the distribution type of this brightness.
175       */
176      public BrightnessDistribution getDistributionType()
177      {
178        return distributionType;
179      }
180      
181      /**
182       * Returns the polarization of this source brightness.
183       * @return the polarization of this source brightness.
184       */
185      public StokesParameter getPolarization()
186      {
187        return polarization;
188      }
189      
190      //============================================================================
191      // TIME, FREQUENCY, & FLUX DENSITY
192      //============================================================================
193    
194      /**
195       * Sets the interval of time for which this brightness is valid.
196       * <p>
197       * If {@code interval} is <i>null</i>, it will be treated as
198       * an non-null interval of zero length.</p>
199       * 
200       * @param interval the interval of time for which this brightness is valid.
201       */
202      public void setValidTime(TimeInterval interval)
203      {
204        if (interval == null)
205          validTime.reset();
206        else
207          validTime = interval;
208      }
209      
210      /**
211       * Returns the interval of time for which this brightness is valid.
212       * <p>
213       * The return value is guaranteed to be non-null.  It is
214       * also the range that is held internally by this source
215       * brightness, so any changes made to the returned range
216       * will be reflected in this object.</p>
217       * 
218       * @return the interval of time for which this brightness is valid.
219       */
220      public TimeInterval getValidTime()
221      {
222        return validTime;
223      }
224      
225      /**
226       * Returns <i>true</i> if this brightness is valid for
227       * the given point in time.
228       * 
229       * @param time the point in time to be checked.
230       */
231      public boolean isValidFor(Date time)
232      {
233        return validTime.contains(time);
234      }
235      
236      /**
237       * Returns the frequency range for which this source brightness
238       * is valid.
239       * <p>
240       * The return value is guaranteed to be non-null.  It is
241       * also the range that is held internally by this source
242       * brightness, so any changes made to the returned range
243       * will be reflected in this object.</p>
244       * 
245       * @return the valid frequency range for this source velocity.
246       */
247      public FrequencyRange getValidFrequency()
248      {
249        return validFrequency;
250      }
251      
252      /**
253       * Returns the peak flux density of this source brightness.
254       * <p>
255       * The returned value is guaranteed to be non-null.
256       * It is also the peak flux density that is held internally by this
257       * source brightness, so any changes made to the returned
258       * flux density will be reflected in this object.</p>
259       * 
260       * @return the peak flux density of this source brightness.
261       */
262      public FluxDensity getPeakFluxDensity()
263      {
264        return peakFluxDensity;
265      }
266      
267      /**
268       * Returns the total flux density of this source brightness.
269       * <p>
270       * The returned value is guaranteed to be non-null.
271       * It is also the total flux density that is held internally by this
272       * source brightness, so any changes made to the returned
273       * flux density will be reflected in this object.</p>
274       * 
275       * @return the total flux density of this source brightness.
276       */
277      public FluxDensity getTotalFluxDensity()
278      {
279        return totalFluxDensity;
280      }
281    
282      //============================================================================
283      // AREA ATTRIBUTES
284      //============================================================================
285    
286      /**
287       * Returns the diameter of the major axis of this source brightness in A.U.
288       * @return the diameter of the major axis of this source brightness in A.U.
289       */
290      public double getMajorAxisDiameter()
291      {
292        return majorAxisDiameter;
293      }
294    
295      /**
296       * Returns the diameter of the minor axis of this source brightness in A.U.
297       * @return the diameter of the minor axis of this source brightness in A.U.
298       */
299      public double getMinorAxisDiameter()
300      {
301        return minorAxisDiameter;
302      }
303    
304      /**
305       * Returns the position angle of this source brightness in degrees.
306       * The position angle is based on the major axis.  A north-south orientation
307       * of the major axis is taken to be zero degrees.  Positive angles are
308       * measured eastward from north.
309       * 
310       * @return the position angle of this source brightness in degrees.
311       */
312      public double getPositionAngle()
313      {
314        return positionAngle;
315      }
316    
317      /**
318       * Returns the limb darkening of this source brightness.
319       * <p>
320       * The value returned is a measure of shape.</p>
321       * 
322       * @return the limb darkening of this source brightness.
323       */
324      public double getLimbDarkening()
325      {
326        return limbDarkening;
327      }
328      
329      //============================================================================
330      // FILE
331      //============================================================================
332      
333      /**
334       * Returns <i>true</i> if this source brightness if based on the contents of
335       * a file, <i>false</i> otherwise.
336       * 
337       * @return <i>true</i> if this source brightness if based on the contents of
338       *         a file.
339       */
340      public boolean isFileBased()
341      {
342        return distributionType.equals(BrightnessDistribution.FITS_FILE) ||
343               distributionType.equals(BrightnessDistribution.CLEAN_COMPONENTS_FILE);
344      }
345      
346      /**
347       * Returns the file upon which this source brightness is based, if any.
348       * If this source is not file based, a phony URL will be returned.
349       * 
350       * @return the file upon which this source brightness is based, if any.
351       */
352      public URL getBrightnessFile()
353      {
354        return brightnessFile;
355      }
356    
357      //============================================================================
358      // OBSERVATIONS 
359      //============================================================================
360    
361      /**
362       * Returns text that represents the observational data upon which this
363       * brightness is based.  If there is no such data, the empty string
364       * (<tt>""</tt>) will be returned.
365       * 
366       * @return text representation of an observation of this brightness.
367       */
368      public String getObservation()
369      {
370        return vlaObservation == null ? "" : vlaObservation.toString();
371      }
372      
373      /**
374       * Returns the VLA flux data upon which this brightness is based.
375       * @return the VLA flux data upon which this brightness is based.
376       */
377      @XmlElement
378      public VlaFluxObservation getVlaObservation()
379      {
380        return vlaObservation;
381      }
382      
383      /** Here for persistence mechanisms, such as Hibernate & JAXB. */
384      @SuppressWarnings("unused")
385      private void setVlaObservation(VlaFluxObservation newObs)
386      {
387        vlaObservation = newObs;
388      }
389      
390      //============================================================================
391      // 
392      //============================================================================
393    
394      /**
395       *  Returns a source brightness that is equal to this one.
396       *  <p>
397       *  If anything goes wrong during the cloning procedure,
398       *  a {@code RuntimeException} will be thrown.</p>
399       */
400      @Override
401      public SourceBrightness clone()
402      {
403        SourceBrightness clone = null;
404    
405        try
406        {
407          //This line takes care of the primitive & immutable fields properly
408          clone = (SourceBrightness)super.clone();
409          
410          //We do NOT want the clone to have the same ID as the original.
411          //The ID is here for the persistence layer; it is in charge of
412          //setting IDs.  To help it, we put the clone's ID in the uninitialized
413          //state.
414          clone.id = Identifiable.UNIDENTIFIED;
415          
416          //This works because getValidFrequency already returns a clone
417          clone.validFrequency = this.getValidFrequency();
418          clone.setValidTime(this.getValidTime().clone());
419          clone.peakFluxDensity = this.peakFluxDensity.clone();
420          clone.brightnessFile = new URL(this.brightnessFile.toString());
421          
422          if (this.vlaObservation != null)
423            clone.vlaObservation = this.vlaObservation.clone();
424        }
425        catch (Exception ex)
426        {
427          throw new RuntimeException(ex);
428        }
429        
430        return clone;
431      }
432      
433      /** Returns <i>true</i> if {@code o} is equal to this source brightness. */
434      @Override
435      public boolean equals(Object o)
436      {
437        //Quick exit if o is this
438        if (o == this)
439          return true;
440        
441        //Quick exit if o is null
442        if (o == null)
443          return false;
444        
445        //Quick exit if classes are different
446        if (!o.getClass().equals(this.getClass()))
447          return false;
448        
449        SourceBrightness other = (SourceBrightness)o;
450       
451        return other.distributionType.equals(this.distributionType) &&
452               other.polarization.equals(this.polarization) &&
453               other.majorAxisDiameter == this.majorAxisDiameter &&
454               other.minorAxisDiameter == this.minorAxisDiameter &&
455               other.positionAngle     == this.positionAngle     &&
456               other.limbDarkening     == this.limbDarkening     &&
457               other.brightnessFile.toString()
458                                   .equals(this.brightnessFile.toString()) &&
459               other.peakFluxDensity.equals(this.peakFluxDensity) &&
460               other.validFrequency.equals(this.validFrequency) &&
461               other.validTime.equals(this.validTime) &&
462               objectsAreEqual(other.vlaObservation, this.vlaObservation);
463      }
464      
465      private boolean objectsAreEqual(Object thisOne, Object thatOne)
466      {
467        return (thisOne == null) ? (thatOne == null) : thisOne.equals(thatOne);
468      }
469    
470      /** Returns a hash code value for this source brightness. */
471      @Override
472      public int hashCode()
473      {
474        //Taken from the Effective Java book by Joshua Bloch.
475        //The constants 17 & 37 are arbitrary & carry no meaning.
476        int result = 17;
477        
478        result = 37 * result + distributionType.hashCode();
479        result = 37 * result + polarization.hashCode();
480        result = 37 * result + validTime.hashCode();
481        result = 37 * result + validFrequency.hashCode();
482        result = 37 * result + brightnessFile.toString().hashCode();
483        result = 37 * result + peakFluxDensity.hashCode();
484        
485        if (vlaObservation != null)
486          result = 37 * result + vlaObservation.hashCode();
487    
488        result = 37 * result + new Double(majorAxisDiameter).hashCode();
489        result = 37 * result + new Double(minorAxisDiameter).hashCode();
490        result = 37 * result + new Double(positionAngle).hashCode();
491        result = 37 * result + new Double(limbDarkening).hashCode();
492        
493        return result;
494      }
495      
496      /**
497       * Compares this brightness to {@code other} for order.
498       * <p>
499       * One brightness is deemed to be "less than" the other if it has
500       * a valid frequency range that is less than that of the other.
501       * In the case that two brighnesses have the same valid frequency range,
502       * the valid time range is used as a tie-breaker.  If these are the
503       * same other attributes are compared until a difference is found.
504       * If none are found, the return value is zero.</p>
505       * 
506       * @param other the brightness to which this one is compared.
507       * 
508       * @return a negative integer, zero, or a positive integer as this brightness
509       *         is less than, equal to, or greater than the other brightness.
510       */
511      public int compareTo(SourceBrightness other)
512      {
513        //First compare the valid frequency ranges
514        int answer = this.validFrequency.compareTo(other.validFrequency);
515        if (answer != 0)
516          return answer;
517    
518        //Valid time intervals
519        answer = this.validTime.compareTo(other.validTime);
520        if (answer != 0)
521          return answer;
522    
523        //Polarization
524        answer = this.polarization.compareTo(other.polarization);
525        if (answer != 0)
526          return answer;
527    
528        //Distribution type
529        answer = this.distributionType.compareTo(other.distributionType);
530        if (answer != 0)
531          return answer;
532    
533        //Flux density
534        answer = this.peakFluxDensity.compareTo(other.peakFluxDensity);
535        if (answer != 0)
536          return answer;
537    
538        //File name
539        answer = this.brightnessFile.toString()
540                                    .compareTo(other.brightnessFile.toString());
541        if (answer != 0)
542          return answer;
543        
544        answer = (int)Math.signum(this.majorAxisDiameter - other.majorAxisDiameter);
545        if (answer != 0)
546          return answer;
547        
548        answer = (int)Math.signum(this.minorAxisDiameter - other.minorAxisDiameter);
549        if (answer != 0)
550          return answer;
551        
552        answer = (int)Math.signum(this.positionAngle - other.positionAngle);
553        if (answer != 0)
554          return answer;
555        
556        answer = (int)Math.signum(this.limbDarkening - other.limbDarkening);
557        if (answer != 0)
558          return answer;
559    
560        //No differences found
561        return 0;
562      }
563    }