001    package edu.nrao.sss.model.project.scan;
002    
003    import java.io.FileNotFoundException;
004    import java.io.Reader;
005    import java.util.ArrayList;
006    import java.util.List;
007    import java.util.SortedSet;
008    
009    import javax.xml.bind.JAXBException;
010    import javax.xml.bind.annotation.XmlElement;
011    import javax.xml.bind.annotation.XmlElementWrapper;
012    import javax.xml.bind.annotation.XmlRootElement;
013    import javax.xml.stream.XMLStreamException;
014    
015    import edu.nrao.sss.astronomy.CelestialCoordinateSystem;
016    import edu.nrao.sss.measure.TimeDuration;
017    import edu.nrao.sss.util.JaxbUtility;
018    
019    /**
020     * A scan that holds a list of {@link PointingPosition pointing positions}.
021     * <p>
022     * <b>CVS Info:</b>
023     * <table style="margin-left:2em">
024     *   <tr><td>$Revision: 161 $</td></tr>
025     *   <tr><td>$Date: 2006-12-15 11:48:34 -0700 (Fri, 15 Dec 2006) $</td></tr>
026     *   <tr><td>$Author: btruitt $</td></tr>
027     * </table></p>
028     *  
029     * @author David M. Harland
030     * @since 2006-07-10
031     */
032    @XmlRootElement
033    public class PointingScan
034      extends Scan
035    {
036      private List<PointingPosition>    positions;
037      private PositionType              positionType;
038      private CelestialCoordinateSystem coordinateSystem;
039      
040      /** Creates a new instance. */
041      PointingScan()
042      {
043        super();
044        
045        positions        = new ArrayList<PointingPosition>();
046        positionType     = PositionType.OFFSET;
047        coordinateSystem = CelestialCoordinateSystem.getDefault();
048      }
049      
050      /**
051       * Replaces this scan's list of positions with a list based on the given
052       * pattern.
053       * If the name is that of a standard pattern, this scan will be updated
054       * with pointing positions that emulate the pattern.  Each position will
055       * have its duration set to {@code timeAtPosition}.  If the name is
056       * not recognized this scan will contain an empty list of positions.
057       * <p>
058       * <b>Side Effects:</b>
059       * <ol>
060       *   <li>This scan's celestial coordinate system will be
061       *       updated to match that of the standard pattern.  If the given name
062       *       was not a standard pattern, the default coordinate system will be
063       *       used.</li>
064       *   <li>This scan's position type will be set to OFFSET.</li>
065       * </ol></p>
066       * 
067       * @param nameOfStandardPattern the name of a standard NRAO pointing pattern.
068       * 
069       * @param timeAtPosition the amount of time to spend at each position in the
070       *                       pattern.  A <i>null</i> value will be treated as a
071       *                       duration of zero length.
072       * 
073       * @see #getNamesOfStandardPatterns()
074       */
075      public void setPositions(String       nameOfStandardPattern,
076                               TimeDuration timeAtPosition)
077      {
078        PointingPattern pp = PointingPattern.getPattern(nameOfStandardPattern);
079        
080        if (pp != null)
081        {
082          //TODO get multiplier from Resource
083          setPositions(pp.makeOffsets(timeAtPosition, 1.0));
084          setCoordinateSystem(pp.getCoordinateSystem());
085        }
086        else
087        {
088          setPositions(null);
089          setCoordinateSystem(CelestialCoordinateSystem.getDefault());
090        }
091        
092        setPositionType(PositionType.OFFSET);
093      }
094      
095      /**
096       * Sets the list of pointing positions held by this scan.
097       * A <i>null</i> {@code replacementList} will be interpreted
098       * as a new empty list.
099       * <p>
100       * This scan will hold a reference to {@code replacementList}
101       * (unless it is <i>null</i>), so any changes made to the list
102       * after calling this method will be reflected in this object.</p>
103       * 
104       * @param replacementList a list of pointing positions to be held by this
105       *                        scan.
106       */
107      public void setPositions(List<PointingPosition> replacementList)
108      {
109        positions = (replacementList == null) ? new ArrayList<PointingPosition>()
110                                              : replacementList;
111      }
112      
113      /**
114       * Returns the list of pointing positions held by this scan.
115       * The returned list is guaranteed to be non-null, but may
116       * be empty.
117       * <p>
118       * The returned list is the actual list held by this scan,
119       * so changes made to the list will be reflected in this
120       * object.</p>
121       * 
122       * @return the list of pointing positions held by this scan.
123       */
124      @XmlElementWrapper
125      @XmlElement(name="pointingPosition")
126      public List<PointingPosition> getPositions()
127      {
128        return positions;
129      }
130    
131      /**
132       * Sets the type of positions contained by this scan.
133       * The position type will be used for interpreting the contained positions
134       * as either absolute positions, or as positions that are offsets from this
135       * scan's central position.
136       * <p>
137       * If {@code posType} is <i>null</i>, it will be iterpreted as
138       * {@code PositionType.OFFSET}.</p>
139       * 
140       * @param posType the type of positions contained by this scan.
141       */
142      public void setPositionType(PositionType posType)
143      {
144        positionType = (posType == null) ? PositionType.OFFSET : posType;
145      }
146    
147      /**
148       * Returns the type of positions contained by this scan.
149       * See {@link #setPositionType(PositionType)} for details.
150       * 
151       * @return the type of positions contained by this scan.
152       */
153      public PositionType getPositionType()
154      {
155        return positionType;
156      }
157    
158      /**
159       * Sets the coordinate system for this scan.  The coordinate system
160       * will be used for interpreting latitudinal and longitudinal values
161       * in the position elements.
162       * <p>
163       * If {@code system} is <i>null</i>, it will be iterpreted as
164       * the default coordinate system.</p>
165       * 
166       * @param system the coordinate system for this scan.
167       */
168      public void setCoordinateSystem(CelestialCoordinateSystem system)
169      {
170        coordinateSystem =
171          (system == null) ? CelestialCoordinateSystem.getDefault() : system;
172      }
173    
174      /**
175       * Returns the coordinate system for this scan.
176       * See {@link #setCoordinateSystem(CelestialCoordinateSystem)} for
177       * details.
178       * 
179       * @return the coordinate system for this scan.
180       */
181      public CelestialCoordinateSystem getCoordinateSystem()
182      {
183        return coordinateSystem;
184      }
185    
186      //============================================================================
187      // TEXT
188      //============================================================================
189    
190      /* (non-Javadoc)
191       * @see edu.nrao.sss.model.project.scan.ScanLoopElement#toString()
192       */
193      public String toSummaryString()
194      {
195        StringBuilder buff = new StringBuilder(super.toSummaryString());
196        
197        buff.append(", posType=").append(positionType);
198        buff.append(", coordSys=").append(coordinateSystem);
199        buff.append(", posCnt=").append(positions.size());
200        
201        return buff.toString();
202      }
203      
204      /**
205       * Creates a new scan from the XML data in the given file.
206       * 
207       * @param xmlFile the name of an XML file.  This method will attempt to locate
208       *                the file by using {@link Class#getResource(String)}.
209       *                
210       * @return a new scan from the XML data in the given file.
211       * 
212       * @throws FileNotFoundException if the XML file cannot be found.
213       * 
214       * @throws JAXBException if the schema file used (if any) is malformed, if
215       *           the XML file cannot be read, or if the XML file is not
216       *           schema-valid.
217       * 
218       * @throws XMLStreamException if there is a problem opening the XML file,
219       *           if the XML is not well-formed, or for some other
220       *           "unexpected processing conditions".
221       */
222      public static PointingScan fromXml(String xmlFile)
223        throws JAXBException, XMLStreamException, FileNotFoundException
224      {
225        return JaxbUtility.getSharedInstance()
226                          .xmlFileToObject(xmlFile, PointingScan.class);
227      }
228      
229      /**
230       * Creates a new scan based on the XML data read from {@code reader}.
231       * 
232       * @param reader the source of the XML data.
233       *               If this value is <i>null</i>, <i>null</i> is returned.
234       *               
235       * @return a new scan based on the XML data read from {@code reader}.
236       * 
237       * @throws XMLStreamException if the XML is not well-formed,
238       *           or for some other "unexpected processing conditions".
239       *           
240       * @throws JAXBException if anything else goes wrong during the
241       *           transformation.
242       */
243      public static PointingScan fromXml(Reader reader)
244        throws JAXBException, XMLStreamException
245      {
246        return JaxbUtility.getSharedInstance()
247                          .readObjectAsXmlFrom(reader, PointingScan.class, null);
248      }
249    
250      //============================================================================
251      // 
252      //============================================================================
253    
254      /**
255       *  Returns a pointing scan that is a copy of this one.
256       *  <p>
257       *  The returned scan is, for the most part, a deep copy of this one.
258       *  However, there are a few exceptions noted in the
259       *  {@link ScanLoop#clone() clone method} of this class's parent.</p>
260       *  <p>
261       *  If anything goes wrong during the cloning procedure,
262       *  a {@code RuntimeException} will be thrown.</p>
263       */
264      public PointingScan clone()
265      {
266        PointingScan clone = null;
267    
268        try
269        {
270          //This line takes care of the primitive & immutable fields properly
271          clone = (PointingScan)super.clone();
272          
273          //Need to clone set AND contained elements.
274          clone.positions = new ArrayList<PointingPosition>();
275          for (PointingPosition pp : this.positions)
276            clone.positions.add(pp.clone());
277        }
278        catch (Exception ex)
279        {
280          throw new RuntimeException(ex);
281        }
282        
283        return clone;
284      }
285    
286      /**
287       * Returns <i>true</i> if {@code o} is equal to this pointing scan.
288       * <p>
289       * In order for {@code o} to be equal to this scan, it must have
290       * equal pointing positions in the same order as those of this scan.
291       * It must also follow the rules set forth in the
292       * {@link ScanLoop#equals(Object) equals method} of this class's parent.</p>
293       */
294      public boolean equals(Object o)
295      {
296        //Quick exit if o is this object
297        if (o == this)
298          return true;
299        
300        //Not equal if super class says not equal
301        if (!super.equals(o))
302          return false;
303        
304        //Super class tested for Class equality, so cast is safe
305        PointingScan other = (PointingScan)o;
306        
307        //Compare attributes
308        return other.positionType.equals(this.positionType) &&
309               other.coordinateSystem.equals(this.coordinateSystem) &&
310               other.positions.equals(this.positions);
311      }
312    
313      /* (non-Javadoc)
314       * @see edu.nrao.sss.model.project.scan.Scan#hashCode()
315       */
316      public int hashCode()
317      {
318        //Taken from the Effective Java book by Joshua Bloch.
319        //The constant 37 is arbitrary & carries no meaning.
320        int result = 37 * super.hashCode();
321        
322        result = 37 * result + positionType.hashCode();
323        result = 37 * result + coordinateSystem.hashCode();
324        result = 37 * result + positions.hashCode();
325        
326        return result;
327      }
328    
329      //============================================================================
330      // CLASS METHODS
331      //============================================================================
332    
333      /**
334       * Returns the names of NRAO's standard pointing patterns.
335       * @return the names of NRAO's standard pointing patterns.
336       */
337      public static SortedSet<String> getNamesOfStandardPatterns()
338      {
339        return PointingPattern.getPatternNames();
340      }
341      
342      /**
343       * Creates a new pointing scan based on the given pattern name.
344       * If the name is that of a standard pattern, the returned scan will
345       * contain pointing positions that emulate the pattern.  Each position will
346       * have its duration set to {@code timeAtPosition}.  If the name is
347       * not recognized the returned scan will contain no positions.
348       * 
349       * @param scanMode  the observation mode for which a scan is desired.
350       * 
351       * @param nameOfStandardPattern the name of a standard NRAO pointing pattern.
352       * 
353       * @param timeAtPosition the amount of time to spend at each position in the
354       *                     pattern.  A <i>null</i> value will be treated as a
355       *                     duration of zero length.
356       *                     
357       * @return a new pointing scan.
358       * 
359       * @see #getNamesOfStandardPatterns()
360       */
361      public static PointingScan create(ScanMode     scanMode,
362                                        String       nameOfStandardPattern,
363                                        TimeDuration timeAtPosition)
364      {
365        PointingScan result = null;
366        
367        try
368        {
369          result = (PointingScan)Scan.createFor(scanMode);
370        }
371        catch (ClassCastException ex)
372        {
373          throw new IllegalArgumentException(
374            "Cannot make pointing scan for mode " + scanMode +
375            "; that mode gives class " + result.getClass().getSimpleName());
376        }
377        
378        result.setPositions(nameOfStandardPattern, timeAtPosition);
379        
380        return result;
381      }
382    
383      //============================================================================
384      // 
385      //============================================================================
386      /*
387      public static void main(String[] args)
388      {
389        System.out.println(PointingScan.getNamesOfStandardPatterns());
390        PointingScan scan = PointingScan.create(ScanMode.INTERFEROMETRIC_POINTING,
391                                                "Craig Cross", new TimeDuration(0.12));
392        System.out.println(scan);
393        scan.setPositions("bogus", null);
394        System.out.println(scan);
395        scan.setPositions("Standard 5-Point", null);
396        System.out.println(scan);
397      }
398      */
399    }