001    package edu.nrao.sss.model.project;
002    
003    import java.io.FileNotFoundException;
004    import java.io.Reader;
005    import java.io.Writer;
006    import java.math.BigDecimal;
007    import java.util.ArrayList;
008    import java.util.Calendar;
009    import java.util.Collection;
010    import java.util.Collections;
011    import java.util.Comparator;
012    import java.util.Date;
013    import java.util.HashSet;
014    import java.util.List;
015    import java.util.Set;
016    import java.util.UUID;
017    
018    import javax.xml.bind.JAXBException;
019    import javax.xml.bind.annotation.XmlAttribute;
020    import javax.xml.bind.annotation.XmlElement;
021    import javax.xml.bind.annotation.XmlElementRef;
022    import javax.xml.bind.annotation.XmlElementRefs;
023    import javax.xml.bind.annotation.XmlElementWrapper;
024    import javax.xml.bind.annotation.XmlID;
025    import javax.xml.bind.annotation.XmlIDREF;
026    import javax.xml.bind.annotation.XmlRootElement;
027    import javax.xml.bind.annotation.XmlTransient;
028    import javax.xml.bind.annotation.XmlType;
029    import javax.xml.stream.XMLStreamException;
030    
031    import edu.nrao.sss.astronomy.CoordinateConversionException;
032    import edu.nrao.sss.measure.TimeDuration;
033    import edu.nrao.sss.measure.TimeInterval;
034    import edu.nrao.sss.measure.TimeOfDayInterval;
035    import edu.nrao.sss.model.UserAccountable;
036    import edu.nrao.sss.model.project.scan.Scan;
037    import edu.nrao.sss.model.project.scan.ScanLoop;
038    import edu.nrao.sss.model.project.scan.ScanLoopElement;
039    import edu.nrao.sss.model.project.scan.ScanMode;
040    import edu.nrao.sss.model.project.scan.SwitchingScan;
041    import edu.nrao.sss.model.project.scheduling.Schedulable;
042    import edu.nrao.sss.model.project.scheduling.constraint.Constraint;
043    import edu.nrao.sss.model.project.scheduling.priority.Priority;
044    import edu.nrao.sss.model.resource.evla.EvlaPointingPosition;
045    import edu.nrao.sss.model.source.Source;
046    
047    import edu.nrao.sss.validation.ValidationFailure;
048    import edu.nrao.sss.validation.ValidationException;
049    import edu.nrao.sss.validation.FailureSeverity;
050    
051    import static edu.nrao.sss.util.EventSetStatus.*;
052    
053    import edu.nrao.sss.util.AlterationStatus;
054    import edu.nrao.sss.util.EqualityMethod;
055    import edu.nrao.sss.util.EventSetStatus;
056    import edu.nrao.sss.util.EventStatus;
057    import edu.nrao.sss.util.Identifiable;
058    import edu.nrao.sss.util.IllegalTransitionException;
059    import edu.nrao.sss.util.JaxbUtility;
060    import edu.nrao.sss.util.SourceNotFoundException;
061    
062    /**
063     * The shortest allowable contiguous block of observing time on a telescope.
064     * This is the atomic unit of observing.
065     * <p>
066     * A {@code SchedulingBlock} is contained in a {@link ProgramBlock}.  The program
067     * block defines the configuration of the telescope.  A program block may
068     * need to be broken into several scheduling blocks if, for example,
069     * 20 hours of observing time was needed for the program block, but the
070     * target source was not available for 20 consecutive hours of observing.</p>
071     * <p>
072     * <b>Version Info:</b>
073     * <table style="margin-left:2em">
074     *   <tr><td>$Revision: 2277 $</td>
075     *   <tr><td>$Date: 2009-04-29 11:19:38 -0600 (Wed, 29 Apr 2009) $</td>
076     *   <tr><td>$Author: dharland $</td> 
077     * </table></p>
078     * 
079     * @author David M. Harland
080     * @since 2006-02-24
081     */
082    @XmlRootElement
083    @XmlType(propOrder={"name",
084                        "createdBy",                "createdOn",
085                        "lastUpdatedBy",            "lastUpdatedOn",
086                        "type",                     "fixedStartTime",
087                        "executionStatus",          "alterationStatus",
088                        "authorizedCount",          "completedCount",
089                        "abortedCount",
090                        "preferredDateRange",       "lstStartRange",
091                        "monitoringInterval",       "serviceCalibrations",
092                        "environmentalConstraints", "priorities",
093                        "assumedTelescopePointing",
094                        "comments",                 "commentsToOperator",
095                        "scanSequence",             "prerequisite",
096                        "parentBlock",              "execBlocks"})
097    public class SchedulingBlock
098      implements Schedulable, Cloneable, Identifiable, UserAccountable
099    {
100      public static final String DEFAULT_NAME = "[New Scheduling Block]";
101    
102      private static final String NO_COMMENTS = "";
103    
104      private static PrereqComparator PREREQ_COMPARATOR;
105    
106      //IDENTIFICATION
107      private Long   id;          //A unique identifier for the persistence layer.
108      private String name;
109    
110      @XmlAttribute(required=true)
111      @XmlID
112      String xmlId;
113    
114      //USER TRACKING
115      private Long createdBy;      //This is a user ID
116      private Date createdOn;
117      private Long lastUpdatedBy;  //This is a user ID
118      private Date lastUpdatedOn;
119    
120      //CONTAINER (ProgramBlock) & CONTAINED OBJECTS (ExecutionBlocks and Scans)
121      private ProgramBlock         programBlock;
122      private ScanLoop             scanSequence;
123      private List<ExecutionBlock> executionBlocks;
124      
125      //LINKS TO OTHER SCHEDULING BLOCKS
126      private Set<SchedulingBlock> prereqs;
127      private SchedulingBlock      parentBlock;
128     
129      //TYPE, STATUS, & COUNTS
130      @XmlElement
131      private SchedulingType   type;
132      private EventSetStatus   executionStatus;
133      private AlterationStatus alterationStatus;
134      
135      @XmlElement private int authorizedCount;
136      @XmlElement private int completedCount;
137      @XmlElement private int abortedCount;
138    
139      //OBSERVATION TIMES
140      @XmlElement
141      private Date              fixedStartTime;  //Used only for fixed-date SBs
142      private TimeInterval      preferredDateRange;
143      private TimeOfDayInterval lstStartRange;
144      private TimeDuration      monitoringInterval;
145      
146      //SCHEDULING AIDS
147      @XmlElement
148      private EvlaPointingPosition     assumedTelescopePointing;
149      private EnvironmentalConstraints environmentalConstraints;
150      private List<Priority>           priorities;
151    
152      //OTHER
153      private List<ServiceCalibration> serviceCalibrations;
154      private String                   comments;
155      private String                   commentsToOperator;
156    
157      /** Creates a new instance. */
158      public SchedulingBlock()
159      {
160        //Create contained objects
161        scanSequence = new ScanLoop();
162        scanSequence.setSchedulingBlock(this);
163        scanSequence.setName("Main Loop");
164        scanSequence.setBracketed(false);
165    
166        executionBlocks = new ArrayList<ExecutionBlock>();
167        
168        prereqs = new HashSet<SchedulingBlock>();
169        
170        preferredDateRange = new TimeInterval();
171        lstStartRange      = new TimeOfDayInterval();
172        monitoringInterval = new TimeDuration();
173        
174        assumedTelescopePointing = null;
175        environmentalConstraints = new EnvironmentalConstraints();
176        priorities  = new ArrayList<Priority>();
177        
178        serviceCalibrations  = new ArrayList<ServiceCalibration>();
179        
180        //Initialize primitives and objects
181        xmlId = "schedBlock-" + UUID.randomUUID().toString();
182        initialize();
183      }
184      
185      /** Initializes the instance variables of this class.  */
186      private void initialize()
187      {
188        id    = Identifiable.UNIDENTIFIED;
189        name  = DEFAULT_NAME;
190    
191        programBlock = null;
192        parentBlock  = null;
193    
194        createdBy     = UserAccountable.NULL_USER_ID;
195        createdOn     = new Date();
196        lastUpdatedBy = UserAccountable.NULL_USER_ID;
197        lastUpdatedOn = new Date();
198    
199        type             = SchedulingType.getDefault();
200        executionStatus  = NOT_READY_TO_BE_SCHEDULED;
201        alterationStatus = AlterationStatus.UNKNOWN;
202        authorizedCount  = 1;
203        completedCount   = 0;
204        abortedCount     = 0;
205    
206        fixedStartTime     = null;
207        comments           = NO_COMMENTS;
208        commentsToOperator = NO_COMMENTS;
209      }
210      
211      /**
212       *  Resets this scheduling block to its initial state.
213       *  A reset block has the same state as a new block. 
214       */
215      public void reset()
216      {
217        initialize();
218        
219        scanSequence.reset();
220        scanSequence.setBracketed(false);
221        removeAllPrerequisites();
222        
223        preferredDateRange.reset();
224        lstStartRange.reset();
225        monitoringInterval.reset();
226        
227        assumedTelescopePointing = null;
228        environmentalConstraints.reset();
229        priorities.clear();
230        
231        serviceCalibrations.clear();
232      }
233    
234      //============================================================================
235      // IDENTIFICATION
236      //============================================================================
237    
238      /* (non-Javadoc)
239       * @see edu.nrao.sss.util.Identifiable#getId()
240       */
241      @XmlAttribute
242      public Long getId()
243      {
244        return id;
245      }
246    
247      void setId(Long id)
248      {
249        this.id = id;
250      }
251    
252      /**
253       * Resets this sb's id to UNIDENTIFIED and calls it's scan sequence's and
254       * execution block's clearId() methods.
255       */
256      public void clearId()
257      {
258        id = Identifiable.UNIDENTIFIED;
259    
260        getScanSequence().clearId();
261        for (ExecutionBlock eb : getExecutionBlocks())
262          eb.clearId();
263      }
264    
265      /**
266       * Sets the name of this scheduling block.
267       * <p>
268       * If {@code newName} is <i>null</i> or the empty string
269       * (<tt>""</tt>), the request to change the name will be
270       * denied and the current name will remain in place.</p>
271       * 
272       * @param newName the new name of this scheduling block.
273       */
274      @XmlElement
275      public void setName(String newName)
276      {
277        if (newName != name && newName.length() > 0)
278          name = newName;
279      }
280      
281      /**
282       * Returns the name of this scheduling block.
283       * @return the name of this scheduling block.
284       */
285      public String getName()
286      {
287        return name;
288      }
289      
290      @XmlTransient
291      @Deprecated
292      /** @deprecated Use {@link #getName()}. */
293      public String getLongName()  { return getName(); }
294      
295      @Deprecated
296      /** @deprecated Use {@link #setName(String)}. */
297      public void setLongName(String newName)  { setName(newName); }
298      
299      @XmlTransient
300      @Deprecated
301      /** @deprecated Use {@link #getName()}. */
302      public String getShortName()  { return getName(); }
303      
304      @Deprecated
305      /** @deprecated Use {@link #setName(String)}. */
306      public void setShortName(String newName)  { setName(newName); }
307      
308      //---------------------------------------------------------------------------
309      //                Naming for Scheduling
310      //---------------------------------------------------------------------------
311      //TODO: Scheduling needs a quick way to access the name of the program block, the
312      //project name, and the proposal name from the scheduling block.  What these
313      //exact identifies are, needs to be determined, but they need to uniquely identify
314      //the scheduling block in the database, but also be semi-human readable.  For example, Barry uses
315      //the legacy id for the proposal.  For right now, I just have initial approximations
316      //in place.SML
317      
318      public String getProgramName(){
319              String name = "";
320              if ( programBlock != null ){
321                      name = programBlock.getName();
322              }
323              return name;
324      }
325      
326      public String getProjectName(){
327              String name = "";
328              Project proj = getProject();
329              if ( proj != null ){
330                      name = proj.getProjectCode();
331              }
332              return name;
333      }
334      
335      public String getProposalName(){
336              String name = "";
337              Project proj = getProject();
338              if ( proj != null ){
339                      name = proj.getProposalCode();
340              }
341              return name;
342      }
343      
344      public String getCompleteName(){
345              return getProposalName() + Schedulable.NAME_SEPARATOR + getProjectName()+ Schedulable.NAME_SEPARATOR +
346              getProgramName() + Schedulable.NAME_SEPARATOR + getName();
347      }
348    
349      //============================================================================
350      // PROJECT
351      //============================================================================
352    
353      /**
354       * Returns the project to which this scheduling block belongs.
355       * If this scheduling block is not currently contained by a project,
356       * the returned value will be <i>null</i>.
357       * 
358       * @return the project that contains this scheduling block or <i>null</i>.
359       */
360      public Project getProject()
361      {
362        return hasProgramBlock() ? programBlock.getProject() : null;
363      }
364    
365      //============================================================================
366      // CONTAINER (ProgramBlock)
367      //============================================================================
368    
369      /**
370       * Sets the program block to which this scheduling block belongs.
371       * <p>
372       * If this scheduling block is currently contained in a program block
373       * that is not the same as the {@code newProgBlock} parameter,
374       * the current program block will be told to remove this scheduling block
375       * from its collection of scheduling blocks.  If {@code newProgBlock}
376       * is not <i>null</i>, it will be told to add this scheduling block
377       * to its collection.  Finally, this scheduling block's program block
378       * will be set to {@code newProgBlock}, even if it is <i>null</i>.</p>
379       * <p>
380       * Passing this method a {@code newProgBlock} of <i>null</i> has the
381       * effect of disconnecting this scheduling block from any program block.</p> 
382       * 
383       * @param newProgBlock the program block to which this scheduling block belongs.
384       */
385      @XmlTransient
386      public void setProgramBlock(ProgramBlock newProgBlock)
387      {
388        ProgramBlock formerProgramBlock = this.programBlock;
389        
390        //Quick exit if this scheduling block already belongs to newProgBlock.
391        //(Intentional use of "==" here.)
392        if (formerProgramBlock == newProgBlock)
393          return;
394    
395        //If newProgBlock is NOT null, it will be in charge of telling
396        //formerProgramBlock about its loss of this scheduling block
397        if (newProgBlock != null)
398        {
399          newProgBlock.addSchedulingBlock(this);
400        }
401        //Otherwise, we must tell the former program block ourselves
402        else //newProgBlock == null
403        {
404          if (formerProgramBlock != null)
405            formerProgramBlock.removeSchedulingBlock(this);
406        }
407        
408        //Could be null or a real program block
409        this.programBlock = newProgBlock;
410      }
411      
412      /**
413       * Sets this scheduling block's program block to {@code newProgBlock} without
414       * contacting either the former or new program block.  This method is
415       * used only by the ProgramBlock class.
416       */
417      void simplySetProgramBlock(ProgramBlock newProgBlock)
418      {
419        this.programBlock = newProgBlock;
420      }
421    
422      /**
423       * Returns the program block to which this scheduling block belongs, if any.
424       * <p>
425       * This scheduling block may be one of several that belong to
426       * the same program block.  If this scheduling block belongs to
427       * no program block, the value returned is <i>null</i>.</p>
428       * 
429       * @return the program block that contains this scheduling block, if any.
430       * 
431       * @see #hasProgramBlock()
432       */
433      public ProgramBlock getProgramBlock()
434      {
435        return programBlock;
436      }
437      
438      /**
439       * Returns <i>true</i> if this scheduling block has a non-null program block.
440       * <p>
441       * Scheduling blocks should normally be contained within, and therefore
442       * have a non-null, program block.  However, there are some situations
443       * where this method will return <i>false</i>:
444       * <ol>
445       *   <li>This scheduling block has just been created and its program block
446       *       has not yet been set.</li>
447       *   <li>A client removed this scheduling block from its program block and 
448       *       did not place it in a new program block.</li>
449       *   <li>A client explicitly set this scheduling block's program block to
450       *       <i>null</i>.</li>
451       * </ol></p>
452       * 
453       * @return <i>true</i> if this scheduling block has a non-null program block.
454       *         Therefore a return value of <i>true</i> means that 
455       *         you can call {@link #getProgramBlock()} and know that
456       *         it will return a non-null object. 
457       */
458      public boolean hasProgramBlock()
459      {
460        return programBlock != null;
461      }
462    
463      //============================================================================
464      // CONTAINED OBJECTS (Scans)
465      //============================================================================
466      
467      /**
468       * Creates and returns a new scan that is suitable for use with
469       * this scheduling block.
470       * It will also be of a variety that is appropriate for the given
471       * observation mode.
472       * The returned scan has <i>not</i> been added to
473       * this scheduling block.
474       * 
475       * @return a new scan that can later be added to this scheduling block.
476       */
477      public Scan createScan(ScanMode scanMode)
478      {
479        return Scan.createFor(scanMode);
480      }
481    
482      /**
483       * Removes all occurrences of the given scan from this scheduling block.
484       * <p>
485       * In order for {@code element} to be removed from this block, it must
486       * be contained by this block.
487       * Containment is determined by the {@code equalityMethod} provided.
488       * If this parameter is <i>null</i>, {@link EqualityMethod#VALUE} will
489       * be used.</p>
490       * 
491       * @param scan the scan to be removed.
492       * 
493       * @param equalityMethod determines whether containment is based on reference
494       *                       or value equality.  A value of <i>null</i> will be
495       *                       interpreted as a signal to use value equality.
496       */
497      public void removeScan(Scan scan, EqualityMethod equalityMethod)
498      {
499        if (scan != null)
500          scanSequence.removeRecursively(scan, equalityMethod);
501      }
502    
503      /**
504       * Returns the scans that belong to this scheduling block.
505       * <p>
506       * The returned {@code Set} is <i>not</i> held directly by this
507       * scheduling block, so changes made to it will not be reflected
508       * in this object.  The scans in the returned set, though, are
509       * those held by this scheduling block, so changes to those
510       * <i>will</i> be reflected in this object.</p>
511       * <p>
512       * This is a convenience method that is equivalent to calling
513       * {@code getScanSequence().toScanSet()}.</p>
514       *
515       * @return the scans that belong to this scheduling block.
516       */
517      public Set<Scan> getScans()
518      {
519        return scanSequence.toScanSet();
520      }
521    
522      /**
523       * Sets this scheduling block's sequence of scans.
524       * Each scan contained in the sequence is told that this is its
525       * scheduling block.
526       * 
527       * @param sequence the sequence of scans for this scheduling block.
528       */
529      @SuppressWarnings("unused")  //JAXB use
530      private void setScanSequence(ScanLoop sequence)
531      {
532        scanSequence = (sequence == null) ? new ScanLoop() : sequence;
533        scanSequence.setBracketed(false);
534        scanSequence.setSchedulingBlock(this);
535        
536        //Tell each scan that it belongs to this scheduling block
537        for (Scan scan : getScans())
538          scan.setSchedulingBlock(this);
539      }
540      
541      /**
542       * Returns this scheduling block's sequence of scans.
543       * @return this scheduling block's sequence of scans.
544       */
545      @XmlElement
546      public ScanLoop getScanSequence()
547      {
548        return scanSequence;
549      }
550      
551      /**
552       * Returns <i>true</i> if this scheduling block contains
553       * {@code scanOrScanLoop}.
554       * <p>
555       * Containment is determined by the {@code equalityMethod} provided.
556       * If this parameter is <i>null</i>, {@link EqualityMethod#VALUE} will
557       * be used.</p>
558       * 
559       * @param scanOrScanLoop the scan or scan loop to be tested for containment.
560       * 
561       * @param equalityMethod determines whether containment is based on reference
562       *                       or value equality.  A value of <i>null</i> will be
563       *                       interpreted as a signal to use value equality.
564       * 
565       * @return <i>true</i> if this scheduling block contains
566       *         {@code scanOrScanLoop}.
567       */
568      public boolean contains(ScanLoopElement scanOrScanLoop,
569                              EqualityMethod  equalityMethod)
570      {
571        return scanSequence.contains(scanOrScanLoop, equalityMethod);
572      }
573      
574      //============================================================================
575      // PREREQUISITES
576      //============================================================================
577    
578      /**
579       * Adds {@code newPrereq} to this scheduling block's set of direct
580       * prerequisites.  If this scheduling block belongs to a program block, and if
581       * {@code newPrereq} becomes a prerequisite of this one, then
582       * {@code newPrereq} is added to this program block, because a scheduling
583       * block that is part of a program block may have as prerequisites only those
584       * scheduling blocks that belong to the same program block.
585       * <p>
586       * In order to prevent a circular chain of dependencies, {@code newPrereq}
587       * will not be added to this scheduling block's set if this block is
588       * a prerequisite of {@code newPrereq}.</p>
589       * <p>
590       * <a name="directIndirect">
591       * <b><u>Direct vs. Indirect Prerequisites</u></b></a>
592       * <br/>
593       * The <i>prerequisite</i> methods of this class often
594       * refer to <i>direct</i> or <i>indirect</i> prerequisites.  This section
595       * explains the meanings of those terms.  Consider this set of dependencies:
596       * <pre>
597       *   A)--+---------------+
598       *       |-->E)--+       |
599       *   B)--+       |       |-->G)-->H
600       *   C           |-->F)--+
601       *   D)----------+
602       * </pre>
603       * where the notation {@code X)-->Y} means that {@code Y} is dependent on the
604       * completion of {@code X} or, alternatively, {@code X} is a prerequisite of
605       * {@code Y}.</p>
606       * <p>
607       * Scheduling block {@code E} has two prerequisites, {@code A} and {@code B},
608       * each of which is direct.<br/>
609       * Scheduling block {@code F} has two direct prerequisites, {@code D} and
610       * {@code E}, and two indirect prerequisites, {@code A} and {@code B}.<br/>
611       * Scheduling block {@code G} is interesting because {@code A} serves as both
612       * a direct and an indirect (via {@code F} and {@code E}) prerequisite.</p>
613       * <p>
614       * Methods that refer only to <i>prerequisite</i> without the <i>direct</i>
615       * or <i>indirect</i> adjective mean a prerequisite of any type.</p>
616       * 
617       * @param newPrereq a new direct prerequisite for this scheduling block.
618       * 
619       * @return <i>true</i> if {@code newPrereq} was added to this scheduling
620       *         block's set of direct prerequisites.<br/>
621       *         The conditions that lead to a return value of <i>false</i> are:
622       *         <ol>
623       *           <li>{@code newPrereq} is <i>null</i></li>
624       *           <li>{@code newPrereq} is already an direct prerequisite
625       *               of this scheduling block</li>
626       *           <li>This scheduling block is currently a prequisite, direct
627       *               or otherwise, of {@code newPrereq}.
628       *         </ol>
629       */
630      public boolean addPrerequisite(SchedulingBlock newPrereq)
631      {
632        boolean prereqWasAdded;
633        
634        //Do not update the set of prerequisites if the new one is
635        //null or if this scheduling block is a prerequisite of newPrereq.
636        if (newPrereq == null || this.isPrerequisiteOf(newPrereq))
637          prereqWasAdded = false;
638        else
639          prereqWasAdded = prereqs.add(newPrereq);
640        
641        //The prerequisite SB must belong to the same PB as this SB
642        if (prereqWasAdded)
643          newPrereq.setProgramBlock(programBlock);
644        
645        return prereqWasAdded;
646      }
647    
648      /**
649       * Removes {@code oldPrereq} from this scheduling block's set of
650       * direct prerequisites.
651       * (For an explanation of <i>direct</i> versus <i>indirect</i>
652       * prequisites, see the <a href="#directIndirect">note</a> in the
653       * {@link #addPrerequisite(SchedulingBlock)} method.) 
654       * 
655       * @param oldPrereq the prerequisite to be removed from this scheduling
656       *                  block's set of direct prerequisites.
657       *                  
658       * @return <i>true</i> if {@code oldPrereq} was successfully removed.
659       *         Note that a return value of <i>false</i> might mean that
660       *         {@code oldPrereq} was not an direct prerequisite of
661       *         this scheduling block.
662       */
663      public boolean removePrerequisite(SchedulingBlock oldPrereq)
664      {
665        return prereqs.remove(oldPrereq);
666      }
667      
668      /**
669       * Clears this scheduling block's set of prerequisites.
670       */
671      public void removeAllPrerequisites()
672      {
673        prereqs.clear();
674      }
675      
676      /**
677       * Returns <i>true</i> if this scheduling block is either a
678       * direct or indirect prerequisite of {@code schedBlock}.
679       * (For an explanation of <i>direct</i> versus <i>indirect</i>
680       * prerequisites, see the <a href="#directIndirect">note</a> in the
681       * {@link #addPrerequisite(SchedulingBlock)} method.) 
682       * 
683       * @param schedBlock the scheduling block to test.
684       * 
685       * @return <i>true</i> if this scheduling block is a prerequisite
686       *         of {@code schedBlock}.
687       *         
688       * @see #isDirectPrerequisiteOf(SchedulingBlock)
689       */
690      public boolean isPrerequisiteOf(SchedulingBlock schedBlock)
691      {
692        Set<SchedulingBlock> othersPrereqs = schedBlock.getAllPrerequisites();
693        
694        return othersPrereqs.contains(this);
695      }
696    
697      /**
698       * Returns <i>true</i> if this scheduling block is a <i>direct</i>
699       * prerequisite of {@code schedBlock}.
700       * (For an explanation of <i>direct</i> versus <i>indirect</i>
701       * prequisites, see the <a href="#directIndirect">note</a> in the
702       * {@link #addPrerequisite(SchedulingBlock)} method.) 
703       * 
704       * @param schedBlock the scheduling block to test.
705       * 
706       * @return <i>true</i> if this scheduling block is an <i>direct</i>
707       *         prerequisite of {@code schedBlock}.
708       *         
709       * @see #isPrerequisiteOf(SchedulingBlock)
710       */
711      public boolean isDirectPrerequisiteOf(SchedulingBlock schedBlock)
712      {
713        Set<SchedulingBlock> othersPrereqs = schedBlock.getDirectPrerequisites();
714        
715        return othersPrereqs.contains(this);
716      }
717      
718      /**
719       * Returns <i>true</i> if this block has one or more prequisite blocks.
720       * @return <i>true</i> if this block has one or more prequisite blocks.
721       */
722      public boolean hasPrerequisites()
723      {
724        return prereqs.size() > 0;
725      }
726      
727      /**
728       * Returns this scheduling block's set of direct prerequisites.
729       * <p>
730       * The returned {@code Set} is an <i>unmodifiable</i> set
731       * that will not permit additions of new elements or
732       * deletions of existing elements.  Attempts to modify
733       * the set will result in {@link UnsupportedOperationException}s.</p>
734       * 
735       * @return this scheduling block's set of direct prerequisites.
736       */
737      public Set<SchedulingBlock> getDirectPrerequisites()
738      {
739        return Collections.unmodifiableSet(prereqs);
740      }
741      
742      /**
743       * Returns a set containing all direct and indirect prerequisites of
744       * this scheduling block.
745       * (For an explanation of <i>direct</i> versus <i>indirect</i>
746       * prequisites, see the <a href="#directIndirect">note</a> in the
747       * {@link #addPrerequisite(SchedulingBlock)} method.) 
748       * 
749       * @return a set containing all prerequisites of this scheduling block.
750       */
751      public Set<SchedulingBlock> getAllPrerequisites()
752      {
753        Set<SchedulingBlock> result = new HashSet<SchedulingBlock>();
754        
755        addAllPrereqsTo(result);
756        
757        return result;
758      }
759      
760      /**
761       * Adds all of this scheduling block's prerequisites, both direct
762       * and indirect, to {@code result}.
763       * 
764       * @param result the set containing all prerequisites of this scheduling
765       *               block.  
766       */
767      private void addAllPrereqsTo(Set<SchedulingBlock> result)
768      {
769        if (result != null)
770        {
771          for (SchedulingBlock prereq : prereqs)
772          {
773            result.add(prereq);
774            prereq.addAllPrereqsTo(result);
775          }
776        }
777      }
778    
779      @XmlIDREF
780      @SuppressWarnings("unused")  //Used by JAXB
781      private void setPrerequisite(Set<SchedulingBlock> prerequisites)
782      {
783        prereqs.addAll(prerequisites);
784      }
785      
786      @SuppressWarnings("unused")  //Used by JAXB
787      private Set<SchedulingBlock> getPrerequisite()
788      {
789        return prereqs;
790      }
791    
792      //============================================================================
793      // PROGENITOR SCHEDULING BLOCK
794      //============================================================================
795    
796      /**
797       * Returns <i>true</i> if this block has a parent block.
798       * Most blocks do not have parent blocks.  One known situation where a block
799       * does have a parent is when it was created as the result of an
800       * {@link #unsubmit() unsubmit} operation on the parent.
801       * 
802       * @return
803       *   <i>true</i> if this block has a parent block.
804       *   
805       * @see #getParentBlock()
806       */
807      public boolean hasParentBlock()
808      {
809        return parentBlock != null;
810      }
811      
812      /**
813       * Returns this block's parent, or <i>null</i> if it has no parent.
814       * Most blocks do not have parent blocks.
815       * 
816       * @return
817       *   <i>true</i> if this block has a parent block.
818       *  
819       * @see #hasParentBlock()
820       */
821      @XmlIDREF
822      public SchedulingBlock getParentBlock()
823      {
824        return parentBlock;
825      }
826    
827      @SuppressWarnings("unused")  //Used by JAXB
828      private void setParentBlock(SchedulingBlock newParent)
829      {
830        parentBlock = newParent; //even if null
831      }
832      
833      //============================================================================
834      // TYPE & STATUS
835      //============================================================================
836    
837      /**
838       * Sets the scheduling type to use for this scheduling block.
839       * If {@code newType} is <i>null</i>, it will be interpreted as the
840       * {@link SchedulingType#getDefault() default type}.
841       * 
842       * @param newType the new scheduling type for this block.
843       */
844      public void setType(SchedulingType newType)
845      {
846        if (!type.equals(newType))
847        {
848          type = (newType == null) ? SchedulingType.getDefault() : newType;
849        
850          fixedStartTime = type.equals(SchedulingType.FIXED_DATE) ? new Date() : null;
851        }
852      }
853      
854      /**
855       * Returns the scheduling type to use for this scheduling block.
856       * @return the scheduling type to use for this scheduling block.
857       */
858      @XmlTransient  //using variable directly
859      public SchedulingType getType()
860      {
861        return type;
862      }
863      
864      /**
865       * Returns <i>true</i> if this block may be scheduled dynamically.
866       * The only time this method returns <i>false</i> is when this block's
867       * {@link #getType() type} is {@link SchedulingType#FIXED_DATE}.
868       * 
869       * @return <i>true</i> if this block may be scheduled dynamically.
870       *   
871       * @since 2008-07-25
872       */
873      public boolean mayBeScheduledDynamically()
874      {
875        return !type.equals(SchedulingType.FIXED_DATE);
876      }
877    
878      /**
879       * Sets the alteration status of this scheduling block.
880       * 
881       * @param newStatus the alteration status of this scheduling block.
882       *                  If this parameter is <i>null</i> it will be
883       *                  interepreted as {@code AlterationStatus.UNKNOWN}.
884       */
885      void setAlterationStatus(AlterationStatus newStatus)
886      {
887        alterationStatus = (newStatus == null) ? AlterationStatus.UNKNOWN
888                                               : newStatus;
889      }
890    
891      /**
892       * Returns information about the manner in which this scheduling block was
893       * created and altered.
894       * 
895       * @return this scheduling block's alteration status.
896       */
897      @XmlElement
898      public AlterationStatus getAlterationStatus()
899      {
900        return alterationStatus;
901      }
902      
903      /**
904       * Returns <i>true</i> if this block is part of a
905       * {@link ProgramBlock#isTest() test program}.
906       * @return <i>true</i> if this block is part of a test program.
907       */
908      public boolean isTest()
909      {
910        return hasProgramBlock() && getProgramBlock().isTest();
911      }
912    
913      /**
914       * This method is here for persistence mechanisms such as Hibernate
915       * and JAXB.  It is NOT appropriate to let other objects set the
916       * status, because the status of this object is derived from that
917       * of contained objects.
918       */
919      @XmlElement
920      @SuppressWarnings("unused")  //Used by JAXB and Hibernate
921      private void setExecutionStatus(EventSetStatus newStatus)
922      {
923        executionStatus = (newStatus == null) ? NOT_READY_TO_BE_SCHEDULED : newStatus;
924      }
925    
926      /**
927       * Returns this scheduling block's execution status.
928       * 
929       * @return this scheduling block's execution status.
930       */
931      public EventSetStatus getExecutionStatus()
932      {
933        if (!executionStatus.isFinal())
934          recomputeStatus();
935        
936        return executionStatus;
937      }
938      
939      /**
940       * Sets this SB's status based on the combined statuses of
941       * this SB's execution blocks.
942       */
943      private void recomputeStatus()
944      {
945        int execBlockCount = executionBlocks.size();
946        
947        EventStatus[] statuses = new EventStatus[execBlockCount];
948        
949        for (int e=0; e < execBlockCount; e++)
950          statuses[e] = executionBlocks.get(e).getExecutionStatus();
951        
952        executionStatus = EventSetStatus.createFrom(statuses);
953      }
954    
955      /**
956       * Puts on hold any executions of this scheduling block that have
957       * not yet begun.  Executions that are on hold will be removed
958       * from the scheduler's queue.  While on hold, executions may
959       * only be canceled or released.
960       * <p>
961       * If this block has no executions that may be put on hold,
962       * this method does nothing.</p>
963       * 
964       * @param numberOfNewExecBlocksToMake
965       *   in addition to putting existing blocks on hold, this method
966       *   can be told to make new blocks and put them on hold, too.
967       *   This is useful if an execution block aborted while running;
968       *   the observer gets a "do over".
969       *   
970       * @return <i>true</i> if any executions were put on hold.
971       */
972      boolean hold(int numberOfNewExecBlocksToMake)
973      {
974        boolean held = false;
975        
976        //Add new blocks and transition them to submitted
977        for (int i=0; i < numberOfNewExecBlocksToMake; i++)
978        {
979          ExecutionBlock newEB = new ExecutionBlock(this);
980          try
981          {
982            newEB.updateStatusSubmit();
983            executionBlocks.add(newEB);
984          }
985          catch (IllegalTransitionException ex)
986          {
987            throw new RuntimeException(
988              "PROGRAMMER ERROR: should have been able to submit a brand new exec block.", ex);
989          }
990        }
991        
992        //Put eligible blocks on hold
993        for (ExecutionBlock execBlock : executionBlocks)
994        {
995          try
996          {
997            execBlock.updateStatusHold();
998            held = true;
999          }
1000          catch (IllegalTransitionException ex)
1001          {
1002            //Do nothing; we're just trying to hold whatever may be held
1003          }
1004        }
1005        
1006        return held;
1007      }
1008      
1009      /**
1010       * Releases any executions of this scheduling block that are on hold.
1011       * Once released, those executions will be labeled as not-yet-scheduled
1012       * and will be ready for assessment by the scheduler.
1013       * <p>
1014       * If there are no execution blocks on hold, this method does nothing.</p>
1015       *
1016       * @return <i>true</i> if any executions were released.
1017       */
1018      boolean release()
1019      {
1020        boolean released = false;
1021        
1022        for (ExecutionBlock execBlock : executionBlocks)
1023        {
1024          if (execBlock.getExecutionStatus().equals(EventStatus.ON_HOLD))
1025          {
1026            try
1027            {
1028              execBlock.updateStatusRelease();
1029              released = true;
1030            }
1031            catch (IllegalTransitionException ex)
1032            {
1033              throw new RuntimeException(
1034                "PROGRAMMER ERROR: we should have been able to release an "+
1035                "exec block that is on hold.", ex);
1036            }
1037          }
1038        }
1039        
1040        return released;
1041            }
1042    
1043      /**
1044       * Prepares this scheduling block for scheduling.
1045       * <p>
1046       * This method is intended to be used no more than once on this scheduling
1047       * block.  It will not have any effect unless this scheduling block is in
1048       * its initial status of not-ready-to-be-scheduled.  Note that this method
1049       * no longer checks this block for warnings and errors.  It is now the
1050       * responsibility of clients to decide if this block is fit for scheduling.</p>
1051       * <p>
1052       * When called on a block in its initial status, this method will result in
1053       * the creation of a number of execution blocks equal to the
1054       * {@link #getAuthorizedCount() authorized count} of this block and will
1055       * result in a new status of not-yet-scheduled.</p>
1056       * 
1057       * @throws RuntimeException
1058       *   if this block has not yet been scheduled but already has execution blocks.
1059       *   Only an internal programmer error should lead to this situation
1060       *   (or perhaps bad data coming from a persistent store, such as XML or a
1061       *   database).
1062       */
1063      public void submit()
1064      {
1065        //Contract w/ clients is that a scheduling block may be submitted for
1066        //scheduling no more than once.  We will silently ignore requests to resubmit.
1067        if (getExecutionStatus().equals(NOT_READY_TO_BE_SCHEDULED))
1068        {
1069          //There should not yet be any execution blocks
1070          if (executionBlocks.size() != 0)
1071            throw new RuntimeException(
1072              "PROGRAMMER ERROR: client should not have called submit on a scheduling "+
1073              "block that already has execution blocks.");
1074          
1075          addOrRemoveExecBlocks(0, authorizedCount);
1076        }
1077      }
1078    
1079            /**
1080             * Puts an end to the life cycle of this block and potentially creates a
1081             * replacement block.
1082             * <p>
1083             * <b><u>Modifications of This Block</u></b><br/>
1084             * The authorization count of this block is set to the number of successful
1085             * iterations.  This means that after the call this block is no longer
1086             * authorized for more iterations, and that it has a status of <tt>complete</tt>
1087             * or <tt>canceled</tt>.</p>
1088             * <p>
1089             * <b><u>Creation of a Replacement Block</u></b><br/>
1090             * If at the time this block was unsubmitted it had more authorized iterations,
1091             * those iterations will be transfered to a replacement block.  It is this
1092             * replacement block that is returned.  The replacement block will have an
1093             * authorized count equal to the unrun iterations of this block.  The
1094             * replacement block will have this block as a {@link #getParentBlock() parent}.
1095             * The replacement block will be in a not-ready-to-be-scheduled state with
1096             * no execution blocks, meaning that it may be later submitted.  Finally,
1097             * if this block was a prerequisite of any other blocks, those blocks will
1098             * now depend on the replacement and not on this one.</p>
1099             * <p>
1100             * If this block had no more authorized iterations at the time of the unsubmit,
1101             * there will be no replacement and the return value will be <i>null</i>.</p>
1102             * 
1103             * @return
1104             *   a new scheduling block to replace this one, or <i>null</i>.
1105             */
1106            public SchedulingBlock unsubmit()
1107            {
1108              int successfulIterations = 0;
1109    
1110              //Cancel any uncompleted iterations of THIS block
1111              for (ExecutionBlock execBlock : executionBlocks)
1112              {
1113                EventStatus execStat = execBlock.getExecutionStatus();
1114                
1115                if (execStat.equals(EventStatus.COMPLETED))
1116                {
1117                  successfulIterations++;
1118                }
1119                else if (!execBlock.getExecutionStatus().isFinal())
1120                {
1121                  try {
1122                    execBlock.updateStatusCancel();
1123                  }
1124                  catch (IllegalTransitionException ex) {
1125                    throw new RuntimeException(
1126                      "PROGRAMMER ERROR: should have been able to cancel a non-final exec block");
1127                  }
1128                }
1129              }
1130              
1131              //Authorized count for REPLACEMENT block is orig auth count less successes
1132        int newAuthCount  = authorizedCount - successfulIterations;
1133    
1134        //Set the authorized count of THIS block to the number of successful iterations
1135              authorizedCount = successfulIterations;
1136              
1137              //Create a REPLACEMENT scheduling block that user can submitted later
1138              SchedulingBlock replacementBlock = null;
1139              
1140              //Make a REPLACEMENT block only if it has more iterations available
1141              if (newAuthCount > 0)
1142              {
1143                replacementBlock = this.clone();
1144              
1145                //REPLACEMENT block should be treated as if it had never been submitted
1146          replacementBlock.authorizedCount = newAuthCount;
1147                replacementBlock.executionStatus = NOT_READY_TO_BE_SCHEDULED;
1148                replacementBlock.executionBlocks.clear();
1149    
1150                //Link REPLACEMENT block to this one 
1151                replacementBlock.parentBlock = this;
1152                
1153                //Add REPLACEMENT to this block's program block
1154                if (this.programBlock != null)
1155                  this.programBlock.addSchedulingBlock(replacementBlock);
1156                
1157                //TODO (see EVL-881): find SBs that rely on this one as a prereq and
1158                //                    change'm so that they now rely on replacement as prereq
1159              }
1160              
1161                    return replacementBlock;
1162            }
1163    
1164      //============================================================================
1165      // SCHEDULE ENTRIES & EXECUTION BLOCKS
1166      //============================================================================
1167      
1168      /**
1169       * Returns the number of executions authorized for this block.
1170       * @return the number of executions authorized for this block.
1171       */
1172      @XmlTransient  //JAXB will use inst var
1173      public int getAuthorizedCount()
1174      {
1175        return authorizedCount;
1176      }
1177    
1178      /**
1179       * Sets the number of executions authorized for this block.
1180       * <p>
1181       * The authorized count may never be set to a value less than that of the
1182       * number of successful executions already completed.  An attempt to set
1183       * the value below this minimum will be silently reinterpreted as a
1184       * request to set it to this minimum.  This means that a convenient way
1185       * to prevent future runs of this block is to use a value less than one.</p>
1186       * <p>
1187       * If the request to increase or decrease the number of authorized iterations
1188       * is made prior to submitting this block for scheduling, then the authorized
1189       * count is simply updated to the requested value (subject to the paragraph
1190       * above).  If, though, the request is made after this block has already been
1191       * submitted, this method will attempt to add or remove execution blocks.</p>
1192       * 
1193       * @param newCount
1194       *   the new number of executions authorized for this block.
1195       */
1196      public void setAuthorizedCount(int newCount)
1197      {
1198        //Don't allow client to set to a value less than the number of successful
1199        //iterations of this block.
1200        int authCntMin = getCompletedCount();
1201        
1202        if (newCount < authCntMin)
1203          newCount = authCntMin;
1204    
1205        //No problems if we have not yet submitted, but if we have then we need
1206        //to change the number of execution blocks.
1207        if (getExecutionStatus().equals(NOT_READY_TO_BE_SCHEDULED))
1208        {
1209          authorizedCount = newCount;
1210        }
1211        else //already submitted
1212        {
1213          authorizedCount = addOrRemoveExecBlocks(authorizedCount, newCount);
1214        }
1215      }
1216      
1217      /**
1218       *  Helps setAuthorizedCount and submit.
1219       *  Assumes newAuthCount >= completedCount.
1220       *  Returns number of exec blocks after call.
1221       */
1222      private int addOrRemoveExecBlocks(int oldAuthCount, int newAuthCount)
1223      {
1224        if (newAuthCount > oldAuthCount)
1225        {
1226          for (int e=oldAuthCount; e < newAuthCount; e++)
1227          {
1228            ExecutionBlock newEB = new ExecutionBlock(this);
1229            
1230            //We're in this method only if the SB was already submitted.  Since this
1231            //is the case, we need to tell the EBs that they're ready to be sched.
1232            try
1233            {
1234              newEB.updateStatusSubmit();
1235            }
1236            catch (IllegalTransitionException ex)
1237            {
1238              throw new RuntimeException(
1239                "PROGRAMMER ERROR: we should have been able to submit a "+
1240                "newly formed exec block.", ex);
1241            }
1242    
1243            executionBlocks.add(newEB);
1244          }
1245        }
1246        else if (newAuthCount < oldAuthCount)
1247        {
1248          //Make a collection of EBs to remove, preferring those that
1249          //are at the earliest stages of their life cycles.
1250          int numberToRemove = oldAuthCount - newAuthCount;
1251    
1252          Collection<ScheduleEntry> removalCandidates =
1253            getScheduleEntries(EventStatus.NOT_READY_TO_BE_SCHEDULED, null);
1254          
1255          if (removalCandidates.size() < numberToRemove)
1256            removalCandidates = getScheduleEntries(EventStatus.NOT_YET_SCHEDULED,
1257                                                   removalCandidates);
1258          
1259          if (removalCandidates.size() < numberToRemove)
1260            removalCandidates = getScheduleEntries(EventStatus.ON_HOLD,
1261                                                   removalCandidates);
1262          
1263          //TODO: removing already-scheduled blocks might be troublesome
1264          //      for scheduler.  Look into a Listener interface for use
1265          //      with status transitions of ScheduleEntry.
1266          if (removalCandidates.size() < numberToRemove)
1267            removalCandidates = getScheduleEntries(EventStatus.SCHEDULED_BUT_NOT_STARTED,
1268                                                   removalCandidates);
1269          
1270          if (removalCandidates.size() < numberToRemove)
1271          {
1272            throw new RuntimeException(
1273              "PROGRAMMER ERROR: trying to decrease authorizedCount from " +
1274              oldAuthCount + " to " + newAuthCount +
1275              ". The logic did not find enough execution blocks to remove.");
1276          }
1277          
1278          int i=1;
1279          for (ScheduleEntry entry : removalCandidates)
1280          {
1281            try
1282            {
1283              entry.updateStatusCancel();
1284            }
1285            catch (IllegalTransitionException ex)
1286            {
1287              throw new RuntimeException(
1288                "PROGRAMMER ERROR: we should have been able to cancel an "+
1289                "exec block that is not in its final state.", ex);
1290            }
1291            
1292            if (++i > numberToRemove)
1293              break;
1294          }
1295        }
1296        //else newAuth == auth & nothing to do
1297        
1298        return executionBlocks.size();
1299      }
1300        
1301      /**
1302       * Returns the number of successful executions of this block.
1303       * @return the number of successful executions of this block.
1304       */
1305      @XmlTransient //JAXB will use inst var
1306      public int getCompletedCount()
1307      {
1308        //Recompute from exec blocks if necessary
1309        if (!executionStatus.isFinal())  //Don't use getExecutionStatus here
1310        {
1311          completedCount = 0;
1312          
1313          for (ExecutionBlock execBlock : executionBlocks)
1314          {
1315            if (execBlock.getExecutionStatus().equals(EventStatus.COMPLETED))
1316              completedCount++;
1317          }
1318          
1319          recomputeStatus(); //Just in case we're at final & didn't know it
1320        }
1321        
1322        return completedCount;
1323      }
1324      
1325      /**
1326       * Returns the number of times an execution of this scheduling block has
1327       * been aborted.
1328       * @return the number of aborted executions for this block.
1329       */
1330      @XmlTransient //JAXB will use inst var
1331      public int getAbortedCount()
1332      {
1333        //Recompute from exec blocks if necessary
1334        if (!executionStatus.isFinal())  //Don't use getExecutionStatus here
1335        {
1336          abortedCount = 0;
1337          
1338          for (ExecutionBlock execBlock : executionBlocks)
1339          {
1340            if (execBlock.wasAborted())
1341              abortedCount++;
1342          }
1343          
1344          recomputeStatus(); //Just in case we're at final & didn't know it
1345        }
1346        
1347        return abortedCount;
1348      }
1349    
1350      /**
1351       * Returns the schedule entries of this block that 
1352       * have an execution status of {@code execStatus}.  Only the status of the
1353       * entries is considered.  This method does not look, for example, at the
1354       * {@link #isTest()} property.
1355       * 
1356       * @param execStatus the execution status of the schedule entries to be added
1357       *                   to {@code destination}.
1358       *                   
1359       * @param destination the collection to which the schedule entries should be
1360       *                    added.  If this collection is <i>null</i>, a new one
1361       *                    will be created.  This collection is returned.
1362       *                    
1363       * @return the collection holding the schedule entries.  This will be either
1364       *         {@code destination} or a new collection, if {@code destination} is
1365       *         <i>null</i>.
1366       *         
1367       * @see #getReadyToScheduleEntries(Collection)
1368       */
1369      public Collection<ScheduleEntry> getScheduleEntries(
1370                                         EventStatus               execStatus,
1371                                         Collection<ScheduleEntry> destination)
1372      {
1373        if (destination == null)
1374          destination = new ArrayList<ScheduleEntry>();
1375        
1376        for (ScheduleEntry entry : executionBlocks)
1377        {
1378          if (entry.getExecutionStatus().equals(execStatus))
1379            destination.add(entry);
1380        }
1381        
1382        return destination;
1383      }
1384    
1385      /**
1386       * Returns the schedule entries of this block that are ready for
1387       * scheduling.  If this is a test scheduling block, or if this
1388       * block has no <tt>NOT_YET_SCHEDULED</tt> entries, the returned
1389       * collection will be empty.
1390       * 
1391       * @param destination the collection to which the ready-to-be-scheduled
1392       *                    entries should be added.  If this collection is
1393       *                    <i>null</i>, a new one will be created.  This
1394       *                    collection is returned.
1395       *                    
1396       * @return a collection of ready-to-be-scheduled entries, or an empty
1397       *         collection.  The returned collection will be either
1398       *         {@code destination} or a new collection, if {@code destination} is
1399       *         <i>null</i>.
1400       *         
1401       * @see #getScheduleEntries(EventStatus, Collection)
1402       */
1403      public Collection<ScheduleEntry>
1404        getReadyToScheduleEntries(Collection<ScheduleEntry> destination)
1405      {
1406        if (destination == null)
1407          destination = new ArrayList<ScheduleEntry>();
1408        
1409        if (!isTest())
1410          getScheduleEntries(EventStatus.NOT_YET_SCHEDULED, destination);
1411        
1412        return destination;
1413      }
1414      
1415      /**
1416       * Returns a complete collection of this scheduling block's execution
1417       * blocks.  The returned collection is <i>not</i> held internally by
1418       * this scheduling block, so changes made to it will not be reflected
1419       * herein.
1420       * 
1421       * @return this SB's execution blocks.
1422       */
1423      public List<ExecutionBlock> getExecutionBlocks()
1424      {
1425        return new ArrayList<ExecutionBlock>(executionBlocks);
1426      }
1427    
1428      @XmlElementWrapper(name="executionBlocks")
1429      @XmlElement(name="executionBlock")
1430      @SuppressWarnings("unused")  //JAXB use
1431      private void setExecBlocks(ExecutionBlock[] replacements)
1432      {
1433        executionBlocks.clear();
1434        
1435        for (ExecutionBlock eb : replacements)
1436        {
1437          eb.simplySetSchedulingBlock(this);
1438          executionBlocks.add(eb);
1439        }
1440      }
1441      
1442      @SuppressWarnings("unused")  //JAXB use
1443      private ExecutionBlock[] getExecBlocks()
1444      {
1445        if (executionBlocks.size() == 0)
1446          return null;
1447        else
1448          return executionBlocks.toArray(new ExecutionBlock[executionBlocks.size()]);
1449      }
1450    
1451      //============================================================================
1452      // OBSERVATION TIMES
1453      //============================================================================
1454      
1455      /**
1456       * Returns <i>true</i> if this block must be executed at an exact point
1457       * in time.
1458       * 
1459       * @return
1460       *   <i>true</i> if this block must be executed at an exact point in time.
1461       *   
1462       * @since 2008-07-25
1463       */
1464      public boolean hasFixedStartTime()
1465      {
1466        return fixedStartTime != null;
1467      }
1468      
1469      /**
1470       * Returns either the time this block must be executed or <i>null</i>, if
1471       * this block may be dynamically scheduled.
1472       * 
1473       * @return
1474       *   if this block must be executed at a particular point in time, that
1475       *   point in time.  Otherwise <i>null</i> is returned, indicating that
1476       *   this block may be scheduled dynamically.
1477       *   
1478       * @since 2008-07-25
1479       */
1480      @XmlTransient  //using variable directly
1481      public Date getFixedStartTime()
1482      {
1483        return fixedStartTime;
1484      }
1485    
1486      /**
1487       * Changes the type of this block to {@link SchedulingType#FIXED_DATE} and
1488       * sets the start time.
1489       * 
1490       * @param startTime
1491       *   the time at which this block must be executed.  If this value is
1492       *   <i>null</i> this method does nothing.
1493       *   
1494       * @since 2008-07-25
1495       */
1496      public void setFixedStartTime(Date startTime)
1497      {
1498        if (startTime != null)
1499        {
1500          setType(SchedulingType.FIXED_DATE);
1501          fixedStartTime = startTime;
1502        }
1503      }
1504    
1505      /**
1506       * Returns the amount of time allocated for one execution of this scheduling
1507       * block.
1508       * @return the amount of time allocated for one execution of this scheduling
1509       *         block.
1510       * @throws ValidationException if this method is unable to calculate the SB's
1511       * length due to a lack of information in the SB.
1512       */
1513      public TimeDuration calculateTimePerExecution()
1514      {
1515        AbstractScheduleIterator iter = new AbstractScheduleIterator();
1516        iter.setValidatesSchedulingBlock(false);
1517    
1518        try
1519        {
1520          iter.iterateOverSchedulingBlock(this);
1521        }
1522    
1523        catch (CoordinateConversionException cce)
1524        {
1525          ValidationException ve = new ValidationException(cce);
1526          ve.getFailures().add(new ValidationFailure(
1527            "There has been an internal error trying to convert coordinate systems.",
1528            "There has been an internal error trying to convert coordinate systems." + cce.getMessage(),
1529            FailureSeverity.ERROR,
1530            this,
1531            "",
1532            ""
1533          ));
1534    
1535          throw ve;
1536        }
1537    
1538        catch (IllegalArgumentException iae)
1539        {
1540          ValidationException ve = new ValidationException(iae);
1541          ve.getFailures().add(new ValidationFailure(
1542            iae.getMessage(),
1543            iae.getMessage(),
1544            FailureSeverity.ERROR,
1545            this,
1546            "",
1547            ""
1548          ));
1549    
1550          throw ve;
1551        }
1552    
1553        catch (SourceNotFoundException snf)
1554        {
1555          ValidationException ve = new ValidationException(snf);
1556          ve.getFailures().add(new ValidationFailure(
1557            snf.getMessage(),
1558            snf.getMessage(),
1559            FailureSeverity.ERROR,
1560            this,
1561            "",
1562            ""
1563          ));
1564    
1565          throw ve;
1566        }
1567    
1568        return iter.getScheduleDuration();
1569      }
1570      
1571      /**
1572       * Returns the total time allocated to this scheduling block.
1573       * The total time is defined as: <tt>calculateTimePerExecution() *
1574       * getAuthorizedCount()</tt>.
1575       * 
1576       * @return the total time allocated to this scheduling block.
1577       */
1578      public TimeDuration getTotalTime()
1579      {
1580        TimeDuration totalTime = calculateTimePerExecution();
1581    
1582        totalTime.multiplyBy(new BigDecimal(authorizedCount));
1583    
1584        return totalTime;
1585      }
1586    
1587      /**
1588       * Sets the preferred date range for execution of this scheduling block.
1589       * <p>
1590       * Note that this scheduling block will not hold a reference to
1591       * {@code range}, but will instead keep a copy of it.</p>
1592       * 
1593       * @param range the preferred date range for execution of this scheduling
1594       *              block.
1595       */
1596      public void setPreferredDateRange(TimeInterval range)
1597      {
1598        Calendar calendar = Calendar.getInstance();
1599    
1600        //Make sure date at start of range is also at start of day
1601        calendar.setTime(range.getStart());
1602        calendar.set(Calendar.HOUR_OF_DAY, 0);
1603        calendar.set(Calendar.MINUTE,      0);
1604        calendar.set(Calendar.SECOND,      0);
1605        calendar.set(Calendar.MILLISECOND, 0);
1606        Date start = calendar.getTime();
1607    
1608        //Make sure date at end of range is also at end of day
1609        calendar.setTime(range.getEnd());
1610        calendar.set(Calendar.HOUR_OF_DAY,  23);
1611        calendar.set(Calendar.MINUTE,       59);
1612        calendar.set(Calendar.SECOND,       59);
1613        calendar.set(Calendar.MILLISECOND, 999);
1614        Date end = calendar.getTime();
1615        
1616        preferredDateRange.set(start, end);
1617      }
1618      
1619      /**
1620       * Returns the preferred date range for execution of this scheduling block.
1621       * The returned interval is a copy of the one held internally by this
1622       * block.
1623       * 
1624       * @return the preferred date range for execution of this scheduling block.
1625       */
1626      @XmlElement
1627      public TimeInterval getPreferredDateRange()
1628      {
1629        return preferredDateRange.clone();
1630      }
1631      
1632      /**
1633       * Sets the range of allowable execution start times for this scheduling
1634       * block, expressed in local sidereal time.
1635       * <p>
1636       * Note that this scheduling block will hold a reference to {@code range}
1637       * (unless it is <i>null</i>); it will not store a copy.  This means
1638       * that any changes a client makes to {@code range} <i>after</i>
1639       * calling this method will be reflected in this object.</p>
1640       * 
1641       * @param range
1642       */
1643      public void setLstStartRange(TimeOfDayInterval range)
1644      {
1645        lstStartRange = (range == null) ? new TimeOfDayInterval() : range;
1646      }
1647      
1648      /**
1649       * Returns the range of allowable execution start times for this scheduling
1650       * block, expressed in local sidereal time.
1651       * <p>
1652       * The returned range, which is guaranteed to be non-null,
1653       * is the actual range held by this block,
1654       * so changes made to it will be reflected in this object.</p>
1655       * 
1656       * @return the allowable LST range for beginning execution of this scheduling
1657       *         block.
1658       */
1659      @XmlElement
1660      public TimeOfDayInterval getLstStartRange()
1661      {
1662        return lstStartRange;
1663      }
1664    
1665      /**
1666       * Sets the amount of time between successive executions of this block.
1667       * <p>
1668       * Note that this scheduling block will hold a reference to {@code interval}
1669       * (unless it is <i>null</i>); it will not store a copy.  This means
1670       * that any changes a client makes to {@code interval} <i>after</i>
1671       * calling this method will be reflected in this object.</p>
1672       * 
1673       * @param interval the amount of time between successive executions of this
1674       *                 block.
1675       */
1676      public void setMonitoringInterval(TimeDuration interval)
1677      {
1678        monitoringInterval = (interval == null) ? new TimeDuration() : interval;
1679      }
1680      
1681      /**
1682       * Returns the amount of time between successive executions of this block.
1683       * <p>
1684       * The returned amount, which is guaranteed to be non-null,
1685       * is the actual amount held by this block,
1686       * so changes made to it will be reflected in this object.</p>
1687       * 
1688       * @return the amount of time between successive executions of this block.
1689       */
1690      public TimeDuration getMonitoringInterval()
1691      {
1692        return monitoringInterval; 
1693      }
1694      
1695      //============================================================================
1696      // SCHEDULING AIDES 
1697      //============================================================================
1698      
1699      /** @deprecated Does nothing. */
1700      @Deprecated public void setConstraints(List<Constraint> replacementList) { }
1701    
1702      /** @deprecated Returns an empty list. */
1703      @Deprecated @XmlTransient public List<Constraint> getConstraints()
1704      {
1705        return new ArrayList<Constraint>();
1706      }
1707      
1708      /**
1709       * Returns the environmental scheduling constraints of this block.
1710       * <p>
1711       * The returned object, which is guaranteed to be non-null,
1712       * is the actual instance held by this block,
1713       * so changes made to it will be reflected in this scheduling block.</p>
1714       * 
1715       * @return the environmental scheduling constraints of this block.
1716       */
1717      public EnvironmentalConstraints getEnvironmentalConstraints()
1718      {
1719        return environmentalConstraints;
1720      }
1721      
1722      //Here for persistence mechanisms such as JAXB & Hibernate
1723      @XmlElement
1724      @SuppressWarnings("unused")
1725      private void setEnvironmentalConstraints(EnvironmentalConstraints newEC)
1726      {
1727        environmentalConstraints =
1728          (newEC == null) ? new EnvironmentalConstraints() : newEC;
1729      }
1730      
1731      /**
1732       * Sets the position at which the telescope is assumed to be pointing
1733       * just prior to the start of this scheduling block.
1734       * <p>
1735       * A value of <i>null</i> is permitted and will result in the
1736       * {@link #getAssumedTelescopePointing() corresponding getter} returning
1737       * a default position.  Note that when {@code newPosition} is not <i>null</i>
1738       * this method does <i>not</i> hold a reference to it, but holds a copy.</p>
1739       *  
1740       * @param newPosition
1741       *   the new assumed telescope pointing position.
1742       *   
1743       * @since 2009-02-18
1744       */
1745      @XmlTransient
1746      public void setAssumedTelescopePointing(EvlaPointingPosition newPosition)
1747      {
1748        assumedTelescopePointing = (newPosition == null) ? null : newPosition.clone();
1749      }
1750      
1751      /**
1752       * Returns the position at which the telescope is assumed to be pointing
1753       * just prior to the start of this scheduling block.  The returned value
1754       * is never <i>null</i>.  It is either equal to the value most recently
1755       * send to the {@link #setAssumedTelescopePointing(EvlaPointingPosition)
1756       * corresponding setter} or, if that value was <i>null</i>, is a default
1757       * position.
1758       * <p>
1759       * The returned object is not held internally by this scheduling block,
1760       * so changes made to it will not be reflected herein.</p>
1761       * 
1762       * @return
1763       *   the position at which the telescope is assumed to be pointing just
1764       *   prior to the start of this scheduling block.
1765       *   
1766       * @since 2009-02-18
1767       */
1768      public EvlaPointingPosition getAssumedTelescopePointing()
1769      {
1770        //If it had been set, return a clone
1771        if (assumedTelescopePointing != null)
1772          return assumedTelescopePointing.clone();
1773        
1774        //Otherwise, return a default position
1775        return new EvlaPointingPosition();
1776      }
1777      
1778      /**
1779       * Returns <i>true</i> if the position provided by
1780       * {@link #getAssumedTelescopePointing()} is a default position.
1781       * 
1782       * @return
1783       *   Returns <i>true</i> if the position provided by
1784       *   <tt>getAssumedTelescopePointing()</tt> is a default position.
1785       */
1786      public boolean useDefaultAssumedTelescopePointing()
1787      {
1788        return assumedTelescopePointing == null;
1789      }
1790      
1791      /**
1792       * Sets the set of priorities of this scheduling block.
1793       * 
1794       * @param replacementList a set of priorities representing the importance of
1795       *          this scheduling block with regard to a variety of criteria such
1796       *          as scientific rating, etc.
1797       *          If {@code replacementList} is <i>null</i>, it will be interpreted
1798       *          as an empty list.
1799       */
1800      @XmlElementWrapper
1801      @XmlElementRefs
1802      ({
1803        @XmlElementRef( type=Priority.class )
1804      })
1805      public void setPriorities(List<Priority> replacementList)
1806      {
1807        priorities = (replacementList == null) ? new ArrayList<Priority>()
1808                                               : replacementList;
1809      }
1810    
1811      /**
1812       * Returns the set of priorities for this scheduling block.
1813       * 
1814       * @return the set of priorities for this scheduling block.
1815       */
1816      public List<Priority> getPriorities()
1817      {
1818        return priorities;
1819      }
1820    
1821      //============================================================================
1822      // SOURCES (TARGET & CALIBRATORS) 
1823      //============================================================================
1824    
1825      /**
1826       * Returns the sources to be observed by this scheduling block at
1827       * the current time.  The current time is used to evaluate
1828       * any {@link edu.nrao.sss.model.source.SourceLookupTable}s held
1829       * by this scheduling block.
1830       * 
1831       * @return the sources to be observed at the current time.
1832       */
1833      public Set<Source> getSources()
1834      {
1835        return getSources(new Date());
1836      }
1837      
1838      /**
1839       * Returns the sources to be observed by this scheduling block at
1840       * {@code dateTime}.  The {@code dateTime} parameter is used to evaluate
1841       * any {@link edu.nrao.sss.model.source.SourceLookupTable}s held
1842       * by this scheduling block.
1843       * 
1844       * @param dateTime the time for which sources are requested.
1845       * 
1846       * @return the sources to be observed at the given time.
1847       */
1848      public Set<Source> getSources(Date dateTime)
1849      {
1850        Set<Source> sources = new HashSet<Source>();
1851        
1852        for (Scan scan : scanSequence.toScanSet())
1853        {
1854          if (scan instanceof SwitchingScan)
1855            sources.addAll(((SwitchingScan)scan).getSources(dateTime));
1856          else
1857            sources.add(scan.getSource(dateTime));
1858        }
1859        
1860        return sources;
1861      }
1862      
1863      //============================================================================
1864      // CALIBRATIONS 
1865      //============================================================================
1866    
1867      /**
1868       * Replaces this block's list of service calibrations with
1869       * {@code replacementList}.
1870       * 
1871       * @param replacementList a replacement list of service calibrations for this
1872       *                        scheduling block.  If {@code replacementList} is
1873       *                        <i>null</i>, it will be interpreted as an empty
1874       *                        list.
1875       */
1876      @XmlElementWrapper
1877      @XmlElement(name="serviceCalibration")
1878      public void setServiceCalibrations(List<ServiceCalibration> replacementList)
1879      {
1880        serviceCalibrations =
1881          (replacementList == null) ? new ArrayList<ServiceCalibration>()
1882                                    : replacementList;
1883      }
1884      
1885      /**
1886       * Returns a list of the service calibrations required by this scheduling
1887       * block.
1888       * <p>
1889       * The return value is guaranteed to be non-null.  It is
1890       * also the list that is held internally by this scheduling
1891       * block, so any changes made to the returned list
1892       * will be reflected in this object.</p>
1893       * 
1894       * @return a list of the service calibrations required by this scheduling
1895       *         block.
1896       */
1897      public List<ServiceCalibration> getServiceCalibrations()
1898      {
1899        return serviceCalibrations;
1900      }
1901      
1902      //============================================================================
1903      // 
1904      //============================================================================
1905    
1906      /**
1907       * Returns <i>true</i> if this scheduling block can be scheduled for
1908       * observation in the face of the given conditions.
1909       * <p>
1910       * This scheduling block compares the given conditions to its
1911       * constraints and decides whether or not it may be submitted for
1912       * scheduling.  This method is biased toward returning a value
1913       * of <i>true</i> so that the scheduler can be presented with as
1914       * many scheduling blocks as possible.  This method returns <i>false</i>
1915       * only when the conditions are so negative (for example, if the
1916       * source is not in the sky at the time of condition XXX) that
1917       * scheduling makes no sense at all.</p>
1918       * 
1919       * @return <i>true</i> if this scheduling block can be scheduled for
1920       *         observation.
1921       *         
1922       * @see #evaluateSchedulability()
1923       */
1924      /*public boolean canBeScheduled(Object conditions1, Object conditions2)
1925      {
1926        return evaluateSchedulability(conditions1, conditions2).equals(SchedulingConstraint.NONE);
1927      }*/
1928      
1929      //Scheduling Stuff - modification I made.
1930      //My idea was to evaluate the external stuff like weather and time in the scheduler as
1931      //a Constraint.
1932      //Things that would be evaluated here would be "internal" things like whether the prerequisites on
1933      //the scheduling block had been met, whether its status was correct, etc.  We can change it back
1934      //if you like the other way - just shooting for a compile at this point.
1935      public boolean canBeScheduled(){
1936              return evaluateSchedulability().equals( SchedulingConstraint.NONE );
1937      }
1938      
1939      /**
1940       * Returns {@code SchedulingConstraint.NONE} if this scheduling block
1941       * may be scheduled for observation in the face of the given conditions.
1942       * <p>
1943       * If the given conditions are outside the contraints of this scheduling
1944       * block, this method will return a value that indicates which constraint
1945       * thwarted the scheduling.  If multiple conditions are outside this
1946       * block's constraints...TODO.</p>
1947       * 
1948       * @return {@code SchedulingConstraint.NONE} if this scheduling block
1949       *         may be scheduled for observation.
1950       */
1951     /* public SchedulingConstraint evaluateSchedulability(Object conditions1, Object conditions2)
1952      {
1953        //Test our own scheduling status
1954        SchedulingConstraint constraint = testSchedulingConstraint(getStatus());
1955        
1956        //Test the prequisite scheduling blocks
1957        if (constraint.equals(SchedulingConstraint.NONE))
1958          constraint = testSchedulingConstraint(getAllPrerequisites());
1959        
1960        //Test the date and time
1961        //if (constraint.equals(SchedulingConstraint.NONE))
1962        //  constraint = testSchedulingConstraint(conditions1);
1963          
1964        //Test the weather
1965        //if (constraint.equals(SchedulingConstraint.NONE))
1966        //  constraint = testSchedulingConstraint(conditions2);
1967        
1968        //Test...
1969        //if (constraint.equals(SchedulingConstraint.NONE))
1970        //  constraint = testSchedulingConstraint(conditions3);
1971        
1972        return constraint;
1973      }*/
1974      
1975      //Idea is that this tests the schedulability based on INTERNAL factors.
1976      public SchedulingConstraint evaluateSchedulability(){
1977                    
1978                    //Test our own scheduling status
1979                SchedulingConstraint constraint = testSchedulingConstraint(getExecutionStatus());
1980                
1981                //Test the prequisite scheduling blocks
1982                if (constraint.equals(SchedulingConstraint.NONE)){
1983                  constraint = testSchedulingConstraint(getAllPrerequisites());
1984                }
1985                return constraint;
1986      }
1987      
1988      private SchedulingConstraint testSchedulingConstraint(EventSetStatus schedStatus)
1989      {
1990        SchedulingConstraint constraint;
1991    
1992        switch (getExecutionStatus())
1993        {
1994          case CANCELED:                   //intentional fall-through
1995          case COMPLETED:                  //intentional fall-through
1996          case IN_PROGRESS:                //intentional fall-through
1997          case SCHEDULED_BUT_NOT_STARTED:
1998            constraint = SchedulingConstraint.SCHEDULING_STATUS;
1999            break;
2000          default:
2001            constraint = SchedulingConstraint.NONE;
2002        }
2003        
2004        return constraint;
2005      }
2006      
2007      private SchedulingConstraint testSchedulingConstraint(Set<SchedulingBlock> prereqSet)
2008      {
2009        SchedulingConstraint constraint = SchedulingConstraint.NONE;
2010    
2011        for (SchedulingBlock prereq : prereqSet)
2012        {
2013          switch (prereq.getExecutionStatus())
2014          {
2015            case IN_PROGRESS:                //intentional fall-through
2016            case NOT_YET_SCHEDULED:          //intentional fall-through
2017            case SCHEDULED_BUT_NOT_STARTED:
2018              constraint = SchedulingConstraint.PREREQUISITE;
2019              break;
2020            default:
2021              //do nothing
2022          }
2023    
2024          if (!constraint.equals(SchedulingConstraint.NONE))
2025            break;
2026        }
2027        
2028        return constraint;
2029      }
2030    
2031      //============================================================================
2032      // INTERFACE UserAccountable
2033      //============================================================================
2034      
2035      public void setCreatedBy(Long userId)
2036      {
2037        createdBy = (userId == null) ? UserAccountable.NULL_USER_ID : userId;
2038      }
2039      
2040      public void setCreatedOn(Date d)
2041      {
2042        if (d != null)
2043          createdOn = d;
2044      }
2045    
2046      public void setLastUpdatedBy(Long userId)
2047      {
2048        lastUpdatedBy = (userId == null) ? UserAccountable.NULL_USER_ID : userId;
2049      }
2050    
2051      public void setLastUpdatedOn(Date d)
2052      {
2053        if (d != null)
2054          lastUpdatedOn = d;
2055      }
2056    
2057      public Long getCreatedBy()      { return createdBy;     }
2058      public Date getCreatedOn()      { return createdOn;     }
2059      public Long getLastUpdatedBy()  { return lastUpdatedBy; }
2060      public Date getLastUpdatedOn()  { return lastUpdatedOn; }
2061    
2062      /**
2063       * Returns an observation script that is used to control a telescope.
2064       * @return an observation script that can be used by the executor that
2065       *         runs a telescope.
2066       */
2067      public String toObserveScript()
2068        throws ValidationException
2069      {
2070        return toObserveScript(null);
2071      }
2072      
2073      /**
2074       * Returns an observation script that is used to control a telescope.
2075       *
2076       * @param startingDate the start date and time of the script, overrides SB
2077       *        properties.  Can be null (values in this SB will not be overridden
2078       *        in this case).
2079       *
2080       * @return an observation script that can be used by the executor that
2081       *         runs a telescope.
2082       *
2083       * @throws ValidationException if this SB fails validation checks.
2084       */
2085      public String toObserveScript(Date startingDate)
2086        throws ValidationException
2087      {
2088        ObserveScriptBuilder builder = new ObserveScriptBuilder();
2089        String script = builder.toScript(this, startingDate);
2090    
2091        if (builder.hasValidationErrors())
2092        {
2093          // some validation failures are just warnings, only throw and exception
2094          // if they're actually errors.
2095    
2096          boolean foundRealErrors = false;
2097          for (ValidationFailure failure : builder.getValidationFailures())
2098          {
2099            FailureSeverity severity = failure.getSeverity();
2100            if (FailureSeverity.ERROR.equals(severity) || FailureSeverity.FATAL.equals(severity))
2101            {
2102              foundRealErrors = true;
2103              break;
2104            }
2105          }
2106    
2107          if (foundRealErrors)
2108          {
2109            ValidationException ve = new ValidationException();
2110            ve.getFailures().addAll(builder.getValidationFailures());
2111            throw ve;
2112          }
2113        }
2114    
2115        return script;
2116      }
2117    
2118      //============================================================================
2119      // COMMENTS
2120      //============================================================================
2121    
2122      /**
2123       * Sets comments about this scheduling block.
2124       * 
2125       * @param replacementComments
2126       *   free-form text about this scheduling block.
2127       *   These comments replace all previously set comments.
2128       *   A <i>null</i> value will be replaced by the empty string (<tt>""</tt>).
2129       * 
2130       * @see #setCommentsToOperator(String)
2131       * @see #appendComments(String)
2132       */
2133      public void setComments(String replacementComments)
2134      {
2135        comments = (replacementComments == null) ? NO_COMMENTS : replacementComments;
2136      }
2137      
2138      /**
2139       * Adds additional comments to those already associated with this scheduling block.
2140       * 
2141       * @param additionalComments
2142       *   new, additional, comments about this scheduling block.
2143       *   
2144       * @see #appendCommentsToOperator(String)
2145       * @see #setComments(String)
2146       */
2147      public void appendComments(String additionalComments)
2148      {
2149        if ((additionalComments != null) && (additionalComments.length() > 0))
2150        {
2151          if (!comments.equals(NO_COMMENTS))
2152            comments = comments + System.getProperty("line.separator");
2153          
2154          comments = comments + additionalComments;
2155        }
2156      }
2157    
2158      /**
2159       * Returns comments about this scheduling block.
2160       * The value returned is guaranteed to be non-null.
2161       * 
2162       * @return
2163       *   free-form text about this scheduling block.
2164       *   
2165       * @see #getComments()
2166       * @see #appendComments(String)
2167       * @see #setComments(String)
2168       */
2169      public String getComments()
2170      {
2171        return comments;
2172      }
2173    
2174      /**
2175       * Stores comments intended for use by an operator.
2176       * 
2177       * @param replacementComments
2178       *   comments intended for use by an operator.
2179       *   These comments replace all previously set comments.
2180       *   A <i>null</i> value will be replaced by the empty string (<tt>""</tt>}).
2181       * 
2182       * @see #setComments(String)
2183       * @see #appendCommentsToOperator(String)
2184       */
2185      public void setCommentsToOperator(String replacementComments)
2186      {
2187        commentsToOperator = (replacementComments == null) ? NO_COMMENTS : replacementComments;
2188      }
2189      
2190      /**
2191       * Adds additional comments to those already associated with this scheduling block.
2192       * These comments are intended for use by an operator.
2193       * 
2194       * @param additionalComments
2195       *   new, additional, comments to an operator for this scheduling block.
2196       *   
2197       * @see #appendComments(String)
2198       * @see #setCommentsToOperator(String)
2199       */
2200      public void appendCommentsToOperator(String additionalComments)
2201      {
2202        if ((additionalComments != null) && (additionalComments.length() > 0))
2203        {
2204          if (!commentsToOperator.equals(NO_COMMENTS))
2205            commentsToOperator = commentsToOperator + System.getProperty("line.separator");
2206          
2207          commentsToOperator = commentsToOperator + additionalComments;
2208        }
2209      }
2210    
2211      /**
2212       * Returns comments intended for use by an operator.
2213       * The value returned is guaranteed to be non-null.
2214       * 
2215       * @return
2216       *   comments intended for use by an operator.
2217       *   
2218       * @see #getComments()
2219       * @see #appendCommentsToOperator(String)
2220       * @see #setCommentsToOperator(String)
2221       */
2222      public String getCommentsToOperator()
2223      {
2224        return commentsToOperator;
2225      }
2226    
2227      //============================================================================
2228      // TEXT
2229      //============================================================================
2230      
2231      /**
2232       * Returns a text representation of this scheduling block.
2233       * The default form of the text is XML.  However, if anything goes wrong
2234       * during the conversion to XML, an alternate, and much abbreviated, form
2235       * will be returned.
2236       * 
2237       * @return a text representation of this scheduling block.
2238       * 
2239       * @see #toSummaryString()
2240       */
2241      public String toString()
2242      {
2243        try {
2244          return toXml();
2245        }
2246        catch (Exception ex) {
2247          return toSummaryString();
2248        }
2249      }
2250      
2251      /**
2252       * Returns a short textual description of this scheduling block.
2253       * @return a short textual description of this scheduling block.
2254       */
2255      public String toSummaryString()
2256      {
2257        StringBuilder buff = new StringBuilder();
2258        
2259        buff.append("name=").append(name).append( "\n");
2260        buff.append("id=").append(id).append( "\n");
2261        
2262       
2263        return buff.toString();
2264      }
2265    
2266      /**
2267       * Returns an XML representation of this scheduling block.
2268       * @return an XML representation of this scheduling block.
2269       * @throws JAXBException if anything goes wrong during the conversion to XML.
2270       * @see #writeAsXmlTo(Writer)
2271       */
2272      public String toXml() throws JAXBException
2273      {
2274        return JaxbUtility.getSharedInstance().objectToXmlString(this);
2275      }
2276      
2277      /**
2278       * Writes an XML representation of this scheduling block to {@code writer}.
2279       * @param writer the device to which XML is written.
2280       * @throws JAXBException if anything goes wrong during the conversion to XML.
2281       */
2282      public void writeAsXmlTo(Writer writer) throws JAXBException
2283      {
2284        JaxbUtility.getSharedInstance().writeObjectAsXmlTo(writer, this, null);
2285      }
2286      
2287      /**
2288       * Creates a new scheduling block  from the XML data in the given file.
2289       * 
2290       * @param xmlFile the name of an XML file.  This method will attempt to locate
2291       *                the file by using {@link Class#getResource(String)}.
2292       *                
2293       * @return a new scheduling block  from the XML data in the given file.
2294       * 
2295       * @throws FileNotFoundException if the XML file cannot be found.
2296       * 
2297       * @throws JAXBException if the schema file used (if any) is malformed, if
2298       *           the XML file cannot be read, or if the XML file is not
2299       *           schema-valid.
2300       * 
2301       * @throws XMLStreamException if there is a problem opening the XML file,
2302       *           if the XML is not well-formed, or for some other
2303       *           "unexpected processing conditions".
2304       */
2305      public static SchedulingBlock fromXml(String xmlFile)
2306        throws JAXBException, XMLStreamException, FileNotFoundException
2307      {
2308        SchedulingBlock newBlock =
2309          JaxbUtility.getSharedInstance().xmlFileToObject(xmlFile, SchedulingBlock.class);
2310        
2311        newBlock.testScansForResourceFromJaxb();
2312        
2313        return newBlock;
2314      }
2315      
2316      /**
2317       * Creates a new scheduling block based on the XML data read from
2318       * {@code reader}.
2319       * 
2320       * @param reader the source of the XML data.
2321       *               If this value is <i>null</i>, <i>null</i> is returned.
2322       *               
2323       * @return a new scheduling block based on the XML data read from
2324       * {@code reader}.
2325       * 
2326       * @throws XMLStreamException if the XML is not well-formed,
2327       *           or for some other "unexpected processing conditions".
2328       *           
2329       * @throws JAXBException if anything else goes wrong during the
2330       *           transformation.
2331       */
2332      public static SchedulingBlock fromXml(Reader reader)
2333        throws JAXBException, XMLStreamException
2334      {
2335        SchedulingBlock newBlock =
2336          JaxbUtility.getSharedInstance().readObjectAsXmlFrom(reader, SchedulingBlock.class, null);
2337        
2338        newBlock.testScansForResourceFromJaxb();
2339        
2340        return newBlock;
2341      }
2342    
2343      /**
2344       * Meant for use by containers of sched blocks; most clients should not use this method.
2345       * @throws JAXBException
2346       *   if the any scan of this block has
2347       *   no resource and the useResourceOfPriorScan flag is false.
2348       */
2349      void testScansForResourceFromJaxb() throws JAXBException
2350      {
2351        scanSequence.testScansForResourceFromJaxb();
2352      }
2353    
2354      //============================================================================
2355      // 
2356      //============================================================================
2357      
2358      /**
2359       *  Returns a scheduling block that is almost a copy of this one.
2360       *  <p>
2361       *  The returned element is, for the most part, a deep copy of this one.
2362       *  However, there are a few exceptions:
2363       *  <ol>
2364       *    <li>The ID will be set to
2365       *        {@link Identifiable#UNIDENTIFIED}.</li>
2366       *    <li>The programBlock will be <i>null</i>.</li>
2367       *    <li>The parentBlock will be <i>null</i>.</li>
2368       *    <li>The createdOn and lastUpdatedOn attributes will be set to the
2369       *        current system time.</li>
2370       *  </ol></p>
2371       *  <p>
2372       *  If anything goes wrong during the cloning procedure,
2373       *  a {@code RuntimeException} will be thrown.</p>
2374       *  
2375       * @return a near copy of this scheduling block.
2376       */
2377      public SchedulingBlock clone()
2378      {
2379        SchedulingBlock clone = cloneWithoutPrerequisites();
2380    
2381        //The call above ensures that the clone has its own list.
2382        //We now populate that list with clones of our prerequisites.
2383        for (SchedulingBlock sb : this.prereqs)
2384          clone.addPrerequisite(sb.clone());
2385        
2386        return clone;
2387      }
2388      
2389      /**
2390       *  Returns a scheduling block that is almost a copy of this one.
2391       *  <p>
2392       *  The returned element is, for the most part, a deep copy of this one.
2393       *  However, there are a few exceptions:
2394       *  <ol>
2395       *    <li>The ID will be set to
2396       *        {@link Identifiable#UNIDENTIFIED}.</li>
2397       *    <li>The XML ID will be given a new UUID.</li>
2398       *    <li>The programBlock will be <i>null</i>.</li>
2399       *    <li>The parentBlock will be <i>null</i>.</li>
2400       *    <li>The createdOn and lastUpdatedOn attributes will be set to the
2401       *        current system time.</li>
2402       *    <li>The list of prerequisites will be empty.</li>
2403       *  </ol></p>
2404       *  <p>
2405       *  If anything goes wrong during the cloning procedure,
2406       *  a {@code RuntimeException} will be thrown.</p>
2407       *  
2408       * @return a near copy of this scheduling block, without prerequisites.
2409       */
2410      public SchedulingBlock cloneWithoutPrerequisites()
2411      {
2412        SchedulingBlock clone = null;
2413        
2414        try
2415        {
2416          //This line takes care of the primitive & immutable fields properly
2417          clone = (SchedulingBlock)super.clone();
2418          
2419          //We do NOT want the clone to have the same ID as the original.
2420          //The ID is here for the persistence layer; it is in charge of
2421          //setting IDs.  To help it, we put the clone's ID in the uninitialized
2422          //state.
2423          clone.id = Identifiable.UNIDENTIFIED;
2424          
2425          clone.xmlId = "schedBlock-" + UUID.randomUUID().toString();
2426    
2427          clone.createdOn     = new Date();
2428          clone.lastUpdatedOn = clone.createdOn;
2429    
2430          clone.programBlock = null;
2431          clone.parentBlock  = null;
2432     
2433          //Make two-way link between new SB and its scanSequence ScanLoop
2434          clone.scanSequence = this.scanSequence.clone();
2435          clone.scanSequence.setSchedulingBlock(clone);
2436          
2437          //New empty set of prereqs
2438          clone.prereqs = new HashSet<SchedulingBlock>();
2439          
2440          if (this.fixedStartTime != null)
2441            clone.fixedStartTime = (Date)this.fixedStartTime.clone();
2442          
2443          clone.preferredDateRange = this.preferredDateRange.clone();
2444          clone.lstStartRange      = this.lstStartRange.clone();
2445          clone.monitoringInterval = this.monitoringInterval.clone();
2446          
2447          if (this.assumedTelescopePointing != null)
2448            clone.assumedTelescopePointing = this.assumedTelescopePointing.clone();
2449          
2450          clone.environmentalConstraints = this.environmentalConstraints.clone();
2451          
2452          //Clone the set AND the contained elements
2453          clone.serviceCalibrations = new ArrayList<ServiceCalibration>();
2454          for (ServiceCalibration sc : this.serviceCalibrations)
2455            clone.serviceCalibrations.add(sc.clone());
2456          
2457          //Clone the execution blocks
2458          clone.executionBlocks = new ArrayList<ExecutionBlock>();
2459          for (ExecutionBlock origEB : executionBlocks)
2460          {
2461            ExecutionBlock clonedEB = origEB.clone();
2462            clonedEB.simplySetSchedulingBlock(clone);
2463            clone.executionBlocks.add(clonedEB);
2464          }
2465        }
2466        catch (Exception ex)
2467        {
2468          throw new RuntimeException(ex);
2469        }
2470        
2471        return clone;
2472      }
2473      
2474      /**
2475       * Returns <i>true</i> if {@code o} is equal to this scheduling block.
2476       * <p>
2477       * In order to be equal to this scheduling block, {@code o} must be non-null
2478       * and of the same class as this block. Equality is determined by examining
2479       * the equality of corresponding attributes, with the following exceptions,
2480       * which are ignored when assessing equality: 
2481       * <ol>
2482       *   <li>id</li>
2483       *   <li>programBlock</li>
2484       *   <li>parentBlock</li>
2485       *   <li>createdOn</li>
2486       *   <li>createdBy</li>
2487       *   <li>lastUpdatedOn</li>
2488       *   <li>lastUpdatedBy</li>
2489       *   <li>alteration status <i>(Under the theory that two things may be
2490       *         equal regardless of how they got that way.)</i></li>
2491       * </ol></p>
2492       */
2493      @Override
2494      public boolean equals(Object o)
2495      {
2496        //Quick exit if o is null
2497        if (o == null)
2498          return false;
2499        
2500        //Quick exit if o is this
2501        if (o == this)
2502          return true;
2503        
2504        //Quick exit if classes are different
2505        if (!o.getClass().equals(this.getClass()))
2506          return false;
2507        
2508        SchedulingBlock other = (SchedulingBlock)o;
2509        
2510        //Attributes that we INTENTIONALLY DO NOT COMPARE:
2511        //  id,
2512        //  programBlock, parentBlock
2513        //  createdOn, createdBy, lastUpdatedOn, lastUpdatedBy,
2514        //  alterationStatus
2515    
2516        //Simple attributes
2517        if (!other.name.equals(this.name)                             ||
2518            !other.comments.equals(this.comments)                     ||
2519            !other.commentsToOperator.equals(this.commentsToOperator) ||
2520            !other.type.equals(this.type)                             ||
2521             other.authorizedCount != this.authorizedCount            ||
2522             other.completedCount  != this.completedCount             ||
2523             other.abortedCount    != this.abortedCount               ||
2524            !other.getExecutionStatus().equals(this.getExecutionStatus()))
2525          return false;
2526        
2527        //Observation times
2528        if (!objectsAreEqual(this.fixedStartTime, other.fixedStartTime))
2529          return false;
2530        
2531        if (!other.preferredDateRange.equals(this.preferredDateRange) ||
2532            !other.lstStartRange.equals(this.lstStartRange)           ||
2533            !other.monitoringInterval.equals(this.monitoringInterval))
2534          return false;
2535        
2536        //Scheduling aides
2537        if (!objectsAreEqual(this.assumedTelescopePointing, other.assumedTelescopePointing))
2538          return false;
2539        
2540        if (!other.environmentalConstraints.equals(this.environmentalConstraints))
2541          return false;
2542        
2543        //Service calibrations
2544        if (!other.serviceCalibrations.equals(this.serviceCalibrations))
2545          return false;
2546        
2547        //Scans
2548        if (!other.scanSequence.equals(this.scanSequence))
2549          return false;
2550        
2551        //Exec blocks
2552        if (!other.executionBlocks.equals(this.executionBlocks))
2553          return false;
2554    
2555        //Prerequisites.  This is an expensive test, so save for last.
2556        if (other.prereqs.size() != this.prereqs.size())
2557        {
2558          return false;
2559        }
2560        else //same # of prereqs
2561        {
2562          //HashSet's equals method goes through its contains method, which
2563          //does not work properly for members whose hash codes have changed
2564          //since being added to the set, which seems to happen w/ JAXB
2565          //resolution of IDREFs.  (I have submitted a bug to Sun on this.)
2566          //If we make fresh sets, the set's equals method will work properly.
2567          HashSet<SchedulingBlock> these = new HashSet<SchedulingBlock>( this.prereqs);
2568          HashSet<SchedulingBlock> those = new HashSet<SchedulingBlock>(other.prereqs);
2569          
2570          if (!those.equals(these))
2571            return false;
2572        }
2573    
2574        //No differences found
2575        return true;
2576      }
2577      
2578      private boolean objectsAreEqual(Object thisOne, Object thatOne)
2579      {
2580        return (thisOne == null) ? (thatOne == null) : thisOne.equals(thatOne);
2581      }
2582    
2583      /* (non-Javadoc)
2584       * @see java.lang.Object#hashCode()
2585       */
2586      @Override
2587      public int hashCode()
2588      {
2589        //Taken from the Effective Java book by Joshua Bloch.
2590        //The constants 17 & 37 are arbitrary & carry no meaning.
2591        int result = 17;
2592        
2593        //You MUST keep this method in sync w/ the equals method
2594    
2595        //Simple attributes
2596        result = 37 * result + name.hashCode();
2597        result = 37 * result + comments.hashCode();
2598        result = 37 * result + commentsToOperator.hashCode();
2599        result = 37 * result + type.hashCode();
2600        result = 37 * result + new Integer(authorizedCount).hashCode();
2601        result = 37 * result + new Integer(completedCount).hashCode();
2602        result = 37 * result + new Integer(abortedCount).hashCode();
2603        result = 37 * result + getExecutionStatus().hashCode();
2604        
2605        //Observation times
2606        if (fixedStartTime != null)
2607          result = 37 * result + fixedStartTime.hashCode();
2608        
2609        result = 37 * result + preferredDateRange.hashCode();
2610        result = 37 * result + lstStartRange.hashCode();
2611        result = 37 * result + monitoringInterval.hashCode();
2612        
2613        //Scheduling aides
2614        if (assumedTelescopePointing != null)
2615          result = 37 * result + assumedTelescopePointing.hashCode();
2616        
2617        result = 37 * result + environmentalConstraints.hashCode();
2618        
2619        //Service calibrations
2620        result = 37 * result + serviceCalibrations.hashCode();
2621        
2622        //Scans
2623        result = 37 * result + scanSequence.hashCode();
2624        
2625        //Exec blocks
2626        result = 37 * result + executionBlocks.hashCode();
2627        
2628        //Prerequisites
2629        result = 37 * result + prereqs.hashCode();
2630    
2631        return result;
2632      }
2633    
2634      /**
2635       * Returns a comparator that places all prerequisites before the blocks
2636       * that depend upon them.  Blocks that have no prereqs, and are prereqs
2637       * of nothing, could be placed anywhere in the sorting process.
2638       * 
2639       * @return a comparator that places all prerequisites before the blocks
2640       *         that depend upon them.
2641       */
2642      public static Comparator<SchedulingBlock> getPrequisiteComparator()
2643      {
2644        if (PREREQ_COMPARATOR == null)
2645          PREREQ_COMPARATOR = new PrereqComparator();
2646        
2647        return PREREQ_COMPARATOR;
2648      }
2649      
2650      /**
2651       * A comparator that says prerequisites come after the things
2652       * that depend on them. SBs that have no prereqs and are prereqs
2653       * of nothing could be placed anywhere in the sorting process.
2654       * 
2655       * Stateless & immutable.
2656       */
2657      private static class PrereqComparator implements Comparator<SchedulingBlock>
2658      {
2659        public int compare(SchedulingBlock a, SchedulingBlock b)
2660        {
2661          if (a.isPrerequisiteOf(b))
2662          {
2663            return +1;
2664          }
2665          else if (b.isPrerequisiteOf(a))
2666          {
2667            return -1;
2668          }
2669          else //neither is prereq of other
2670          {
2671            //Put block with more prereqs before other
2672            int sizeDiff = b.getAllPrerequisites().size() -
2673                           a.getAllPrerequisites().size(); 
2674            
2675            if (sizeDiff != 0)
2676              return sizeDiff;
2677            
2678            //Final tie breaker is name
2679            return a.getName().compareTo(b.getName());
2680          }
2681        }
2682      }
2683    
2684      //============================================================================
2685      // 
2686      //============================================================================
2687    
2688      //This is here for quick manual testing
2689      /*
2690      public static void main(String[] args)
2691      {
2692        ProjectBuilder builder = new ProjectBuilder();
2693        builder.setIdentifiers(true);
2694        
2695        SchedulingBlock sb = builder.makeSchedulingBlock();
2696        
2697        if (Math.random() < 0.5)
2698        {
2699          System.out.println("Submitting for scheduling.");
2700          sb.submit();
2701        }
2702    
2703        try
2704        {
2705          sb.writeAsXmlTo(new java.io.PrintWriter(System.out));
2706        }
2707        catch (JAXBException ex)
2708        {
2709          System.out.println("Trouble w/ sb.toXml.  Msg:");
2710          System.out.println(ex.getMessage());
2711          ex.printStackTrace();
2712          
2713          System.out.println("Attempting to write XML w/out schema verification:");
2714          JaxbUtility.getSharedInstance().setLookForDefaultSchema(false);
2715          try
2716          {
2717            sb.writeAsXmlTo(new java.io.PrintWriter(System.out));
2718          }
2719          catch (JAXBException ex2)
2720          {
2721            System.out.println("Still had trouble w/ sb.toXml.  Msg:");
2722            System.out.println(ex.getMessage());
2723            ex.printStackTrace();
2724          }
2725        }
2726      }
2727      */
2728      /*
2729      public static void main(String[] args)
2730      {
2731        ScanBuilder builder = new ScanBuilder();
2732        
2733        SchedulingBlock sb = new SchedulingBlock();
2734        
2735        builder.makeDelayScanFor(sb.scanSequence);
2736        builder.makeSimpleScanFor(sb.scanSequence);
2737        builder.makeTippingScanFor(sb.scanSequence);
2738        builder.makeDelayScanFor(sb.scanSequence);
2739        builder.makeFocusScanFor(sb.scanSequence);
2740    
2741        ValidationManager mgr = new ValidationManager();
2742        mgr.validate(sb, ValidationPurpose.CERTIFY_READY_TO_USE);
2743        
2744        sb = new SchedulingBlock();
2745    
2746        builder.makeSimpleScanFor(sb.scanSequence);
2747        builder.makeTippingScanFor(sb.scanSequence);
2748    
2749        mgr.validate(sb, ValidationPurpose.CERTIFY_READY_TO_USE);
2750      }
2751      */
2752    }