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