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