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