001    package edu.nrao.sss.model.resource;
002    
003    import java.io.FileNotFoundException;
004    import java.io.Reader;
005    import java.io.Writer;
006    import java.util.SortedMap;
007    import java.util.SortedSet;
008    import java.util.TreeMap;
009    import java.util.TreeSet;
010    
011    import javax.xml.bind.JAXBException;
012    import javax.xml.bind.annotation.XmlList;
013    import javax.xml.bind.annotation.XmlTransient;
014    import javax.xml.bind.annotation.XmlType;
015    import javax.xml.stream.XMLStreamException;
016    
017    import edu.nrao.sss.astronomy.StokesParameter;
018    import edu.nrao.sss.measure.Frequency;
019    import edu.nrao.sss.measure.FrequencyRange;
020    import edu.nrao.sss.measure.TimeDuration;
021    import edu.nrao.sss.measure.TimeUnits;
022    import edu.nrao.sss.util.Identifiable;
023    import edu.nrao.sss.util.JaxbUtility;
024    import edu.nrao.sss.util.StringUtil;
025    
026    /**
027     * A specification for observing a portion of the electromagnetic spectrum.
028     * Instances of this class are used to help choose and configure instrumentation
029     * that can obtain data that conform to the encapsulated specifications.
030     * <p>
031     * <b>Version Info:</b>
032     * <table style="margin-left:2em">
033     *   <tr><td>$Revision: 1709 $</td></tr>
034     *   <tr><td>$Date: 2008-11-14 11:22:37 -0700 (Fri, 14 Nov 2008) $</td></tr>
035     *   <tr><td>$Author: dharland $</td></tr>
036     * </table></p>
037     *  
038     * @author David M. Harland
039     * @since 2006-09-12
040     */
041    @XmlType(propOrder={"frequencyRange", "frequencyResolution", "timeResolution",
042                        "polarizationProducts"})
043    public class SkyFrequencySpecification
044      implements Cloneable
045    {
046      //This ID field is here for the persistance layer
047      @SuppressWarnings("unused")
048      private Long id;
049      
050      private FrequencyRange             frequencyRange;
051      private Frequency                  frequencyResolution;
052      private TimeDuration               timeResolution;
053      private SortedSet<StokesParameter> polarizations;
054      private SortedMap<String, String>  additionalSpecifications;
055    
056      /** Creates a new instance. */
057      public SkyFrequencySpecification()
058      {
059        id = Identifiable.UNIDENTIFIED;
060        
061        frequencyRange      = null;
062        frequencyResolution = new Frequency();
063        timeResolution      = new TimeDuration("0.0", TimeUnits.MILLISECOND);
064        
065        polarizations            = new TreeSet<StokesParameter>();
066        additionalSpecifications = new TreeMap<String, String>();
067      }
068    
069      /** Creates a new instance using the given range. */
070      public SkyFrequencySpecification(FrequencyRange skyFrequencyRange)
071      {
072        this();
073        setFrequencyRange(skyFrequencyRange);
074      }
075    
076      /**
077       * Resets the identifier of this specification
078       * to a value that represents the unidentified state.
079       * <p>
080       * This method is useful for preparing a specification for storage in a
081       * database.
082       * The ID property (as of now, though this may change in the future) is
083       * used by our persistence mechanism to identify objects.  If you are
084       * persisting this object for the first time, you may need to call
085       * this method before performing a save.  This is especially true if
086       * you have created this object from XML, as the XML unmarshalling
087       * brings along the ID property.</p> 
088       */
089      void clearId()
090      {
091        this.id = Identifiable.UNIDENTIFIED;
092      }
093    
094      //============================================================================
095      //
096      //============================================================================
097      
098      /**
099       * Sets the range of sky frequencies to use for this specification.
100       * <p>
101       * Unless {@code newRange} is <i>null</i>, 
102       * this object will hold a reference to the parameter,
103       * so any changes made to it will be reflected in this object.
104       * A value of <i>null</i> will be interpreted as a range that
105       * contains all positive frequencies.</p>
106       * 
107       * @param newRange the range of sky frequencies to use for this specification.
108       */
109      public void setFrequencyRange(FrequencyRange newRange)
110      {
111        frequencyRange = (newRange == null) ? new FrequencyRange() : newRange;
112      }
113      
114      /**
115       * Returns the range of sky frequencies to use for this specification.
116       * <p>
117       * Note that the returned range is the one held by this specification,
118       * so any changes made to it will be reflected in this object.
119       * This value is guaranteed to be non-null.</p>
120       * 
121       * @return the range of sky frequencies to use for this specification.
122       */
123      public FrequencyRange getFrequencyRange()
124      {
125        return frequencyRange;
126      }
127    
128      /**
129       * Sets the width of a single sky frequency channel.
130       * <p>
131       * Unless {@code width} is <i>null</i>, 
132       * this object will hold a reference to the parameter,
133       * so any changes made to it will be reflected in this object.
134       * A value of <i>null</i> will be interpreted as a frequency
135       * of zero gigahertz.</p>
136       * 
137       * @param width the width of a single sky frequency channel.
138       */
139      public void setFrequencyResolution(Frequency width)
140      {
141        frequencyResolution = (width == null) ? new Frequency() : width;
142      }
143      
144      /**
145       * Returns the width of a single sky frequency channel.
146       * <p>
147       * Note that the returned width is the one held by this specification,
148       * so any changes made to it will be reflected in this object.
149       * This value is guaranteed to be non-null.</p>
150       * 
151       * @return the width of a single sky frequency channel.
152       */
153      public Frequency getFrequencyResolution()
154      {
155        return frequencyResolution;
156      }
157      
158      /**
159       * Returns the number of whole spectral channels in this specification.
160       * The number of channels is the bandwidth divided by frequency
161       * resolution.  The amount returned is the integer portion of this division.
162       * That is, the floating point result is "floored" down to the greatest
163       * integer that is less than or equal to the floating point result.
164       *  
165       * @return the number of whole spectral channels in this specification.
166       */
167      public long getNumberOfSpectralChannels()
168      {
169        return
170          getFrequencyRange().getWidth().toUnits(frequencyResolution.getUnits())
171                             .divideToIntegralValue(frequencyResolution.getValue())
172                             .longValue();
173      }
174    
175      //============================================================================
176      //
177      //============================================================================
178    
179      /**
180       * Sets the time resolution of this specification.
181       * <p>
182       * Unless {@code resolution} is <i>null</i>, 
183       * this object will hold a reference to the parameter,
184       * so any changes made to it will be reflected in this object.
185       * A value of <i>null</i> will be interpreted as a duration
186       * of zero milliseconds.</p>
187       * 
188       * @param resolution the time resolution of this specification.
189       */
190      public void setTimeResolution(TimeDuration resolution)
191      {
192        timeResolution =
193          (resolution == null) ? new TimeDuration("0.0", TimeUnits.MILLISECOND)
194                               : resolution;
195      }
196    
197      /**
198       * Returns the time resolution of this specification.
199       * <p>
200       * Note that the returned duration is the one held by this specification,
201       * so any changes made to it will be reflected in this object.
202       * This value is guaranteed to be non-null.</p>
203       * 
204       * @return the time resolution of this specification.
205       */
206      public TimeDuration getTimeResolution()
207      {
208        return timeResolution;
209      }
210    
211      //============================================================================
212      //
213      //============================================================================
214    
215      /**
216       * Sets the polarization products requested by this specification.
217       * <p>
218       * Unless {@code newSet} is <i>null</i>, 
219       * this object will hold a reference to the parameter,
220       * so any changes made to it will be reflected in this object.
221       * A value of <i>null</i> will be interpreted as a new empty set.</p>
222       * 
223       * @param newSet the polarizations requested by this specification.
224       *               If this value is <i>null</i>, it will be interpreted
225       *               as an empty set.
226       */
227      public void setPolarizationProducts(SortedSet<StokesParameter> newSet)
228      {
229        polarizations = (newSet == null) ? new TreeSet<StokesParameter>()
230                                         : newSet;
231      }
232      
233      /**
234       * Returns the polarization products requested by this specification.
235       * <p>
236       * Note that the returned set is the one held by this specification,
237       * so any changes made to it will be reflected in this object.
238       * This set is guaranteed to be non-null, but it may be empty.</p>
239       * 
240       * @return the polarizations product requested by this specification.
241       */
242      @XmlList
243      public SortedSet<StokesParameter> getPolarizationProducts()
244      {
245        return polarizations;
246      }
247    
248      //============================================================================
249      //
250      //============================================================================
251      
252      /**
253       * Sets a map of key/value pairs that hold additional specifications.
254       * <p>
255       * Unless {@code newMap} is <i>null</i>, 
256       * this object will hold a reference to the parameter,
257       * so any changes made to it will be reflected in this object.
258       * A value of <i>null</i> will be interpreted as a new empty map.</p>
259       * <p>
260       * See {@link #getAdditionalSpecifications()} for more information.</p>
261       * 
262       * @param newMap a map of key/value pairs that hold additional specifications.
263       */
264      public void setAdditionalSpecifications(SortedMap<String, String> newMap)
265      {
266        additionalSpecifications = (newMap == null) ? new TreeMap<String, String>()
267                                                    : newMap;
268      }
269    
270      /**
271       * Returns any additional specifications for this specification.
272       * <p>
273       * "Additional specifications" are key/value pairs that are typically
274       * targeted at particular pieces of hardware. For example, the WIDAR
275       * correlator has a concept called "recirculation", so it could define
276       * the keyword <i>recirculationFactor</i> that could be given a value
277       * here and passed on to the WIDAR correlator.  If, however, this
278       * specification is passed to different hardware, then the fact that
279       * this specification is carrying a <i>recirculationFactor</i> value
280       * will be irrelevant (unless we have namespace collissions -- may want
281       * to prefix all keywords with hardware name).</p>
282       * <p>
283       * Note that the returned map is the one held by this specification,
284       * so any changes made to it will be reflected in this object.
285       * This set is guaranteed to be non-null, but it may be empty.</p>
286       * 
287       * @return any additional specifications for this specification.
288       */
289      //TODO allow into XML
290      @XmlTransient
291      public SortedMap<String, String> getAdditionalSpecifications()
292      {
293        return additionalSpecifications;
294      }
295    
296      //============================================================================
297      // TEXT
298      //============================================================================
299      
300      /**
301       * Returns a text representation of this specification.
302       * @return a text representation of this specification.
303       */
304      public String toString()
305      {
306        StringBuilder buff = new StringBuilder();
307        
308        final String EOL = StringUtil.EOL;
309        
310        buff.append("Frequency Range:  ").append(frequencyRange).append(EOL);
311        buff.append("Frequency Res'n:  ").append(frequencyResolution).append(EOL);
312        buff.append("Time Resolution:  ").append(timeResolution).append(EOL);
313        buff.append("Polarizations:    ").append(polarizations).append(EOL);
314        buff.append("Additional Specs: ").append(additionalSpecifications).append(EOL);
315        
316        return buff.toString();
317      }
318    
319      /**
320       * Returns an XML representation of this specification.
321       * @return an XML representation of this specification.
322       * @throws JAXBException if anything goes wrong during the conversion to XML.
323       * @see #writeAsXmlTo(Writer)
324       */
325      public String toXml() throws JAXBException
326      {
327        return JaxbUtility.getSharedInstance().objectToXmlString(this);
328      }
329      
330      /**
331       * Writes an XML representation of this specification to {@code writer}.
332       * @param writer the device to which XML is written.
333       * @throws JAXBException if anything goes wrong during the conversion to XML.
334       */
335      public void writeAsXmlTo(Writer writer) throws JAXBException
336      {
337        JaxbUtility.getSharedInstance().writeObjectAsXmlTo(writer, this, null);
338      }
339      
340      /**
341       * Creates a new specification from the XML data in the given file.
342       * 
343       * @param xmlFile the name of an XML file.  This method will attempt to locate
344       *                the file by using {@link Class#getResource(String)}.
345       *                
346       * @return a new specification from the XML data in the given file.
347       * 
348       * @throws FileNotFoundException if the XML file cannot be found.
349       * 
350       * @throws JAXBException if the schema file used (if any) is malformed, if
351       *           the XML file cannot be read, or if the XML file is not
352       *           schema-valid.
353       * 
354       * @throws XMLStreamException if there is a problem opening the XML file,
355       *           if the XML is not well-formed, or for some other
356       *           "unexpected processing conditions".
357       */
358      public static SkyFrequencySpecification fromXml(String xmlFile)
359        throws JAXBException, XMLStreamException, FileNotFoundException
360      {
361        return JaxbUtility.getSharedInstance()
362                          .xmlFileToObject(xmlFile, SkyFrequencySpecification.class);
363      }
364      
365      /**
366       * Creates a new specification based on the XML data read from {@code reader}.
367       * 
368       * @param reader the specification of the XML data.
369       *               If this value is <i>null</i>, <i>null</i> is returned.
370       *               
371       * @return a new specification based on the XML data read from {@code reader}.
372       * 
373       * @throws XMLStreamException if the XML is not well-formed,
374       *           or for some other "unexpected processing conditions".
375       *           
376       * @throws JAXBException if anything else goes wrong during the
377       *           transformation.
378       */
379      public static SkyFrequencySpecification fromXml(Reader reader)
380        throws JAXBException, XMLStreamException
381      {
382        return JaxbUtility.getSharedInstance()
383                          .readObjectAsXmlFrom(reader,
384                                               SkyFrequencySpecification.class, null);
385      }
386    
387      //============================================================================
388      // 
389      //============================================================================
390      
391      /**
392       *  Returns a specification that is a copy of this one.
393       *  <p>
394       *  If anything goes wrong during the cloning procedure,
395       *  a {@code RuntimeException} will be thrown.</p>
396       */
397      @Override
398      public SkyFrequencySpecification clone()
399      {
400        SkyFrequencySpecification clone = null;
401        
402        try
403        {
404          clone = (SkyFrequencySpecification)super.clone();
405    
406          //We do NOT want the clone to have the same ID as the original.
407          //The ID is here for the persistence layer; it is in charge of
408          //setting IDs.  To help it, we put the clone's ID in the uninitialized
409          //state.
410          clone.id = Identifiable.UNIDENTIFIED;
411          
412          clone.frequencyRange      = this.frequencyRange.clone();
413          clone.frequencyResolution = this.frequencyResolution.clone();
414          clone.timeResolution      = this.timeResolution.clone();
415    
416          //These are collections of immutables, so we need clone only the collection
417          //variables, not the contents therein.
418          clone.polarizations = new TreeSet<StokesParameter>(this.polarizations);
419          
420          clone.additionalSpecifications =
421            new TreeMap<String, String>(this.additionalSpecifications);
422        }
423        catch (Exception ex)
424        {
425          throw new RuntimeException(ex);
426        }
427        
428        return clone;
429      }
430      
431      /** Returns <i>true</i> if {@code o} is equal to this specification. */
432      @Override
433      public boolean equals(Object o)
434      {
435        //Quick exit if o is this
436        if (o == this)
437          return true;
438        
439        //Quick exit if o is null
440        if (o == null)
441          return false;
442        
443        //Quick exit if classes are different
444        if (!o.getClass().equals(this.getClass()))
445          return false;
446        
447        SkyFrequencySpecification other = (SkyFrequencySpecification)o;
448        
449        //NOTE: absence of ID field is intentional
450        
451        return other.frequencyRange.equals(this.frequencyRange) &&
452               other.frequencyResolution.equals(this.frequencyResolution) &&
453               other.timeResolution.equals(this.timeResolution) &&
454               other.polarizations.equals(this.polarizations) &&
455               other.additionalSpecifications.equals(this.additionalSpecifications);
456    
457        //Note the comparison of the polarizations Set is OK because
458        //the elements are immutable.
459      }    
460    
461      /** Returns a hash code value for this specification. */
462      @Override
463      public int hashCode()
464      {
465        //Taken from the Effective Java book by Joshua Bloch.
466        //The constants 17 & 37 are arbitrary & carry no meaning.
467        int result = 17;
468        
469        result = 37 * result + frequencyRange.hashCode();
470        result = 37 * result + frequencyResolution.hashCode();
471        result = 37 * result + timeResolution.hashCode();
472        result = 37 * result + polarizations.hashCode();
473        result = 37 * result + additionalSpecifications.hashCode();
474        
475        return result;
476      }
477      
478      //============================================================================
479      // 
480      //============================================================================
481      /*
482      public static void main(String[] args) throws Exception
483      {
484        ResourceBuilder builder = new ResourceBuilder();
485        SkyFrequencySpecification spec = builder.makeSkyFrequencySpecification();
486        
487        System.out.println(spec);
488      }
489      */
490    }