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.util.ArrayList;
007    import java.util.Collection;
008    import java.util.Collections;
009    import java.util.Comparator;
010    import java.util.Date;
011    import java.util.HashSet;
012    import java.util.List;
013    import java.util.Set;
014    import java.util.UUID;
015    
016    import javax.xml.bind.JAXBException;
017    import javax.xml.bind.annotation.XmlAttribute;
018    import javax.xml.bind.annotation.XmlElement;
019    import javax.xml.bind.annotation.XmlElementWrapper;
020    import javax.xml.bind.annotation.XmlID;
021    import javax.xml.bind.annotation.XmlIDREF;
022    import javax.xml.bind.annotation.XmlList;
023    import javax.xml.bind.annotation.XmlRootElement;
024    import javax.xml.bind.annotation.XmlTransient;
025    import javax.xml.bind.annotation.XmlType;
026    import javax.xml.stream.XMLStreamException;
027    
028    import edu.nrao.sss.model.UserAccountable;
029    import edu.nrao.sss.model.project.scheduling.ProgBlock;
030    import edu.nrao.sss.model.resource.TelescopeConfiguration;
031    import edu.nrao.sss.util.EventSetStatus;
032    import edu.nrao.sss.util.EventStatus;
033    import edu.nrao.sss.util.Identifiable;
034    import edu.nrao.sss.util.JaxbUtility;
035    import edu.nrao.sss.validation.FailureSeverity;
036    import edu.nrao.sss.validation.ValidationException;
037    import edu.nrao.sss.validation.ValidationFailure;
038    
039    /**
040     * A set of observations that share a common telescope configuration.
041     * <p>
042     * A {@code ProgramBlock} is part of a {@link Project} and describes in
043     * detail how observations are to be completed.
044     * A {@code ProgramBlock} is also a container of {@link SchedulingBlock}s.</p>
045     * <p>
046     * <b>Version Info:</b>
047     * <table style="margin-left:2em">
048     *   <tr><td>$Revision: 2277 $</td>
049     *   <tr><td>$Date: 2009-04-29 11:19:38 -0600 (Wed, 29 Apr 2009) $</td>
050     *   <tr><td>$Author: dharland $</td>
051     * </table></p>
052     * 
053     * @author David M. Harland
054     * @since 2006-02-24
055     */
056    @XmlRootElement
057    @XmlType(propOrder={"name",
058                        "createdBy","createdOn","lastUpdatedBy","lastUpdatedOn",
059                        "executionStatus",
060                        "acceptableConfigurations", "comments", "prerequisite",
061                        "schedBlocks"})
062    public class ProgramBlock
063      implements Identifiable, UserAccountable, Cloneable, ProgBlock
064    {
065      public static final String DEFAULT_NAME = "[New Program Block]";
066    
067      private static final String NO_COMMENTS = "";
068    
069      private static PrereqComparator PREREQ_COMPARATOR;
070    
071      //IDENTIFICATION
072      private Long   id;         //A unique identifier for the persistence layer.
073      private String name;
074    
075      @XmlAttribute(required=true)
076      @XmlID
077      String xmlId;
078    
079      //USER TRACKING
080      private Long createdBy;      //This is a user ID
081      private Date createdOn;
082      private Long lastUpdatedBy;  //This is a user ID
083      private Date lastUpdatedOn;
084      
085      //CONTAINER (Project) & CONTAINED OBJECTS (SchedulingBlocks)
086      private List<SchedulingBlock> schedBlocks;
087      private Project               project;
088      
089      //OTHER ATTRIBUTES
090      @XmlTransient  //Using IDREFS; see set/getPrerequisite
091      private Set<ProgramBlock>            prereqs;
092      private List<TelescopeConfiguration> acceptableConfigurations;
093      private EventSetStatus               executionStatus;
094      private String                       comments;
095    
096    
097      /** Creates a new instance. */
098      public ProgramBlock()
099      {
100        schedBlocks              = new ArrayList<SchedulingBlock>();
101        prereqs                  = new HashSet<ProgramBlock>();
102        acceptableConfigurations = new ArrayList<TelescopeConfiguration>();
103    
104        xmlId = "progBlock-" + UUID.randomUUID().toString();
105        
106        initialize();
107      }
108      
109      /** Initializes the instance variables of this class.  */
110      private void initialize()
111      {
112        id    = Identifiable.UNIDENTIFIED;
113        name  = DEFAULT_NAME;
114    
115        project = null;
116    
117        createdBy     = UserAccountable.NULL_USER_ID;
118        createdOn     = new Date();
119        lastUpdatedBy = UserAccountable.NULL_USER_ID;
120        lastUpdatedOn = new Date();
121        
122        executionStatus = EventSetStatus.NOT_YET_SCHEDULED;
123        comments        = NO_COMMENTS;
124      }
125      
126      /**
127       *  Resets this project to its initial state.  A reset project has the same
128       *  state as a new project. 
129       */
130      public void reset()
131      {
132        initialize();
133        
134        removeAllSchedulingBlocks();
135        removeAllPrerequisites();
136        acceptableConfigurations.clear();
137      }
138    
139      //============================================================================
140      // IDENTIFICATION
141      //============================================================================
142    
143      /* (non-Javadoc)
144       * @see edu.nrao.sss.model.util.Identifiable#getId()
145       */
146      @XmlAttribute
147      public Long getId()
148      {
149        return id;
150      }
151    
152      void setId(Long id)
153      {
154        this.id = id;
155      }
156    
157      /**
158       * Resets this pb's id to UNIDENTIFIED and calls all of it's
159       * SchedulingBlock's clearId() methods.
160       */
161      public void clearId()
162      {
163        id = Identifiable.UNIDENTIFIED;
164    
165        for (SchedulingBlock sb : getSchedulingBlocks())
166          sb.clearId();
167      }
168    
169      /**
170       * Sets the name of this program block.
171       * <p>
172       * If {@code newName} is <i>null</i> or the empty string
173       * (<tt>""</tt>), the request to change the name will be
174       * denied and the current name will remain in place.</p>
175       * 
176       * @param newName the new name of this program block.
177       */
178      @XmlElement
179      public void setName(String newName)
180      {
181        if (newName != name && newName.length() > 0)
182          name = newName;
183      }
184      
185      /**
186       * Returns the name of this program block.
187       * @return the name of this program block.
188       */
189      public String getName()
190      {
191        return name;
192      }
193      
194      @XmlTransient
195      @Deprecated
196      /** @deprecated Use {@link #getName()}. */
197      public String getLongName()  { return getName(); }
198      
199      @Deprecated
200      /** @deprecated Use {@link #setName(String)}. */
201      public void setLongName(String newName)  { setName(newName); }
202      
203      @XmlTransient
204      @Deprecated
205      /** @deprecated Use {@link #getName()}. */
206      public String getShortName()  { return getName(); }
207      
208      @Deprecated
209      /** @deprecated Use {@link #setName(String)}. */
210      public void setShortName(String newName)  { setName(newName); }
211     
212      //============================================================================
213      // MAIN PROPERTIES
214      //============================================================================
215    
216      /** For use by Hibernate and JAXB. */
217      @XmlList
218      public void setAcceptableConfigurations(List<TelescopeConfiguration> newList)
219      {
220        acceptableConfigurations =
221          (newList == null) ? new ArrayList<TelescopeConfiguration>() : newList;
222      }
223    
224      /**
225       * Returns a list of the telescope configurations that are acceptable to this
226       * program block.  The list is ordered by priority.  In other words, the
227       * configuration found in the 0<sup>th</sup> position is the preferred
228       * configuration, the one found in the 1<sup>st</sup> position is the
229       * runner-up, and so on.
230       * <p>
231       * The returned list is guaranteed to be non-null.  It will normally have
232       * one or more elements, but it is possible for it to be empty.  This
233       * program block does no validation on the elements of the returned list.
234       * It could have repeated elements or elements that pertain to different
235       * kinds of telescopes.  Ideally, though, the list will contain one or more
236       * configurations of the same telescope with no repeated entries.</p>
237       * <p>
238       * The list returned is the one actually held by this program block.
239       * This means that changes to the list made by a client after calling
240       * this method <i>will</i> be reflected in this object.  It also means
241       * that to manipulate this list, clients must first fetch it using
242       * this method; there is not equivalent {@code set} method.</p>
243       * 
244       * @return a list of acceptable telescope configurations.
245       */
246      public List<TelescopeConfiguration> getAcceptableConfigurations()
247      {
248        return acceptableConfigurations;
249      }
250      
251      //============================================================================
252      // CONTAINER (Project)
253      //============================================================================
254    
255      /**
256       * Sets the project to which this program block belongs.
257       * <p>
258       * If this program block is currently contained in a project
259       * that is not the same as the {@code newProject} parameter,
260       * the current project will be told to remove this program block
261       * from its collection of program blocks.  If {@code newProject}
262       * is not <i>null</i>, it will be told to add this program block
263       * to its collection.  Finally, this program block's project
264       * will be set to {@code newProject}, even if it is <i>null</i>.</p>
265       * <p>
266       * Passing this method a {@code newProject} of <i>null</i> has the
267       * effect of disconnecting this program block from any project.</p>
268       * 
269       * @param newProject the project to which this program block belongs.
270       */
271      @XmlTransient
272      public void setProject(Project newProject)
273      {
274        Project formerProject = this.project;
275        
276        //Quick exit if this program block already belongs to newProject.
277        //(Intentional use of "==" here.)
278        if (formerProject == newProject)
279          return;
280    
281        //If newProject is NOT null, it will be in charge of telling
282        //formerProject about its loss of this program block
283        if (newProject != null)
284        {
285          newProject.addProgramBlock(this);
286        }
287        //Otherwise, we must tell the former project ourselves
288        else //newProject == null
289        {
290          if (formerProject != null)
291            formerProject.removeProgramBlock(this);
292        }
293        
294        //Could be null or a real project
295        this.project = newProject;
296      }
297      
298      /**
299       * Sets this program block's project to {@code newProject} without
300       * contacting either the former or new project.  This method is
301       * used only by the Project class.
302       */
303      void simplySetProject(Project newProject)
304      {
305        this.project = newProject;
306      }
307    
308      /**
309       * Returns the project to which this program block belongs, if any.
310       * <p>
311       * This program block may be one of several that belong to
312       * the same project.  If this program block belongs to
313       * no project, the value returned is <i>null</i>.</p>
314       * 
315       * @return the project to which this program block belongs, if any.
316       * 
317       * @see #hasProject()
318       */
319      public Project getProject()
320      {
321        return project;
322      }
323      
324      /**
325       * Returns <i>true</i> if this program block has a non-null project.
326       * <p>
327       * Program blocks should normally be contained within, and therefore
328       * have a non-null, project.  However, there are some situations
329       * where this method will return <i>false</i>:
330       * <ol>
331       *   <li>This program block has just been created and its project
332       *       has not yet been set.</li>
333       *   <li>A client removed this program block from its project and 
334       *       did not place it in a new project.</li>
335       *   <li>A client explicitly set this program block's project to
336       *       <i>null</i>.</li>
337       * </ol></p>
338       * 
339       * @return <i>true</i> if this program block has a non-null project.
340       *         Therefore a return value of <i>true</i> means that 
341       *         you can call {@link #getProject()} and know that
342       *         it will return a non-null object. 
343       */
344      public boolean hasProject()
345      {
346        return project != null;
347      }
348      
349      //Scheduling Stuff
350      //Note:  I have not implemented this, just put it in because it seems like it
351      //is useful for scheduling.  Idea is to make this recursive, and return true
352      //if any of the repetitions of the scheduling block have been scheduled.
353      //Reason this might be useful is that we may want to use an algorithm that increases
354      //the priority of a partially scheduled block in the interest of trying to finish
355      //up things that have been started.
356      public boolean isProjectPartiallyScheduled(){
357        return false;
358      }
359    
360      //============================================================================
361      // CONTAINED OBJECTS (SchedulingBlocks)
362      //============================================================================
363      
364      /**
365       * Creates and returns a new scheduling block that is suitable for use with
366       * this program block.
367       * The returned scheduling block has <i>not</i> been added to
368       * this program block.
369       * 
370       * @return a new scheduling block that can later be added to this program
371       *         block.
372       */
373      public SchedulingBlock createSchedulingBlock()
374      {
375        return new SchedulingBlock();
376      }
377    
378      /**
379       * Adds the given scheduling block to this program block.
380       * <p>
381       * If {@code schedBlock} is already part of this program block,
382       * or if it is <i>null</i>, no action is taken.
383       * Otherwise it is added to this program block, removed from
384       * the program block to which it had been attached (if any),
385       * and updated so that it knows it belongs to this program block.</p>
386       * 
387       * @param schedBlock the scheduling block to be added to this program block.
388       */
389      public void addSchedulingBlock(SchedulingBlock schedBlock)
390      {
391        addSchedulingBlock(schedBlocks.size(), schedBlock);
392      }
393      
394      /**
395       * Adds the given scheduling block to this program block at index {@code idx}.
396       * <p>
397       * If {@code schedBlock} is already part of this program block,
398       * or if it is <i>null</i>, no action is taken.
399       * Otherwise it is added to this program block, removed from
400       * the program block to which it had been attached (if any),
401       * and updated so that it knows it belongs to this program block.</p>
402       * 
403       * @param idx the index in our list of scheduling blocks at which to add {@code schedBlock}.
404       * @param schedBlock the scheduling block to be added to this program block.
405       */
406      public void addSchedulingBlock(int idx, SchedulingBlock schedBlock)
407      {
408        //Quick exit if schedBlock is null, or if its program block is this
409        //program block.  (Intentional use of "==" here.)
410        if ((schedBlock == null) || (schedBlock.getProgramBlock() == this))
411          return;
412        
413        //TODO Make sure schedBlock is of correct variety
414        
415        //Remove schedBlock from its former program block
416        ProgramBlock formerProgblock = schedBlock.getProgramBlock();
417        if (formerProgblock != null)
418          formerProgblock.schedBlocks.remove(schedBlock);
419        
420        if (idx < 0 || idx > schedBlocks.size())
421        {
422          throw new IndexOutOfBoundsException(idx +
423            " is not within the bounds: (0, " + schedBlocks.size() + ")");
424        }
425    
426        //Add schedBlock to our collection
427        schedBlocks.add(idx, schedBlock);
428        
429        //Tell schedBlock it now belongs to this program block
430        schedBlock.simplySetProgramBlock(this);
431        
432        //Add any direct prereqs and/or adjust direct prereq references
433        addOrAdjustDirectPrereqsOf(schedBlock);
434      }
435      
436      //The incoming SB might have prerequisites.  If a given prereq is not
437      //already part of this PB, we need to add it.  If a given prereq is
438      //EQUAL TO an SB already in this PB, we need to tell the incoming SB
439      //to use the equivalent SB in this PB as a prereq.
440      //TODO Reexamine that last statement.  Are we saying this PB cannot
441      //     hold multiple value-equal SBs?
442      private void addOrAdjustDirectPrereqsOf(SchedulingBlock sb)
443      {
444        //Make a new set to avoid concurrent modification exceptions
445        Set<SchedulingBlock> prereqs =
446          new HashSet<SchedulingBlock>(sb.getDirectPrerequisites());
447        
448        for (SchedulingBlock prereq : prereqs)
449        {
450          int index = schedBlocks.indexOf(prereq);
451          
452          if (index >= 0) //this PB has an SB equal to prereq
453          {
454            SchedulingBlock equalSB = schedBlocks.get(index);
455            
456            //The SB in this PB is equal to, but not the same object as, prereq.
457            //We need sb's list of prereqs to use the equiv SB from PB.
458            if (prereq != equalSB)
459            {
460              sb.removePrerequisite(prereq);
461              sb.addPrerequisite(equalSB);
462            }
463            //else prereq == equalSB, so do nothing more
464          }
465          else //this PB does NOT have SB equal to prereq
466          {
467            //If the prereq is in another PB, we do not disturb that PB.
468            //Instead, we copy the prereq and adjust the reference to prereq
469            //held by sb.  This also has the effect of adding sb to this PB.
470            if (prereq.hasProgramBlock())
471            {
472              SchedulingBlock copyOfPrereq = prereq.clone();
473              copyOfPrereq.setProgramBlock(null);
474              sb.removePrerequisite(prereq);
475              sb.addPrerequisite(copyOfPrereq);
476            }
477            //If the prereq is not already in another PB, we just add it here
478            else
479            {
480              addSchedulingBlock(prereq);
481            }
482          }
483        }
484      }
485      
486      /**
487       * Removes the given scheduling block from this program block.
488       * <p>
489       * If {@code schedBlock} is <i>null</i>, or if it does
490       * not belong to this program block, nothing happens.
491       * If it is a prerequisite of one or more of the scheduling
492       * blocks held by this program block, an exception is thrown
493       * and it is not removed from this program block.
494       * Otherwise, {@code schedBlock} is removed from this
495       * program block and has its program block attribute set to
496       * <i>null</i>.</p>
497       * 
498       * @param schedBlock the scheduling block to be removed.
499       * 
500       * @throws ValidationException if {@code schedBlock} is a prerequisite of one
501       *           or more of this program block's scheduling blocks.
502       */
503      public void removeSchedulingBlock(SchedulingBlock schedBlock)
504        throws ValidationException
505      {
506        //Quick exit if schedBlock is null or does not belong to this program block
507        if ((schedBlock == null) || (schedBlock.getProgramBlock() != this))
508          return;
509        
510        //Throw exception if schedBlock is a prereq of another SB
511        for (SchedulingBlock sb : schedBlocks)
512        {
513          if ((sb != schedBlock) && schedBlock.isDirectPrerequisiteOf(sb))
514            throw prereqRemovalError(schedBlock);
515        }
516        
517        //Remove the schedBlock from our collection
518        schedBlocks.remove(schedBlock);
519        
520        //Tell schedBlock that it belongs to no program block
521        schedBlock.simplySetProgramBlock(null);
522      }
523    
524      /** Constructs and returns an exception. */
525      private ValidationException prereqRemovalError(SchedulingBlock target)
526      {
527        StringBuilder buff = new StringBuilder("Scheduling block '");
528        
529        buff.append(target.getName())
530            .append("' is a direct prerequisite of the following SBs:");
531    
532        for (SchedulingBlock sb : schedBlocks)
533        {
534          if ((sb != target) && target.isDirectPrerequisiteOf(sb))
535            buff.append(' ').append(sb.getName()).append(',');
536        }
537        
538        int buffLen = buff.length();
539        buff.replace(buffLen-1, buffLen, ".");
540        
541        buff.append("  Because of this, ").append(target.getName())
542            .append(" was not removed.  Please first remove it from the ")
543            .append("prerequisite lists of each of the above SBs.");
544        
545        ValidationFailure failure =
546          new ValidationFailure(buff.toString(), "Attempt to delete prereq SB.",
547                                FailureSeverity.ERROR, target,
548                                this.getClass().getName(),
549                                "prereqRemovalError");
550        
551        ValidationException exception =
552          new ValidationException("Cannot remove Scheduling Block.");
553        
554        exception.getFailures().add(failure);
555        
556        return exception;
557      }
558      
559      /**
560       * Removes all scheduling blocks from this program block.
561       * Each scheduling block is notified that it no longer has
562       * a containing program block.
563       */
564      public void removeAllSchedulingBlocks()
565      {
566        for (SchedulingBlock schedBlock : schedBlocks)
567          schedBlock.simplySetProgramBlock(null);
568        
569        schedBlocks.clear();
570      }
571      
572      /**
573       * Returns <i>true</i> if this block has one or more prequisite blocks.
574       * @return <i>true</i> if this block has one or more prequisite blocks.
575       */
576      public boolean hasPrerequisites()
577      {
578        return prereqs.size() > 0;
579      }
580    
581      /**
582       * Returns the scheduling blocks that belong to this program block.
583       * <p>
584       * The returned {@code List} is a copy of the one held internally
585       * by this program block, so changes made to it will not be reflected
586       * herein.</p>
587       *
588       * @return the scheduling blocks that belong to this program block.
589       */
590      public List<SchedulingBlock> getSchedulingBlocks()
591      {
592        return new ArrayList<SchedulingBlock>(schedBlocks);
593      }
594      
595      /**
596       * Returns a list of this program block's scheduling blocks sorted such that
597       * any prerequisites of the block at index i are at in indices greater than i.
598       * <p>
599       * Note that the sort determines only how prerequisites and things
600       * dependent on them are place relative to one another.  For example,
601       * imagine four blocks A, B, C, and D.  C has A and B as prerequisites
602       * and no other block has prerequisites.
603       * The returned list will place C in a lower index than A and B.
604       * However, we know nothing about how A and B will be placed relative
605       * to each other, nor do we know ahead of time where D will be placed.</p>
606       * 
607       * @return scheduling blocks sorted by prerequisite relationships.
608       */
609      public List<SchedulingBlock> getSchedulingBlocksSortedByPrereqs()
610      {
611        List<SchedulingBlock> sbs = getSchedulingBlocks();
612        
613        Collections.sort(sbs, SchedulingBlock.getPrequisiteComparator());
614        
615        return sbs;
616      }
617      
618      /** Returns the position of schedBlock in our list using "==". */
619      private int getSchedBlockIndex(SchedulingBlock schedBlock)
620      {
621        int sbCount = schedBlocks.size();
622        int index   = -1;
623        
624        for (int s=0; s < sbCount; s++)
625        {
626          //Intentional use of "=="
627          if (schedBlocks.get(s) == schedBlock)
628          {
629            //Break at first match; "add" method does not allow multiple
630            //entries of same instance, so this is safe.
631            index = s;
632            break;
633          }
634        }
635    
636        return index;
637      }
638    
639      /**
640       * Returns the schedule entries of this block that 
641       * have an execution status of {@code execStatus}.  Only the status of the
642       * entries is considered.  This method does not look, for example, at the
643       * {@link #isTest()} property.
644       * 
645       * @param execStatus the execution status of the schedule entries to be added
646       *                   to {@code destination}.
647       *                   
648       * @param destination the collection to which the schedule entries should be
649       *                    added.  If this collection is <i>null</i>, a new one
650       *                    will be created.  This collection is returned.
651       *                    
652       * @return the collection holding the schedule entries.  This will be either
653       *         {@code destination} or a new collection, if {@code destination} is
654       *         <i>null</i>.
655       *         
656       * @see #getReadyToScheduleEntries(Collection)
657       */
658      public Collection<ScheduleEntry> getScheduleEntries(
659                                         EventStatus               execStatus,
660                                         Collection<ScheduleEntry> destination)
661      {
662        if (destination == null)
663          destination = new ArrayList<ScheduleEntry>();
664        
665        for (SchedulingBlock schedBlock : schedBlocks)
666          schedBlock.getScheduleEntries(execStatus, destination);
667        
668        return destination;
669      }
670    
671      /**
672       * Returns the schedule entries of this block that are ready for
673       * scheduling.  If this is a test program block, or if this
674       * block has no <tt>NOT_YET_SCHEDULED</tt> entries, the returned
675       * collection will be empty.
676       * 
677       * @param destination the collection to which the ready-to-be-scheduled
678       *                    entries should be added.  If this collection is
679       *                    <i>null</i>, a new one will be created.  This
680       *                    collection is returned.
681       *                    
682       * @return a collection of ready-to-be-scheduled entries, or an empty
683       *         collection.  The returned collection will be either
684       *         {@code destination} or a new collection, if {@code destination} is
685       *         <i>null</i>.
686       *         
687       * @see #getScheduleEntries(EventStatus, Collection)
688       */
689      public Collection<ScheduleEntry>
690        getReadyToScheduleEntries(Collection<ScheduleEntry> destination)
691      {
692        if (destination == null)
693          destination = new ArrayList<ScheduleEntry>();
694        
695        if (!isTest())
696          getScheduleEntries(EventStatus.NOT_YET_SCHEDULED, destination);
697        
698        return destination;
699      }
700    
701      @XmlElementWrapper(name="schedulingBlocks")
702      @XmlElement(name="schedulingBlock")
703      @SuppressWarnings("unused")  //JAXB use
704      private void setSchedBlocks(SchedulingBlock[] replacements)
705      {
706        schedBlocks.clear();
707        
708        for (SchedulingBlock sb : replacements)
709        {
710          sb.simplySetProgramBlock(this);
711          schedBlocks.add(sb);
712        }
713      }
714      
715      @SuppressWarnings("unused")  //JAXB use
716      private SchedulingBlock[] getSchedBlocks()
717      {
718        return schedBlocks.toArray(new SchedulingBlock[schedBlocks.size()]);
719      }
720    
721      //============================================================================
722      // PREREQUISITES
723      //============================================================================
724    
725      /**
726       * Adds {@code newPrereq} to this program block's set of direct
727       * prerequisites.  If this program block belongs to a project, and if
728       * {@code newPrereq} becomes a prerequisite of this one, then
729       * {@code newPrereq} is added to this project, because a program block that
730       * is part of a project may have as prerequisites only those program blocks
731       * that belong to the same project.
732       * <p>
733       * In order to prevent a circular chain of dependencies, {@code newPrereq}
734       * will not be added to this program block's set if this block is
735       * a prerequisite of {@code newPrereq}.</p>
736       * <p>
737       * <a name="directIndirect">
738       * <b><u>Direct vs. Indirect Prerequisites</u></b></a>
739       * <br/>
740       * The <i>prerequisite</i> methods of this class often
741       * refer to <i>direct</i> or <i>indirect</i> prerequisites.  This section
742       * explains the meanings of those terms.  Consider this set of dependencies:
743       * <pre>
744       *   A)--+---------------+
745       *       |-->E)--+       |
746       *   B)--+       |       |-->G)-->H
747       *   C           |-->F)--+
748       *   D)----------+
749       * </pre>
750       * where the notation {@code X)-->Y} means that {@code Y} is dependent on the
751       * completion of {@code X} or, alternatively, {@code X} is a prerequisite of
752       * {@code Y}.</p>
753       * <p>
754       * Program block {@code E} has two prerequisites, {@code A} and {@code B},
755       * each of which is direct.<br/>
756       * Program block {@code F} has two direct prerequisites, {@code D} and
757       * {@code E}, and two indirect prerequisites, {@code A} and {@code B}.<br/>
758       * Program block {@code G} is interesting because {@code A} serves as both
759       * a direct and an indirect (via {@code F} and {@code E}) prerequisite.</p>
760       * <p>
761       * Methods that refer only to <i>prerequisite</i> without the <i>direct</i>
762       * or <i>indirect</i> adjective mean a prerequisite of any type.</p>
763       * 
764       * @param newPrereq a new direct prerequisite for this program block.
765       * 
766       * @return <i>true</i> if {@code newPrereq} was added to this program
767       *         block's set of direct prerequisites.<br/>
768       *         The conditions that lead to a return value of <i>false</i> are:
769       *         <ol>
770       *           <li>{@code newPrereq} is <i>null</i></li>
771       *           <li>{@code newPrereq} is already an direct prerequisite
772       *               of this program block</li>
773       *           <li>This program block is currently a prequisite, direct
774       *               or otherwise, of {@code newPrereq}.
775       *         </ol>
776       */
777      public boolean addPrerequisite(ProgramBlock newPrereq)
778      {
779        boolean prereqWasAdded;
780        
781        //Do not update the set of prerequisites if the new one is
782        //null or if this scheduling block is a prerequisite of newPrereq.
783        if (newPrereq == null || this.isPrerequisiteOf(newPrereq))
784          prereqWasAdded = false;
785        else
786          prereqWasAdded = prereqs.add(newPrereq);
787        
788        //The prerequisite PB must belong to the same project as this PB
789        if (prereqWasAdded)
790          newPrereq.setProject(project);
791        
792        return prereqWasAdded;
793      }
794    
795      /**
796       * Removes {@code oldPrereq} from this program block's set of
797       * direct prerequisites.
798       * (For an explanation of <i>direct</i> versus <i>indirect</i>
799       * prequisites, see the <a href="#directIndirect">note</a> in the
800       * {@link #addPrerequisite(ProgramBlock)} method.) 
801       * 
802       * @param oldPrereq the prerequisite to be removed from this scheduling
803       *                  block's set of direct prerequisites.
804       *                  
805       * @return <i>true</i> if {@code oldPrereq} was successfully removed.
806       *         Note that a return value of <i>false</i> might mean that
807       *         {@code oldPrereq} was not an direct prerequisite of
808       *         this program block.
809       */
810      public boolean removePrerequisite(ProgramBlock oldPrereq)
811      {
812        return prereqs.remove(oldPrereq);
813      }
814      
815      /**
816       * Clears this program block's set of prerequisites.
817       */
818      public void removeAllPrerequisites()
819      {
820        prereqs.clear();
821      }
822      
823      /**
824       * Returns <i>true</i> if this program block is either a
825       * direct or indirect prerequisite of {@code progBlock}.
826       * (For an explanation of <i>direct</i> versus <i>indirect</i>
827       * prerequisites, see the <a href="#directIndirect">note</a> in the
828       * {@link #addPrerequisite(ProgramBlock)} method.) 
829       * 
830       * @param progBlock the program block to test.
831       * 
832       * @return <i>true</i> if this program block is a prerequisite
833       *         of {@code progBlock}.
834       *         
835       * @see #isDirectPrerequisiteOf(ProgramBlock)
836       */
837      public boolean isPrerequisiteOf(ProgramBlock progBlock)
838      {
839        Set<ProgramBlock> othersPrereqs = progBlock.getAllPrerequisites();
840        
841        return othersPrereqs.contains(this);
842      }
843    
844      /**
845       * Returns <i>true</i> if this program block is an <i>direct</i>
846       * prerequisite of {@code progBlock}.
847       * (For an explanation of <i>direct</i> versus <i>indirect</i>
848       * prequisites, see the <a href="#directIndirect">note</a> in the
849       * {@link #addPrerequisite(ProgramBlock)} method.) 
850       * 
851       * @param progBlock the program block to test.
852       * 
853       * @return <i>true</i> if this program block is an <i>direct</i>
854       *         prerequisite of {@code progBlock}.
855       *         
856       * @see #isPrerequisiteOf(ProgramBlock)
857       */
858      public boolean isDirectPrerequisiteOf(ProgramBlock progBlock)
859      {
860        Set<ProgramBlock> othersPrereqs = progBlock.getDirectPrerequisites();
861        
862        return othersPrereqs.contains(this);
863      }
864      
865      /**
866       * Returns this program block's set of direct prerequisites.
867       * <p>
868       * The returned {@code Set} is an <i>unmodifiable</i> set
869       * that will not permit additions of new elements or
870       * deletions of existing elements.  Attempts to modify
871       * the set will result in {@link UnsupportedOperationException}s.</p>
872       * 
873       * @return this program block's set of direct prerequisites.
874       */
875      public Set<ProgramBlock> getDirectPrerequisites()
876      {
877        return Collections.unmodifiableSet(prereqs);
878      }
879      
880      /**
881       * Returns a set containing all direct and indirect prerequisites of
882       * this program block.
883       * (For an explanation of <i>direct</i> versus <i>indirect</i>
884       * prequisites, see the <a href="#directIndirect">note</a> in the
885       * {@link #addPrerequisite(ProgramBlock)} method.) 
886       * 
887       * @return a set containing all prerequisites of this program block.
888       */
889      public Set<ProgramBlock> getAllPrerequisites()
890      {
891        Set<ProgramBlock> result = new HashSet<ProgramBlock>();
892        
893        addAllPrereqsTo(result);
894        
895        return result;
896      }
897      
898      /**
899       * Adds all of this program block's prerequisites, both direct
900       * and indirect, to {@code result}.
901       * 
902       * @param result the set containing all prerequisites of this program
903       *               block.  
904       */
905      private void addAllPrereqsTo(Set<ProgramBlock> result)
906      {
907        if (result != null)
908        {
909          for (ProgramBlock prereq : prereqs)
910          {
911            result.add(prereq);
912            prereq.addAllPrereqsTo(result);
913          }
914        }
915      }
916      
917      //============================================================================
918      // 
919      //============================================================================
920    
921      //TODO Clone these methods from sched block?
922      //       canBeScheduled, evaluateSchedulability
923      
924      //============================================================================
925      // STATUS
926      //============================================================================
927      
928      /**
929       * Returns <i>true</i> if this block is part of a
930       * {@link Project#isTest() test project}.
931       * @return <i>true</i> if this block is part of a test project.
932       */
933      public boolean isTest()
934      {
935        return hasProject() && getProject().isTest();
936      }
937      
938      /**
939       * This method is here for persistence mechanisms such as Hibernate
940       * and JAXB.  It is NOT appropriate to let other objects set the
941       * status, because the status of this object is derived from that
942       * of contained objects.
943       */
944      @XmlElement
945      @SuppressWarnings("unused")
946      private void setExecutionStatus(EventSetStatus newStatus)
947      {
948        executionStatus = newStatus;
949      }
950     
951      /**
952       * Returns this program block's execution status.
953       * 
954       * @return this program block's execution status.
955       */
956      public EventSetStatus getExecutionStatus()
957      {
958        if (!executionStatus.isFinal())
959          recomputeStatus();
960        
961        return executionStatus;
962      }
963      
964      /**
965       * Sets this PB's status based on the combined statuses of
966       * this PB's scheduling blocks.
967       */
968      private void recomputeStatus()
969      {
970        int execBlockCount = schedBlocks.size();
971        
972        EventSetStatus[] statuses = new EventSetStatus[execBlockCount];
973        
974        for (int e=0; e < execBlockCount; e++)
975          statuses[e] = schedBlocks.get(e).getExecutionStatus();
976        
977        executionStatus = EventSetStatus.createFrom(statuses);
978      }
979    
980      //============================================================================
981      // INTERFACE UserAccountable
982      //============================================================================
983      
984      public void setCreatedBy(Long userId)
985      {
986        createdBy = (userId == null) ? UserAccountable.NULL_USER_ID : userId;
987      }
988      
989      public void setCreatedOn(Date d)
990      {
991        if (d != null)
992          createdOn = d;
993      }
994    
995      public void setLastUpdatedBy(Long userId)
996      {
997        lastUpdatedBy = (userId == null) ? UserAccountable.NULL_USER_ID : userId;
998      }
999    
1000      public void setLastUpdatedOn(Date d)
1001      {
1002        if (d != null)
1003          lastUpdatedOn = d;
1004      }
1005    
1006      public Long getCreatedBy()      { return createdBy;     }
1007      public Date getCreatedOn()      { return createdOn;     }
1008      public Long getLastUpdatedBy()  { return lastUpdatedBy; }
1009      public Date getLastUpdatedOn()  { return lastUpdatedOn; }
1010    
1011      //============================================================================
1012      // COMMENTS
1013      //============================================================================
1014    
1015      /**
1016       * Sets comments about this program block.
1017       * 
1018       * @param replacementComments
1019       *   free-form text about this program block.
1020       *   These comments replace all previously set comments.
1021       *   A <i>null</i> value will be replaced by the empty string (<tt>""</tt>).
1022       * 
1023       * @see #appendComments(String)
1024       */
1025      public void setComments(String replacementComments)
1026      {
1027        comments = (replacementComments == null) ? NO_COMMENTS : replacementComments;
1028      }
1029      
1030      /**
1031       * Adds additional comments to those already associated with this program block.
1032       * 
1033       * @param additionalComments
1034       *   new, additional, comments about this program block.
1035       *   
1036       * @see #setComments(String)
1037       */
1038      public void appendComments(String additionalComments)
1039      {
1040        if ((additionalComments != null) && (additionalComments.length() > 0))
1041        {
1042          if (!comments.equals(NO_COMMENTS))
1043            comments = comments + System.getProperty("line.separator");
1044          
1045          comments = comments + additionalComments;
1046        }
1047      }
1048    
1049      /**
1050       * Returns comments about this program block.
1051       * The value returned is guaranteed to be non-null.
1052       * 
1053       * @return
1054       *   free-form text about this program block.
1055       *   
1056       * @see #appendComments(String)
1057       * @see #setComments(String)
1058       */
1059      public String getComments()
1060      {
1061        return comments;
1062      }
1063    
1064      //============================================================================
1065      // TEXT
1066      //============================================================================
1067      
1068      /**
1069       * Returns a text representation of this program block.
1070       * The default form of the text is XML.  However, if anything goes wrong
1071       * during the conversion to XML, an alternate, and much abbreviated, form
1072       * will be returned.
1073       * 
1074       * @return a text representation of this program block.
1075       * 
1076       * @see #toSummaryString()
1077       */
1078      public String toString()
1079      {
1080        try {
1081          return toXml();
1082        }
1083        catch (Exception ex) {
1084          return toSummaryString();
1085        }
1086      }
1087      
1088      /**
1089       * Returns a short textual description of this program block.
1090       * @return a short textual description of this program block.
1091       */
1092      public String toSummaryString()
1093      {
1094        StringBuilder buff = new StringBuilder();
1095        
1096        buff.append("name=").append(name);
1097        buff.append(", id=").append(id);
1098        
1099        return buff.toString();
1100      }
1101    
1102      /**
1103       * Returns an XML representation of this program block.
1104       * @return an XML representation of this program block.
1105       * @throws JAXBException if anything goes wrong during the conversion to XML.
1106       * @see #writeAsXmlTo(Writer)
1107       */
1108      public String toXml() throws JAXBException
1109      {
1110        return JaxbUtility.getSharedInstance().objectToXmlString(this);
1111      }
1112      
1113      /**
1114       * Writes an XML representation of this program block to {@code writer}.
1115       * @param writer the device to which XML is written.
1116       * @throws JAXBException if anything goes wrong during the conversion to XML.
1117       */
1118      public void writeAsXmlTo(Writer writer) throws JAXBException
1119      {
1120        JaxbUtility.getSharedInstance().writeObjectAsXmlTo(writer, this, null);
1121      }
1122      
1123      /**
1124       * Creates a new program block from the XML data in the given file.
1125       * 
1126       * @param xmlFile the name of an XML file.  This method will attempt to locate
1127       *                the file by using {@link Class#getResource(String)}.
1128       *                
1129       * @return a new program block from the XML data in the given file.
1130       * 
1131       * @throws FileNotFoundException if the XML file cannot be found.
1132       * 
1133       * @throws JAXBException if the schema file used (if any) is malformed, if
1134       *           the XML file cannot be read, or if the XML file is not
1135       *           schema-valid.
1136       * 
1137       * @throws XMLStreamException if there is a problem opening the XML file,
1138       *           if the XML is not well-formed, or for some other
1139       *           "unexpected processing conditions".
1140       */
1141      public static ProgramBlock fromXml(String xmlFile)
1142        throws JAXBException, XMLStreamException, FileNotFoundException
1143      {
1144        return ProgramBlock.fromXml(xmlFile, ProgramBlock.class);
1145      }
1146      
1147      /**
1148       * Creates a new program block based on the XML data read from {@code reader}.
1149       * 
1150       * @param reader the source of the XML data.
1151       *               If this value is <i>null</i>, <i>null</i> is returned.
1152       *               
1153       * @return a new program block based on the XML data read from {@code reader}.
1154       * 
1155       * @throws XMLStreamException if the XML is not well-formed,
1156       *           or for some other "unexpected processing conditions".
1157       *           
1158       * @throws JAXBException if anything else goes wrong during the
1159       *           transformation.
1160       */
1161      public static ProgramBlock fromXml(Reader reader)
1162        throws JAXBException, XMLStreamException
1163      {
1164        return ProgramBlock.fromXml(reader, ProgramBlock.class);
1165      }
1166    
1167      /**
1168       * Creates a new program block from the XML data in the given file.
1169       * <p>
1170       * Sample usage:<pre>
1171       *   VlaProgramBlock myPb = ProgramBlock.fromXml(VlaProgramBlock.class,
1172       *                                               myFile);</pre>
1173       * 
1174       * @param <T> the particular subclass of {@code ProgramBlock} returned.
1175       * 
1176       * @param pbType the {@code Class} of an object that extends, or is a,
1177       *               {@code ProgramBlock}. 
1178       * 
1179       * @param xmlFile the name of an XML file.  This method will attempt to locate
1180       *                the file by using {@link Class#getResource(String)}.
1181       *                
1182       * @return a new program block from the XML data in the given file.
1183       * 
1184       * @throws FileNotFoundException if the XML file cannot be found.
1185       * 
1186       * @throws JAXBException if the schema file used (if any) is malformed, if
1187       *           the XML file cannot be read, or if the XML file is not
1188       *           schema-valid.
1189       * 
1190       * @throws XMLStreamException if there is a problem opening the XML file,
1191       *           if the XML is not well-formed, or for some other
1192       *           "unexpected processing conditions".
1193       */
1194      public static <T extends ProgramBlock> T fromXml(String xmlFile,
1195                                                       Class<T> pbType)
1196        throws JAXBException, XMLStreamException, FileNotFoundException
1197      {
1198        T newBlock = JaxbUtility.getSharedInstance().xmlFileToObject(xmlFile, pbType);
1199        
1200        newBlock.testScansForResourceFromJaxb();
1201        
1202        return newBlock;
1203      }
1204      
1205      /**
1206       * Creates a new program block based on the XML data read from {@code reader}.
1207       * <p>
1208       * Sample usage:<pre>
1209       *   VlaProgramBlock myPb = ProgramBlock.fromXml(VlaProgramBlock.class,
1210       *                                               myReader);</pre>
1211       * 
1212       * @param <T> the particular subclass of {@code ProgramBlock} returned.
1213       * 
1214       * @param pbType the {@code Class} of an object that extends {@code Scan}. 
1215       * 
1216       * @param reader the source of the XML data.
1217       *               If this value is <i>null</i>, <i>null</i> is returned.
1218       *               
1219       * @return a new program block based on the XML data read from {@code reader}.
1220       * 
1221       * @throws XMLStreamException if the XML is not well-formed,
1222       *           or for some other "unexpected processing conditions".
1223       *           
1224       * @throws JAXBException if anything else goes wrong during the
1225       *           transformation.
1226       */
1227      public static <T extends ProgramBlock> T fromXml(Reader reader,
1228                                                       Class<T> pbType)
1229        throws JAXBException, XMLStreamException
1230      {
1231        T newBlock = JaxbUtility.getSharedInstance().readObjectAsXmlFrom(reader, pbType, null);
1232        
1233        newBlock.testScansForResourceFromJaxb();
1234        
1235        return newBlock;
1236      }
1237    
1238      /**
1239       * Meant for use by containers of sched blocks; most clients should not use this method.
1240       * @throws JAXBException
1241       *   if the any scan of this block has
1242       *   no resource and the useResourceOfPriorScan flag is false.
1243       */
1244      void testScansForResourceFromJaxb() throws JAXBException
1245      {
1246        for (SchedulingBlock sb : schedBlocks)
1247          sb.testScansForResourceFromJaxb();
1248      }
1249    
1250      //----------------------------------------------------------------------------
1251      // XML Helpers
1252      //----------------------------------------------------------------------------
1253      
1254      //These methods are here solely to help JAXB do its thing.
1255    
1256      @XmlIDREF
1257      @SuppressWarnings("unused")
1258      private void setPrerequisite(Set<ProgramBlock> prerequisites)
1259      {
1260        prereqs.addAll(prerequisites);
1261      }
1262      
1263      @SuppressWarnings("unused")
1264      private Set<ProgramBlock> getPrerequisite()
1265      {
1266        return prereqs;
1267      }
1268      
1269      //============================================================================
1270      // 
1271      //============================================================================
1272    
1273      /**
1274       *  Returns a program block that is almost a copy of this one.
1275       *  <p>
1276       *  The returned element is, for the most part, a deep copy of this one.
1277       *  However, there are a few exceptions:
1278       *  <ol>
1279       *    <li>The ID will be set to
1280       *        {@link Identifiable#UNIDENTIFIED}.</li>
1281       *    <li>The project will be <i>null</i>.</li>
1282       *    <li>The createdOn and lastUpdatedOn attributes will be set to the
1283       *        current system time.</li>
1284       *  </ol></p>
1285       *  <p>
1286       *  If anything goes wrong during the cloning procedure,
1287       *  a {@code RuntimeException} will be thrown.</p>
1288       */
1289      public ProgramBlock clone()
1290      {
1291        ProgramBlock clone = cloneWithoutPrerequisites();
1292    
1293        //The call above ensures that the clone has its own list.
1294        //We now populate that list with clones of our prerequisites.
1295        for (ProgramBlock sb : this.prereqs)
1296          clone.addPrerequisite(sb.clone());
1297        
1298        return clone;
1299      }
1300    
1301      /**
1302       *  Returns a program block that is almost a copy of this one.
1303       *  <p>
1304       *  The returned element is, for the most part, a deep copy of this one.
1305       *  However, there are a few exceptions:
1306       *  <ol>
1307       *    <li>The ID will be set to
1308       *        {@link Identifiable#UNIDENTIFIED}.</li>
1309       *    <li>The XML ID will be given a new UUID.</li>
1310       *    <li>The project will be <i>null</i>.</li>
1311       *    <li>The createdOn and lastUpdatedOn attributes will be set to the
1312       *        current system time.</li>
1313       *    <li>The list of prerequisites will be empty.</li>
1314       *  </ol></p>
1315       *  <p>
1316       *  If anything goes wrong during the cloning procedure,
1317       *  a {@code RuntimeException} will be thrown.</p>
1318       *  
1319       * @return a near copy of this program block, without prerequisites.
1320       */
1321      public ProgramBlock cloneWithoutPrerequisites()
1322      {
1323        ProgramBlock clone = null;
1324        
1325        try
1326        {
1327          //This line takes care of the primitive & immutable fields properly
1328          clone = (ProgramBlock)super.clone();
1329          
1330          //We do NOT want the clone to have the same ID as the original.
1331          //The ID is here for the persistence layer; it is in charge of
1332          //setting IDs.  To help it, we put the clone's ID in the uninitialized
1333          //state.
1334          clone.id = Identifiable.UNIDENTIFIED;
1335          
1336          clone.xmlId = "progBlock-" + UUID.randomUUID().toString();
1337    
1338          clone.createdOn     = new Date();
1339          clone.lastUpdatedOn = clone.createdOn;
1340          
1341          clone.project = null;
1342    
1343          //New empty set of prereqs
1344          clone.prereqs = new HashSet<ProgramBlock>();
1345    
1346          //Clone the collection but NOT the contained elements
1347          clone.acceptableConfigurations = new ArrayList<TelescopeConfiguration>();
1348          for (TelescopeConfiguration tc : this.acceptableConfigurations)
1349            clone.acceptableConfigurations.add(tc);
1350          
1351          //Clone the collection and partially clone the contained elements
1352          clone.schedBlocks = new ArrayList<SchedulingBlock>();
1353          for (SchedulingBlock sb : this.schedBlocks)
1354            clone.addSchedulingBlock(sb.cloneWithoutPrerequisites());
1355    
1356          //Recreate for the cloned SBs analogous prerequisite trees
1357          fixClonesSchedBlockPrereqs(clone);
1358        }
1359        catch (Exception ex)
1360        {
1361          throw new RuntimeException(ex);
1362        }
1363        
1364        return clone;
1365      }
1366      
1367      /** Helps the clone method get the clone's SB relationships straight. */
1368      private void fixClonesSchedBlockPrereqs(ProgramBlock clonedPb)
1369      {
1370        //STRATEGY
1371        //Loop through all of our own SBs.  For each SB that has
1372        //direct prerequisites, note the positions in our list of
1373        //the prereqs.  The clone's SB at the same position as the
1374        //current SB should receive as direct prereqs the SBs at
1375        //the indices we found.
1376        
1377        List<SchedulingBlock> sbsOfClone = clonedPb.getSchedulingBlocks();
1378        
1379        int sbCount = schedBlocks.size();
1380        
1381        for (int s=0; s < sbCount; s++)
1382        {
1383          SchedulingBlock mySb      = schedBlocks.get(s);
1384          SchedulingBlock mySbClone = sbsOfClone.get(s);
1385          
1386          for (SchedulingBlock prereqOfMySb : mySb.getDirectPrerequisites())
1387          {
1388            int prereqIndex = getSchedBlockIndex(prereqOfMySb);
1389            
1390            if (prereqIndex >= 0)
1391              mySbClone.addPrerequisite(sbsOfClone.get(prereqIndex));
1392          }
1393        }
1394      }
1395    
1396      /**
1397       * Returns <i>true</i> if {@code o} is equal to this program block.
1398       * <p>
1399       * In order to be equal to this program block, {@code o} must be non-null
1400       * and of the same class as this block. Equality is determined by examining
1401       * the equality of corresponding attributes, with the following exceptions,
1402       * which are ignored when assessing equality: 
1403       * <ol>
1404       *   <li>id</li>
1405       *   <li>project</li>
1406       *   <li>createdOn</li>
1407       *   <li>createdBy</li>
1408       *   <li>lastUpdatedOn</li>
1409       *   <li>lastUpdatedBy</li>
1410       * </ol></p>
1411       */
1412      @Override
1413      public boolean equals(Object o)
1414      {
1415        //Quick exit if o is null
1416        if (o == null)
1417          return false;
1418        
1419        //Quick exit if o is this
1420        if (o == this)
1421          return true;
1422        
1423        //Quick exit if classes are different
1424        if (!o.getClass().equals(this.getClass()))
1425          return false;
1426        
1427        ProgramBlock other = (ProgramBlock)o;
1428        
1429        //Attributes that we INTENTIONALLY DO NOT COMPARE:
1430        //  id,
1431        //  project,
1432        //  createdOn, createdBy, lastUpdatedOn, lastUpdatedBy
1433    
1434        //Simple properties
1435        if (!other.name.equals(this.name)                                 ||
1436            !other.comments.equals(this.comments)                         ||
1437            !other.getExecutionStatus().equals(this.getExecutionStatus()) ||
1438            !other.acceptableConfigurations.equals(this.acceptableConfigurations))
1439          return false;
1440        
1441        //Scheduling blocks
1442        if (!other.schedBlocks.equals(this.schedBlocks))
1443          return false;
1444    
1445        //Prerequisites.  This is an expensive test, so save for last.
1446        if (other.prereqs.size() != this.prereqs.size())
1447        {
1448          return false;
1449        }
1450        else //same # of prereqs
1451        {
1452          //HashSet's equals method goes through its contains method, which
1453          //does not work properly for members whose hash codes have changed
1454          //since being added to the set, which seems to happen w/ JAXB
1455          //resolution of IDREFs.  (I have submitted a bug to Sun on this.)
1456          //If we make fresh sets, the set's equals method will work properly.
1457          HashSet<ProgramBlock> these = new HashSet<ProgramBlock>( this.prereqs);
1458          HashSet<ProgramBlock> those = new HashSet<ProgramBlock>(other.prereqs);
1459          
1460          if (!those.equals(these))
1461            return false;
1462        }
1463    
1464        //No differences found
1465        return true;
1466      }
1467      
1468      /* (non-Javadoc)
1469       * @see java.lang.Object#hashCode()
1470       */
1471      @Override
1472      public int hashCode()
1473      {
1474        //Taken from the Effective Java book by Joshua Bloch.
1475        //The constants 17 & 37 are arbitrary & carry no meaning.
1476        int result = 17;
1477        
1478        //You MUST keep this method in sync w/ the equals method
1479    
1480        //Simple properties
1481        result = 37 * result + name.hashCode();
1482        result = 37 * result + comments.hashCode();
1483        result = 37 * result + acceptableConfigurations.hashCode();
1484        result = 37 * result + getExecutionStatus().hashCode();
1485    
1486        //Scheduling blocks
1487        result = 37 * result + schedBlocks.hashCode();
1488        
1489        //Prerequisites
1490        result = 37 * result + prereqs.hashCode();
1491    
1492        return result;
1493      }
1494    
1495      /**
1496       * Returns a comparator that places all prerequisites before the blocks
1497       * that depend upon them.  Blocks that have no prereqs, and are prereqs
1498       * of nothing, could be placed anywhere in the sorting process.
1499       * 
1500       * @return a comparator that places all prerequisites before the blocks
1501       *         that depend upon them.
1502       */
1503      public static Comparator<ProgramBlock> getPrequisiteComparator()
1504      {
1505        if (PREREQ_COMPARATOR == null)
1506          PREREQ_COMPARATOR = new PrereqComparator();
1507        
1508        return PREREQ_COMPARATOR;
1509      }
1510      
1511      /**
1512       * A comparator that says prerequisites come after the things
1513       * that depend on them. PBs that have no prereqs and are prereqs
1514       * of nothing could be placed anywhere in the sorting process.
1515       * 
1516       * Stateless & immutable.
1517       */
1518      private static class PrereqComparator implements Comparator<ProgramBlock>
1519      {
1520        public int compare(ProgramBlock a, ProgramBlock b)
1521        {
1522          if (a.isPrerequisiteOf(b))
1523          {
1524            return +1;
1525          }
1526          else if (b.isPrerequisiteOf(a))
1527          {
1528            return -1;
1529          }
1530          else //neither is prereq of other
1531          {
1532            //Put block with more prereqs before other
1533            int sizeDiff = b.getAllPrerequisites().size() -
1534                           a.getAllPrerequisites().size(); 
1535            
1536            if (sizeDiff != 0)
1537              return sizeDiff;
1538            
1539            //Final tie breaker is name
1540            return a.getName().compareTo(b.getName());
1541          }
1542        }
1543      }
1544      
1545      //============================================================================
1546      // 
1547      //============================================================================
1548    
1549      //This is here for quick manual testing
1550      /*
1551      public static void main(String[] args)
1552      {
1553        ProjectBuilder builder = new ProjectBuilder();
1554        builder.setIdentifiers(true);
1555        
1556        ProgramBlock pb = builder.makeProgramBlock();
1557    
1558        //Submit then unsubmit one scheduling block; should create new SB & link the 2
1559        List<SchedulingBlock> sbList = pb.getSchedulingBlocks();
1560        if (sbList.size() > 0)
1561        {
1562          SchedulingBlock sb = sbList.get(0);
1563          sb.submit();
1564          sb.unsubmit();
1565        }
1566        
1567        try
1568        {
1569          pb.writeAsXmlTo(new java.io.PrintWriter(System.out));
1570        }
1571        catch (JAXBException ex)
1572        {
1573          System.out.println("Trouble w/ pb.toXml.  Msg:");
1574          System.out.println(ex.getMessage());
1575          ex.printStackTrace();
1576          
1577          System.out.println("Attempting to write XML w/out schema verification:");
1578          JaxbUtility.getSharedInstance().setLookForDefaultSchema(false);
1579          try
1580          {
1581            pb.writeAsXmlTo(new java.io.PrintWriter(System.out));
1582          }
1583          catch (JAXBException ex2)
1584          {
1585            System.out.println("Still had trouble w/ pb.toXml.  Msg:");
1586            System.out.println(ex.getMessage());
1587            ex.printStackTrace();
1588          }
1589        }
1590      }
1591      */
1592      //This is here for quick manual testing
1593      /*
1594      public static void main(String[] args)
1595      {
1596        ProgramBlock manhattan = new ProgramBlock();
1597        ProgramBlock apollo = new ProgramBlock();
1598        
1599        SchedulingBlock m1 = manhattan.createSchedulingBlock();
1600        SchedulingBlock a1 = apollo.createSchedulingBlock();
1601    
1602        SchedulingBlock m2 = manhattan.createSchedulingBlock();
1603        SchedulingBlock a2 = apollo.createSchedulingBlock();
1604        
1605        SchedulingBlock mTOa1 = manhattan.createSchedulingBlock();
1606        SchedulingBlock aTOm1 = apollo.createSchedulingBlock();
1607    
1608        SchedulingBlock mTOa2 = manhattan.createSchedulingBlock();
1609        SchedulingBlock aTOm2 = apollo.createSchedulingBlock();
1610       
1611        manhattan.setLongName("manhattan");
1612        apollo.setLongName("apollo");
1613        m1.setLongName("m1");
1614        a1.setLongName("a1");
1615        m2.setLongName("m2");
1616        a2.setLongName("a2");
1617        mTOa1.setLongName("mTOa1");
1618        aTOm1.setLongName("aTOm1");
1619        mTOa2.setLongName("mTOa2");
1620        aTOm2.setLongName("aTOm2");
1621        
1622        //Put the "1" series into projects via PB.addSB
1623        manhattan.addSchedulingBlock(m1);
1624        manhattan.addSchedulingBlock(mTOa1);
1625        apollo.addSchedulingBlock(a1);
1626        apollo.addSchedulingBlock(aTOm1);
1627        
1628        //Put the "2" series into projects via SB.setPB
1629        m2.setProgramBlock(manhattan);
1630        mTOa2.setProgramBlock(manhattan);
1631        a2.setProgramBlock(apollo);
1632        aTOm2.setProgramBlock(apollo);
1633        
1634        //Move the "TO?1" series via PB.addSB
1635        apollo.addSchedulingBlock(mTOa1);  //moving from manhattan
1636        manhattan.addSchedulingBlock(aTOm1);  //moving from apollo
1637        
1638        //Move the "TO?2" series via SB.setPB
1639        mTOa2.setProgramBlock(apollo);
1640        aTOm2.setProgramBlock(manhattan);
1641        
1642        System.out.println();
1643        System.out.println(manhattan.getLongName());
1644        for (SchedulingBlock sb : manhattan.getSchedulingBlocks())
1645          System.out.println("  "+sb.getLongName()+"  PB="+sb.getProgramBlock().getLongName());
1646        
1647        System.out.println();
1648        System.out.println(apollo.getLongName());
1649        for (SchedulingBlock sb : apollo.getSchedulingBlocks())
1650          System.out.println("  "+sb.getLongName()+"  PB="+sb.getProgramBlock().getLongName());
1651    
1652        //Remove the "1" series via proj.removePB
1653        manhattan.removeSchedulingBlock(m1);
1654        manhattan.removeSchedulingBlock(aTOm1);
1655        apollo.removeSchedulingBlock(a1);
1656        apollo.removeSchedulingBlock(mTOa1);
1657    
1658        //Remove the "2" series via pb.setProj(null)
1659        manhattan.removeSchedulingBlock(m2);
1660        manhattan.removeSchedulingBlock(aTOm2);
1661        apollo.removeSchedulingBlock(a2);
1662        apollo.removeSchedulingBlock(mTOa2);
1663        
1664        System.out.println();
1665        System.out.println(manhattan.getLongName() + ": " + manhattan.getSchedulingBlocks().size() + " SBs");
1666        System.out.println(apollo.getLongName() + ": " + apollo.getSchedulingBlocks().size() + " SBs");
1667        
1668        System.out.println();
1669        System.out.println("m1.PB="+m1.getProgramBlock());
1670        System.out.println("m2.PB="+m2.getProgramBlock());
1671        
1672        manhattan.addSchedulingBlock(m1);
1673        manhattan.addSchedulingBlock(m2);
1674        manhattan.addSchedulingBlock(aTOm1);
1675        manhattan.addSchedulingBlock(aTOm2);
1676    
1677        manhattan.removeAllSchedulingBlocks();
1678        
1679        System.out.println();
1680        System.out.println(manhattan.getLongName() + ": " + manhattan.getSchedulingBlocks().size() + " SBs");
1681        
1682        System.out.println();
1683      }
1684      */
1685    }