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