001    package edu.nrao.sss.model.project.scan;
002    
003    import java.io.Writer;
004    import java.util.Date;
005    
006    import javax.xml.bind.JAXBException;
007    import javax.xml.bind.annotation.XmlAttribute;
008    import javax.xml.bind.annotation.XmlTransient;
009    import javax.xml.bind.annotation.XmlType;
010    
011    import edu.nrao.sss.model.UserAccountable;
012    import edu.nrao.sss.model.project.ProgramBlock;
013    import edu.nrao.sss.model.project.Project;
014    import edu.nrao.sss.model.project.SchedulingBlock;
015    import edu.nrao.sss.util.Identifiable;
016    import edu.nrao.sss.util.JaxbUtility;
017    
018    /**
019     * The abstract parent of both {@link Scan} and {@link ScanLoop}.
020     * <p>
021     * Note: This class was originally an interface but became an abstract
022     * class due to some JAXB restrictions on marshalling and unmarshalling
023     * to/from XML for interface classes.  I was successful with this
024     * class as an interface while going to XML, but not coming from XML.</p>
025     * <p>
026     * <b>Version Info:</b>
027     * <table style="margin-left:2em">
028     *   <tr><td>$Revision: 1911 $</td></tr>
029     *   <tr><td>$Date: 2009-01-21 10:27:48 -0700 (Wed, 21 Jan 2009) $</td></tr>
030     *   <tr><td>$Author: dharland $</td></tr>
031     * </table></p>
032     *  
033     * @author David M. Harland
034     * @since 2006-07-31
035     */
036    @XmlType(propOrder= {"name",
037                         "createdBy","createdOn","lastUpdatedBy","lastUpdatedOn",
038                         "comments"})
039    public abstract class ScanLoopElement
040      implements Cloneable, Identifiable, UserAccountable
041    {
042      private static final String NO_COMMENTS = "";
043    
044      //IDENTIFICATION
045      private Long   id;  //A unique identifier for the persistence layer.
046      private String name;
047    
048      //USER TRACKING
049      private Long createdBy;      //This is a user ID
050      private Date createdOn;
051      private Long lastUpdatedBy;  //This is a user ID
052      private Date lastUpdatedOn;
053    
054      //CONTAINER (SchedulingBlock OR ExecutionBlock)
055      private SchedulingBlock schedulingBlock;
056    
057      //OTHER
058      private String comments;
059    
060      /** Helps create a new instance. */
061      ScanLoopElement()
062      {
063        initialize();
064    
065        //Objects that may never be null
066        createdOn     = new Date();
067        lastUpdatedOn = new Date();
068      }
069      
070      /** Initializes the instance variables of this class.  */
071      private void initialize()
072      {
073        id              = Identifiable.UNIDENTIFIED;
074        name            = getDefaultName();
075        createdBy       = UserAccountable.NULL_USER_ID;
076        lastUpdatedBy   = UserAccountable.NULL_USER_ID;
077        schedulingBlock = null;
078        comments        = NO_COMMENTS;
079      }
080    
081      /**
082       *  Resets this element to its initial state.
083       *  A reset scan loop element has the same state as a new element. 
084       */
085      public void reset()
086      {
087        initialize();
088      }
089    
090      //============================================================================
091      // IDENTIFICATION
092      //============================================================================
093    
094      /**
095       * Do not use.
096       * This method is here for the persistence mechanism.
097       * Other clients should not set object identifiers.
098       */
099      public void setId(Long id)
100      {
101        this.id = id;
102      }
103    
104      /* (non-Javadoc)
105       * @see edu.nrao.sss.util.Identifiable#getId()
106       */
107      @XmlAttribute
108      public Long getId()
109      {
110        return id;
111      }
112    
113      /**
114       * Resets this scan loop element's id to UNIDENTIFIED.
115       */
116      public void clearId()
117      {
118        id = Identifiable.UNIDENTIFIED;
119      }
120    
121      /**
122       * Sets the name of this element.
123       * <p>
124       * If {@code newName} is <i>null</i> or the empty string
125       * (<tt>""</tt>), the request to change the name will be
126       * denied and the current name will remain in place.</p>
127       * 
128       * @param newName the new name of this element.
129       */
130      public void setName(String newName)
131      {
132        if (newName != name && newName.length() > 0)
133          name = newName;
134      }
135    
136      /** Returns a default name for a scan loop element. */
137      abstract String getDefaultName();
138      
139      /**
140       * Returns the name of this element.
141       * @return the name of this element.
142       */
143      public String getName()
144      {
145        return name;
146      }
147    
148      //============================================================================
149      // CONTAINER (SchedulingBlock)
150      //============================================================================
151    
152      /**
153       * Sets the scheduling block to which this scan loop element belongs.
154       * <p>
155       * This method does <i>not</i> communicate back to {@code newSchedBlock}
156       * about its new scan loop element.  This is because the scheduling block does
157       * not hold these elements directly, except for its single
158       * {@code scanSequence}, which is of type {@code ScanLoop}.
159       * This means that the model is not enforcing integrity in the
160       * container / contained relationship that exists between
161       * {@code SchedulingBlock} and {@code ScanLoopElement}.</p>
162       * <p>
163       * Passing this method a {@code schedulingBlock} of <i>null</i> has the
164       * effect of disconnecting this element from any scheduling block.</p>
165       * 
166       * @param newSchedBlock the scheduling block to which this element belongs.
167       */
168      public void setSchedulingBlock(SchedulingBlock newSchedBlock)
169      {
170        this.schedulingBlock = newSchedBlock;
171      }
172    
173      /**
174       * Returns the scheduling block to which this scan loop element belongs,
175       * if any.  If this element belongs to
176       * no scheduling block, the value returned is <i>null</i>.</p>
177       * 
178       * @return the scheduling block that contains this scan loop element.
179       */
180      @XmlTransient
181      public SchedulingBlock getSchedulingBlock()
182      {
183        return schedulingBlock;
184      }
185      
186      /**
187       * Returns <i>true</i> if this scan loop element is connected, either directly
188       * or indirectly, to a non-null scheduling block.
189       * <p>
190       * Scans should normally be contained directly within either a 
191       * scheduling block or an execution block.  The execution block,
192       * in turn, is normally contained within a scheduling block.  Therefore,
193       * a scan loop element normally has a link to a scheduling block.
194       * However, there are some situations
195       * where this method will return <i>false</i>:
196       * <ol>
197       *   <li>This element has just been created and its
198       *       scheduling block has not yet been set.</li>
199       *   <li>A client removed this element from its scheduling block and 
200       *       did not place it in a new scheduling block.</li>
201       *   <li>A client explicitly set this element's scheduling block to
202       *       <i>null</i>.</li>
203       * </ol></p>
204       * 
205       * @return <i>true</i> if this scan has a non-null scheduling block.
206       *         Therefore a return value of <i>true</i> means that 
207       *         you can call {@link #getSchedulingBlock()} and know that
208       *         it will return a non-null object. 
209       */
210      public boolean hasSchedulingBlock()
211      {
212        return (schedulingBlock != null);
213      }
214    
215      /**
216       * Returns the program block to which this scan belongs.
217       * If this scan is not currently contained by a program block,
218       * the returned value will be <i>null</i>.
219       * 
220       * @return the program block that contains this scan or <i>null</i>.
221       */
222      public ProgramBlock getProgramBlock()
223      {
224        return hasSchedulingBlock() ? schedulingBlock.getProgramBlock() : null;
225      }
226    
227      /**
228       * Returns the project to which this scan belongs.
229       * If this scan is not currently contained by a project,
230       * the returned value will be <i>null</i>.
231       * 
232       * @return the project that contains this scan or <i>null</i>.
233       */
234      public Project getProject()
235      {
236        return hasSchedulingBlock() ? schedulingBlock.getProject() : null;
237      }
238    
239      //============================================================================
240      // COMMENTS
241      //============================================================================
242    
243      /**
244       * Sets comments about this element.
245       * 
246       * @param replacementComments
247       *   free-form text about this element.
248       *   These comments replace all previously set comments.
249       *   A <i>null</i> value will be replaced by the empty string (<tt>""</tt>).
250       * 
251       * @see #appendComments(String)
252       */
253      public void setComments(String replacementComments)
254      {
255        comments = (replacementComments == null) ? NO_COMMENTS : replacementComments;
256      }
257      
258      /**
259       * Adds additional comments to those already associated with this element.
260       * 
261       * @param additionalComments
262       *   new, additional, comments about this element.
263       *   
264       * @see #setComments(String)
265       */
266      public void appendComments(String additionalComments)
267      {
268        if ((additionalComments != null) && (additionalComments.length() > 0))
269        {
270          if (!comments.equals(NO_COMMENTS))
271            comments = comments + System.getProperty("line.separator");
272          
273          comments = comments + additionalComments;
274        }
275      }
276    
277      /**
278       * Returns comments about this element.
279       * The value returned is guaranteed to be non-null.
280       * 
281       * @return
282       *   free-form text about this element.
283       *   
284       * @see #appendComments(String)
285       * @see #setComments(String)
286       */
287      public String getComments()
288      {
289        return comments;
290      }
291    
292      //============================================================================
293      // INTERFACE UserAccountable
294      //============================================================================
295      
296      public void setCreatedBy(Long userId)
297      {
298        createdBy = (userId == null) ? UserAccountable.NULL_USER_ID : userId;
299      }
300      
301      public void setCreatedOn(Date d)
302      {
303        if (d != null)
304          createdOn = d;
305      }
306    
307      public void setLastUpdatedBy(Long userId)
308      {
309        lastUpdatedBy = (userId == null) ? UserAccountable.NULL_USER_ID : userId;
310      }
311    
312      public void setLastUpdatedOn(Date d)
313      {
314        if (d != null)
315          lastUpdatedOn = d;
316      }
317    
318      public Long getCreatedBy()      { return createdBy;     }
319      public Date getCreatedOn()      { return createdOn;     }
320      public Long getLastUpdatedBy()  { return lastUpdatedBy; }
321      public Date getLastUpdatedOn()  { return lastUpdatedOn; }
322    
323      //============================================================================
324      // TEXT
325      //============================================================================
326      
327      /**
328       * Returns a text representation of this scan loop element.
329       * The default form of the text is XML.  However, if anything goes wrong
330       * during the conversion to XML, an alternate, and much abbreviated, form
331       * will be returned.
332       * 
333       * @return a text representation of this scan.
334       * 
335       * @see #toSummaryString()
336       */
337      public String toString()
338      {
339        try {
340          return toXml();
341        }
342        catch (Exception ex) {
343          return toSummaryString();
344        }
345      }
346      
347      /**
348       * Returns a short textual description of this scan loop element.
349       * @return a short textual description of this scan loop element.
350       */
351      public abstract String toSummaryString();
352    
353      /**
354       * Returns an XML representation of this scan loop element.
355       * @return an XML representation of this scan loop element.
356       * @throws JAXBException if anything goes wrong during the conversion to XML.
357       * @see #writeAsXmlTo(Writer)
358       */
359      public String toXml() throws JAXBException
360      {
361        return JaxbUtility.getSharedInstance().objectToXmlString(this);
362      }
363      
364      /**
365       * Writes an XML representation of this scan loop element to {@code writer}.
366       * @param writer the device to which XML is written.
367       * @throws JAXBException if anything goes wrong during the conversion to XML.
368       */
369      public void writeAsXmlTo(Writer writer) throws JAXBException
370      {
371        JaxbUtility.getSharedInstance().writeObjectAsXmlTo(writer, this, null);
372      }
373    
374      //============================================================================
375      // 
376      //============================================================================
377    
378      /**
379       *  Returns a scan loop element that is a copy of this one.
380       *  <p>
381       *  The returned element is, for the most part, a deep copy of this one.
382       *  However, there are a few exceptions:
383       *  <ol>
384       *    <li>The ID will be set to
385       *        {@link Identifiable#UNIDENTIFIED}.</li>
386       *    <li>The schedulingBlock will be <i>null</i>.</li>
387       *    <li>The createdOn and lastUpdatedOn attributes will be set to the
388       *        current system time.</li>
389       *  </ol></p>
390       *  <p>
391       *  If anything goes wrong during the cloning procedure,
392       *  a {@code RuntimeException} will be thrown.</p>
393       */
394      @Override
395      public ScanLoopElement clone()
396      {
397        ScanLoopElement clone;
398        
399        try
400        {
401          clone = (ScanLoopElement)super.clone();
402          
403          //We do NOT want the clone to have the same ID as the original.
404          //The ID is here for the persistence layer; it is in charge of
405          //setting IDs.  To help it, we put the clone's ID in the uninitialized
406          //state.
407          clone.id = Identifiable.UNIDENTIFIED;
408          
409          clone.createdOn     = new Date();
410          clone.lastUpdatedOn = clone.createdOn;
411    
412          clone.schedulingBlock = null;
413        }
414        catch (Exception ex)
415        {
416          throw new RuntimeException(ex);
417        }
418        
419        return clone;
420      }
421      
422      /**
423       * Returns <i>true</i> if {@code o} is equal to this scan loop element.
424       * <p>
425       * In order to be equal to this element, {@code o} must be non-null and
426       * of the same class as this element. Equality is determined by examining
427       * the equality of corresponding attributes, with the following exceptions,
428       * which are ignored when assessing equality: 
429       * <ol>
430       *   <li>id</li>
431       *   <li>schedulingBlock</li>
432       *   <li>createdOn</li>
433       *   <li>createdBy</li>
434       *   <li>lastUpdatedOn</li>
435       *   <li>lastUpdatedBy</li>
436       * </ol></p>
437       */
438      @Override
439      public boolean equals(Object o)
440      {
441        //Quick exit if o is null
442        if (o == null)
443          return false;
444        
445        //Quick exit if o is this
446        if (o == this)
447          return true;
448        
449        //Quick exit if classes are different
450        if (!o.getClass().equals(this.getClass()))
451          return false;
452        
453        //Attributes that we INTENTIONALLY DO NOT COMPARE:
454        //  id,
455        //  schedulingBlock,
456        //  createdOn, createdBy, lastUpdatedOn, lastUpdatedBy
457        
458        ScanLoopElement other = (ScanLoopElement)o;
459    
460        return 
461          other.name.equals(this.name)  &&
462          other.comments.equals(this.comments);
463      }
464    
465      /** Returns a hash code value for this scan loop element. */
466      @Override
467      public int hashCode()
468      {
469        //You MUST keep this method in sync w/ the equals method
470    
471        //Taken from the Effective Java book by Joshua Bloch.
472        //The constants 17 & 37 are arbitrary & carry no meaning.
473        int result = 17;
474    
475        result = 37 * result + name.hashCode();
476        result = 37 * result + comments.hashCode();
477        
478        return result;
479      }
480    }