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 }