001    package edu.nrao.sss.model.project.scan;
002    
003    import java.math.BigDecimal;
004    import java.math.RoundingMode;
005    import java.util.ArrayList;
006    import java.util.Collections;
007    import java.util.List;
008    
009    import javax.xml.bind.annotation.XmlElement;
010    import javax.xml.bind.annotation.XmlElementWrapper;
011    import javax.xml.bind.annotation.XmlRootElement;
012    
013    import edu.nrao.sss.measure.Distance;
014    import edu.nrao.sss.measure.DistanceUnits;
015    import edu.nrao.sss.measure.TimeDuration;
016    
017    /**
018     * A scan that holds a list of {@link FocusOffset focus offsets}.
019     * <p>
020     * <b>Version Info:</b>
021     * <table style="margin-left:2em">
022     *   <tr><td>$Revision: 1314 $</td></tr>
023     *   <tr><td>$Date: 2008-05-30 11:31:14 -0600 (Fri, 30 May 2008) $</td></tr>
024     *   <tr><td>$Author: dharland $</td></tr>
025     * </table></p>
026     *  
027     * @author David M. Harland
028     * @since 2006-07-10
029     */
030    @XmlRootElement
031    public class FocusScan
032      extends Scan
033    {
034      private List<FocusOffset> offsets;
035    
036      /** Creates a new instance. */
037      FocusScan()
038      {
039        super();
040        
041        offsets = new ArrayList<FocusOffset>();
042      }
043      
044      /**
045       * Sets the list of focus offsets held by this scan.
046       * A <i>null</i> {@code replacementList} will be interpreted
047       * as a new empty list.
048       * <p>
049       * This scan will hold a reference to {@code replacementList}
050       * (unless it is <i>null</i>), so any changes made to the list
051       * after calling this method will be reflected in this object.</p>
052       * 
053       * @param replacementList a list of focus offsets to be held by this
054       *                        scan.
055       */
056      public void setOffsets(List<FocusOffset> replacementList)
057      {
058        offsets = (replacementList == null) ? new ArrayList<FocusOffset>()
059                                            : replacementList;
060      }
061      
062      /**
063       * Returns the list of focus offsets held by this scan.
064       * The returned list is guaranteed to be non-null, but may
065       * be empty.
066       * <p>
067       * The returned list is the actual list held by this scan,
068       * so changes made to the list will be reflected in this
069       * object.</p>
070       * 
071       * @return the list of focus offsets held by this scan.
072       */
073      @XmlElementWrapper
074      @XmlElement(name="focusOffset")
075      public List<FocusOffset> getOffsets()
076      {
077        return offsets;
078      }
079    
080      /**
081       * Sorts the internal list of offsets and returns it.
082       * <p>
083       * The returned list is the actual list held by this scan,
084       * so changes made to the list will be reflected in this
085       * object.</p>
086       * 
087       * @return the list of focus offsets held by this scan after, they have
088       *         been sorted.
089       */
090      public List<FocusOffset> sortOffsets()
091      {
092        Collections.sort(offsets);
093        return offsets;
094      }
095    
096      /**
097       * Adds a series of offsets to this scan's list.
098       * The number of new offsets added is equal to
099       * <tt>1 + 2 * numberOfPositiveOffsets</tt>.
100       * If we call the <tt>numberOfPositiveOffsets</tt> "<tt>n</tt>"
101       * and the <tt>incrementalOffset</tt> "<tt>d</tt>", offsets
102       * of the following lengths will be added to this scan:
103       * <p>
104       * <tt>&nbsp;&nbsp;-nd,...,-2d,-d,0,+d,+2d,...,+nd</tt>.</p>
105       * 
106       * @param numberOfPositiveOffsets
107       *   the number of positive offsets to be added.  The total number of offsets
108       *   added is equal to one plus two times this amount.  An {@code
109       *   IllegalArgumentException} will be thrown if this value is negative.
110       *   
111       * @param incrementalOffset
112       *   the spacing between the offsets added by this method.  This value is
113       *   typically in millimeters.  An {@code IllegalArgumentException} will be
114       *   thrown if this value less than or equal to zero.
115       *   
116       * @param timeAtOffset
117       *   the amount of time the focus should be placed at this offset.
118       *   
119       * @throws IllegalArgumentException
120       *   if any of the parameters violate the rules listed above.
121       */
122      public void addOffsets(int          numberOfPositiveOffsets,
123                             Distance     incrementalOffset,
124                             TimeDuration timeAtOffset)
125      {
126        validateParameters(numberOfPositiveOffsets,incrementalOffset,timeAtOffset);
127        
128        DistanceUnits units     = incrementalOffset.getUnits();
129        BigDecimal    increment = incrementalOffset.getValue();
130        BigDecimal    length    = increment.multiply(new BigDecimal(-numberOfPositiveOffsets));
131    
132        int offsetCount = 1 + 2 * numberOfPositiveOffsets;
133    
134        for (int i=1; i <= offsetCount; i++)
135        {
136          FocusOffset offset = new FocusOffset();
137          
138          offset.setOffsetLength(new Distance(length, units));
139          offset.getTimeAtOffset().set(timeAtOffset);
140          
141          offsets.add(offset);
142    
143          length = length.add(increment);
144        }
145      }
146      
147      /**
148       * Adds a series of offsets to this scan's list.
149       * The number of new offsets added is {@code numberOfOffsets}.
150       * The smallest new offset is {@code minimumOffset}, the largest is
151       * {@code maximumOffset}.  The other offsets are spread evenly between
152       * these two endpoints.
153       *  
154       * @param minimumOffset
155       *   the smallest focus offset.  This value is often negative.
156       *   
157       * @param maximumOffset
158       *   the largest focus offset.  An {@code IllegalArgumentException} will be
159       *   thrown if this value is less than or equal to the {@code minimumOffset}.
160       *   
161       * @param numberOfOffsets
162       *    the number of new offsets to be added.
163       *    Special cases:<ul>
164       *      <li>numberOfOffsets < 1: No new offsets will be added.</li>
165       *      <li>numberOfOffsets == 1: Only the {@code minimumOffset}
166       *                                will be added.</li>
167       *      <li>numberOfOffsets == 2: Only the {@code minimumOffset}
168       *                                and {@code maximumOffset}
169       *                                will be added.</li>
170       *    </ul>
171       *   
172       * @param timeAtOffset
173       *   the amount of time the focus should be placed at this offset.
174       *   
175       * @throws IllegalArgumentException
176       *   if any of the parameters violate the rules listed above.
177       */
178      public void addOffsets(Distance minimumOffset, Distance maximumOffset,
179                             int numberOfOffsets, TimeDuration timeAtOffset)
180      {
181        validateParameters(minimumOffset,   maximumOffset,
182                           numberOfOffsets, timeAtOffset);
183        
184        //Special cases
185        if (numberOfOffsets < 1)              //No action
186        {
187          return;
188        }
189        else if (numberOfOffsets <= 2)        //Add only min (& possibly max) offset
190        {
191          FocusOffset lowestOffset = new FocusOffset();
192          lowestOffset.setOffsetLength(minimumOffset);
193          lowestOffset.setTimeAtOffset(timeAtOffset);
194          offsets.add(lowestOffset);
195          
196          if (numberOfOffsets == 2)           //Add only min & max offsets
197          {
198            FocusOffset highestOffset = new FocusOffset();
199            highestOffset.setOffsetLength(maximumOffset);
200            highestOffset.setTimeAtOffset(timeAtOffset);
201            offsets.add(highestOffset);
202          }
203        }
204        //Normal logic
205        else
206        {
207          DistanceUnits units = maximumOffset.getUnits();
208          
209          BigDecimal length = minimumOffset.toUnits(units);
210          
211          BigDecimal increment =
212            maximumOffset.getValue().subtract(length)
213                                    .divide(new BigDecimal(numberOfOffsets - 1),
214                                                           RoundingMode.HALF_UP);
215          for (int i=1; i <= numberOfOffsets; i++)
216          {
217            FocusOffset offset = new FocusOffset();
218            
219            offset.getTimeAtOffset().set(timeAtOffset);
220            offset.setOffsetLength(new Distance(length, units));
221            
222            offsets.add(offset);
223            
224            length = length.add(increment);
225          }
226        }
227      }
228      
229      /** Helps addOffsets(...). */
230      private void validateParameters(int          numberOfPositiveOffsets,
231                                      Distance     incrementalOffset,
232                                      TimeDuration timeAtOffset)
233      {
234        final String errMsgStart = "Illegal value for ";
235        
236        if (numberOfPositiveOffsets < 0)
237        {
238          StringBuilder errMsg = new StringBuilder(errMsgStart);
239          errMsg.append("numberOfPositiveOffsets (")
240                .append(numberOfPositiveOffsets)
241                .append("): value may not be negative.");
242          throw new IllegalArgumentException(errMsg.toString());
243        }
244        
245        if (incrementalOffset.getValue().signum() <= 0)
246        {
247          StringBuilder errMsg = new StringBuilder(errMsgStart);
248          errMsg.append("incrementalOffset (")
249                .append(incrementalOffset)
250                .append("): value must be positive.");
251          throw new IllegalArgumentException(errMsg.toString());
252        }
253      }
254    
255      /** Helps addOffsets(...). */
256      private void validateParameters(Distance     minimumOffset,
257                                      Distance     maximumOffset,
258                                      int          numberOfOffsets,
259                                      TimeDuration timeAtOffset)
260      {
261        if (minimumOffset.compareTo(maximumOffset) >= 0)
262        {
263          StringBuilder errMsg = new StringBuilder("The minimum offset (");
264          errMsg.append(minimumOffset.toString())
265                .append(") must be less than the maximum offset (")
266                .append(maximumOffset.toString()).append(").");
267          throw new IllegalArgumentException(errMsg.toString());
268        }
269      }
270      
271      //============================================================================
272      // TEXT
273      //============================================================================
274    
275      /* (non-Javadoc)
276       * @see edu.nrao.sss.model.project.scan.ScanLoopElement#toString()
277       */
278      public String toSummaryString()
279      {
280        StringBuilder buff = new StringBuilder(super.toSummaryString());
281        
282        buff.append(", offsetCnt=").append(offsets.size());
283        
284        return buff.toString();
285      }
286    
287      //============================================================================
288      // 
289      //============================================================================
290    
291      /**
292       *  Returns a focus scan that is a copy of this one.
293       *  <p>
294       *  The returned scan is, for the most part, a deep copy of this one.
295       *  However, there are a few exceptions noted in the
296       *  {@link ScanLoop#clone() clone method} of this class's parent.</p>
297       *  <p>
298       *  If anything goes wrong during the cloning procedure,
299       *  a {@code RuntimeException} will be thrown.</p>
300       */
301      public FocusScan clone()
302      {
303        FocusScan clone = null;
304    
305        try
306        {
307          clone = (FocusScan)super.clone();
308          
309          //Need to clone set AND contained elements.
310          clone.offsets = new ArrayList<FocusOffset>();
311          for (FocusOffset fo : this.offsets)
312            clone.offsets.add(fo.clone());
313        }
314        catch (Exception ex)
315        {
316          throw new RuntimeException(ex);
317        }
318        
319        return clone;
320      }
321    
322      /**
323       * Returns <i>true</i> if {@code o} is equal to this focus scan.
324       * <p>
325       * In order for {@code o} to be equal to this scan, it must have
326       * equal focus offsets in the same order as those of this scan.
327       * It must also follow the rules set forth in the
328       * {@link ScanLoop#equals(Object) equals method} of this class's parent.</p>
329       */
330      public boolean equals(Object o)
331      {
332        //Quick exit if o is this object
333        if (o == this)
334          return true;
335        
336        //Not equal if super class says not equal
337        if (!super.equals(o))
338          return false;
339        
340        //Super class tested for Class equality, so cast is safe
341        FocusScan other = (FocusScan)o;
342        
343        //Compare attributes
344        return other.offsets.equals(this.offsets);
345      }
346    
347      /* (non-Javadoc)
348       * @see edu.nrao.sss.model.project.scan.Scan#hashCode()
349       */
350      public int hashCode()
351      {
352        //Taken from the Effective Java book by Joshua Bloch.
353        //The constant 37 is arbitrary & carries no meaning.
354        int result = 37 * super.hashCode();
355        
356        result = 37 * result + offsets.hashCode();
357        
358        return result;
359      }
360    
361      //============================================================================
362      // 
363      //============================================================================
364      /*
365      public static void main(String[] args) throws Exception
366      {
367        FocusScan scan1 = new FocusScan();
368        FocusScan scan2 = new FocusScan();
369        
370        scan1.addOffsets(5, new Distance(1.0, DistanceUnits.MILLIMETER),
371                            new TimeDuration(5.0, edu.nrao.sss.measure.TimeUnits.MINUTE));
372        
373        scan2.addOffsets(new Distance(-5.0, DistanceUnits.MILLIMETER),
374                         new Distance(+5.0, DistanceUnits.MILLIMETER),
375                         11,
376                         new TimeDuration(5.0, edu.nrao.sss.measure.TimeUnits.MINUTE));
377        
378        System.out.println("SCAN 1:");
379        for (FocusOffset offset : scan1.getOffsets())
380          System.out.println("  " + offset);
381        
382        System.out.println();
383        
384        System.out.println("SCAN 2:");
385        for (FocusOffset offset : scan2.getOffsets())
386          System.out.println("  " + offset);
387      }
388      */
389    }