001    package edu.nrao.sss.model.project.scan;
002    
003    import java.util.Date;
004    
005    import javax.xml.bind.annotation.XmlElement;
006    import javax.xml.bind.annotation.XmlTransient;
007    import javax.xml.bind.annotation.XmlType;
008    
009    import edu.nrao.sss.measure.TimeDuration;
010    import edu.nrao.sss.model.resource.Resource;
011    import edu.nrao.sss.model.source.Source;
012    import edu.nrao.sss.model.source.SourceCatalogEntry;
013    import edu.nrao.sss.model.source.SourceLookupTable;
014    import edu.nrao.sss.util.Identifiable;
015    
016    /**
017     * A switch setting used by a {@link SwitchingScan}.
018     * <p>
019     * <b>Version Info:</b>
020     * <table style="margin-left:2em">
021     *   <tr><td>$Revision: 2190 $</td></tr>
022     *   <tr><td>$Date: 2009-04-13 15:15:03 -0600 (Mon, 13 Apr 2009) $</td></tr>
023     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
024     * </table></p>
025     *  
026     * @author David M. Harland
027     * @since 2006-07-10
028     */
029    @XmlType(
030      propOrder={
031        "xmlSource", "xmlSourceLookupTable", "resource",
032        "timeAtSetting", "durationType", "useForAutophasing"
033      }
034    )
035    public class SwitchSetting
036      implements Cloneable
037    {
038      private static final ScanTimeType DEFAULT_TYPE =
039        ScanTimeType.ON_SOURCE_SIDEREAL;
040      
041      @SuppressWarnings("unused")
042      private Long id;  //Late add'n due to Hibernate.  See ScanLoopElement.hbm.xml.
043      
044      private SourceCatalogEntry sourceCatalogEntry;
045      private Resource           resource;
046      private TimeDuration       timeAtSetting;
047      private ScanTimeType       durationType;
048      private boolean            useForAutophasing;
049      
050      //These instance variables are not persisted
051      private SwitchingScan scan;  
052      
053      /** Creates a new instance. */
054      public SwitchSetting()
055      {
056        id                 = Identifiable.UNIDENTIFIED;
057    
058        sourceCatalogEntry = null;
059        resource           = null;
060        timeAtSetting      = new TimeDuration();
061        durationType       = DEFAULT_TYPE;
062        useForAutophasing  = false;
063        
064        scan               = null;
065      }
066    
067      //============================================================================
068      // PERSISTED PROPERTIES
069      //============================================================================
070      
071      /**
072       * Sets either the {@code Source} or {@code SourceLookupTable} to use for this
073       * setting.
074       * 
075       * @param sourceOrTable the {@code Source} or {@code SourceLookupTable} to
076       *                      use for this setting. 
077       */
078      public void setSourceCatalogEntry(SourceCatalogEntry sourceOrTable)
079      {
080        //No special coding for null parameter
081        sourceCatalogEntry = sourceOrTable;
082      }
083    
084      /**
085       * Returns either the {@code Source} or {@code SourceLookupTable} that is the
086       * used for this setting.  The returned value may be <i>null</i>.
087       * 
088       * @return the {@code Source} or {@code SourceLookupTable} used for this scan,
089       *         or <i>null</i> if this setting has neither.
090       */
091      @XmlTransient
092      public SourceCatalogEntry getSourceCatalogEntry()
093      {
094        return sourceCatalogEntry;
095      }
096      
097      /**
098       * Returns the source to use at the current time.  If this setting has no such
099       * source, <i>null</i> is returned.
100       * 
101       * @return the source to use at the current
102       *         time, or <i>null</i> if there is no such source.
103       */
104      public Source getSource()
105      {
106        return getSource(new Date());
107      }
108      
109      /**
110       * Returns the source to use at the given time.  If this setting has no such
111       * source, <i>null</i> is returned.
112       * 
113       * @param dateTime the time for which the source is needed.
114       * 
115       * @return the source to use at the given
116       *         time, or <i>null</i> if there is no such source.
117       */
118      public Source getSource(Date dateTime)
119      {
120        Source source = null;
121        
122        if (sourceCatalogEntry != null)
123          source = sourceCatalogEntry.get(dateTime);
124        
125        return source;
126      }
127    
128      /**
129       * Sets the resource to be used for this scan.
130       * This method will accept a value of <i>null</i>.
131       * 
132       * @param newResource the resource to be used for this scan.
133       */
134      public void setResource(Resource newResource)
135      {
136        //No special coding for null parameter
137        resource = newResource;    
138      }
139      
140      /**
141       * Returns the resource to use for this scan, or <i>null</i> if one cannot
142       * be found.
143       * @return the resource to use for this scan, or <i>null</i> if one cannot
144       *         be found.
145       */
146      public Resource getResource()
147      {
148        return resource; 
149      }
150    
151      /**
152       * Sets the amount of time that should be spent at this setting.
153       * <p>
154       * If {@code duration} is <i>null</i>, it will be treated as
155       * an non-null duration of size zero.</p>
156       * 
157       * @param duration the amount of time that should be spent at this setting.
158       */
159      public void setTimeAtSetting(TimeDuration duration)
160      {
161        timeAtSetting = (duration == null) ? new TimeDuration() : duration;
162      }
163    
164      /**
165       * Returns the amount of time that should be spent at this setting.
166       * <p>
167       * The returned value is guaranteed to be non-null.  It is also the 
168       * duration that is held internally by this position, so any changes
169       * made to the returned duration will be reflected in this object.</p>
170       *  
171       * @return the amount of time that should be spent at this setting.
172       */
173      public TimeDuration getTimeAtSetting()
174      {
175        return timeAtSetting;
176      }
177    
178      /**
179       * Sets a new duration type for this setting.
180       * 
181       * @param newType
182       *   the new duration type for this setting.
183       *   If this value is <i>null</i>, the default duration type of
184       *   <tt>ON_SOURCE_SIDEREAL</tt> will be used.
185       *   This value must have its <tt>isDuration()</tt> method
186       *   return <i>true</i>.  If it does not, an
187       *   <tt>IllegalArgumentException</tt> is thrown.
188       *   
189       * @throws IllegalArgumentException
190       *   if {@code newType} is not a duration type.
191       */
192      public void setDurationType(ScanTimeType newType)
193      {
194        if (newType == null)
195          newType = DEFAULT_TYPE;
196        
197        if (!newType.isDuration())
198          throw new IllegalArgumentException("newType '" + newType +
199            "' must be a duration type.");
200        
201        durationType = newType;
202      }
203      
204      /**
205       * Returns the type of duration used by this setting.
206       * This method helps clients interpret the value returned by
207       * {@link #getTimeAtSetting()}.
208       * <p>
209       * The returned type will be non-null and its <tt>isDuration()</tt> method
210       * will always return <i>true</i>.</p>
211       * 
212       * @return
213       *   the type of duration used by this setting.
214       */
215      public ScanTimeType getDurationType()
216      {
217        return durationType;
218      }
219    
220      /**
221       * Determines whether or not the source in this switch setting should
222       * be used for autophasing.
223       * 
224       * @param use <i>true</i> if the source in this setting should be used
225       *            for autophasing.
226       */
227      public void setUseForAutophasing(boolean use)
228      {
229        useForAutophasing = use;
230      }
231      
232      /**
233       * Returns <i>true</i> if the source in this setting should be used
234       * for autophasing.
235       * 
236       * @return <i>true</i> if the source in this setting should be used
237       *         for autophasing.
238       */
239      public boolean getUseForAutophasing()
240      {
241        return useForAutophasing;
242      }
243    
244      //============================================================================
245      // CONTAINER
246      //============================================================================
247    
248      /**
249       * Lets this setting know the scan to which it belongs.
250       * This setting will use its scan to retrieve its resource.
251       */
252      void setScan(SwitchingScan container)
253      {
254        //Let previous container know we've been moved
255        if ((scan != container) && (scan != null))
256          scan.getSwitchSettings().remove(this);
257        
258        scan = container;
259      }
260      
261      //============================================================================
262      // CONVENIENCE METHODS
263      //============================================================================
264      
265      /**
266       * A convenience method for setting the time spent at this setting.
267       * This setting will <i>not</i> hold a reference to <tt>newSpec</tt>.
268       * Instead, this method will use its
269       * {@link ScanTimeSpecification#getTimeType() getTimeType()} and 
270       * {@link ScanTimeSpecification#getDuration() getDuration()} methods
271       * to set the time at setting and duration type of this object. 
272       * 
273       * @param newSpec
274       *   the provider of the time at setting and duration type values for
275       *   this setting.  A value of <i>null</i> will result in a
276       *   {@code NullPointerException}.
277       * 
278       * @throws IllegalArgumentException
279       *   if the time type held by {@code newSpec} is not a duration type.
280       */
281      @XmlTransient
282      public void setTimeSpec(ScanTimeSpecification newSpec)
283      {
284        setDurationType(newSpec.getTimeType());
285        setTimeAtSetting(newSpec.getDuration());
286      }
287      
288      /**
289       * A convenience method for fetching the time spent at this setting.
290       * The returned object is guaranteed to be non-null.
291       * It is not referenced internally by this setting, so any changes made
292       * to it by clients will not affect this object.
293       * 
294       * @return
295       *   the time at setting and duration type of this offset.
296       */
297      public ScanTimeSpecification getTimeSpec()
298      {
299        ScanTimeSpecification spec = new ScanTimeSpecification();
300        
301        spec.set(durationType, timeAtSetting);
302        
303        return spec;
304      }
305    
306      //============================================================================
307      // XML Helpers
308      //============================================================================
309      
310      //These methods are here solely to help JAXB do its thing.  Depending on what
311      //future releases of JAXB bring, we may be able to eliminate, or replace,
312      //these methods later on.  --DMH Oct 2006
313      
314      @SuppressWarnings("unused")
315     private void setXmlSource(Source newSource)
316      {
317        setSourceCatalogEntry(newSource);
318      }
319      
320      @XmlElement(name="source")
321      @SuppressWarnings("unused")
322      private Source getXmlSource()
323      {
324        return (sourceCatalogEntry instanceof Source) ? (Source)sourceCatalogEntry
325                                                      : null;
326      }
327      
328      @SuppressWarnings("unused")
329      private void setXmlSourceLookupTable(SourceLookupTable newTable)
330      {
331        setSourceCatalogEntry(newTable);
332      }
333      
334      @XmlElement(name="sourceLookupTable")
335      @SuppressWarnings("unused")
336      private SourceLookupTable getXmlSourceLookupTable()
337      {
338        return (sourceCatalogEntry instanceof SourceLookupTable) ?
339          (SourceLookupTable)sourceCatalogEntry : null;
340      }
341    
342      //============================================================================
343      // 
344      //============================================================================
345    
346      /**
347       * Returns a text representation of this pointing position.
348       * The returned string is of the form <i>source.name,resource.name,
349       * timeAtSetting,useForAutophasing</i>.
350       */
351      @Override
352      public String toString()
353      {
354        StringBuilder buff = new StringBuilder();
355        
356        buff.append(sourceCatalogEntry.getName());
357        buff.append(',').append(getResource().getName());
358        buff.append(',').append(timeAtSetting);
359        buff.append(',').append(durationType);
360        buff.append(',').append(useForAutophasing);
361        
362        return buff.toString();
363      }
364    
365      /**
366       *  Returns a setting that is a copy of this one.
367       *  <p>
368       *  If anything goes wrong during the cloning procedure,
369       *  a {@code RuntimeException} will be thrown.</p>
370       */
371      @Override
372      public SwitchSetting clone()
373      {
374        SwitchSetting clone = null;
375    
376        try
377        {
378          //This line takes care of primitive & immutable fields properly
379          clone = (SwitchSetting)super.clone();
380          
381          //We do NOT want the clone to have the same ID as the original.
382          //The ID is here for the persistence layer; it is in charge of
383          //setting IDs.  To help it, we put the clone's ID in the uninitialized
384          //state.
385          clone.id = Identifiable.UNIDENTIFIED;
386    
387          clone.timeAtSetting = this.timeAtSetting.clone();
388          
389          if (this.sourceCatalogEntry != null)
390            clone.sourceCatalogEntry = this.sourceCatalogEntry.clone();
391    
392          if (this.resource != null)
393            clone.resource = this.resource.clone();
394          
395          //Do not automatically deem the clone to be part of the same scan
396          clone.scan = null;
397        }
398        catch (Exception ex)
399        {
400          throw new RuntimeException(ex);
401        }
402        
403        return clone;
404      }
405      
406      /** Returns <i>true</i> if {@code o} is equal to this setting. */
407      @Override
408      public boolean equals(Object o)
409      {
410        //Quick exit if o is null
411        if (o == null)
412          return false;
413        
414        //Quick exit if o is this object
415        if (o == this)
416          return true;
417        
418        //Quick exit if classes are different
419        if (!o.getClass().equals(this.getClass()))
420          return false;
421        
422        //A safe cast if we got this far
423        SwitchSetting other = (SwitchSetting)o;
424        
425        //Attributes that we INTENTIONALLY DO NOT COMPARE: id
426    
427        if (!this.durationType.equals(other.durationType))
428          return false;
429        
430        //Compare source & resource
431        if (!objectsAreEqual( this.sourceCatalogEntry,
432                             other.sourceCatalogEntry))
433          return false;
434    
435        if (!objectsAreEqual(this.resource, other.resource))
436          return false;
437    
438        //Compare ohter attributes
439        return (this.timeAtSetting.equals(other.timeAtSetting) &&
440                this.useForAutophasing == other.useForAutophasing);
441      }
442      
443      private boolean objectsAreEqual(Object thisOne, Object thatOne)
444      {
445        return (thisOne == null) ? (thatOne == null) : thisOne.equals(thatOne);
446      }
447    
448      /** Returns a hash code value for this setting. */
449      @Override
450      public int hashCode()
451      {
452        //Taken from the Effective Java book by Joshua Bloch.
453        //The constants 17 & 37 are arbitrary & carry no meaning.
454        int result = 17;
455        
456        //You MUST keep this method in sync w/ the equals method
457    
458        if (this.sourceCatalogEntry != null)
459          result = 37 * result + sourceCatalogEntry.hashCode();
460        
461        if (this.resource != null)
462          result = 37 * result + resource.hashCode();
463    
464        result = 37 * result + durationType.hashCode();
465        result = 37 * result + timeAtSetting.hashCode();
466        result = 37 * result + new Boolean(useForAutophasing).hashCode();
467        
468        return result;
469      }
470    }