001    package edu.nrao.sss.model.project.scan;
002    
003    import javax.xml.bind.annotation.XmlTransient;
004    
005    import edu.nrao.sss.measure.TimeDuration;
006    
007    /**
008     * A setting used in a {@code DelayScan}.
009     * <p>
010     * <b>Version Info:</b>
011     * <table style="margin-left:2em">
012     *   <tr><td>$Revision: 1494 $</td></tr>
013     *   <tr><td>$Date: 2008-08-14 13:51:17 -0600 (Thu, 14 Aug 2008) $</td></tr>
014     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
015     * </table></p>
016     *  
017     * @author David M. Harland
018     * @since 2006-07-21
019     */
020    public class DelaySetting
021      implements Cloneable, Comparable<DelaySetting>
022    {
023      private static final ScanTimeType DEFAULT_TYPE =
024        ScanTimeType.ON_SOURCE_SIDEREAL;
025      
026      private double       nanoseconds;
027      private TimeDuration timeAtSetting;
028      private ScanTimeType durationType;
029    
030      /** Creates a new instance. */
031      public DelaySetting()
032      {
033        nanoseconds   = 0.0;
034        timeAtSetting = new TimeDuration();
035        durationType  = DEFAULT_TYPE;
036      }
037    
038      //============================================================================
039      // PERSISTED PROPERTIES
040      //============================================================================
041    
042      /**
043       * Sets the nanosecond delay used in this setting.
044       * This value is allowed to be negative because it refers to a delay that is
045       * relative to the current setting.
046       *  
047       * @param ns the nanosecond delay used in this setting.
048       */
049      public void setNanoseconds(double ns)
050      {
051        nanoseconds = ns;
052      }
053    
054      /**
055       * Returns the nanosecond delay used in this setting.
056       * @return the nanosecond delay used in this setting.
057       * @see #setNanoseconds(double)
058       */
059      public double getNanoseconds()
060      {
061        return nanoseconds;
062      }
063    
064      /**
065       * Sets the amount of time that should be spent at this setting.
066       * <p>
067       * If {@code duration} is <i>null</i>, it will be treated as
068       * an non-null duration of size zero.</p>
069       * 
070       * @param duration the amount of time that should be spent at this setting.
071       */
072      public void setTimeAtSetting(TimeDuration duration)
073      {
074        timeAtSetting = (duration == null) ? new TimeDuration() : duration;
075      }
076    
077      /**
078       * Returns the amount of time that should be spent at this setting.
079       * <p>
080       * The returned value is guaranteed to be non-null.  It is also the 
081       * duration that is held internally by this position, so any changes
082       * made to the returned duration will be reflected in this object.</p>
083       *  
084       * @return the amount of time that should be spent at this setting.
085       */
086    
087      public TimeDuration getTimeAtSetting()
088      {
089        return timeAtSetting;
090      }
091    
092      /**
093       * Sets a new duration type for this setting.
094       * 
095       * @param newType
096       *   the new duration type for this setting.
097       *   If this value is <i>null</i>, the default duration type of
098       *   <tt>ON_SOURCE_SIDEREAL</tt> will be used.
099       *   This value must have its <tt>isDuration()</tt> method
100       *   return <i>true</i>.  If it does not, an
101       *   <tt>IllegalArgumentException</tt> is thrown.
102       *   
103       * @throws IllegalArgumentException
104       *   if {@code newType} is not a duration type.
105       */
106      public void setDurationType(ScanTimeType newType)
107      {
108        if (newType == null)
109          newType = DEFAULT_TYPE;
110        
111        if (!newType.isDuration())
112          throw new IllegalArgumentException("newType '" + newType +
113            "' must be a duration type.");
114        
115        durationType = newType;
116      }
117      
118      /**
119       * Returns the type of duration used by this setting.
120       * This method helps clients interpret the value returned by
121       * {@link #getTimeAtSetting()}.
122       * <p>
123       * The returned type will be non-null and its <tt>isDuration()</tt> method
124       * will always return <i>true</i>.</p>
125       * 
126       * @return
127       *   the type of duration used by this setting.
128       */
129      public ScanTimeType getDurationType()
130      {
131        return durationType;
132      }
133      
134      //============================================================================
135      // CONVENIENCE METHODS
136      //============================================================================
137      
138      /**
139       * A convenience method for setting the time spent at this setting.
140       * This setting will <i>not</i> hold a reference to <tt>newSpec</tt>.
141       * Instead, this method will use its
142       * {@link ScanTimeSpecification#getTimeType() getTimeType()} and 
143       * {@link ScanTimeSpecification#getDuration() getDuration()} methods
144       * to set the time at setting and duration type of this object. 
145       * 
146       * @param newSpec
147       *   the provider of the time at setting and duration type values for
148       *   this setting.  A value of <i>null</i> will result in a
149       *   {@code NullPointerException}.
150       * 
151       * @throws IllegalArgumentException
152       *   if the time type held by {@code newSpec} is not a duration type.
153       */
154      @XmlTransient
155      public void setTimeSpec(ScanTimeSpecification newSpec)
156      {
157        setDurationType(newSpec.getTimeType());
158        setTimeAtSetting(newSpec.getDuration());
159      }
160      
161      /**
162       * A convenience method for fetching the time spent at this setting.
163       * The returned object is guaranteed to be non-null.
164       * It is not referenced internally by this setting, so any changes made
165       * to it by clients will not affect this object.
166       * 
167       * @return
168       *   the time at setting and duration type of this offset.
169       */
170      public ScanTimeSpecification getTimeSpec()
171      {
172        ScanTimeSpecification spec = new ScanTimeSpecification();
173        
174        spec.set(durationType, timeAtSetting);
175        
176        return spec;
177      }
178    
179      //============================================================================
180      // 
181      //============================================================================
182    
183      /**
184       * Returns a text representation of this delay setting.
185       * The returned string is of the form <i>nanoseconds,timeAtSetting</i>.
186       */
187      @Override
188      public String toString()
189      {
190        StringBuilder buff = new StringBuilder();
191        
192        buff.append(nanoseconds);
193        buff.append(',').append(timeAtSetting);
194        buff.append(',').append(durationType);
195    
196        return buff.toString();
197      }
198      
199      /**
200       * Compares this setting with {@code other} for order.
201       * <p>
202       * If this setting has a smaller delay than the other's, a negative
203       * value is returned; if larger, a positive value is returned.  If both
204       * settings have the delay, a negative value is returned if this
205       * setting has a smaller time duration than the other.  The duration type
206       * is not used for figuring out which time duration is larger or smaller.
207       * If the durations are equal in magnitude, though, a comparison of
208       * durations will be used to break the tie, but only in order to stay
209       * consistent with equals().</p>
210       */
211      public int compareTo(DelaySetting other)
212      {
213        int result = (int)Math.signum(this.nanoseconds - other.nanoseconds);
214        if (result != 0)
215          return result;
216        
217        result = this.timeAtSetting.compareTo(other.timeAtSetting);
218        if (result != 0)
219          return result;
220        
221        return this.durationType.compareTo(other.durationType);
222      }
223    
224      /**
225       *  Returns a setting that is a copy of this one.
226       *  <p>
227       *  If anything goes wrong during the cloning procedure,
228       *  a {@code RuntimeException} will be thrown.</p>
229       */
230      @Override
231      public DelaySetting clone()
232      {
233        DelaySetting clone = null;
234    
235        try
236        {
237          //This line takes care of primitive & immutable fields properly
238          clone = (DelaySetting)super.clone();
239          
240          clone.timeAtSetting = this.timeAtSetting.clone();
241        }
242        catch (Exception ex)
243        {
244          throw new RuntimeException(ex);
245        }
246        
247        return clone;
248      }
249      
250      /** Returns <i>true</i> if {@code o} is equal to this setting. */
251      @Override
252      public boolean equals(Object o)
253      {
254        //Quick exit if o is null
255        if (o == null)
256          return false;
257        
258        //Quick exit if o is this object
259        if (o == this)
260          return true;
261        
262        //Quick exit if classes are different
263        if (!o.getClass().equals(this.getClass()))
264          return false;
265        
266        //A safe cast if we got this far
267        DelaySetting other = (DelaySetting)o;
268        
269        //Compare attributes
270        return
271          this.nanoseconds == other.nanoseconds &&
272          this.durationType.equals(other.durationType) &&
273          this.timeAtSetting.equals(other.timeAtSetting);
274      }
275    
276      /** Returns a hash code value for this setting. */
277      @Override
278      public int hashCode()
279      {
280        //Taken from the Effective Java book by Joshua Bloch.
281        //The constants 17 & 37 are arbitrary & carry no meaning.
282        int result = 17;
283        
284        result = 37 * result + new Double(nanoseconds).hashCode();
285        result = 37 * result + durationType.hashCode();
286        result = 37 * result + timeAtSetting.hashCode();
287        
288        return result;
289      }
290    }