001    package edu.nrao.sss.model.project.scan;
002    
003    import java.io.FileNotFoundException;
004    import java.io.Reader;
005    import java.math.BigDecimal;
006    import java.util.ArrayList;
007    import java.util.HashSet;
008    import java.util.List;
009    import java.util.Set;
010    
011    import javax.xml.bind.JAXBException;
012    import javax.xml.bind.annotation.XmlElementRef;
013    import javax.xml.bind.annotation.XmlElementRefs;
014    import javax.xml.bind.annotation.XmlElementWrapper;
015    import javax.xml.bind.annotation.XmlRootElement;
016    import javax.xml.bind.annotation.XmlType;
017    import javax.xml.stream.XMLStreamException;
018    
019    import edu.nrao.sss.measure.TimeDuration;
020    import edu.nrao.sss.model.project.SchedulingBlock;
021    import edu.nrao.sss.util.EqualityMethod;
022    import edu.nrao.sss.util.Identifiable;
023    import edu.nrao.sss.util.JaxbUtility;
024    
025    /**
026     * A loop of scans and scan loops.
027     * <p>
028     * This class is used primarily by the
029     * {@link edu.nrao.sss.model.project.SchedulingBlock}.
030     * It allows an observer to create a scan sequence that allows
031     * multiple nesting of repeated loops of scans.</p>
032     * <p>
033     * This loop consists of an iteration count and zero or more
034     * {@link ScanLoopElement}s.  Each element is executed once
035     * per iteration in the order in which they appear in this
036     * loop's list of elements.  Note that some of the elements
037     * may themselves be loops, allowing the construction of
038     * arbritrarily deep nesting of loops.</p>
039     * <p>
040     * <b>Version Info:</b>
041     * <table style="margin-left:2em">
042     *   <tr><td>$Revision: 2277 $</td></tr>
043     *   <tr><td>$Date: 2009-04-29 11:19:38 -0600 (Wed, 29 Apr 2009) $</td></tr>
044     *   <tr><td>$Author: dharland $</td></tr>
045     * </table></p>
046     *  
047     * @author David M. Harland
048     * @since 2006-07-31
049     */
050    @XmlRootElement
051    @XmlType(propOrder={"iterationCount", "maximumDuration", "bracketed", "xmlElements"})
052    public class ScanLoop
053      extends ScanLoopElement
054    {
055      //Used for setting max duration to some nonrestrictive value.
056      private static final BigDecimal LOTS_OF_HOURS = new BigDecimal("999.0");
057      
058      private static final String DEFAULT_NAME = "[New Loop]";
059    
060      private int                   iterationCount;
061      private boolean               bracketed;
062      private TimeDuration          maximumDuration;
063      private List<ScanLoopElement> elements;
064      
065      /**
066       * Creates a new loop with no elements and with an iteration
067       * count of one. 
068       */
069      public ScanLoop()
070      {
071        iterationCount  = 1;
072        bracketed       = false;
073        maximumDuration = new TimeDuration(LOTS_OF_HOURS);
074        elements        = new ArrayList<ScanLoopElement>();
075      }
076      
077      /**
078       * Resets this loop so that it has no elements and an iteration
079       * count of one.
080       */
081      public void reset()
082      {
083        super.reset();
084        
085        iterationCount = 1;
086        bracketed      = false;
087    
088        //TODO: should we be reseting maximumDuration here as well?
089        elements.clear();
090      }
091      
092      String getDefaultName()  { return ScanLoop.DEFAULT_NAME; }
093    
094      /**
095       * Sets the number of times that this loop should be executed.
096       * @param count
097       */
098      public void setIterationCount(int count)
099      {
100        if (count > 0)
101          iterationCount = count;
102        else
103          throw new IllegalArgumentException("Iteration count of " + count
104            + " is not valid.  Must be > 0.");
105      }
106      
107      /**
108       * Returns the number of times that this loop should be executed.
109       * @return the number of times that this loop should be executed.
110       */
111      public int getIterationCount()
112      {
113        return iterationCount;
114      }
115      
116      /**
117       * Sets whether or not this loop is a "bracketed" loop.  A bracketed loop
118       * will repeat it's first ScanLoopElement once more at the end such that the
119       * loop begins and ends with the same element when unrolled. 
120       *
121       * <p>In other words, if a loop contains Scans {@code A} and {@code B}, with
122       * an interation count of 3, a regular loop will unroll to: {@code A B A B A B}
123       * whereas a bracketed loop would unroll to {@code A B A B A B A}.</p>
124       *
125       * <p>Default is true.</p>
126       */
127      public void setBracketed(boolean b)
128      {
129        this.bracketed = b;
130      }
131    
132      /**
133       * Returns whether or not this loop is a "bracketed" loop.  A bracketed loop
134       * will repeat it's first ScanLoopElement once more at the end such that the
135       * loop begins and ends with the same element when unrolled. 
136       *
137       * <p>In other words, if a loop contains Scans {@code A} and {@code B}, with
138       * an interation count of 3, a regular loop will unroll to: {@code A B A B A B}
139       * whereas a bracketed loop would unroll to {@code A B A B A B A}.</p>
140       *
141       * <p>Default is true.</p>
142       *
143       * @return whether or not this loop is a "bracketed" loop.
144       */
145      public boolean getBracketed()
146      {
147        return this.bracketed;
148      }
149    
150      /**
151       * Sets the maximum amount of time that may be spent in this loop.
152       * If {@code maxTime} is <i>null</i>, the maximum duration will be
153       * set to an arbitrarily large number.
154       * 
155       * @param maxTime the maximum amount of time that may be spent in this loop.
156       */
157      public void setMaximumDuration(TimeDuration maxTime)
158      {
159        if (maxTime != null)
160          maximumDuration = maxTime;
161        else
162          maximumDuration = new TimeDuration(LOTS_OF_HOURS);
163      }
164      
165      /**
166       * Returns the maximum amount of time that may be spent in this loop.
167       * @return the maximum amount of time that may be spent in this loop.
168       */
169      public TimeDuration getMaximumDuration()
170      {
171        return maximumDuration;
172      }
173      
174      /**
175       * Returns the number of elements in this loop.  Note that each element is
176       * either a {@code Scan} or a {@code ScanLoop}.
177       * 
178       * @return the number of elements in this loop.
179       */
180      public int size()
181      {
182        return elements.size();
183      }
184    
185      /**
186       * Sets the scheduling block to which this scan loop belongs.
187       * <p>
188       * This method does <i>not</i> communicate back to
189       * {@code newSchedBlock}
190       * about its new loop.  This is because the scheduling block does not
191       * hold loops scans directly, other than its main loop.
192       * This means that the model is not enforcing integrity in the
193       * container / contained relationship that exists between
194       * {@code SchedulingBlock} and {@code ScanLoop}.</p>
195       * <p>
196       * If this loop is currently the main loop of a scheduling block,
197       * this method will not allow a change of scheduling block.</p>
198       * <p>
199       * Passing this method a {@code schedulingBlock} of <i>null</i> has the
200       * effect of disconnecting this loop from any scheduling block.</p>
201       * 
202       * @param newSchedBlock the scheduling block to which this scan belongs.
203       */
204      public void setSchedulingBlock(SchedulingBlock newSchedBlock)
205      {
206        SchedulingBlock currentSB = getSchedulingBlock();
207        
208        //May not change scheduling block of a loop that is the scanSequence
209        //of a scheduling block.
210        if ((currentSB != null) &&
211            (currentSB.getScanSequence() == this))
212        {
213          ; //do nothing
214        }
215        else //Changing the scheduling block
216        {
217          super.setSchedulingBlock(newSchedBlock);
218          
219          //Communicate new SB to all members of loop
220          for (ScanLoopElement e : elements)
221            e.setSchedulingBlock(newSchedBlock);
222        }
223      }
224    
225      //============================================================================
226      // FETCHING ELEMENTS
227      //============================================================================
228    
229      /**
230       * Returns a list of the elements of this loop.
231       * <p>
232       * The returned list is not held internally by this loop.  This means that
233       * any changes made to the list after the call will <i>not</i> be reflected
234       * in this object.  The elements <i>in</i> the list, however, are the actual
235       * elements of this loop, so changes made to them <i>will</i> be reflected
236       * in this object.</p>
237       * 
238       * @return a list of the elements of this loop.
239       */
240      public List<ScanLoopElement> getElements()
241      {
242        return new ArrayList<ScanLoopElement>(elements);
243      }
244      
245      /**
246       * DO NOT USE.
247       * This method is for JAXB only and was originally private.
248       * It was given default access only after we discovered that Hibernate
249       * was lazily fetching the loop elements.  When this method was private,
250       * the fetch of the elements was not made and the XML for scan loops fetched
251       * from the database resulted in empty loops.  Setting "lazy=false" in the
252       * Hibernate mapping file did not help.
253       * This issue was discovered as part of EVL-724.   --DMH 2008-Oct-07
254       */
255      @XmlElementWrapper(name="elements")
256      @XmlElementRefs
257      (
258        {
259          @XmlElementRef(type=DelayScan.class),
260          @XmlElementRef(type=FocusScan.class),
261          @XmlElementRef(type=PointingScan.class),
262          @XmlElementRef(type=ScanLoop.class),
263          @XmlElementRef(type=SimpleScan.class),
264          @XmlElementRef(type=SwitchingScan.class),
265          @XmlElementRef(type=TippingScan.class)
266        }
267      )
268      //Introduced for use by JAXB, after 2.1.x introduced
269      //trouble with collections.
270      //See http://forums.java.net/jive/thread.jspa?threadID=41000&tstart=0
271      // 
272      // 2009-Jan-14 DMH
273      //  1. Changed getter from returning list to returning array.
274      //  2. Added setter.
275      //  3. In setter, ensuring that SB is set, if available.
276      //  
277      //  #s 1 & 2 are our std way of dealing w/ collections for JAXB
278      ScanLoopElement[] getXmlElements()
279      {
280        return elements.toArray(new ScanLoopElement[elements.size()]);
281      }
282      
283      void setXmlElements(ScanLoopElement[] replacements)
284      {
285        elements.clear();
286        
287        SchedulingBlock sb = this.getSchedulingBlock();
288        
289        for (ScanLoopElement sle : replacements)
290        {
291          if (sb != null)
292            sle.setSchedulingBlock(sb);
293          
294          elements.add(sle);
295        }
296      }
297      
298      /**
299       * Returns the element at index.
300       * @param index see {@link List#get(int)} for a discussion of this
301       *              parameter.
302       * @return the element at index.
303       */
304      public ScanLoopElement getElement(int index)
305      {
306        return elements.get(index);
307      }
308    
309      /**
310       * Returns <i>true</i> if {@code element} is held either directly, or
311       * indirectly, by this loop.
312       * If this loop has no element equal to {@code element}, but contains a
313       * nested loop that does contain {@code element}, this method will return
314       * <i>true</i> because it does contain {@code element}, albeit indirectly.
315       * <p>
316       * Containment is determined by the {@code equalityMethod} provided.
317       * If this parameter is <i>null</i>, {@link EqualityMethod#VALUE} will
318       * be used.</p>
319       * 
320       * @param element the element to test for containment.
321       * 
322       * @param equalityMethod determines whether containment is based on reference
323       *                       or value equality.  A value of <i>null</i> will be
324       *                       interpreted as a signal to use value equality.
325       * 
326       * @return <i>true</i> if {@code element} is held directly or indirectly
327       *         by this loop.
328       * 
329       * @see #containsDirectly(ScanLoopElement, EqualityMethod)
330       */
331      public boolean contains(ScanLoopElement element,
332                              EqualityMethod  equalityMethod)
333      {
334        if (containsDirectly(element, equalityMethod))
335          return true;
336        
337        for (ScanLoopElement e : elements)
338          if ( (e instanceof ScanLoop) &&
339               ((ScanLoop)e).contains(element, equalityMethod) )
340            return true;
341        
342        return false;
343      }
344    
345      /**
346       * Returns <i>true</i> if {@code element} is held directly by this loop.
347       * If this loop has no element equal to {@code element}, but contains a
348       * nested loop that does contain {@code element}, this method still returns
349       * <i>false</i> because it does not <i>directly</i> hold {@code element}.
350       * <p>
351       * Containment is determined by the {@code equalityMethod} provided.
352       * If this parameter is <i>null</i>, {@link EqualityMethod#VALUE} will
353       * be used.</p>
354       * 
355       * @param element the element to test for containment.
356       * 
357       * @param equalityMethod determines whether containment is based on reference
358       *                       or value equality.  A value of <i>null</i> will be
359       *                       interpreted as a signal to use value equality.
360       * 
361       * @return <i>true</i> if {@code element} is held directly by this loop.
362       * 
363       * @see #contains(ScanLoopElement, EqualityMethod)
364       */
365      public boolean containsDirectly(ScanLoopElement element,
366                                      EqualityMethod  equalityMethod)
367      {
368        if (equalityMethod.equals(EqualityMethod.REFERENCE))
369        {
370          for (ScanLoopElement e : elements)
371          {
372            if (e == element)
373              return true;
374          }
375          return false;
376        }
377        else //method is VALUE or null
378        {
379          return elements.contains(element);
380        }
381      }
382       
383      //============================================================================
384      // ADDING ELEMENTS
385      //============================================================================
386    
387      /**
388       * Adds the given scan to this loop.
389       * 
390       * @param scan the scan to be added to this loop.
391       * 
392       * @throws IllegalArgumentException if {@code scan} is <i>null</i>.
393       */
394      public void add(Scan scan)
395      {
396        if (scan == null)
397          throw new IllegalArgumentException("May not add NULL scan to a ScanLoop.");
398    
399        scan.setSchedulingBlock(getSchedulingBlock());
400        elements.add(scan);
401      }
402      
403      /**
404       * Adds the given scan to this loop at the given position.
405       * 
406       * @param index the position at which to add the new scan.
407       *              See the {@code add(int, E)} method of {@link List}
408       *              for a discussion of the insertion.
409       *        
410       * @param scan the scan to be added to this loop.
411       * 
412       * @throws IllegalArgumentException if {@code scan} is <i>null</i>.
413       */
414      public void add(int index, Scan scan)
415      {
416        if (scan == null)
417          throw new IllegalArgumentException("May not add NULL scan to a ScanLoop.");
418    
419        scan.setSchedulingBlock(getSchedulingBlock());
420        elements.add(index, scan);
421      }
422      
423      /**
424       * Adds a <i>copy</i> of the given loop to this loop.
425       * 
426       * @param loop the loop to be added to this loop.
427       * 
428       * @return the copy of the loop that was added to this loop.
429       * 
430       * @throws IllegalArgumentException if {@code loop} is <i>null</i> or is
431       *           this loop.
432       *           
433       * @see #addCopyOf(int, ScanLoop)
434       */
435      public ScanLoop addCopyOf(ScanLoop loop)
436      {
437        loop = prepForAddCopyOf(loop);
438    
439        elements.add(loop);
440        
441        return loop;
442      }
443      
444      /**
445       * Adds a <i>copy</i> of the given loop to this loop at the given position.
446       * <p>
447       * The reason for adding a copy, instead of {@code loop} itself, is to
448       * ensure that the model could never be put into a state where loops 
449       * swallow their own tails.  That is, we seek to prevent a situation where
450       * loop A holds loop B which holds loop A which holds...</p>
451       * 
452       * @param loop the loop to be added to this loop.
453       * 
454       * @param index the position at which to add the new loop.
455       *              See the {@code add(int, E)} method of {@link List}
456       *              for a discussion of the insertion.
457       * 
458       * @return the copy of the loop that was added to this loop.
459       * 
460       * @throws IllegalArgumentException if {@code loop} is <i>null</i> or is
461       *           this loop.
462       */
463      public ScanLoop addCopyOf(int index, ScanLoop loop)
464      {
465        loop = prepForAddCopyOf(loop);
466        
467        elements.add(index, loop);
468        
469        return loop;
470      }
471    
472      /** Prepares a scan loop for addition into this one. */
473      private ScanLoop prepForAddCopyOf(ScanLoop oldLoop)
474      {
475        if (oldLoop == null)
476          throw new IllegalArgumentException("May not add NULL loop to a ScanLoop.");
477    
478        ScanLoop newLoop = oldLoop.cloneAllButElements();
479        
480        newLoop.setSchedulingBlock(getSchedulingBlock());
481    
482        newLoop.elements = new ArrayList<ScanLoopElement>();
483        
484        int elemCount = oldLoop.elements.size();
485        for (int e=0; e < elemCount; e++)
486        {
487          ScanLoopElement elem = oldLoop.elements.get(e);
488          
489          //Add either a copy of any contained loops...
490          if (elem instanceof ScanLoop)
491          {
492            newLoop.addCopyOf((ScanLoop)elem);
493          }
494          //...or references to the same scans
495          else //Scan
496          {
497            newLoop.elements.add(elem);
498          }
499        }
500        
501        return newLoop;
502      }
503      
504      //============================================================================
505      // MOVING ELEMENTS
506      //============================================================================
507    
508      /**
509       * Moves {@code element} to an index one higher than its current position in
510       * this loop.  The element will not be moved if it is not in this loop, or if
511       * it is already in the highest position.
512       * <p>
513       * If {@code element} is in this loop in multiple positions, the instance
514       * with the <i>lowest</i> index will be moved.</p> 
515       * 
516       * @param element the element to be moved.
517       * 
518       * @return <i>true</i> if the element was successfully moved.
519       */
520      public boolean incrementIndexOf(ScanLoopElement element)
521      {
522        int currentIndex = elements.indexOf(element);
523        int highestIndex = elements.size() - 1;
524        
525        boolean move = (0 <= currentIndex) && (currentIndex < highestIndex);
526        
527        if (move)
528        {
529          elements.remove(currentIndex);
530          elements.add(currentIndex + 1, element);
531        }
532        
533        return move;
534      }
535    
536      /**
537       * Moves {@code element} to an index one lower than its current position in
538       * this loop.  The element will not be moved if it is not in this loop, or if
539       * it is already in the lowest position.
540       * <p>
541       * If {@code element} is in this loop in multiple positions, the instance
542       * with the <i>highest</i> index will be moved.</p> 
543       * 
544       * @param element the element to be moved.
545       * 
546       * @return <i>true</i> if the element was successfully moved.
547       */
548      public boolean decrementIndexOf(ScanLoopElement element)
549      {
550        int currentIndex = elements.lastIndexOf(element);
551        
552        boolean move = (0 < currentIndex);
553        
554        if (move)
555        {
556          elements.remove(currentIndex);
557          elements.add(currentIndex - 1, element);
558        }
559        
560        return move;
561      }
562      
563      /**
564       * Moves the element at {@code index} to an index of {@code index}-plus-one.
565       * No move will occur if {@code index} is out of bounds, or if it is already
566       * the highest index of this loop.
567       * 
568       * @param index the index of the element to be moved.
569       * 
570       * @return <i>true</i> if the element was successfully moved.
571       */
572      public boolean incrementIndexOfElementAt(int index)
573      {
574        int highestIndex = elements.size() - 1;
575        
576        boolean move = (0 <= index) && (index < highestIndex);
577        
578        if (move)
579        {
580          ScanLoopElement wanderer = elements.get(index);
581          elements.remove(index);
582          elements.add(index + 1, wanderer);
583        }
584        
585        return move;
586      }
587      
588      /**
589       * Moves the element at {@code index} to an index of {@code index}-minus-one.
590       * No move will occur if {@code index} is out of bounds, or if it is already
591       * the lowest index of this loop.
592       * 
593       * @param index the index of the element to be moved.
594       * 
595       * @return <i>true</i> if the element was successfully moved.
596       */
597      public boolean decrementIndexOfElementAt(int index)
598      {
599        int highestIndex = elements.size() - 1;
600        
601        boolean move = (0 < index) && (index <= highestIndex);
602        
603        if (move)
604        {
605          ScanLoopElement wanderer = elements.get(index);
606          elements.remove(index);
607          elements.add(index - 1, wanderer);
608        }
609        
610        return move;
611      }
612      
613      /**
614       * Swaps the elements at the given positions.
615       * <p>
616       * If the indices are equal, no action is taken.
617       * If one or both of the indices are not in the range
618       * [0,size()), the underlying {@link List} will throw
619       * an exception.</p>
620       * 
621       * @param index1 the position of the one of the elements.
622       * @param index2 the position of another of the elements.
623       * 
624       * @return <i>true</i> if the positions were swapped.
625       */
626      public boolean swapElements(int index1, int index2)
627      {
628        boolean swap = (index1 != index2);
629        
630        if (swap)
631        {
632          int low  = Math.min(index1, index2);
633          int high = Math.max(index1, index2);
634        
635          ScanLoopElement lowElem  = elements.get(low);
636          ScanLoopElement highElem = elements.get(high);
637        
638          elements.remove(high);
639          elements.add(high, lowElem);
640          elements.remove(low);
641          elements.add(low, highElem);
642        }
643        
644        return swap;
645      }
646    
647      //============================================================================
648      // REPLACING ELEMENTS
649      //============================================================================
650    
651      /**
652       * Replaces all scans in this loop that are equal to {@code replacmentScan}
653       * with {@code replacmentScan}.  This is when you want the loop to hold
654       * multiple references to the exact same scan.
655       * <p>
656       * Note that this method operates only on scans held directly by this loop,
657       * not on those of inner loops contained within this one.  To replace all
658       * scans in this loop and all contained loops, use
659       * {@link #replaceEqualScansRecursivelyWith(Scan)}.</p>
660       * 
661       * @param replacmentScan a scan used to replace all scans in this loop that
662       *                       are equal to it.
663       */
664      public void replaceEqualScansWith(Scan replacmentScan)
665      {
666        if (replacmentScan != null)
667          for (int i = elements.size()-1; i >= 0; i--)
668            if (elements.get(i).equals(replacmentScan))
669              elements.set(i, replacmentScan);
670      }
671    
672      /**
673       * Recursively replaces all scans in this loop that are equal to
674       * {@code replacmentScan} with {@code replacmentScan}.
675       * 
676       * @param replacmentScan a scan used to replace all scans in this loop that
677       *                       are equal to it.
678       * 
679       * @see #replaceEqualScansWith(Scan)
680       */
681      public void replaceEqualScansRecursivelyWith(Scan replacmentScan)
682      {
683        //Quick exit for null scan
684        if (replacmentScan == null)
685          return;
686        
687        for (int i = elements.size()-1; i >= 0; i--)
688        {
689          ScanLoopElement element = elements.get(i);
690          
691          if (element instanceof ScanLoop)
692          {
693            ((ScanLoop)element).replaceEqualScansRecursivelyWith(replacmentScan);
694          }
695          else //element instanceof Scan
696          {
697            if (element.equals(replacmentScan))
698              elements.set(i, replacmentScan);
699          }
700        }
701      }
702      
703      //============================================================================
704      // REMOVING ELEMENTS
705      //============================================================================
706    
707      @Deprecated
708      /** @deprecated Use remove(int). */
709      public ScanLoopElement removeElement(int index)
710      {
711         return remove(index);
712      }
713    
714      /**
715       * Removes the element at index.
716       * If the removed element is no longer contained anywhere in its scheduling
717       * block, its scheduling block is set to <i>null</i>.
718       * 
719       * @param index see {@link List#remove(int)} for a discussion of this
720       *              parameter.
721       *              
722       * @return the element previously at the specified position.
723       */
724      public ScanLoopElement remove(int index)
725      {
726        ScanLoopElement element = elements.remove(index);
727        
728        if (element != null)
729          nullifySchedBlockOf(element);
730        
731        return element;
732      }
733    
734      /**
735       * Removes {@code element} from this loop and all nested loops.
736       * <p>
737       * In order for {@code element} to be removed from this loop, it must
738       * be contained by this loop.
739       * Containment is determined by the {@code equalityMethod} provided.
740       * If this parameter is <i>null</i>, {@link EqualityMethod#VALUE} will
741       * be used.</p>
742       * <p>
743       * To remove {@code element} from this loop while leaving nested loops
744       * untouched, call {@link #remove(ScanLoopElement, EqualityMethod)}.</p>
745       * 
746       * @param element the element to be removed from this loop and all
747       *                nested loops.
748       * 
749       * @param equalityMethod determines whether containment is based on reference
750       *                       or value equality.  A value of <i>null</i> will be
751       *                       interpreted as a signal to use value equality.
752       */
753      public void removeRecursively(ScanLoopElement element,
754                                    EqualityMethod  equalityMethod)
755      {
756        //First remove element from all of this loop's inner loops
757        for (ScanLoopElement e : elements)
758          if (e instanceof ScanLoop)
759            ((ScanLoop)e).removeRecursively(element, equalityMethod);
760        
761        //Now remove it from this loop
762        remove(element, equalityMethod);
763      }
764      
765      /**
766       * Removes all occurrences the given element from this loop.
767       * <p>
768       * In order for {@code element} to be removed from this loop, it must
769       * be contained by this loop.
770       * Containment is determined by the {@code equalityMethod} provided.
771       * If this parameter is <i>null</i>, {@link EqualityMethod#VALUE} will
772       * be used.</p>
773       * 
774       * @param element the element to be removed from this loop.
775       * 
776       * @param equalityMethod determines whether containment is based on reference
777       *                       or value equality.  A value of <i>null</i> will be
778       *                       interpreted as a signal to use value equality.
779       */
780      public void remove(ScanLoopElement element,
781                         EqualityMethod  equalityMethod)
782      {
783        if (equalityMethod == null)
784          equalityMethod = EqualityMethod.VALUE;
785        
786        if (element instanceof ScanLoop)
787        {
788          ScanLoop scanLoop = (ScanLoop)element;
789          
790          if (equalityMethod.equals(EqualityMethod.REFERENCE))
791            removeInnerLoopByReference(scanLoop);
792          else
793            removeInnerLoopByValue(scanLoop);
794        }
795        else //element instanceof Scan
796        {
797          Scan scan = (Scan)element;
798          
799          if (equalityMethod.equals(EqualityMethod.REFERENCE))
800            removeScanByReference(scan);
801          else
802            removeScanByValue(scan);
803        }
804      }
805    
806      /** Removes all elements from this loop. */
807      public void removeAll()
808      {
809        //Save references to our elements
810        List<ScanLoopElement> removedElements =
811          new ArrayList<ScanLoopElement>(elements);
812        
813        //Remove our elements
814        elements.clear();
815        
816        //Set to null the SBs of any elements that have been orphaned
817        for (ScanLoopElement removedElement : removedElements)
818          nullifySchedBlockOf(removedElement);
819      }
820    
821      /**
822       * Removes all elements from this loop that are equal to {@code scan}.
823       * 
824       * @param scan the scan to be removed from this loop.
825       */
826      private void removeScanByValue(Scan scan)
827      {
828        List<Scan> removedScans = new ArrayList<Scan>();
829        
830        int lastIndexOfScan = elements.lastIndexOf(scan); 
831    
832        while (lastIndexOfScan >= 0)
833        {
834          Scan removedScan = (Scan)elements.remove(lastIndexOfScan);
835          
836          if (removedScan != null)
837            removedScans.add(removedScan);
838          
839          lastIndexOfScan = elements.lastIndexOf(scan); 
840        }
841        
842        for (Scan removedScan : removedScans)
843        {
844          SchedulingBlock sb = removedScan.getSchedulingBlock();
845    
846          //The use of REFERENCE here is intentional, even though we've removed
847          //scans based on value.
848          //Why the null check?  If an instance of a scan appears in the removal
849          //list more than once, it's SB will be null on iterations after its first.
850          if ((sb != null) && !sb.contains(removedScan, EqualityMethod.REFERENCE))
851            scan.setSchedulingBlock(null);
852        }
853      }
854      
855      /**
856       * Removes all elements from this loop that are equal to
857       * {@code innerLoop}.
858       * 
859       * @param innerLoop the loop to be removed from this one.
860       */
861      private void removeInnerLoopByValue(ScanLoop innerLoop)
862      {
863        List<ScanLoop> removedLoops = new ArrayList<ScanLoop>();
864        
865        int lastIndexOfInnerLoop = elements.lastIndexOf(innerLoop); 
866        
867        while (lastIndexOfInnerLoop >= 0)
868        {
869          ScanLoop removedLoop = (ScanLoop)elements.remove(lastIndexOfInnerLoop);
870          
871          if (removedLoop != null)
872            removedLoops.add(removedLoop);
873          
874          lastIndexOfInnerLoop = elements.lastIndexOf(innerLoop); 
875        }
876        
877        for (ScanLoop removedLoop : removedLoops)
878          nullifySchedBlockOf(removedLoop);
879      }
880      
881      /**
882       * Removes all scans from this loop that are reference-equal
883       * ("==") to {@code scan}.
884       * 
885       * @param scan the scan to be removed from this loop.
886       */
887      private void removeScanByReference(Scan scan)
888      {
889        //Remove all elements of this loop that == scan
890        for (int e=elements.size()-1; e >= 0; e--)
891          if (elements.get(e) == scan)
892            elements.remove(e);
893    
894        nullifySchedBlockOf(scan);
895      }
896    
897      /**
898       * Removes all elements from this loop that are reference-equal
899       * ("==") to {@code innerLoop}.
900       * 
901       * @param innerLoop the loop to be removed from this one.
902       */
903      private void removeInnerLoopByReference(ScanLoop innerLoop)
904      {
905        //Remove all elements of this loop that == innerLoop
906        for (int e=elements.size()-1; e >= 0; e--)
907          if (elements.get(e) == innerLoop)
908            elements.remove(e);
909    
910        nullifySchedBlockOf(innerLoop);
911      }
912      
913      private void nullifySchedBlockOf(ScanLoopElement element)
914      {
915        if (element instanceof ScanLoop)
916          nullifySchedBlockOf((ScanLoop)element);
917    
918        else //element instanceof Scan
919          nullifySchedBlockOf((Scan)element);
920      }
921    
922      /**
923       * Sets the scheduling block of {@code loop} and its elements to <i>null</i>,
924       * provided that the loop or element does not exist elsewhere in the SB.
925       * This method is meant to be called on recently removed loops.
926       *  
927       * @param loop a recently removed loop.
928       */
929      private void nullifySchedBlockOf(ScanLoop loop)
930      {
931        SchedulingBlock sb = loop.getSchedulingBlock();
932        
933        //We always use REFERENCE, even if the remove method that called this one
934        //is using VALUE.
935        if ((sb != null) && !sb.contains(loop, EqualityMethod.REFERENCE))
936        {
937          //First handle the elements of the loop 
938          for (ScanLoopElement e : loop.elements)
939            nullifySchedBlockOf(e);
940        
941          //Now handle the loop itself
942          loop.setSchedulingBlock(null);
943        }
944      }
945      
946      private void nullifySchedBlockOf(Scan scan)
947      {
948        SchedulingBlock sb = scan.getSchedulingBlock();
949        
950        //If the scan's SB has no more references to scan, set SB to null.
951        //Note that the SB could have refs to scan in its other loops.
952        if ((sb != null) && !sb.contains(scan, EqualityMethod.REFERENCE))
953          scan.setSchedulingBlock(null);
954      }
955    
956      //============================================================================
957      // UNRAVELING LOOP
958      //============================================================================
959    
960      /**
961       * Returns a list of scans that represents the expansion of this loop
962       * and all of its contained loops.  The returned list represents the
963       * same sequence of scans as this loop does, but in a longhand form.
964       * 
965       * @return the expanded form of this loop.
966       * 
967       * @see #toScanSet()
968       */
969      public List<Scan> toScanList()
970      {
971        List<Scan> result = new ArrayList<Scan>();
972        
973        for (int i=0; i < iterationCount; i++)
974          expand(result);
975    
976        // If this is a bracketed loop, we must add the first element again at the
977        // end.
978        if (this.bracketed && !this.elements.isEmpty())
979        {
980          ScanLoopElement first = this.elements.get(0);
981          if (first instanceof ScanLoop)
982            result.addAll(((ScanLoop)first).toScanList());
983    
984          else
985            result.add((Scan)first);
986        }
987          
988        return result;
989      }
990      
991      /**
992       * Expands one iteration of this loop into a list of scans.
993       * If an element of this loop is itself a loop, it is fully 
994       * expanded into a list of scans, which are added to the
995       * destination list.  Otherwise, if the element is a scan,
996       * it is added to the destination list immediately.
997       * 
998       * @param dest the destination list of scans.
999       */
1000      private void expand(List<Scan> dest)
1001      {
1002        for (ScanLoopElement element : elements)
1003        {
1004          if (element instanceof ScanLoop)
1005            dest.addAll(((ScanLoop)element).toScanList());
1006          else
1007            dest.add((Scan)element);
1008        }
1009      }
1010      
1011      /**
1012       * Returns the set of scans held by this loop and recursively
1013       * down through all inner loops.  If a scan appears in multiple
1014       * loops, it has only one entry in the set.  The ordering of scans
1015       * within the returned set is arbitrary.
1016       * 
1017       * @return the set of scans held by this loop and all its inner loops.
1018       * 
1019       * @see #toScanList()
1020       */
1021      public Set<Scan> toScanSet()
1022      {
1023        Set<Scan> result = new HashSet<Scan>();
1024        
1025        addScansTo(result);
1026        
1027        return result;
1028      }
1029      
1030      /**
1031       * Recursively adds scans to dest by probing loops.
1032       */
1033      private void addScansTo(Set<Scan> dest)
1034      {
1035        for (ScanLoopElement element : elements)
1036        {
1037          if (element instanceof ScanLoop)
1038            ((ScanLoop)element).addScansTo(dest);
1039          else
1040            dest.add((Scan)element);
1041        }
1042      }
1043      
1044      private List<Scan> getAllUniqueScans()
1045      {
1046        Set<ScanWrapper> wrappers = new HashSet<ScanWrapper>();
1047        
1048        addUniqueScansTo(wrappers);
1049        
1050        List<Scan> result = new ArrayList<Scan>(wrappers.size());
1051        for (ScanWrapper wrapper : wrappers)
1052          result.add(wrapper.scan);
1053        
1054        return result;
1055      }
1056      
1057      private void addUniqueScansTo(Set<ScanWrapper> dest)
1058      {
1059        for (ScanLoopElement element : elements)
1060        {
1061          if (element instanceof ScanLoop)
1062            ((ScanLoop)element).addUniqueScansTo(dest);
1063          else
1064            dest.add(new ScanWrapper((Scan)element));
1065        }
1066      }
1067    
1068      /** Helps loop find set of unique scan REFERENCES. */
1069      private class ScanWrapper
1070      {
1071        Scan scan;
1072        ScanWrapper(Scan s)  { scan = s; }
1073        public boolean equals(Object o)  { return scan == o; }
1074      }
1075      
1076      //============================================================================
1077      // TEXT
1078      //============================================================================
1079      
1080      /* (non-Javadoc)
1081       * @see ScanLoopElement#toSummaryString()
1082       */
1083      public String toSummaryString()
1084      {
1085        StringBuilder buff = new StringBuilder();
1086        
1087        buff.append("id=").append(getId());
1088        buff.append(", size=").append(elements.size());
1089        buff.append(", maxDur=").append(maximumDuration);
1090        
1091        return buff.toString();
1092      }
1093      
1094      /**
1095       * Creates a new scan loop from the XML data in the given file.
1096       * 
1097       * @param xmlFile the name of an XML file.  This method will attempt to locate
1098       *                the file by using {@link Class#getResource(String)}.
1099       *                
1100       * @return a new scan loop from the XML data in the given file.
1101       * 
1102       * @throws FileNotFoundException if the XML file cannot be found.
1103       * 
1104       * @throws JAXBException if the schema file used (if any) is malformed, if
1105       *           the XML file cannot be read, or if the XML file is not
1106       *           schema-valid.
1107       * 
1108       * @throws XMLStreamException if there is a problem opening the XML file,
1109       *           if the XML is not well-formed, or for some other
1110       *           "unexpected processing conditions".
1111       */
1112      public static ScanLoop fromXml(String xmlFile)
1113        throws JAXBException, XMLStreamException, FileNotFoundException
1114      {
1115        ScanLoop newLoop = JaxbUtility.getSharedInstance()
1116                                      .xmlFileToObject(xmlFile, ScanLoop.class);
1117        
1118        newLoop.testScansForResourceFromJaxb();
1119        
1120        return newLoop;
1121      }
1122      
1123      /**
1124       * Creates a new scan loop based on the XML data read from {@code reader}.
1125       * 
1126       * @param reader the source of the XML data.
1127       *               If this value is <i>null</i>, <i>null</i> is returned.
1128       *               
1129       * @return a new scan loop based on the XML data read from {@code reader}.
1130       * 
1131       * @throws XMLStreamException if the XML is not well-formed,
1132       *           or for some other "unexpected processing conditions".
1133       *           
1134       * @throws JAXBException if anything else goes wrong during the
1135       *           transformation.
1136       */
1137      public static ScanLoop fromXml(Reader reader)
1138        throws JAXBException, XMLStreamException
1139      {
1140        ScanLoop newLoop = JaxbUtility.getSharedInstance()
1141                                       .readObjectAsXmlFrom(reader, ScanLoop.class, null);
1142        
1143        newLoop.testScansForResourceFromJaxb();
1144        
1145        return newLoop;
1146      }
1147    
1148      /**
1149       * Meant for use by containers of scan loops; most clients should not use this method.
1150       * @throws JAXBException
1151       *   if the any scan of this loop has
1152       *   no resource and the useResourceOfPriorScan flag is false.
1153       */
1154      public void testScansForResourceFromJaxb() throws JAXBException
1155      {
1156        for (Scan s : getAllUniqueScans())
1157          s.testForResourceFromJaxb();
1158      }
1159    
1160      /**
1161       * Resets this loop's id to UNIDENTIFIED and calls all of it's
1162       * chilren's clearId() methods.
1163       */
1164      public void clearId()
1165      {
1166        super.clearId();
1167    
1168        for (ScanLoopElement e : getElements())
1169          e.clearId();
1170      }
1171    
1172      //============================================================================
1173      // 
1174      //============================================================================
1175    
1176      /**
1177       *  Returns a scan loop that is a deep copy of this one.
1178       *  <p>
1179       *  The returned loop is, for the most part, a deep copy of this one.
1180       *  For example, any scans or (inner) scan loops in the returned loop
1181       *  will be copies of their counterparts in this loop.
1182       *  However, there are a few exceptions:
1183       *  <ol>
1184       *    <li>The ID will be set to
1185       *        {@link Identifiable#UNIDENTIFIED}.</li>
1186       *    <li>The schedulingBlock will be <i>null</i>.</li>
1187       *    <li>The createdOn and lastUpdatedOn attributes will be set to the
1188       *        current system time.</li>
1189       *  </ol></p>
1190       *  <p>
1191       *  If anything goes wrong during the cloning procedure,
1192       *  a {@code RuntimeException} will be thrown.</p>
1193       */
1194      public ScanLoop clone()
1195      {
1196        ScanLoop clone = cloneAllButElements();
1197        
1198        //If this (the original) loop has multiple instances of the same
1199        //scan (or has inner loops that have references to scans in this
1200        //or other inner loops), the clone should be constructed in such
1201        //a way that it, too, has shared references to scans.  These
1202        //references, though, should not be shared with this (the original)
1203        //loop.  What we're doing here is setting a mapping from our
1204        //original scans to a set of clones that should be used in their
1205        //place.  This will keep us from creating unwanted clones.
1206        //Example: This loop has scans A, B, A.  Were we to blindly clone
1207        //scans, the cloned loop would have scans A', B', A", when we
1208        //really want A', B', A'.
1209        List<Scan> originalScans = this.getAllUniqueScans();
1210        int scanCount = originalScans.size();
1211        List<Scan> clonedScans = new ArrayList<Scan>(scanCount);
1212        
1213        for (int s=0; s < scanCount; s++)
1214          clonedScans.add(originalScans.get(s).clone());
1215        
1216        //Give the clone its own set of elements
1217        cloneElementsInto(clone, originalScans, clonedScans);
1218        
1219        return clone;
1220      }
1221      
1222      /** Called only from the public clone method. */
1223      private ScanLoop clone(List<Scan> originals, List<Scan> clones)
1224      {
1225        ScanLoop clone = cloneAllButElements();
1226    
1227        cloneElementsInto(clone, originals, clones);
1228        
1229        return clone;
1230      }
1231      
1232      /** Clones all attributes of this loop, except for its elements. */
1233      private ScanLoop cloneAllButElements()
1234      {
1235        ScanLoop clone;
1236        
1237        try
1238        {
1239          //This line takes care of the primitive & immutable fields properly
1240          clone = (ScanLoop)super.clone();
1241    
1242          clone.maximumDuration = this.maximumDuration.clone();
1243        }
1244        catch (Exception ex)
1245        {
1246          throw new RuntimeException(ex);
1247        }
1248        
1249        return clone;
1250      }
1251      
1252      /** Clones only the elements of this loop. */
1253      private void cloneElementsInto(ScanLoop clonedLoop, List<Scan> originals,
1254                                                          List<Scan> clones)
1255      {
1256        clonedLoop.elements = new ArrayList<ScanLoopElement>();
1257        
1258        for (ScanLoopElement e : this.elements)
1259        {
1260          if (e instanceof Scan)
1261            clonedLoop.elements.add(getClonedScan((Scan)e, originals, clones));
1262          
1263          else //e instanceof ScanLoop
1264            clonedLoop.elements.add(((ScanLoop)e).clone(originals, clones));
1265        }
1266      }
1267    
1268      private Scan getClonedScan(Scan scan, List<Scan> originals, List<Scan> clones)
1269      {
1270        for (int s=originals.size()-1; s >= 0; s--)
1271          if (originals.get(s) == scan) //Intentional use of "==" instead of .equals
1272            return clones.get(s);
1273        
1274        throw new RuntimeException(
1275          "Programmer error: clone for scan should already exist.");
1276      }
1277      
1278      /**
1279       * Returns <i>true</i> if {@code o} is equal to this scan loop.
1280       * <p>
1281       * In order to be equal to this loop, {@code o} must be non-null and
1282       * of the same class as this loop.  In order for two loops to be
1283       * equal, they must contain equal elements in the same order.  Their
1284       * iteration counts and maximum durations must also be equal.</p>
1285       */
1286      public boolean equals(Object o)
1287      {
1288        //Quick exit if o is this
1289        if (o == this)
1290          return true;
1291        
1292        //Quick exit if parent class says objects not equal
1293        if (!super.equals(o))
1294          return false;
1295        
1296        //A safe cast because super class ensures classes are same
1297        ScanLoop other = (ScanLoop)o;
1298    
1299        //Must be same size and have same iteration count
1300        if ((other.iterationCount != this.iterationCount) ||
1301            (other.size()         != this.size())         ||
1302            !other.maximumDuration.equals(this.maximumDuration))
1303          return false;
1304        
1305        //Loops are equal only if their elements are equal and in same order
1306        for (int e=0; e < size(); e++)
1307          if (!other.elements.get(e).equals(this.elements.get(e)))
1308            return false;
1309        
1310        //No differences found
1311        return true;
1312      }
1313    
1314      /* (non-Javadoc)
1315       * @see ScanLoopElement#hashCode()
1316       */
1317      public int hashCode()
1318      {
1319        //Taken from the Effective Java book by Joshua Bloch.
1320        //The constants 17 & 37 are arbitrary & carry no meaning.
1321        int result = super.hashCode();
1322        
1323        //You MUST keep this method in sync w/ the equals method
1324        
1325        result = 37 * result + new Integer(iterationCount).hashCode();
1326        result = 37 * result + maximumDuration.hashCode();
1327        result = 37 * result + elements.hashCode();
1328        
1329        return result;
1330      }
1331    
1332      //============================================================================
1333      // 
1334      //============================================================================
1335    
1336      //This is here for quick manual testing
1337      /*
1338      public static void main(String[] args)
1339      {
1340        ScanBuilder builder = new ScanBuilder();
1341        builder.setIdentifiers(true);
1342        
1343        ScanLoop loop = builder.makeScanLoop();
1344        
1345        try {
1346          System.out.println(loop.toXml());
1347        }
1348        catch (JAXBException ex) {
1349          System.out.println("Trouble w/ ScanLoop.toXml.  Msg:");
1350          System.out.println(ex.getMessage());
1351          ex.printStackTrace();
1352          
1353          System.out.println("Attempting to write XML w/out schema verification:");
1354          JaxbUtility.getSharedInstance().setLookForDefaultSchema(false);
1355          try
1356          {
1357            System.out.println(loop.toXml());
1358          }
1359          catch (JAXBException ex2)
1360          {
1361            System.out.println("Still had trouble w/ ScanLoop.toXml.  Msg:");
1362            System.out.println(ex.getMessage());
1363            ex.printStackTrace();
1364          }
1365        }
1366        try
1367        {
1368          java.io.FileWriter writer =
1369            new java.io.FileWriter("/export/home/calmer/dharland/JUNK/ScanLoop.xml");
1370          loop.writeAsXmlTo(writer);
1371        }
1372        catch (Exception ex3)
1373        {
1374          System.out.println(ex3.getMessage());
1375          ex3.printStackTrace();
1376        }
1377      }
1378      */
1379      /*
1380      public static void main(String... args) throws Exception
1381      {
1382        java.io.FileReader reader =
1383          new java.io.FileReader("/export/home/calmer/dharland/JUNK/ScanLoop.xml");
1384        
1385        ScanLoop loop = ScanLoop.fromXml(reader);
1386        
1387        java.io.FileWriter writer =
1388          new java.io.FileWriter("/export/home/calmer/dharland/JUNK/ScanLoop-OUT.xml");
1389        loop.writeAsXmlTo(writer);
1390      }
1391      */
1392    }