001    package edu.nrao.sss.model.project.scan;
002    
003    import java.io.FileNotFoundException;
004    import java.io.Reader;
005    import java.util.Collection;
006    import java.util.Date;
007    import java.util.HashMap;
008    import java.util.HashSet;
009    import java.util.Map;
010    import java.util.Set;
011    
012    import javax.xml.bind.JAXBException;
013    import javax.xml.bind.annotation.XmlAttribute;
014    import javax.xml.bind.annotation.XmlElement;
015    import javax.xml.bind.annotation.XmlElementWrapper;
016    import javax.xml.bind.annotation.XmlList;
017    import javax.xml.bind.annotation.XmlTransient;
018    import javax.xml.bind.annotation.XmlType;
019    import javax.xml.stream.XMLStreamException;
020    
021    import edu.nrao.sss.astronomy.DopplerTracker;
022    import edu.nrao.sss.astronomy.SkyPosition;
023    import edu.nrao.sss.astronomy.VelocityConvention;
024    import edu.nrao.sss.astronomy.VelocityFrame;
025    import edu.nrao.sss.geom.EarthPosition;
026    import edu.nrao.sss.measure.Frequency;
027    import edu.nrao.sss.measure.LinearVelocity;
028    import edu.nrao.sss.measure.LinearVelocityUnits;
029    import edu.nrao.sss.model.resource.AntennaSelection;
030    import edu.nrao.sss.model.resource.Resource;
031    import edu.nrao.sss.model.resource.TelescopeType;
032    import edu.nrao.sss.model.source.Source;
033    import edu.nrao.sss.model.source.SourceCatalogEntry;
034    import edu.nrao.sss.model.source.SourceLookupTable;
035    import edu.nrao.sss.model.source.SourceVelocity;
036    import edu.nrao.sss.model.source.Subsource;
037    import edu.nrao.sss.util.Identifiable;
038    import edu.nrao.sss.util.JaxbUtility;
039    import edu.nrao.sss.util.StringUtil;
040    
041    /**
042     * A sequence of one or more observations that share a single goal.
043     * A scan is made up of a target source, a calibrator, resources,
044     * timing information, and an observing mode.
045     * <p>
046     * A {@code Scan} is usually an observation of a single target source
047     * or calibrator, but may involve different pointing and focus
048     * patterns.</p>
049     * <p>
050     * <b>Version Info:</b>
051     * <table style="margin-left:2em">
052     *   <tr><td>$Revision: 2277 $</td>
053     *   <tr><td>$Date: 2009-04-29 11:19:38 -0600 (Wed, 29 Apr 2009) $</td>
054     *   <tr><td>$Author: dharland $</td>
055     * </table></p>
056     *
057     * @author David M. Harland
058     * @since 2006-02-24
059     */
060    @XmlType(
061      propOrder={
062        "mode",                    "timeSpec",
063        "source",                  "sourceLookupTable",
064        "useResourceOfPriorScan",  "resource",
065        "intents",
066        "subarray",                "referenceAntennas",
067        "applyLastPhase",          "applyLastReferencePointing",
068        "applyLastReferenceFocus", "applyLastReferenceDelay",
069        "solarObserving",          "allowOverTheTop",
070        "antennaWrap",             "xmlDopplerSpecs"
071      }
072    )
073    
074    public abstract class Scan
075      extends ScanLoopElement
076    {
077      public static final String DEFAULT_NAME = "[New Scan]";
078    
079      //IDENTIFICATION
080      private ScanMode mode;
081      
082      //TIMING
083      private ScanTimeSpecification timeSpec;
084      
085      //SCIENCE - Special Note re: Sources
086      //We originally used a single instance variable, 
087      //private SourceCatalogEntry sourceCatalogEntry, for the source.
088      //However, we ran into a Hibernate issue when ScanValidator was run.
089      //Specifically, the Hibernate proxy class threw an internal class-cast
090      //exception when the validator called scan.getSource(Date).  The split
091      //of the variable into these two is a work-around for that problem.
092      //Note that we did not change the public API at all.
093      //The code herein ensures that these two can never simultaneously
094      //be non-null.
095      @XmlElement private Source            source;
096      @XmlElement private SourceLookupTable sourceLookupTable;
097      
098      //SCIENCE
099      private boolean         useResourceOfPriorScan;
100      private Resource        resource;
101      private Set<ScanIntent> intents;
102      private boolean         applyLastPhase;
103      private boolean         applyLastReferencePointing;
104      private boolean         applyLastReferenceFocus;
105      private boolean         applyLastReferenceDelay;
106      private boolean         solarObserving;
107      private boolean         allowOverTheTop;
108      private AntennaWrap     antennaWrap;
109      
110      private Map<String, ScanDopplerSpecs> dtSpecs;
111    
112      @XmlElement private AntennaSelection subarray;
113      @XmlElement private AntennaSelection referenceAntennas;
114    
115      //============================================================================
116      // CREATION
117      //============================================================================
118    
119      /**
120       * A factory method for creating a new scan.
121       * The returned scan will be of a variety that is appropriate for the given
122       * observation mode.
123       * 
124       * @param scanMode the observation mode for which a scan is desired.
125       *
126       * @return a new scan instance.
127       * 
128       * @throws IllegalArgumentException if {@code scanMode} is <i>null</i>.
129       */
130      public static Scan createFor(ScanMode scanMode)
131      {
132        if (scanMode == null)
133          throw new IllegalArgumentException("Cannot create scan for NULL scanMode.");
134        
135        Scan newScan = null;
136        
137        // Added the selection of a default ScanIntent where appropriate.  Not done
138        // in constructor because the ScanMode is not passed into the constructor and
139        // some subclasses represent several modes.  Also not done in the private
140        // setMode() method for fear of hosing JAXB or Hibernate. --BWT
141        switch (scanMode)
142        {
143          case AMPLITUDE_DELAY_CALIBRATING:
144            newScan = new DelayScan();
145            newScan.getIntents().add(ScanIntent.CALIBRATE_DELAY_AMPLITUDE_STYLE);
146            break;
147            
148          case REFERENCE_FOCUSING:
149            newScan = new FocusScan();
150            newScan.getIntents().add(ScanIntent.CALIBRATE_FOCUS);
151            break;
152            
153          case HOLOGRAPHY:
154            newScan = new PointingScan();
155            newScan.getIntents().add(ScanIntent.MAP_ANTENNA_SURFACE);
156            break;
157    
158          case INTERFEROMETRIC_POINTING:
159            newScan = new PointingScan();
160            newScan.getIntents().add(ScanIntent.CALIBRATE_OFFSET_POINTING);
161            break;
162    
163          case MOSAICKING:
164            newScan = new PointingScan();
165            newScan.getIntents().add(ScanIntent.OBSERVE_TARGET);
166            break;
167    
168          case SINGLE_DISH_POINTING:
169            newScan = new PointingScan();
170            newScan.getIntents().add(ScanIntent.DETERMINE_SINGLE_DISH_POINTING);
171            break;
172            
173          case FAST_SWITCHING:
174            newScan = new SwitchingScan();
175            newScan.getIntents().add(ScanIntent.OBSERVE_TARGET);
176            break;
177            
178          case TIPPING:
179            newScan = new TippingScan();
180            newScan.getIntents().add(ScanIntent.DETERMINE_OPACITY_TIPPING_STYLE);
181            break;
182            
183          case STANDARD_OBSERVING:
184          default:
185            newScan = new SimpleScan();
186            newScan.getIntents().add(ScanIntent.OBSERVE_TARGET);
187            break;
188        }
189        
190        newScan.setMode(scanMode);
191        
192        return newScan;
193      }
194    
195      /** Helps create a new instance. */
196      Scan()
197      {
198        initialize();
199        
200        //Objects that may never be null
201        timeSpec          = new ScanTimeSpecification();
202        intents           = new HashSet<ScanIntent>(); 
203        referenceAntennas = new AntennaSelection();
204        subarray          = new AntennaSelection();
205        dtSpecs           = new HashMap<String, ScanDopplerSpecs>();
206      }
207      
208      /** Initializes the instance variables of this class.  */
209      private void initialize()
210      {
211      //mode                       = intentionally unset
212        source                     = null;
213        sourceLookupTable          = null;
214        resource                   = null;
215        useResourceOfPriorScan     = false;
216        applyLastPhase             = false;
217        applyLastReferencePointing = false;
218        applyLastReferenceFocus    = false;
219        applyLastReferenceDelay    = false;
220        solarObserving             = false;
221        allowOverTheTop            = false;
222        antennaWrap                = AntennaWrap.NO_PREFERENCE;
223      }
224    
225      /**
226       *  Resets this scan to its initial state.
227       *  A reset scan has the same state as a new scan. 
228       */
229      @Override
230      public void reset()
231      {
232        super.reset();
233        
234        initialize();
235        
236        timeSpec.reset();
237        intents.clear();
238        referenceAntennas.reset();
239        subarray.reset();
240      }
241    
242      //============================================================================
243      // IDENTIFICATION
244      //============================================================================
245      
246    
247      /**
248       * Resets this scan's id to UNIDENTIFIED, an similarly resets it's
249       * source catalog entry and resource id's.
250       */
251      public void clearId()
252      {
253        super.clearId();
254        Resource resrc = getResource();
255        if (resrc != null)
256          resrc.clearId();
257    
258        SourceCatalogEntry entry = getSourceCatalogEntry();
259        if (entry != null)
260          entry.clearId();
261      }
262    
263      String getDefaultName()  { return Scan.DEFAULT_NAME; }
264      
265      /**
266       * Returns <i>true</i> if we may change the name of this scan if it is
267       * given a new source.
268       */
269      boolean nameMayBeAutoUpdated()
270      {
271        //Note that we're not defending against the situation where some one
272        //explicitly set the name to xxx and this just happens to also be
273        //the name of the current source.  If that becomes important, we'll
274        //handle it at that time.
275        String currentName = getName();
276        
277        return
278          currentName.equals(getDefaultName()) ||
279          (source != null && currentName.equals(source.getName())) ||
280          (sourceLookupTable != null &&
281             currentName.equals(sourceLookupTable.getName()));
282      }
283      
284      @XmlTransient
285      @Deprecated
286      /** @deprecated Use {@link #getName()}. */
287      public String getLongName()  { return getName(); }
288      
289      @Deprecated
290      /** @deprecated Use {@link #setName(String)}. */
291      public void setLongName(String newName)  { setName(newName); }
292      
293      @XmlTransient
294      @Deprecated
295      /** @deprecated Use {@link #getName()}. */
296      public String getShortName()  { return getName(); }
297      
298      @Deprecated
299      /** @deprecated Use {@link #setName(String)}. */
300      public void setShortName(String newName)  { setName(newName); }
301      
302      /**
303       * Returns the mode of this scan.
304       * @return the mode of this scan.
305       */
306      @XmlElement
307      public ScanMode getMode()
308      {
309        return mode;
310      }
311      
312      private void setMode(ScanMode newMode)
313      {
314        mode = (newMode == null) ? ScanMode.getDefault() : newMode;
315      }
316    
317      //============================================================================
318      // TIMING
319      //============================================================================
320    
321      /**
322       * Returns the timing specification for this scan.
323       * The returned object will never be <i>null</i> and is the actual instance
324       * held by this scan, so changes made to it will be reflected herein.
325       * 
326       * @return
327       *   the timing specification for this scan.
328       */
329      public ScanTimeSpecification getTimeSpec()
330      {
331        return timeSpec;
332      }
333      
334      //This is here for persistence mechanisms, such as JAXB and Hibernate.
335      @XmlElement
336      @SuppressWarnings("unused")
337      private void setTimeSpec(ScanTimeSpecification newSpec)
338      {
339        if (newSpec != null)
340          timeSpec = newSpec;
341      }
342      
343      //============================================================================
344      // SOURCES
345      //============================================================================
346      
347      /**
348       * Returns the source to use at the current time.  If this scan has no such
349       * source, <i>null</i> is returned.
350       * 
351       * @return the source to use at the current
352       *         time, or <i>null</i> if there is no such source.
353       */
354      public Source getSource()
355      {
356        return getSource(new Date());
357      }
358      
359      /**
360       * Returns the source to use at the given time.  If this scan has no such
361       * source, <i>null</i> is returned.
362       * 
363       * @param dateTime the time for which the source is needed.
364       * 
365       * @return the source to use at the given
366       *         time, or <i>null</i> if there is no such source.
367       */
368      public Source getSource(Date dateTime)
369      {
370        Source answer;
371        
372        if (source != null)
373          answer = source;
374        else if (sourceLookupTable != null)
375          answer = sourceLookupTable.get(dateTime);
376        else
377          answer = null;
378    
379        return answer;
380      }
381      
382      /**
383       * Sets either the {@code Source} or {@code SourceLookupTable} that is the
384       * focus of this scan.
385       * 
386       * @param sourceOrTable the {@code Source} or {@code SourceLookupTable} to
387       *                      use for this scan. 
388       */
389      public void setSourceCatalogEntry(SourceCatalogEntry sourceOrTable)
390      {
391        if (sourceOrTable == null)
392        {
393          source            = null;
394          sourceLookupTable = null;
395        }
396    
397        else
398        {
399          // changing the name BEFORE updating the source because
400          // nameMayBeAutoUpdated() checks to see if the current name matches the
401          // current source's name.  (Which isn't a useful check if we've already
402          // changed the source).
403          if (nameMayBeAutoUpdated())
404            setName(sourceOrTable.getName());
405    
406          if (sourceOrTable instanceof Source)
407          {
408            source            = (Source)sourceOrTable;
409            sourceLookupTable = null;
410          }
411          else if (sourceOrTable instanceof SourceLookupTable)
412          {
413            source            = null;
414            sourceLookupTable = (SourceLookupTable)sourceOrTable;
415          }
416          else
417          {
418            throw new RuntimeException("Unknown type of SourceCatalogEntry found (" +
419                sourceOrTable.getClass().getName() + ")");
420          }
421        }
422      }
423      
424      /**
425       * Returns either the {@code Source} or {@code SourceLookupTable} that is the
426       * focus of this scan.  The returned value may be <i>null</i>.
427       * 
428       * @return the {@code Source} or {@code SourceLookupTable} used for this scan,
429       *         or <i>null</i> if this scan has neither.
430       */
431      @XmlTransient
432      public SourceCatalogEntry getSourceCatalogEntry()
433      {
434        return (source == null) ? sourceLookupTable : source;
435      }
436    
437      //============================================================================
438      // RESOURCES
439      //============================================================================
440    
441      /**
442       * Indicates whether this scan should use its own hardware configuration or
443       * that of the prior scan.
444       * <p>
445       * Calling this method has no impact on the value of the
446       * {@link #setResource(Resource) resource} property.</p>
447       *  
448       * @param usePriorResource
449       *   <i>true</i> if this scan should the hardware configuration of the
450       *   prior scan, <i>false</i> if it should use its own configuration.
451       *   
452       * @see #setResource(Resource)
453       */
454      public void setUseResourceOfPriorScan(boolean usePriorResource)
455      {
456        useResourceOfPriorScan = usePriorResource;
457      }
458      
459      /**
460       * Indicates whether this scan should use its own hardware configuration or
461       * that of the prior scan.
462       *  
463       * @return
464       *   <i>true</i> if this scan should the hardware configuration of the
465       *   prior scan, <i>false</i> if it should use its own configuration.
466       *   
467       * @see #getResource()
468       */
469      public boolean getUseResourceOfPriorScan()
470      {
471        return useResourceOfPriorScan;
472      }
473      
474      /**
475       * Sets the resource to be used for this scan.
476       * This method will accept a value of <i>null</i>.
477       * <p>
478       * The <tt>newResource</tt> should be used only if the value returned
479       * by {@link #getUseResourceOfPriorScan()} is <i>true</i>.</p>
480       * <p>
481       * Calling this method has no impact on the value of the
482       * {@link #setUseResourceOfPriorScan(boolean) use-resource-of-prior-scan}
483       * property.</p>
484       * 
485       * @param newResource the resource to be used for this scan.
486       * 
487       * @see #setUseResourceOfPriorScan(boolean)
488       */
489      public void setResource(Resource newResource)
490      {
491        //No special coding for null parameter
492        resource = newResource;    
493      }
494      
495      /**
496       * Returns the resource to use for this scan, or <i>null</i> if one cannot
497       * be found.  Most clients will first want to call
498       * {@link #getUseResourceOfPriorScan()} and then call this method only if
499       * the value returned by that method is <i>true</i>.
500       * <p>
501       * Note that this method may return a non-null resource even when
502       * <tt>getUseResourceOfPriorScan</tt> returns false.</p>
503       * 
504       * @return
505       *   the resource to use for this scan, or <i>null</i> if one cannot be found.
506       */
507      public Resource getResource()
508      {
509        return resource; 
510      }
511      
512      /**
513       * Returns the antennas selected for a subarray.
514       * @return the antennas selected for a subarray.
515       */
516      public AntennaSelection getSubarray()
517      {
518        return subarray;
519      }
520      
521      /**
522       * Returns a set of antennas to be used as references for this scan.
523       * @return a set of antennas to be used as references for this scan.
524       */
525      public AntennaSelection getReferenceAntennas()
526      {
527        return referenceAntennas;
528      }
529      
530      //----------------------------------------------------------------------------
531      // Doppler Tracking
532      //----------------------------------------------------------------------------
533      
534      /**
535       * Saves Doppler tracking specifications using the given key.
536       * 
537       * @param key
538       *   the key for retrieving {@code dopplerInfo} from this scan.
539       *   See the description of the {@code key} parameter to the
540       *   {@link #getDopplerTracker(String, Date, Frequency)} method
541       *   for important information.
542       *    
543       * @param dopplerInfo
544       *   information regarding doppler tracking for this scan.
545       *   
546       * @since 2009-09-10
547       */
548      public void setDopplerSpecs(String key, ScanDopplerSpecs dopplerInfo)
549      {
550        dtSpecs.put(key, dopplerInfo);
551      }
552      
553      /**
554       * Removes the Doppler tracking information stored previously with the
555       * given key.
556       * 
557       * @param key
558       *   a key used previously in {@link #setDopplerSpecs(String, ScanDopplerSpecs)}
559       *   for storing Doppler tracking information.
560       *   See the description of the {@code key} parameter to the
561       *   {@link #getDopplerTracker(String, Date, Frequency)} method
562       *   for important information.
563       *   
564       * @since 2009-09-10
565       */
566      public void removeDopplerSpecs(String key)
567      {
568        dtSpecs.remove(key);
569      }
570      
571      /**
572       * Returns the Doppler tracking information associated with {@code key}, 
573       * if any.
574       * 
575       * @param key
576       *   a key used previously in {@link #setDopplerSpecs(String, ScanDopplerSpecs)}
577       *   for storing Doppler tracking information.
578       *   See the description of the {@code key} parameter to the
579       *   {@link #getDopplerTracker(String, Date, Frequency)} method
580       *   for important information.
581       *   
582       * @return
583       *    the Doppler tracking information associated with {@code key}, if any.
584       *    If no Doppler information exists for {@code key}, <i>null</i> is returned.
585       *    
586       * @since 2008-09-22
587       */
588      public ScanDopplerSpecs getDopplerSpecs(String key)
589      {
590        return dtSpecs.get(key);
591      }
592      
593      /**
594       * Returns a copy of this scan's map of Doppler tracking information.
595       * <p>
596       * The returned map is not reference by this scan, so changes made to
597       * it will <i>not</i> be reflected herein.  The returned map might be
598       * empty but will never be <i>null</i>.</p>
599       * 
600       * @return
601       *    a copy of this scan's map of Doppler tracking information.
602       *    
603       * @since 2008-09-22
604       */
605      public Map<String, ScanDopplerSpecs> getDopplerSpecs()
606      {
607        return new HashMap<String, ScanDopplerSpecs>(dtSpecs);
608      }
609      
610      /**
611       * Returns a new Doppler tracker based on the given parameters and the
612       * properties of this scan.
613       * <p>
614       * This method first looks for a <tt>ScanDopplerSpecs</tt> object associated
615       * with {@code key}.  If one is found, it is queried for source position and
616       * velocity information.  If the found specification is missing position or
617       * velocity information, that information is sought from this scan's source.
618       * If no Doppler specs are found for {@code key}, this method will again
619       * use this scan's source for the needed information.  The
620       * <tt>EarthPosition</tt> used in the returned <tt>DopplerTracker</tt>
621       * comes from the telescope used by this scan's <tt>Resource</tt>.
622       * If this scan's resource is <i>null</i>, the <tt>DopplerTracker</tt>
623       * will use a default position.</p>
624       * <p>
625       * It is important to note that this method makes no determination about
626       * whether or not Doppler tracking should be used; that decision is up to
627       * the client.  This method will never return a <i>null</i> tracker.</p>
628       * 
629       * @param key
630       *   a key used previously for
631       *   {@link #setDopplerSpecs(String, ScanDopplerSpecs) storing Doppler
632       *   specifications}.
633       *   At this point the key can be any arbitrary text chosen by clients.
634       *   We would like in the future, though, to use the name of a signal
635       *   as the key.  By "signal" we mean one of the outputs from the
636       *   antenna electronics of this scan's resource.  If we were to
637       *   adopt this convention, we could eliminate the
638       *   {@code restFrequency} parameter (see below).
639       *   The hardware configuration code is not yet ready for this,
640       *   so clients currently have the inconvenience of furnishing
641       *   a rest frequency.
642       * 
643       * @param dateTime
644       *   used to fetch the source from this scan.
645       *   If this parameter is <i>null</i> the current system time is used.
646       * 
647       * @param restFrequency
648       *   an optional parameter that is used only if this method needs to fetch
649       *   velocity information from this scan's source.  If this scan has
650       *   Doppler tracking information for {@code signalName}, and if that
651       *   object has velocity information, this parameter will not be used.
652       *   <p/>
653       *   Ideally this parameter should not be needed, and if we enhance
654       *   the <tt>Resource</tt> class or one of its components, we should
655       *   be able to eliminate it.  If the {@code key} parameter is
656       *   truly the name of a signal, this method should be able to talk to
657       *   its resource, get the named signal, and fetch the central frequency
658       *   from it.
659       *   
660       * @return
661       *   a new Doppler tracker based on properties of this scan.
662       *   
663       * @since 2009-09-10
664       */
665      public DopplerTracker getDopplerTracker(String    key,
666                                              Date      dateTime,
667                                              Frequency restFrequency)
668      {
669        if (dateTime == null)
670          dateTime = new Date();
671        
672        ScanDopplerSpecs dopplerInfo = dtSpecs.get(key);
673        boolean haveDoppler = (dopplerInfo != null);
674        
675        Source         src = getSource(dateTime);
676        Subsource      ss  = (src == null) ? null : src.getCentralSubsource();
677        SourceVelocity sv  = ( ss == null) ? null : ss.getVelocity(restFrequency);
678        
679        EarthPosition observer;
680        if (resource != null)
681          observer = resource.getTelescope().getLocation();
682        else
683          observer = TelescopeType.EVLA.getLocation();
684    
685        SkyPosition srcPos = haveDoppler ? dopplerInfo.getPosition() : null;
686        if (srcPos == null && ss != null)
687          srcPos = ss.getPosition();
688          
689        LinearVelocity srcVel = haveDoppler ? dopplerInfo.getVelocity() : null;
690        if (srcVel == null && sv != null)
691          srcVel = sv.getRadialVelocity();
692    
693        VelocityFrame frame = haveDoppler ? dopplerInfo.getRestFrame() : null;
694        if (frame == null && sv != null)
695          frame = sv.getRestFrame();
696        
697        VelocityConvention velConv = haveDoppler ? dopplerInfo.getVelocityConvention() : null;
698        if (velConv == null && sv != null)
699          velConv = sv.getConvention();
700        
701        //Downstream of the OPT only conventions optical and radio are
702        //supported.  For redshift, we will convert Z to km/s and use
703        //optical.  What about relativistic?  Arbitrarily going w/ radio.
704        //   --DMH 2009-02-03 & 2009-02-26, JIRA EVL-794 & 799
705        if (VelocityConvention.REDSHIFT.equals(velConv) ||
706            (srcVel != null && srcVel.getUnits().equals(LinearVelocityUnits.Z)))
707          velConv = VelocityConvention.OPTICAL;
708        else if (VelocityConvention.RELATIVISTIC.equals(velConv))
709          velConv = VelocityConvention.RADIO;
710    
711        return new DopplerTracker(observer, srcPos, srcVel, frame, velConv);
712      }
713    
714      //----------------------------------------------------------------------------
715      // Special JAXB code for handling map of Doppler specs
716      //----------------------------------------------------------------------------
717      
718      private static class XmlScanDopSpec extends ScanDopplerSpecs
719      {
720        @XmlAttribute String signalName;
721        
722        XmlScanDopSpec() { super(); }
723        
724        XmlScanDopSpec(SkyPosition   pos,   LinearVelocity     vel,
725                       VelocityFrame frame, VelocityConvention conv)
726        {
727          super(pos, vel, frame, conv);
728        }
729      }
730      
731      @XmlElementWrapper(name="dopplerTracking")
732      @XmlElement(name="dopplerSpecs")
733      @SuppressWarnings("unused")
734      private XmlScanDopSpec[] getXmlDopplerSpecs()
735      {
736        int specCount = dtSpecs.size();
737        
738        if (specCount == 0)
739          return null;
740        
741        int s = 0;
742        XmlScanDopSpec[] specs = new XmlScanDopSpec[specCount];
743        
744        for (String key : dtSpecs.keySet())
745        {
746          ScanDopplerSpecs spec = dtSpecs.get(key);
747          
748          XmlScanDopSpec xmlSpec =
749            new XmlScanDopSpec(spec.getPosition(),  spec.getVelocity(),
750                               spec.getRestFrame(), spec.getVelocityConvention());
751          
752          xmlSpec.signalName = key;
753          
754          specs[s++] = xmlSpec;
755        }
756        
757        return specs;
758      }
759      
760      @SuppressWarnings("unused")
761      private void setXmlDopplerSpecs(XmlScanDopSpec[] newSpecs)
762      {
763        dtSpecs.clear();
764        
765        for (XmlScanDopSpec xmlSpec : newSpecs)
766        {
767          ScanDopplerSpecs spec =
768            new ScanDopplerSpecs(xmlSpec.getPosition(),  xmlSpec.getVelocity(),
769                                 xmlSpec.getRestFrame(), xmlSpec.getVelocityConvention());
770          
771          dtSpecs.put(xmlSpec.signalName, spec);
772        }
773      }
774      
775      //============================================================================
776      // SCIENCE
777      //============================================================================
778    
779      /**
780       * Sets the purposes for which this scan is intended.
781       * If {@code replacementSet} is <i>null</i>, it will be intrepreted as
782       * a new, empty, set.
783       * <p>
784       * This scan will hold a reference to {@code replacementSet}
785       * (unless it is <i>null</i>), so any changes made to the set
786       * after calling this method will be reflected in this object.</p>
787       * 
788       * @param replacementSet a set of the purposes for which this scan is intended.
789       */
790      public void setIntents(Set<ScanIntent> replacementSet)
791      {
792        intents = (replacementSet == null) ? new HashSet<ScanIntent>()
793                                           : replacementSet;
794      }
795      
796      /**
797       * Returns a set of the purposes for which this scan is intended.
798       * <p>
799       * The returned set is the actual set held by this scan,
800       * so changes made to the set will be reflected in this
801       * object.</p>
802       * 
803       * @return a set of the purposes for which this scan is intended.
804       */
805      @XmlList
806      public Set<ScanIntent> getIntents()
807      {
808        return intents;
809      }
810      
811      /**
812       * Tells this scan whether or not it should apply the most recent set of
813       * phase offsets.
814       * 
815       * @param apply <i>true</i> if this scan should apply the most recent set of
816       *              phase offsets.
817       */
818      public void setApplyLastPhase(boolean apply)  { applyLastPhase = apply; }
819      
820      /**
821       * Returns <i>true</i> if this scan should apply the most recent set of
822       * phase offsets.
823       * 
824       * @return <i>true</i> if this scan should apply the most recent set of
825       *         phase offsets.
826       */
827      public boolean getApplyLastPhase()  { return applyLastPhase; }
828      
829      /**
830       * Tells this scan whether or not is should apply the most recent set of
831       * reference pointing offsets.
832       * 
833       * @param apply <i>true</i> if this scan should apply the most recent set of
834       *              reference pointing offsets.
835       */
836      public void setApplyLastReferencePointing(boolean apply)
837      {
838        applyLastReferencePointing = apply;
839      }
840    
841      /**
842       * Returns <i>true</i> if this scan should apply the most recent set of
843       * reference pointing offsets.
844       * 
845       * @return <i>true</i> if this scan should apply the most recent set of
846       *         reference pointing offsets.
847       */
848      public boolean getApplyLastReferencePointing()
849      {
850        return applyLastReferencePointing;
851      }
852      
853      /**
854       * Tells this scan whether or not is should apply the most recent set of
855       * reference focus offsets.
856       * 
857       * @param apply <i>true</i> if this scan should apply the most recent set of
858       *              reference focus offsets.
859       */
860      public void setApplyLastReferenceFocus(boolean apply)
861      {
862        applyLastReferenceFocus = apply;
863      }
864    
865      /**
866       * Returns <i>true</i> if this scan should apply the most recent set of
867       * reference focus offsets.
868       * 
869       * @return <i>true</i> if this scan should apply the most recent set of
870       *         reference focus offsets.
871       */
872      public boolean getApplyLastReferenceFocus()
873      {
874        return applyLastReferenceFocus;
875      }
876    
877      /**
878       * Tells this scan whether or not is should apply the most recent set of
879       * reference delays.
880       * 
881       * @param apply <i>true</i> if this scan should apply the most recent set of
882       *              reference delays.
883       */
884      public void setApplyLastReferenceDelay(boolean apply)
885      {
886        applyLastReferenceDelay = apply;
887      }
888    
889      /**
890       * Returns <i>true</i> if this scan should apply the most recent set of
891       * reference delays.
892       * 
893       * @return <i>true</i> if this scan should apply the most recent set of
894       *         reference delays.
895       */
896      public boolean getApplyLastReferenceDelay()
897      {
898        return applyLastReferenceDelay;
899      }
900    
901      /**
902       * Indicates whether or not this scan is for solar observing.
903       * 
904       * @param solar <i>true</i> if this scan is for solar observing.
905       */
906      public void setSolarObserving(boolean solar)  { solarObserving = solar; }
907      
908      /**
909       * Returns <i>true</i> if this scan is for solar observing.
910       * 
911       * @return <i>true</i> if this scan is for solar observing.
912       */
913      public boolean getSolarObserving()  { return solarObserving; }
914    
915      /**
916       * Indicates whether or not this scan will allow the telescope to tip beyond
917       * the zenith.
918       * 
919       * @param allow <i>true</i> if this scan allows the telescope to tip beyond
920       *              the zenith.
921       */
922      public void setAllowOverTheTop(boolean allow)  { allowOverTheTop = allow; }
923      
924      /**
925       * Returns <i>true</i> if this scan allows the telescope to tip beyond
926       * the zenith.
927       * 
928       * @return <i>true</i> if this scan allows the telescope to tip beyond
929       *         the zenith.
930       */
931      public boolean getAllowOverTheTop()  { return allowOverTheTop; }
932      
933      /**
934       * Sets the antenna wrapping direction for this scan.
935       * 
936       * @param wrap the antenna wrapping direction for this scan.
937       */
938      public void setAntennaWrap(AntennaWrap wrap)
939      {
940        antennaWrap = (wrap == null) ? AntennaWrap.NO_PREFERENCE : wrap;
941      }
942      
943      /**
944       * Returns the antenna wrapping direction for this scan.
945       * 
946       * @return the antenna wrapping direction for this scan.
947       */
948      public AntennaWrap getAntennaWrap()  { return antennaWrap; }
949    
950      //============================================================================
951      // TEXT
952      //============================================================================
953      
954      /* (non-Javadoc)
955       * @see ScanLoopElement#toSummaryString()
956       */
957      public String toSummaryString()
958      {
959        StringBuilder buff = new StringBuilder();
960        
961        buff.append("name=").append(getName());
962        buff.append(", id=").append(getId());
963        buff.append(", mode=").append(mode);
964        
965        return buff.toString();
966      }
967    
968      /**
969       * Creates a new scan from the XML data in the given file.
970       * <p>
971       * Sample usage:<pre>
972       *   FocusScan myScan = Scan.fromXml(FocusScan.class, myFile);</pre>
973       * 
974       * @param <T> the particular subclass of {@code Scan} returned.
975       * 
976       * @param scanType the {@code Class} of an object that extends {@code Scan}. 
977       * 
978       * @param xmlFile the name of an XML file.  This method will attempt to locate
979       *                the file by using {@link Class#getResource(String)}.
980       *                
981       * @return a new scan from the XML data in the given file.
982       * 
983       * @throws FileNotFoundException if the XML file cannot be found.
984       * 
985       * @throws JAXBException if the schema file used (if any) is malformed, if
986       *           the XML file cannot be read, or if the XML file is not
987       *           schema-valid.
988       * 
989       * @throws XMLStreamException if there is a problem opening the XML file,
990       *           if the XML is not well-formed, or for some other
991       *           "unexpected processing conditions".
992       */
993      public static <T extends Scan> T fromXml(Class<T> scanType, String xmlFile)
994        throws JAXBException, XMLStreamException, FileNotFoundException
995      {
996        T newScan = JaxbUtility.getSharedInstance().xmlFileToObject(xmlFile, scanType);
997    
998        newScan.testForResourceFromJaxb();
999    
1000        return newScan; 
1001      }
1002      
1003      
1004      /**
1005       * Creates a new scan based on the XML data read from {@code reader}.
1006       * <p>
1007       * Sample usage:<pre>
1008       *   DelayScan myScan = Scan.fromXml(DelayScan.class, myReader);</pre>
1009       * 
1010       * @param <T> the particular subclass of {@code Scan} returned.
1011       * 
1012       * @param scanType the {@code Class} of an object that extends {@code Scan}. 
1013       * 
1014       * @param reader the source of the XML data.
1015       *               If this value is <i>null</i>, <i>null</i> is returned.
1016       *               
1017       * @return a new scan based on the XML data read from {@code reader}.
1018       * 
1019       * @throws XMLStreamException if the XML is not well-formed,
1020       *           or for some other "unexpected processing conditions".
1021       *           
1022       * @throws JAXBException if anything else goes wrong during the
1023       *           transformation.
1024       */
1025      public static <T extends Scan> T fromXml(Class<T> scanType, Reader reader)
1026        throws JAXBException, XMLStreamException
1027      {
1028        T newScan = JaxbUtility.getSharedInstance()
1029                               .readObjectAsXmlFrom(reader, scanType, null);
1030    
1031        newScan.testForResourceFromJaxb();
1032    
1033        return newScan; 
1034      }
1035    
1036      /**
1037       * Meant for use by containers of scans; most clients should not use this method.
1038       * @throws JAXBException
1039       *   if the scan has no resource and the useResourceOfPriorScan flag is false.
1040       */
1041      void testForResourceFromJaxb() throws JAXBException
1042      {
1043        if (!useResourceOfPriorScan && resource == null)
1044          throw new JAXBException(
1045            "When useResourceOfPriorScan is false, the resource element must be provided.  Scan "+
1046            getName()+".");
1047      }
1048      
1049      //============================================================================
1050      // PERSISTENCE HELPERS
1051      //============================================================================
1052    
1053      //This pair of methods converts Set<ScanIntent> to/from single string
1054      @SuppressWarnings("unused")
1055      private String getPersistentIntents()
1056      {
1057        Collection<String> strings = new HashSet<String>();
1058        
1059        for (ScanIntent intent : intents)
1060          strings.add(intent.name());
1061        
1062        return StringUtil.getInstance().fromCollection(strings, " | ");
1063      }
1064      
1065      @SuppressWarnings("unused")
1066      private void setPersistentIntents(String text)
1067      {
1068        intents.clear();
1069    
1070        if (text != null)
1071        {
1072          Collection<String> intentNames =
1073            StringUtil.getInstance().toCollection(text, " | ", null);
1074          
1075          for (String intentName : intentNames)
1076            intents.add(ScanIntent.fromString(intentName));
1077        }
1078      }
1079      
1080      //============================================================================
1081      // 
1082      //============================================================================
1083      
1084      /**
1085       *  Returns a scan that is a copy of this one.
1086       *  <p>
1087       *  The returned scan is, for the most part, a deep copy of this one.
1088       *  However, there are a few exceptions:
1089       *  <ol>
1090       *    <li>The ID will be set to
1091       *        {@link Identifiable#UNIDENTIFIED}.</li>
1092       *    <li>The schedulingBlock will be <i>null</i>.</li>
1093       *    <li>The createdOn and lastUpdatedOn attributes will be set to the
1094       *        current system time.</li>
1095       *  </ol></p>
1096       *  <p>
1097       *  If anything goes wrong during the cloning procedure,
1098       *  a {@code RuntimeException} will be thrown.</p>
1099       */
1100      public Scan clone()
1101      {
1102        Scan clone = null;
1103    
1104        try
1105        {
1106          //This line takes care of the primitive & immutable fields properly
1107          clone = (Scan)super.clone();
1108          
1109          clone.timeSpec = this.timeSpec.clone();
1110          
1111          //Need to clone set.
1112          //Do not need to clone elements because they are immutable.
1113          clone.intents = new HashSet<ScanIntent>();
1114          clone.intents.addAll(this.intents);
1115          
1116          clone.referenceAntennas = this.referenceAntennas.clone();
1117          clone.subarray = this.subarray.clone();
1118          
1119          if (this.source != null)
1120            clone.source = this.source.clone();
1121          
1122          if (this.sourceLookupTable != null)
1123            clone.sourceLookupTable = this.sourceLookupTable.clone();
1124          
1125          if (this.resource != null)
1126            clone.resource = this.resource.clone();
1127          
1128          //Clone map and each of its elements
1129          clone.dtSpecs = new HashMap<String, ScanDopplerSpecs>();
1130          for (String key : this.dtSpecs.keySet())
1131            clone.dtSpecs.put(key, this.dtSpecs.get(key).clone());
1132        }
1133        catch (Exception ex)
1134        {
1135          throw new RuntimeException(ex);
1136        }
1137        
1138        return clone;
1139      }
1140      
1141      /**
1142       * Returns <i>true</i> if {@code o} is equal to this scan.
1143       * <p>
1144       * In order to be equal to this element, {@code o} must be non-null and
1145       * of the same class as this element. Equality is determined by examining
1146       * the equality of corresponding attributes, with the following exceptions,
1147       * which are ignored when assessing equality: 
1148       * <ol>
1149       *   <li>id</li>
1150       *   <li>schedulingBlock</li>
1151       *   <li>createdOn</li>
1152       *   <li>createdBy</li>
1153       *   <li>lastUpdatedOn</li>
1154       *   <li>lastUpdatedBy</li>
1155       * </ol></p>
1156       */
1157      public boolean equals(Object o)
1158      {
1159        //Quick exit if o is this
1160        if (o == this)
1161          return true;
1162        
1163        //Quick exit if parent class says objects not equal
1164        if (!super.equals(o))
1165          return false;
1166        
1167        //A safe cast because super class ensures classes are same
1168        Scan other = (Scan)o;
1169    
1170        //Compare most important attributes
1171        if (!other.mode.equals(this.mode) ||
1172            !other.intents.equals(this.intents))
1173          return false;
1174        
1175        //Compare timing info
1176        if (!other.timeSpec.equals(this.timeSpec))
1177          return false;
1178        
1179        //Compare source & resource
1180        if (!objectsAreEqual(this.source, other.source))
1181           return false;
1182    
1183        if (!objectsAreEqual(this.sourceLookupTable, other.sourceLookupTable))
1184           return false;
1185    
1186        //When a scan is using resource of previous scan, we will NOT compare
1187        //the cached resources that this and other scan may or may not have.
1188        if (other.useResourceOfPriorScan)
1189        {
1190          if (!this.useResourceOfPriorScan)
1191            return false;  //not equal if one uses prior & one does not
1192        }
1193        else //other is using its own resource
1194        {
1195          if (this.useResourceOfPriorScan)
1196            return false;  //not equal if one uses prior & one does not
1197          
1198          //Neither this nor other is using rsrc of prev scan; compare their resources
1199          if (!objectsAreEqual(this.resource, other.resource))
1200            return false;
1201        }
1202    
1203        //Compare remaining attributes
1204        if (!other.referenceAntennas.equals(this.referenceAntennas) ||
1205            !other.subarray.equals(this.subarray) ||
1206             other.applyLastPhase             != this.applyLastPhase ||
1207             other.applyLastReferencePointing != this.applyLastReferencePointing ||
1208             other.applyLastReferenceFocus    != this.applyLastReferenceFocus ||
1209             other.applyLastReferenceDelay    != this.applyLastReferenceDelay ||
1210             other.solarObserving             != this.solarObserving ||
1211             other.allowOverTheTop            != this.allowOverTheTop ||
1212            !other.antennaWrap.equals(this.antennaWrap) ||
1213            !other.dtSpecs.equals(this.dtSpecs))
1214          return false;
1215        
1216        //No differences found
1217        return true;
1218      }
1219      
1220      private boolean objectsAreEqual(Object thisOne, Object thatOne)
1221      {
1222        return (thisOne == null) ? (thatOne == null) : thisOne.equals(thatOne);
1223      }
1224    
1225      /* (non-Javadoc)
1226       * @see ScanLoopElement#hashCode()
1227       */
1228      public int hashCode()
1229      {
1230        //Taken from the Effective Java book by Joshua Bloch.
1231        //The constants 17 & 37 are arbitrary & carry no meaning.
1232        int result = super.hashCode();
1233        
1234        //You MUST keep this method in sync w/ the equals method
1235        
1236        result = 37 * result + mode.hashCode();
1237        result = 37 * result + intents.hashCode();
1238    
1239        result = 37 * result + timeSpec.hashCode();
1240    
1241        if (source != null)
1242          result = 37 * result + source.hashCode();
1243    
1244        if (sourceLookupTable != null)
1245          result = 37 * result + sourceLookupTable.hashCode();
1246    
1247        if (useResourceOfPriorScan)
1248          result = 37 * result + Boolean.TRUE.hashCode();
1249        else if (resource != null)
1250          result = 37 * result + resource.hashCode();
1251    
1252        result = 37 * result + referenceAntennas.hashCode();
1253        result = 37 * result + subarray.hashCode();
1254        result = 37 * result + Boolean.valueOf(applyLastPhase).hashCode();
1255        result = 37 * result + Boolean.valueOf(applyLastReferencePointing).hashCode();
1256        result = 37 * result + Boolean.valueOf(applyLastReferenceFocus).hashCode();
1257        result = 37 * result + Boolean.valueOf(applyLastReferenceDelay).hashCode();
1258        result = 37 * result + Boolean.valueOf(solarObserving).hashCode();
1259        result = 37 * result + Boolean.valueOf(allowOverTheTop).hashCode();
1260        result = 37 * result + antennaWrap.hashCode();
1261        result = 37 * result + dtSpecs.hashCode();
1262        
1263        return result;
1264      }
1265    
1266      //============================================================================
1267      // 
1268      //============================================================================
1269    
1270      //This is here for quick manual testing
1271      /*
1272      public static void main(String[] args)
1273      {
1274        ScanBuilder builder = new ScanBuilder();
1275        builder.setIdentifiers(true);
1276        
1277        Scan scan = builder.makeScan();
1278        
1279        try {
1280          System.out.println(scan.toXml());
1281        }
1282        catch (JAXBException ex) {
1283          System.out.println("Trouble w/ Scan.toXml.  Msg:");
1284          System.out.println(ex.getMessage());
1285          ex.printStackTrace();
1286          
1287          System.out.println("Attempting to write XML w/out schema verification:");
1288          JaxbUtility.getSharedInstance().setLookForDefaultSchema(false);
1289          try
1290          {
1291            System.out.println(scan.toXml());
1292          }
1293          catch (JAXBException ex2)
1294          {
1295            System.out.println("Still had trouble w/ Scan.toXml.  Msg:");
1296            System.out.println(ex.getMessage());
1297            ex.printStackTrace();
1298          }
1299        }
1300      }
1301      */
1302      /*
1303      public static void main(String[] args)
1304      {
1305        ValidationManager mgr = new ValidationManager();
1306        
1307        ScanBuilder builder = new ScanBuilder();
1308        SwitchingScan scan = builder.makeSwitchingScanFor(new ScanLoop());
1309        
1310        Validator val = mgr.getValidator(scan.getClass());
1311        System.out.println(val.getClass().getName());
1312        
1313        //scan.getSwitchSettings().clear();
1314        //scan.getSwitchSettings().add(new SwitchSetting());
1315        
1316        List<ValidationFailure> failures =
1317          val.validate(scan, ValidationPurpose.CERTIFY_READY_TO_USE);
1318        
1319        for (ValidationFailure failure : failures)
1320        {
1321          System.out.println(failure.getDisplayMessage());
1322        }
1323      }
1324      */
1325    }