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.ArrayList;
007    import java.util.List;
008    
009    import javax.xml.bind.JAXBException;
010    import javax.xml.bind.annotation.XmlElement;
011    import javax.xml.bind.annotation.XmlElementWrapper;
012    import javax.xml.bind.annotation.XmlRootElement;
013    import javax.xml.stream.XMLStreamException;
014    
015    import edu.nrao.sss.measure.FrequencyRange;
016    import edu.nrao.sss.measure.FrequencySpectrum;
017    import edu.nrao.sss.util.JaxbUtility;
018    
019    /**
020     * A science view of the hardware needed for an observation.
021     * <p>
022     * <b>Version Info:</b>
023     * <table style="margin-left:2em">
024     *   <tr><td>$Revision: 851 $</td></tr>
025     *   <tr><td>$Date: 2007-08-27 13:29:48 -0600 (Mon, 27 Aug 2007) $</td></tr>
026     *   <tr><td>$Author: dharland $</td></tr>
027     * </table></p>
028     *  
029     * @author David M. Harland
030     * @since 2006-09-08
031     */
032    @XmlRootElement
033    public class ResourceSpecification
034      implements Cloneable
035    {
036      private List<SkyFrequencySpecification> skyFrequencySpecs;
037      private List<SpectralLineSpecification> spectralLineSpecs;
038      private List<PulsarSpecification>       pulsarSpecs;
039      
040      /** Creates a new instance. */
041      public ResourceSpecification()
042      {
043        skyFrequencySpecs = new ArrayList<SkyFrequencySpecification>();
044        spectralLineSpecs = new ArrayList<SpectralLineSpecification>();
045        pulsarSpecs       = new ArrayList<PulsarSpecification>();
046      }
047      
048      /**
049       * Creates a new instance based on the given specifications.
050       * 
051       * @param specs an entry in this specification's list of spectral
052       *              specifications.
053       *              
054       * @return a new instance based on the given specifications.
055       */
056      public static ResourceSpecification makeFrom(SkyFrequencySpecification specs)
057      {
058        ResourceSpecification result = new ResourceSpecification();
059        
060        if (specs != null)
061          result.skyFrequencySpecs.add(specs);
062        
063        return result;
064      }
065      
066      /**
067       * Creates a new instance based on the given specifications.
068       * 
069       * @param specs an entry in this specification's list of spectral
070       *              specifications.
071       *              
072       * @return a new instance based on the given specifications.
073       */
074      public static ResourceSpecification makeFrom(SpectralLineSpecification specs)
075      {
076        ResourceSpecification result = new ResourceSpecification();
077        
078        if (specs != null)
079          result.spectralLineSpecs.add(specs);
080        
081        return result;
082      }
083      
084      /**
085       * Creates a new instance based on the given specifications.
086       * 
087       * @param specs an entry in this specification's list of pulsar
088       *              specifications.
089       *              
090       * @return a new instance based on the given specifications.
091       */
092      public static ResourceSpecification makeFrom(PulsarSpecification specs)
093      {
094        ResourceSpecification result = new ResourceSpecification();
095        
096        if (specs != null)
097          result.pulsarSpecs.add(specs);
098        
099        return result;
100      }
101    
102      //============================================================================
103      // 
104      //============================================================================
105    
106      /**
107       * Resets the identifier of the subspecifications of this specification
108       * to a value that represents the unidentified state.
109       * <p>
110       * This method is useful for preparing a specification for storage in a database.
111       * The ID property (as of now, though this may change in the future) is
112       * used by our persistence mechanism to identify objects.  If you are
113       * persisting this object for the first time, you may need to call
114       * this method before performing a save.  This is especially true if
115       * you have created this object from XML, as the XML unmarshalling
116       * brings along the ID property.</p> 
117       */
118      void clearId()
119      {
120        for (SkyFrequencySpecification sfs : skyFrequencySpecs)
121          sfs.clearId();
122        
123        for (SpectralLineSpecification sls: spectralLineSpecs)
124          sls.clearId();
125        
126        for (PulsarSpecification ps : pulsarSpecs)
127          ps.clearId();
128      }
129    
130      //============================================================================
131      // 
132      //============================================================================
133      
134      /**
135       * Sets a new list of sky frequency specifications.
136       * If {@code newSpecs} is <i>null</i>, it will be interpreted
137       * as an empty list.
138       * <p>
139       * Note that this specification will hold an internal reference to
140       * {@code newSpecs} (unless it is <i>null</i>).  This means that any
141       * changes made to {@code newSpecs} after this call <i>will</i> be
142       * reflected in this object.</p>
143       *  
144       * @param newSpecs a list of sky frequency specifications.
145       */
146      public void setSkyFrequencySpecs(List<SkyFrequencySpecification> newSpecs)
147      {
148        skyFrequencySpecs =
149          newSpecs == null ? new ArrayList<SkyFrequencySpecification>() : newSpecs;
150      }
151      
152      /**
153       * Returns this specification's list of sky frequency specifications.
154       * The returned list may be empty, but will never be <i>null</i>.
155       * <p>
156       * Note that the returned list is the one held internally by this
157       * specification.  This means that any
158       * changes made to the returned list after this call <i>will</i> be
159       * reflected in this object.</p>
160       * 
161       * @return this specification's list of sky frequency specifications.
162       */
163      @XmlElementWrapper
164      @XmlElement(name="skyFrequencySpecification")
165      public List<SkyFrequencySpecification> getSkyFrequencySpecs()
166      {
167        return skyFrequencySpecs;
168      }
169      
170      /**
171       * Sets a new list of spectral line specifications.
172       * If {@code newSpecs} is <i>null</i>, it will be interpreted
173       * as an empty list.
174       * <p>
175       * Note that this specification will hold an internal reference to
176       * {@code newSpecs} (unless it is <i>null</i>).  This means that any
177       * changes made to {@code newSpecs} after this call <i>will</i> be
178       * reflected in this object.</p>
179       *  
180       * @param newSpecs a list of spectral line specifications.
181       */
182      public void setSpectralLineSpecs(List<SpectralLineSpecification> newSpecs)
183      {
184        spectralLineSpecs =
185          newSpecs == null ? new ArrayList<SpectralLineSpecification>() : newSpecs;
186      }
187    
188      /**
189       * Returns this specification's list of spectral line specifications.
190       * The returned list may be empty, but will never be <i>null</i>.
191       * <p>
192       * Note that the returned list is the one held internally by this
193       * specification.  This means that any
194       * changes made to the returned list after this call <i>will</i> be
195       * reflected in this object.</p>
196       * 
197       * @return this specification's list of spectral line specifications.
198       */
199      @XmlElementWrapper
200      @XmlElement(name="spectralLineSpecification")
201      public List<SpectralLineSpecification> getSpectralLineSpecs()
202      {
203        return spectralLineSpecs;
204      }
205    
206      /**
207       * Sets a new list of pulsar specifications.
208       * If {@code newSpecs} is <i>null</i>, it will be interpreted
209       * as an empty list.
210       * <p>
211       * Note that this specification will hold an internal reference to
212       * {@code newSpecs} (unless it is <i>null</i>).  This means that any
213       * changes made to {@code newSpecs} after this call <i>will</i> be
214       * reflected in this object.</p>
215       *  
216       * @param newSpecs a list of pulsar specifications.
217       */
218      public void setPulsarSpecs(List<PulsarSpecification> newSpecs)
219      {
220        pulsarSpecs =
221          (newSpecs == null) ? new ArrayList<PulsarSpecification>() : newSpecs;
222      }
223      
224      /**
225       * Returns this specification's list of pulsar specifications.
226       * The returned list may be empty, but will never be <i>null</i>.
227       * <p>
228       * Note that the returned list is the one held internally by this
229       * specification.  This means that any
230       * changes made to the returned list after this call <i>will</i> be
231       * reflected in this object.</p>
232       * 
233       * @return this specification's list of pulsar specifications.
234       */
235      @XmlElementWrapper
236      @XmlElement(name="pulsarSpecification")
237      public List<PulsarSpecification> getPulsarSpecs()
238      {
239        return pulsarSpecs;
240      }
241      
242      //============================================================================
243      // 
244      //============================================================================
245    
246      /**
247       * Returns the portions of the frequency spectrum covered by these
248       * specifications.
249       * 
250       * @return the portions of the frequency spectrum covered by these
251       *         specifications.
252       */
253      public FrequencySpectrum getSpectrum()
254      {
255        FrequencySpectrum result = new FrequencySpectrum();
256        
257        for (SkyFrequencySpecification sfs : skyFrequencySpecs)
258          result.addCoveredRange(sfs.getFrequencyRange());
259        
260        for (SpectralLineSpecification sls : spectralLineSpecs)
261          result.addCoveredRange(sls.toSkyFrequencySpecification()
262                                    .getFrequencyRange());
263        
264        //TODO
265        //for (PulsarSpecification ps : pulsarSpecs)
266        //  result.addCoveredRange(ps.getFrequencyRange());
267        
268        return result;
269      }
270      
271      //============================================================================
272      // MODIFYING THIS SPECIFICATION WITH A FREQUENCY RANGE
273      //============================================================================
274      
275      /**
276       * Modifies this resource specification to fit the given frequency range.
277       * <p>
278       * Any frequency ranges held by this specification that have no overlap with
279       * {@code targetRange} are deleted.  Those ranges that do overlap the target
280       * range are trimmed to the overlapping portions.  The net effect is that the
281       * frequency ranges of this specification after the modification are the
282       * result of intersecting the current ranges with {@code targetRange}.</p>
283       * 
284       * @param targetRange the frequency range used to modify this specification.
285       *                    Only the portions of the ranges held by this
286       *                    specification that intersect this parameter are
287       *                    retained.
288       * 
289       * @return this specification, after modification.
290       */
291      public ResourceSpecification modifyToFit(FrequencyRange targetRange)
292      {
293        modifySkyToFit     (targetRange);
294        modifySpectralToFit(targetRange);
295        modifyPulsarToFit  (targetRange);
296    
297        return this;
298      }
299      
300      /**
301       * Modifies this specification's list of sky frequency specifications.
302       * Specifications whose frequency ranges do not overlap targetRange
303       * are removed.
304       * Specifications whose frequency ranges overlap targetRange
305       * are trimmed to the overlapping portion of their ranges.
306       */
307      private void modifySkyToFit(FrequencyRange targetRange)
308      {
309        List<SkyFrequencySpecification> nonOverlappingRanges =
310          new ArrayList<SkyFrequencySpecification>();
311        
312        for (SkyFrequencySpecification sfs : skyFrequencySpecs)
313        {
314          FrequencyRange specRange = sfs.getFrequencyRange();
315          
316          if (specRange.contains(targetRange))
317          {
318            ; //do nothing
319          }
320          if (specRange.overlaps(targetRange))
321          {
322            specRange.intersectWith(targetRange);
323          }
324          else //no overlap
325          {
326            nonOverlappingRanges.add(sfs);
327          }
328        }
329        
330        skyFrequencySpecs.removeAll(nonOverlappingRanges);
331      }
332    
333      /**
334       * Modifies this specification's list of spectral line specifications.
335       * Specifications whose frequency ranges do not overlap targetRange
336       * are removed.
337       * Specifications whose frequency ranges overlap targetRange
338       * are trimmed to the overlapping portion of their ranges.
339       */
340      private void modifySpectralToFit(FrequencyRange targetRange)
341      {
342        List<SpectralLineSpecification> nonOverlappingRanges =
343          new ArrayList<SpectralLineSpecification>();
344        
345        for (SpectralLineSpecification sls : spectralLineSpecs)
346        {
347          FrequencyRange skySpecRange =
348            sls.toSkyFrequencySpecification().getFrequencyRange();
349    
350          if (skySpecRange.contains(targetRange))
351          {
352            ; //do nothing
353          }
354          else if (skySpecRange.overlaps(targetRange))
355          {
356            skySpecRange.intersectWith(targetRange);
357            sls.adjustVelocityForSkyFrequencyOf(skySpecRange);
358          }
359          else //no overlap
360          {
361            nonOverlappingRanges.add(sls);
362          }
363        }
364        
365        spectralLineSpecs.removeAll(nonOverlappingRanges);
366      }
367      
368      /**
369       * Modifies the list of pulsar specifications.
370       * Specifications whose frequency ranges do not overlap targetRange
371       * are removed.
372       * Specifications whose frequency ranges overlap targetRange
373       * are trimmed to the overlapping portion of their ranges.
374       */
375      private void modifyPulsarToFit(FrequencyRange targetRange)
376      {
377        //TODO
378        
379        /*
380        List<PulsarSpecification> nonOverlappingRanges =
381          new ArrayList<PulsarSpecification>();
382        
383        for (PulsarSpecification ps : pulsarSpecs)
384        {
385          FrequencyRange specRange = ps.getFrequencyRange();
386          
387          if (specRange.overlaps(targetRange))
388          {
389            ps.getFrequencyRange().intersectWith(targetRange));
390          }
391          else //no overlap
392          {
393            nonOverlappingRanges.add(ps);
394          }
395        }
396        
397        pulsarSpecs.removeAll(nonOverlappingRanges);
398        */
399      }
400    
401      //============================================================================
402      // MODIFYING THIS SPECIFICATION WITH A FREQUENCY SPECTRUM
403      //============================================================================
404      
405      /**
406       * Modifies this resource specification to fit the given frequency spectrum.
407       * <p>
408       * Any frequency ranges held by this specification that have no overlap with
409       * {@code targetSpectrum} are deleted.  Those ranges that do overlap the
410       * target spectrum are trimmed to the overlapping portions.  The net effect
411       * is that the frequency ranges of this specification after the modification
412       * are the result of intersecting the current ranges with
413       * {@code targetSpectrum}.</p>
414       * <p>
415       * <b>Note:</b> one consequence of this method is that the internal lists
416       * of subspecifications are replaced with new lists.</p>
417       * 
418       * @param targetSpectrum the frequency speumctr used to modify this
419       *                       specification.  Only the portions of the ranges held
420       *                       by this specification that intersect this parameter
421       *                       are retained.
422       * 
423       * @return this specification, after modification.
424       */
425      public ResourceSpecification modifyToFit(FrequencySpectrum targetSpectrum)
426      {
427        modifySkyToFit     (targetSpectrum);
428        modifySpectralToFit(targetSpectrum);
429        modifyPulsarToFit  (targetSpectrum);
430    
431        return this;
432      }
433    
434      /**
435       * Replaces the list of sky frequency specifications with a new list.
436       * New sky frequency specifications are formed from the overlap of each
437       * current specification with the target frequency spectrum.
438       */
439      private void modifySkyToFit(FrequencySpectrum targetSpectrum)
440      {
441        List<SkyFrequencySpecification> newSpecs =
442          new ArrayList<SkyFrequencySpecification>();
443        
444        //For each sky freq spec, find the overlap of its frequency range
445        //with the target spectrum.  Note that there could be any number
446        //of overlapping regions, not just zero-or-one.
447        for (SkyFrequencySpecification sfs : skyFrequencySpecs)
448        {
449          FrequencySpectrum fs =
450            targetSpectrum.clone().intersectWith(sfs.getFrequencyRange());
451          
452          //For each overlapping region, create a new sky freq spec from the
453          //current one and set its frequency range to the overlapping region.
454          for (FrequencyRange coveredRange : fs.getCoveredRanges())
455          {
456            SkyFrequencySpecification newSpec = sfs.clone();
457            newSpec.setFrequencyRange(coveredRange);
458            newSpecs.add(newSpec);
459          }
460        }
461        
462        //Replace current list with list of new specs
463        skyFrequencySpecs = newSpecs;
464      }
465    
466      /**
467       * Replaces the list of spectral line specifications with a new list.
468       * New spectral line specifications are formed from the overlap of each
469       * current specification with the target frequency spectrum.
470       */
471      private void modifySpectralToFit(FrequencySpectrum targetSpectrum)
472      {
473        List<SpectralLineSpecification> newSpecs =
474          new ArrayList<SpectralLineSpecification>();
475        
476        //For each spectral line spec, find the overlap of the frequency range
477        //of its derived sky frequency specification  with the target spectrum.
478        //Note that there could be any number of overlapping regions,
479        //not just zero-or-one.
480        for (SpectralLineSpecification sls : spectralLineSpecs)
481        {
482          SkyFrequencySpecification skyFreqSpec = sls.toSkyFrequencySpecification();
483    
484          FrequencySpectrum fs =
485            targetSpectrum.clone().intersectWith(skyFreqSpec.getFrequencyRange());
486          
487          //For each overlapping region, create a new spectral line spec from the
488          //current one and adjust its velocities to match the covered sky freqs.
489          for (FrequencyRange coveredRange : fs.getCoveredRanges())
490          {
491            SpectralLineSpecification newSpec = sls.clone();
492            newSpec.adjustVelocityForSkyFrequencyOf(coveredRange);
493            newSpecs.add(newSpec);
494          }
495        }
496        
497        spectralLineSpecs = newSpecs;
498      }
499    
500      /**
501       * Replaces the list of pulsar specifications with a new list.
502       * New pulsar specifications are formed from the overlap of each
503       * current specification with the target frequency spectrum.
504       */
505      private void modifyPulsarToFit(FrequencySpectrum targetSpectrum)
506      {
507        //TODO
508        
509        /*
510        List<PulsarSpecification> newSpecs =
511          new ArrayList<PulsarSpecification>();
512        
513        //For each pulsar spec, find the overlap of its frequency range
514        //with the target spectrum.  Note that there could be any number
515        //of overlapping regions, not just zero-or-one.
516        for (PulsarSpecification ps : pulsarSpecs)
517        {
518          FrequencySpectrum fs =
519            targetSpectrum.clone().intersectWith(ps.getFrequencyRange());
520          
521          //For each overlapping region, create a new pulsar spec from the
522          //current one and set its frequency range to the overlapping region.
523          for (FrequencyRange coveredRange : fs.getCoveredRanges())
524          {
525            PulsarSpecification newSpec = ps.clone();
526            newSpec.setFrequencyRange(coveredRange);
527            newSpecs.add(newSpec);
528          }
529        }
530        
531        //Replace current list with list of new specs
532        continuumSpecs = newSpecs;
533        */
534      }
535      
536      //============================================================================
537      // XML 
538      //============================================================================
539    
540      /**
541       * Returns an XML representation of this specification.
542       * @return an XML representation of this specification.
543       * @throws JAXBException if anything goes wrong during the conversion to XML.
544       * @see #writeAsXmlTo(Writer)
545       */
546      public String toXml() throws JAXBException
547      {
548        return JaxbUtility.getSharedInstance().objectToXmlString(this);
549      }
550      
551      /**
552       * Writes an XML representation of this specification to {@code writer}.
553       * @param writer the device to which XML is written.
554       * @throws JAXBException if anything goes wrong during the conversion to XML.
555       */
556      public void writeAsXmlTo(Writer writer) throws JAXBException
557      {
558        JaxbUtility.getSharedInstance().writeObjectAsXmlTo(writer, this, null);
559      }
560      
561      /**
562       * Creates a new specification from the XML data in the given file.
563       * 
564       * @param xmlFile the name of an XML file.  This method will attempt to locate
565       *                the file by using {@link Class#getResource(String)}.
566       *                
567       * @return a new specification from the XML data in the given file.
568       * 
569       * @throws FileNotFoundException if the XML file cannot be found.
570       * 
571       * @throws JAXBException if the schema file used (if any) is malformed, if
572       *           the XML file cannot be read, or if the XML file is not
573       *           schema-valid.
574       * 
575       * @throws XMLStreamException if there is a problem opening the XML file,
576       *           if the XML is not well-formed, or for some other
577       *           "unexpected processing conditions".
578       */
579      public static ResourceSpecification fromXml(String xmlFile)
580        throws JAXBException, XMLStreamException, FileNotFoundException
581      {
582        return JaxbUtility.getSharedInstance()
583                          .xmlFileToObject(xmlFile, ResourceSpecification.class);
584      }
585      
586      /**
587       * Creates a new specification based on the XML data read from {@code reader}.
588       * 
589       * @param reader the specification of the XML data.
590       *               If this value is <i>null</i>, <i>null</i> is returned.
591       *               
592       * @return a new specification based on the XML data read from {@code reader}.
593       * 
594       * @throws XMLStreamException if the XML is not well-formed,
595       *           or for some other "unexpected processing conditions".
596       *           
597       * @throws JAXBException if anything else goes wrong during the
598       *           transformation.
599       */
600      public static ResourceSpecification fromXml(Reader reader)
601        throws JAXBException, XMLStreamException
602      {
603        return JaxbUtility.getSharedInstance()
604                          .readObjectAsXmlFrom(reader,
605                                               ResourceSpecification.class, null);
606      }
607    
608      //============================================================================
609      // 
610      //============================================================================
611      
612      /**
613       *  Returns a resource specification that is a copy of this one.
614       *  <p>
615       *  If anything goes wrong during the cloning procedure,
616       *  a {@code RuntimeException} will be thrown.</p>
617       */
618      @Override
619      public ResourceSpecification clone()
620      {
621        ResourceSpecification clone = null;
622        
623        try
624        {
625          clone = (ResourceSpecification)super.clone();
626          
627          //Clone the collection AND the contained elements
628          clone.skyFrequencySpecs = new ArrayList<SkyFrequencySpecification>();
629          for (SkyFrequencySpecification sfs : this.skyFrequencySpecs)
630            clone.skyFrequencySpecs.add(sfs.clone());
631          
632          clone.spectralLineSpecs = new ArrayList<SpectralLineSpecification>();
633          for (SpectralLineSpecification sls : this.spectralLineSpecs)
634            clone.spectralLineSpecs.add(sls.clone());
635          
636          clone.pulsarSpecs = new ArrayList<PulsarSpecification>();
637          for (PulsarSpecification ps : this.pulsarSpecs)
638            clone.pulsarSpecs.add(ps.clone());
639        }
640        catch (Exception ex)
641        {
642          throw new RuntimeException(ex);
643        }
644        
645        return clone;
646      }
647      
648      /** Returns <i>true</i> if {@code o} is equal to this specification. */
649      @Override
650      public boolean equals(Object o)
651      {
652        //Quick exit if o is this
653        if (o == this)
654          return true;
655        
656        //Quick exit if o is null
657        if (o == null)
658          return false;
659        
660        //Quick exit if classes are different
661        if (!o.getClass().equals(this.getClass()))
662          return false;
663        
664        ResourceSpecification other = (ResourceSpecification)o;
665        
666        return other.skyFrequencySpecs.equals(this.skyFrequencySpecs) &&
667               other.spectralLineSpecs.equals(this.spectralLineSpecs) &&
668               other.pulsarSpecs.equals(this.pulsarSpecs);
669      }    
670    
671      /** Returns a hash code value for this specification. */
672      @Override
673      public int hashCode()
674      {
675        //Taken from the Effective Java book by Joshua Bloch.
676        //The constants 17 & 37 are arbitrary & carry no meaning.
677        int result = 17;
678        
679        result = 37 * result + skyFrequencySpecs.hashCode();
680        result = 37 * result + spectralLineSpecs.hashCode();
681        result = 37 * result + pulsarSpecs.hashCode();
682        
683        return result;
684      }
685    
686      //============================================================================
687      // 
688      //============================================================================
689      /*
690      public static void main(String[] args)
691      {
692        ResourceBuilder builder = new ResourceBuilder();
693        
694        ResourceSpecification spec = builder.makeResourceSpecification();
695                              //JaxbUtility.getSharedInstance().setLookForDefaultSchema(false);
696        try {
697          System.out.println(spec.toXml());
698        }
699        catch (JAXBException ex) {
700          System.out.println("Trouble w/ spec.toXml.  Msg:");
701          System.out.println(ex.getMessage());
702          ex.printStackTrace();
703        }
704      }
705      */
706    }