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.Date; 009 import java.util.Collections; 010 import java.util.HashSet; 011 import java.util.List; 012 import java.util.Set; 013 014 import javax.xml.bind.JAXBException; 015 import javax.xml.bind.annotation.XmlAttribute; 016 import javax.xml.bind.annotation.XmlElement; 017 import javax.xml.bind.annotation.XmlElementWrapper; 018 import javax.xml.bind.annotation.XmlRootElement; 019 import javax.xml.bind.annotation.XmlTransient; 020 import javax.xml.bind.annotation.XmlType; 021 import javax.xml.stream.XMLStreamException; 022 023 import edu.nrao.sss.model.RepositoryException; 024 import edu.nrao.sss.model.UserAccountable; 025 import edu.nrao.sss.model.proposal.Proposal; 026 import edu.nrao.sss.model.proposal.ProposalProvider; 027 import edu.nrao.sss.model.resource.TelescopeType; 028 import edu.nrao.sss.util.EventSetStatus; 029 import edu.nrao.sss.util.EventStatus; 030 import edu.nrao.sss.util.Identifiable; 031 import edu.nrao.sss.util.JaxbUtility; 032 import edu.nrao.sss.validation.FailureSeverity; 033 import edu.nrao.sss.validation.ValidationException; 034 import edu.nrao.sss.validation.ValidationFailure; 035 036 /** 037 * A scientifically independent subset of the observations set forth in 038 * a {@link Proposal}. 039 * <p> 040 * An approved {@code Proposal} may lead to one or more {@code Project}s. 041 * These projects are created by the Observing Program Committee. 042 * Different projects from the same proposal may have different scientific 043 * ratings. 044 * A proposal is typically split into projects when time is requested on 045 * different telescopes. There is usually a one-to-one corespondence between 046 * project and telescope within a given proposal.</p> 047 * <p> 048 * <b>Version Info:</b> 049 * <table style="margin-left:2em"> 050 * <tr><td>$Revision: 2277 $</td> 051 * <tr><td>$Date: 2009-04-29 11:19:38 -0600 (Wed, 29 Apr 2009) $</td> 052 * <tr><td>$Author: dharland $</td> 053 * </table></p> 054 * 055 * @author David M. Harland 056 * @since 2006-02-24 057 */ 058 @XmlRootElement 059 @XmlType(propOrder={"projectCode", 060 "createdBy","createdOn","lastUpdatedBy","lastUpdatedOn", 061 "executionStatus", "test", "telescope", 062 "title", "editor", "proposalCode", "projectType", 063 "scientificPriority", "allocatedTime", "comments", 064 "progBlocks"}) 065 public class Project 066 implements Identifiable, UserAccountable, Cloneable 067 { 068 static final String NO_PROJECT_CODE = "[None]"; 069 static final String NO_PROPOSAL_CODE = "[None]"; 070 static final String UNINIT_TITLE = "[New Project]"; 071 072 private static final String NO_COMMENTS = ""; 073 074 //IDENTIFICATION 075 private Long id; //A unique identifier for the persistence layer. 076 private String projectCode; //This project's identifier; a problem-domain concept. 077 private String title; //This project's title; c/b diff than proposal's title. 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 private Long editor; //This is a user ID 085 086 //CONTAINER & CONTAINED OBJECTS 087 private Proposal proposal; 088 private String proposalCode; 089 private List<ProgramBlock> progBlocks; 090 091 //OTHER ATTRIBUTES 092 private boolean isTest; 093 private ProjectType projectType; 094 private EventSetStatus executionStatus; 095 private TelescopeType telescope; 096 private ScientificPriority scientificPriority; 097 private String comments; 098 //TODO 1. Use TimeDuration 2. Stop storing timeUsedSoFar 099 private double allocatedTime; //In whole & fractional hours 100 private double timeUsedSoFar; //In whole & fractional hours 101 102 //These instance variables are not persisted 103 private ProposalProvider proposalProvider; 104 105 //============================================================================ 106 // INSTANCE CREATION & INITIALIZATION 107 //============================================================================ 108 109 /** 110 * Creates an instance of a project that is appropriate for the given 111 * telescope. 112 * The new project's telescope will have been communicated to 113 * the project via its {@link #setTelescope(TelescopeType) 114 * setTelescope} method before it is returned. 115 * 116 * @param telescope the telescope for which a new project is desired. 117 * If {@code telescope} is <i>null</i>, it will be 118 * treated as if it had been 119 * {@link edu.nrao.sss.model.resource.TelescopeType#getDefault()}. 120 * 121 * @return a new project for {@code telescope}. 122 */ 123 public static Project createProject(TelescopeType telescope) 124 { 125 if (telescope == null) 126 telescope = TelescopeType.getDefault(); 127 128 //At this time we have no subclasses of project, so create the same 129 //kind of project no matter what kind of telescope we're passed. 130 Project newProject = new Project(); 131 132 newProject.setTelescope(telescope); 133 134 return newProject; 135 } 136 137 /** Creates a new instance. */ 138 public Project() 139 { 140 progBlocks = new ArrayList<ProgramBlock>(); 141 142 initialize(); 143 } 144 145 /** Initializes the instance variables of this class. */ 146 private void initialize() 147 { 148 id = Identifiable.UNIDENTIFIED; 149 projectCode = NO_PROJECT_CODE; 150 title = UNINIT_TITLE; 151 152 createdBy = UserAccountable.NULL_USER_ID; 153 createdOn = new Date(); 154 lastUpdatedBy = UserAccountable.NULL_USER_ID; 155 lastUpdatedOn = new Date(); 156 editor = UserAccountable.NULL_USER_ID; 157 158 proposal = null; 159 proposalCode = NO_PROPOSAL_CODE; 160 proposalProvider = null; 161 162 isTest = true; 163 projectType = ProjectType.getDefault(); 164 executionStatus = EventSetStatus.NOT_YET_SCHEDULED; 165 telescope = TelescopeType.getDefault(); 166 scientificPriority = ScientificPriority.UNKNOWN; 167 allocatedTime = 0.0; 168 timeUsedSoFar = 0.0; 169 comments = NO_COMMENTS; 170 } 171 172 /** 173 * Resets this project to its initial state. A reset project has the same 174 * state as a new project. 175 */ 176 public void reset() 177 { 178 initialize(); 179 180 removeAllProgramBlocks(); 181 } 182 183 //============================================================================ 184 // IDENTIFICATION 185 //============================================================================ 186 187 /* (non-Javadoc) 188 * @see edu.nrao.sss.model.util.Identifiable#getId() 189 */ 190 @XmlAttribute 191 public Long getId() 192 { 193 return id; 194 } 195 196 void setId(Long id) 197 { 198 this.id = id; 199 } 200 201 /** 202 * Resets this project's id to UNIDENTIFIED and calls all of it's 203 * ProgramBlock's clearId() methods. 204 */ 205 public void clearId() 206 { 207 id = Identifiable.UNIDENTIFIED; 208 209 for (ProgramBlock pb : getProgramBlocks()) 210 pb.clearId(); 211 } 212 213 /** 214 * Sets this project's NRAO code. 215 * <p> 216 * Note that this is <i>not</i> necessarily the identifier that the 217 * persistence layer will use to uniquely identify projects in the 218 * data store. (See {@link #getId()} for that concept.) Instead, 219 * this is NRAO's identifier for this project.</p> 220 * 221 * @param newCode this project's NRAO identifier. 222 */ 223 public void setProjectCode(String newCode) 224 { 225 projectCode = (newCode == null) ? NO_PROJECT_CODE : newCode; 226 } 227 228 /** 229 * Returns this project's NRAO code. 230 * 231 * @return this project's NRAO code. 232 * 233 * @see #setProjectCode(String) 234 */ 235 public String getProjectCode() 236 { 237 return projectCode; 238 } 239 240 /** 241 * Sets the title of this project. Note that this project's 242 * title may be different than the title of the 243 * {@link Proposal} that led to this project. 244 * 245 * @param newTitle the title of this project. 246 */ 247 public void setTitle(String newTitle) 248 { 249 title = (newTitle == null) ? UNINIT_TITLE : newTitle; 250 } 251 252 /** 253 * Returns the title of this project. 254 * 255 * @return the title of this project. 256 */ 257 public String getTitle() 258 { 259 return title; 260 } 261 262 //============================================================================ 263 // CONTAINER (Proposal) 264 //============================================================================ 265 266 /** 267 * Sets the proposal that spawned this project. 268 * <p> 269 * If {@code proposal} is the same as {@code this.getProposal()}, 270 * this method will do nothing. Otherwise a reference to {@code proposal} 271 * is saved in this project.</p> 272 * <p> 273 * Consequences of Changing a Project's Proposal: 274 * <ul> 275 * <li>All existing sources in the project are removed and replaced 276 * with the sources from the proposal.</li> 277 * <li>All program blocks are removed.</li> 278 * </ul></p> 279 * 280 * @param proposal the proposal that spawned this project. 281 */ 282 public void setProposal(Proposal proposal) 283 { 284 //Quick exit if this project is already linked to incoming proposal 285 if (this.proposalIsAlreadySetTo(proposal)) 286 return; 287 288 //Save the proposal & its code. Note: The proposal has no link to its 289 //projects, so we do not need to update it. 290 this.proposal = proposal; 291 292 if (proposal != null) 293 { 294 this.proposalCode = proposal.getProposalCode(); 295 296 //TODO Decide if this is really what we want to do: 297 setEditor(proposal.getEditor()); 298 } 299 else 300 { 301 this.proposalCode = NO_PROPOSAL_CODE; 302 } 303 304 //Since we're dealing w/ a new proposal, drop the current program blocks 305 progBlocks.clear(); 306 } 307 308 private boolean proposalIsAlreadySetTo(Proposal proposal) 309 { 310 //If both are null, they're equal 311 if ((proposal == null) && (proposal == this.proposal)) 312 return true; 313 314 if ((proposal != null) && proposal.equals(this.proposal)) 315 return true; 316 317 return false; 318 } 319 320 /** 321 * Returns the proposal that spawned this project. Note that the 322 * returned proposal may have spawned additional projects. 323 * <p> 324 * <i>Note to Developers:</i> This method may be expensive. If 325 * all you need is the proposal's ID code, the 326 * {@link #getProposalCode()} method is more efficient. Of course, 327 * if you need extensive information from the proposal, 328 * you should use this method.</p> 329 * <p> 330 * Note that it is possible for this project to not yet have 331 * a proposal. In this situation, the return value will be 332 * <i>null</i>. Also, if there are any problems encountered 333 * while using the proposal repository, <i>null</i> will 334 * be returned.</p> 335 * 336 * @return the proposal that spawned this project. 337 * 338 * @see #getProposalCode() 339 * @see #hasProposal() 340 */ 341 @XmlTransient 342 public Proposal getProposal() 343 { 344 //Just-in-time fetch of the proposal 345 if (null == proposal) 346 proposal = fetchProposal(); 347 348 return proposal; 349 } 350 351 /** 352 * Returns <i>true</i> if this project has a non-null proposal. 353 * <p> 354 * The only times this method should return <i>false</i> are: 355 * <ol> 356 * <li>This project has just been created and its proposal 357 * has not yet been set.</li> 358 * <li>A client set this project's proposal to <i>null</i></li> 359 * </ol></p> 360 * 361 * @return <i>true</i> if this project has a non-null proposal. 362 */ 363 public boolean hasProposal() 364 { 365 return !NO_PROPOSAL_CODE.equals(proposalCode); 366 } 367 368 /** 369 * Tells this project where to look for proposals if it does not 370 * already have one. 371 * @param newProvider a provider of proposals. 372 */ 373 public void setProposalProvider(ProposalProvider newProvider) 374 { 375 //We're intentionally allowing a null value here 376 proposalProvider = newProvider; 377 } 378 379 /** 380 * Uses a provider to find the proposal to which this project belongs. 381 * 382 * @return the proposal represented by this project's 383 * proposal ID attribute. 384 */ 385 private Proposal fetchProposal() 386 { 387 Proposal result = null; 388 389 //Try to fetch the proposal ONLY if we have an ID for it. 390 if (!NO_PROPOSAL_CODE.equals(proposalCode) && 391 (null != proposalProvider)) 392 { 393 try 394 { 395 result = proposalProvider.findByCode(proposalCode); 396 } 397 catch (RepositoryException de) 398 { 399 //if we get an exception, we want to return null (which is what it is 400 //initialized to). 401 } 402 } 403 404 return result; 405 } 406 407 /** 408 * Returns the NRAO code of the proposal that spawned this project. 409 * <p> 410 * Calling this method instead of {@code getProposal().getPropId()} 411 * may be more efficient.</p> 412 * 413 * @return the NRAO code of the proposal that spawned this project. 414 * 415 * @see #getProposal() 416 */ 417 @XmlElement 418 public String getProposalCode() 419 { 420 return proposalCode; 421 } 422 423 public void setProposalCode(String newCode) 424 { 425 proposalCode = (newCode == null) ? NO_PROPOSAL_CODE : newCode; 426 } 427 428 //============================================================================ 429 // PROGRAM BLOCKS 430 //============================================================================ 431 432 /** 433 * Creates and returns a new program block that is suitable for use with 434 * this project. 435 * The returned program block has <i>not</i> been added to 436 * this project. 437 * 438 * @return a new program block that can later be added to this project. 439 */ 440 public ProgramBlock createProgramBlock() 441 { 442 return new ProgramBlock(); 443 } 444 445 /** 446 * Adds the given program block to this project. 447 * <p> 448 * If {@code progBlock} is already part of this project, 449 * or if it is <i>null</i>, no action is taken. 450 * Otherwise it is added to this project, removed from 451 * the project to which it had been attached (if any), 452 * and updated so that it knows it belongs to this project.</p> 453 * 454 * @param progBlock the program block to be added to this project. 455 */ 456 public void addProgramBlock(ProgramBlock progBlock) 457 { 458 addProgramBlock(progBlocks.size(), progBlock); 459 } 460 461 /** 462 * Adds the given program block to this project at index {@code idx}. 463 * <p> 464 * If {@code progBlock} is already part of this project, 465 * or if it is <i>null</i>, no action is taken. 466 * Otherwise it is added to this project, removed from 467 * the project to which it had been attached (if any), 468 * and updated so that it knows it belongs to this project.</p> 469 * 470 * @param idx the index in our list of program blocks at which to add {@code progBlock}. 471 * @param progBlock the program block to be added to this project. 472 */ 473 public void addProgramBlock(int idx, ProgramBlock progBlock) 474 { 475 //Quick exit if progBlock is null, or if its project is this project. 476 //(Intentional use of "==" here.) 477 if ((progBlock == null) || (progBlock.getProject() == this)) 478 return; 479 480 if (idx < 0 || idx > progBlocks.size()) 481 { 482 throw new IndexOutOfBoundsException(idx + 483 " is not within the bounds: (0, " + progBlocks.size() + ")"); 484 } 485 486 //TODO Make sure progBlock is of correct variety 487 488 //Remove progBlock from its former project 489 Project formerProject = progBlock.getProject(); 490 if (formerProject != null) 491 formerProject.progBlocks.remove(progBlock); 492 493 //Add progBlock to our collection 494 progBlocks.add(idx, progBlock); 495 496 //Tell progBlock it now belongs to this project 497 progBlock.simplySetProject(this); 498 499 //Add any direct prereqs and/or adjust direct prereq references 500 addOrAdjustDirectPrereqsOf(progBlock); 501 } 502 503 //The incoming PB might have prerequisites. If a given prereq is not 504 //already part of this project, we need to add it. If a given prereq is 505 //EQUAL TO a PB already in this project, we need to tell the incoming PB 506 //to use the equivalent PB in this project as a prereq. 507 //TODO Reexamine that last statement. Are we saying this Project cannot 508 // hold multiple value-equal PBs? 509 private void addOrAdjustDirectPrereqsOf(ProgramBlock pb) 510 { 511 //Make a new set to avoid concurrent modification exceptions 512 Set<ProgramBlock> prereqs = 513 new HashSet<ProgramBlock>(pb.getDirectPrerequisites()); 514 515 for (ProgramBlock prereq : prereqs) 516 { 517 int index = progBlocks.indexOf(prereq); 518 519 if (index >= 0) //this project has a PB equal to prereq 520 { 521 ProgramBlock equalPB = progBlocks.get(index); 522 523 //The PB in this project is equal to, but not the same object as, prereq. 524 //We need pb's list of prereqs to use the equiv PB from project. 525 if (prereq != equalPB) 526 { 527 pb.removePrerequisite(prereq); 528 pb.addPrerequisite(equalPB); 529 } 530 //else prereq == equalPB, so do nothing more 531 } 532 else //this project does NOT have PB equal to prereq 533 { 534 //If the prereq is in another project, we do not disturb that project. 535 //Instead, we copy the prereq and adjust the reference to prereq 536 //held by pb. This also has the effect of adding pb to this project. 537 if (prereq.hasProject()) 538 { 539 ProgramBlock copyOfPrereq = prereq.clone(); 540 copyOfPrereq.setProject(null); 541 pb.removePrerequisite(prereq); 542 pb.addPrerequisite(copyOfPrereq); 543 } 544 //If the prereq is not already in another project, we just add it here 545 else 546 { 547 addProgramBlock(prereq); 548 } 549 } 550 } 551 } 552 553 /** 554 * Removes the given program block from this project. 555 * <p> 556 * If {@code progBlock} is <i>null</i>, or if it does 557 * not belong to this project, nothing happens. 558 * If it is a prerequisite of one or more of the program 559 * blocks held by this project, an exception is thrown 560 * and it is not removed from this project. 561 * Otherwise, {@code progBlock} is removed from this 562 * project and has its project attribute set to 563 * <i>null</i>.</p> 564 * 565 * @param progBlock the program block to be removed. 566 */ 567 public void removeProgramBlock(ProgramBlock progBlock) 568 { 569 //Quick exit if progBlock is null or does not belong to this project 570 if ((progBlock == null) || (progBlock.getProject() != this)) 571 return; 572 573 //Throw exception if schedBlock is a prereq of another SB 574 for (ProgramBlock pb : progBlocks) 575 { 576 if ((pb != progBlock) && progBlock.isDirectPrerequisiteOf(pb)) 577 throw prereqRemovalError(progBlock); 578 } 579 580 //Remove the progBlock from our collection 581 progBlocks.remove(progBlock); 582 583 //Tell progBlock that it belongs to no project 584 progBlock.simplySetProject(null); 585 } 586 587 /** Constructs and returns an exception. */ 588 private ValidationException prereqRemovalError(ProgramBlock target) 589 { 590 StringBuilder buff = new StringBuilder("Program block '"); 591 592 buff.append(target.getName()) 593 .append("' is a direct prerequisite of the following PBs:"); 594 595 for (ProgramBlock pb : progBlocks) 596 { 597 if ((pb != target) && target.isDirectPrerequisiteOf(pb)) 598 buff.append(' ').append(pb.getName()).append(','); 599 } 600 601 int buffLen = buff.length(); 602 buff.replace(buffLen-1, buffLen, "."); 603 604 buff.append(" Because of this, ").append(target.getName()) 605 .append(" was not removed. Please first remove it from the ") 606 .append("prerequisite lists of each of the above PBs."); 607 608 ValidationFailure failure = 609 new ValidationFailure(buff.toString(), "Attempt to delete prereq PB.", 610 FailureSeverity.ERROR, target, 611 this.getClass().getName(), 612 "prereqRemovalError"); 613 614 ValidationException exception = 615 new ValidationException("Cannot remove Program Block."); 616 617 exception.getFailures().add(failure); 618 619 return exception; 620 } 621 622 /** 623 * Removes all program blocks from this project. 624 * Each program block is notified that it no longer has 625 * a containing project. 626 */ 627 public void removeAllProgramBlocks() 628 { 629 for (ProgramBlock progBlock : progBlocks) 630 progBlock.simplySetProject(null); 631 632 progBlocks.clear(); 633 } 634 635 /** 636 * Returns the program blocks that belong to this project. 637 * <p> 638 * The returned {@code List} is a copy of the one held internally 639 * by this project, so changes made to it will not be reflected herein.</p> 640 * 641 * @return the program blocks that belong to this project. 642 */ 643 public List<ProgramBlock> getProgramBlocks() 644 { 645 return new ArrayList<ProgramBlock>(progBlocks); 646 } 647 648 /** 649 * Returns a list of this project's program blocks sorted such that 650 * any prerequisites of the block at index i are at in indices greater than i. 651 * <p> 652 * Note that the sort determines only how prerequisites and things 653 * dependent on them are place relative to one another. For example, 654 * imagine four blocks A, B, C, and D. C has A and B as prerequisites 655 * and no other block has prerequisites. 656 * The returned list will place C in a lower index than A and B. 657 * However, we know nothing about how A and B will be placed relative 658 * to each other, nor do we know ahead of time where D will be placed.</p> 659 * 660 * @return program blocks sorted by prerequisite relationships. 661 */ 662 public List<ProgramBlock> getProgramBlocksSortedByPrereqs() 663 { 664 List<ProgramBlock> pbs = getProgramBlocks(); 665 666 Collections.sort(pbs, ProgramBlock.getPrequisiteComparator()); 667 668 return pbs; 669 } 670 671 /** Returns the position of progBlock in our list using "==". */ 672 private int getProgBlockIndex(ProgramBlock progBlock) 673 { 674 int pbCount = progBlocks.size(); 675 int index = -1; 676 677 for (int p=0; p < pbCount; p++) 678 { 679 //Intentional use of "==" 680 if (progBlocks.get(p) == progBlock) 681 { 682 //Break at first match; "add" method does not allow multiple 683 //entries of same instance, so th is is safe. 684 index = p; 685 break; 686 } 687 } 688 689 return index; 690 } 691 692 /** 693 * Returns the schedule entries of this project that 694 * have an execution status of {@code execStatus}. Only the status of the 695 * entries is considered. This method does not look, for example, at the 696 * {@link #isTest()} property. 697 * 698 * @param execStatus the execution status of the schedule entries to be added 699 * to {@code destination}. 700 * 701 * @param destination the collection to which the schedule entries should be 702 * added. If this collection is <i>null</i>, a new one 703 * will be created. This collection is returned. 704 * 705 * @return the collection holding the schedule entries. This will be either 706 * {@code destination} or a new collection, if {@code destination} is 707 * <i>null</i>. 708 * 709 * @see #getReadyToScheduleEntries(Collection) 710 */ 711 public Collection<ScheduleEntry> getScheduleEntries( 712 EventStatus execStatus, 713 Collection<ScheduleEntry> destination) 714 { 715 if (destination == null) 716 destination = new ArrayList<ScheduleEntry>(); 717 718 for (ProgramBlock progBlock : progBlocks) 719 progBlock.getScheduleEntries(execStatus, destination); 720 721 return destination; 722 } 723 724 /** 725 * Returns the schedule entries of this project that are ready for 726 * scheduling. If this is a test project, or if this 727 * project has no <tt>NOT_YET_SCHEDULED</tt> entries, the returned 728 * collection will be empty. 729 * 730 * @param destination the collection to which the ready-to-be-scheduled 731 * entries should be added. If this collection is 732 * <i>null</i>, a new one will be created. This 733 * collection is returned. 734 * 735 * @return a collection of ready-to-be-scheduled entries, or an empty 736 * collection. The returned collection will be either 737 * {@code destination} or a new collection, if {@code destination} is 738 * <i>null</i>. 739 * 740 * @see #getScheduleEntries(EventStatus, Collection) 741 */ 742 public Collection<ScheduleEntry> 743 getReadyToScheduleEntries(Collection<ScheduleEntry> destination) 744 { 745 if (destination == null) 746 destination = new ArrayList<ScheduleEntry>(); 747 748 if (!isTest()) 749 getScheduleEntries(EventStatus.NOT_YET_SCHEDULED, destination); 750 751 return destination; 752 } 753 754 @XmlElementWrapper(name="programBlocks") 755 @XmlElement(name="programBlock") 756 @SuppressWarnings("unused") //JAXB use 757 private void setProgBlocks(ProgramBlock[] replacements) 758 { 759 progBlocks.clear(); 760 761 for (ProgramBlock pb : replacements) 762 { 763 pb.simplySetProject(this); 764 progBlocks.add(pb); 765 } 766 } 767 768 @SuppressWarnings("unused") //JAXB use 769 private ProgramBlock[] getProgBlocks() 770 { 771 return progBlocks.toArray(new ProgramBlock[progBlocks.size()]); 772 } 773 774 //============================================================================ 775 // STATUS 776 //============================================================================ 777 778 /** 779 * This method is here for persistence mechanisms such as Hibernate 780 * and JAXB. It is NOT appropriate to let other objects set the 781 * status, because the status of this object is derived from that 782 * of contained objects. 783 */ 784 @XmlElement 785 @SuppressWarnings("unused") 786 private void setExecutionStatus(EventSetStatus newStatus) 787 { 788 executionStatus = newStatus; 789 } 790 791 /** 792 * Returns this project's execution status. 793 * 794 * @return this project's execution status. 795 */ 796 public EventSetStatus getExecutionStatus() 797 { 798 if (!executionStatus.isFinal()) 799 recomputeStatus(); 800 801 return executionStatus; 802 } 803 804 /** 805 * Sets this project's status based on the combined statuses of 806 * this project's program blocks. 807 */ 808 private void recomputeStatus() 809 { 810 int execBlockCount = progBlocks.size(); 811 812 EventSetStatus[] statuses = new EventSetStatus[execBlockCount]; 813 814 for (int e=0; e < execBlockCount; e++) 815 statuses[e] = progBlocks.get(e).getExecutionStatus(); 816 817 executionStatus = EventSetStatus.createFrom(statuses); 818 } 819 820 //============================================================================ 821 // OTHER ATTRIBUTES 822 //============================================================================ 823 824 /** 825 * Sets the telescope on which this project will be performed. 826 * 827 * @param telescope the telescope used by this project. 828 */ 829 public void setTelescope(TelescopeType telescope) 830 { 831 if (!isTooLateToChangeTelescope()) 832 this.telescope = (telescope == null) ? TelescopeType.getDefault() : telescope; 833 } 834 835 /** 836 * Returns <i>true</i> if it is too late to change the telescope for this project. 837 * 838 * @return <i>true</i> if it is too late to change the telescope for this project. 839 */ 840 private boolean isTooLateToChangeTelescope() 841 { 842 //TODO Do we need this construct? Rationale: once things like program blocks 843 // and scheduling blocks are set up, changing the telescope could be a 844 // big no-no. 845 //return !this.telescope.equals(TelescopeType.UNKNOWN); 846 return false; 847 } 848 849 /** 850 * Returns the telescope used by this project. 851 * 852 * @return telescope used by this project. 853 */ 854 public TelescopeType getTelescope() 855 { 856 return telescope; 857 } 858 859 /** 860 * Sets the scientific priority for this project. 861 * 862 * @param newPriority the scientific priority for this project. 863 */ 864 public void setScientificPriority(ScientificPriority newPriority) 865 { 866 scientificPriority = (newPriority == null) ? ScientificPriority.UNKNOWN 867 : newPriority; 868 } 869 870 /** 871 * Returns the scientific priority of this project. 872 * 873 * @return the scientific priority of this project. 874 */ 875 public ScientificPriority getScientificPriority() 876 { 877 return scientificPriority; 878 } 879 880 /** 881 * Sets the amount of time allocated to this project 882 * by the <i>Time Allocation Committee</i>. 883 * 884 * @param allocatedTime the amount of time allocated to this project 885 * in whole and fractional hours. 886 */ 887 public void setAllocatedTime(double allocatedTime) 888 { 889 if (allocatedTime >= 0.0) 890 this.allocatedTime = allocatedTime; 891 } 892 893 /** 894 * Returns the amount of time allocated to this project 895 * by the <i>Time Allocation Committee</i>. 896 * 897 * @return the amount of time allocated to this project 898 * in whole and fractional hours. 899 */ 900 public double getAllocatedTime() 901 { 902 return allocatedTime; 903 } 904 905 /** 906 * Declares this project as either a test or official project. 907 * The default state of a newly created project is as a test project. 908 * 909 * @param projectIsTest <i>true</i> if this project is a test project. 910 */ 911 public void setTest(boolean projectIsTest) 912 { 913 isTest = projectIsTest; 914 } 915 916 /** 917 * Returns <i>true</i> if this project is a test, as opposed to 918 * official, project. 919 * @return <i>true</i> if this project is unofficial. 920 */ 921 public boolean isTest() 922 { 923 return isTest; 924 } 925 926 /** 927 * Sets the type of this project. 928 * 929 * @param type the type of this project. 930 */ 931 public void setProjectType(ProjectType type) 932 { 933 this.projectType = (type == null) ? ProjectType.getDefault() : type; 934 } 935 936 /** 937 * Returns the type of this project. 938 * 939 * @return the type of this project. 940 */ 941 public ProjectType getProjectType() 942 { 943 return projectType; 944 } 945 946 /** 947 * Returns the telescope time used so far for this project. 948 * 949 * @return the telescope time used so far for this project. 950 */ 951 public double getTimeUsedSoFar() 952 { 953 return timeUsedSoFar; 954 } 955 956 //============================================================================ 957 // INTERFACE UserAccountable 958 //============================================================================ 959 960 public void setCreatedBy(Long userId) 961 { 962 createdBy = (userId == null) ? UserAccountable.NULL_USER_ID : userId; 963 } 964 965 public void setCreatedOn(Date d) 966 { 967 if (d != null) 968 createdOn = d; 969 } 970 971 public void setLastUpdatedBy(Long userId) 972 { 973 lastUpdatedBy = (userId == null) ? UserAccountable.NULL_USER_ID : userId; 974 } 975 976 public void setLastUpdatedOn(Date d) 977 { 978 if (d != null) 979 lastUpdatedOn = d; 980 } 981 982 public Long getCreatedBy() { return createdBy; } 983 public Date getCreatedOn() { return createdOn; } 984 public Long getLastUpdatedBy() { return lastUpdatedBy; } 985 public Date getLastUpdatedOn() { return lastUpdatedOn; } 986 987 /** 988 * Sets the ID of the user who is authorized to edit this project. 989 * 990 * @param userId the ID of the user who is authorized to edit this project. 991 */ 992 public void setEditor(Long userId) 993 { 994 editor = (userId == null) ? UserAccountable.NULL_USER_ID : userId; 995 } 996 997 /** 998 * Returns the ID of the user who is authorized to edit this project. 999 * 1000 * @return the ID of the user who is authorized to edit this project. 1001 */ 1002 public Long getEditor() { return editor; } 1003 1004 //============================================================================ 1005 // COMMENTS 1006 //============================================================================ 1007 1008 /** 1009 * Sets comments about this project. 1010 * 1011 * @param replacementComments 1012 * free-form comments about this project. 1013 * These comments replace all previously set comments. 1014 * A <i>null</i> value will be replaced by the empty string (<tt>""</tt>). 1015 * 1016 * @see #appendComments(String) 1017 */ 1018 public void setComments(String replacementComments) 1019 { 1020 comments = (replacementComments == null) ? NO_COMMENTS : replacementComments; 1021 } 1022 1023 /** 1024 * Adds additional comments to those already associated with this project. 1025 * 1026 * @param additionalComments 1027 * new, additional, comments about this project. 1028 * 1029 * @see #setComments(String) 1030 */ 1031 public void appendComments(String additionalComments) 1032 { 1033 if ((additionalComments != null) && (additionalComments.length() > 0)) 1034 { 1035 if (!comments.equals(NO_COMMENTS)) 1036 comments = comments + System.getProperty("line.separator"); 1037 1038 comments = comments + additionalComments; 1039 } 1040 } 1041 1042 /** 1043 * Returns comments about this project. 1044 * The value returned is guaranteed to be non-null. 1045 * 1046 * @return 1047 * free-form comments about this project. 1048 * 1049 * @see #appendComments(String) 1050 * @see #setComments(String) 1051 */ 1052 public String getComments() 1053 { 1054 return comments; 1055 } 1056 1057 //============================================================================ 1058 // TEXT 1059 //============================================================================ 1060 1061 /** 1062 * Returns a text representation of this project. 1063 * The default form of the text is XML. However, if anything goes wrong 1064 * during the conversion to XML, an alternate, and much abbreviated, form 1065 * will be returned. 1066 * 1067 * @return a text representation of this project. 1068 */ 1069 public String toString() 1070 { 1071 try { 1072 return toXml(); 1073 } 1074 catch (Exception ex) { 1075 return toSummaryString(); 1076 } 1077 } 1078 1079 /** 1080 * Returns a short textual description of this project. 1081 * @return a short textual description of this project. 1082 */ 1083 public String toSummaryString() 1084 { 1085 StringBuilder buff = new StringBuilder(); 1086 1087 buff.append("code=").append(projectCode); 1088 buff.append(", id=").append(id); 1089 buff.append(", title=").append(title); 1090 buff.append(", proposal=").append(proposalCode); 1091 1092 return buff.toString(); 1093 } 1094 1095 /** 1096 * Returns an XML representation of this project. 1097 * @return an XML representation of this project. 1098 * @throws JAXBException if anything goes wrong during the conversion to XML. 1099 * @see #writeAsXmlTo(Writer) 1100 */ 1101 public String toXml() throws JAXBException 1102 { 1103 return JaxbUtility.getSharedInstance().objectToXmlString(this); 1104 } 1105 1106 /** 1107 * Writes an XML representation of this project to {@code writer}. 1108 * @param writer the device to which XML is written. 1109 * @throws JAXBException if anything goes wrong during the conversion to XML. 1110 */ 1111 public void writeAsXmlTo(Writer writer) throws JAXBException 1112 { 1113 JaxbUtility.getSharedInstance().writeObjectAsXmlTo(writer, this, null); 1114 } 1115 1116 /** 1117 * Creates a new project from the XML data in the given file. 1118 * 1119 * @param xmlFile the name of an XML file. This method will attempt to locate 1120 * the file by using {@link Class#getResource(String)}. 1121 * 1122 * @return a new project from the XML data in the given file. 1123 * 1124 * @throws FileNotFoundException if the XML file cannot be found. 1125 * 1126 * @throws JAXBException if the schema file used (if any) is malformed, if 1127 * the XML file cannot be read, or if the XML file is not 1128 * schema-valid. 1129 * 1130 * @throws XMLStreamException if there is a problem opening the XML file, 1131 * if the XML is not well-formed, or for some other 1132 * "unexpected processing conditions". 1133 */ 1134 public static Project fromXml(String xmlFile) 1135 throws JAXBException, XMLStreamException, FileNotFoundException 1136 { 1137 Project newProj = JaxbUtility.getSharedInstance().xmlFileToObject(xmlFile, Project.class); 1138 1139 newProj.testScansForResourceFromJaxb(); 1140 1141 return newProj; 1142 } 1143 1144 /** 1145 * Creates a new project based on the XML data read from {@code reader}. 1146 * 1147 * @param reader the source of the XML data. 1148 * If this value is <i>null</i>, <i>null</i> is returned. 1149 * 1150 * @return a new project based on the XML data read from {@code reader}. 1151 * 1152 * @throws XMLStreamException if the XML is not well-formed, 1153 * or for some other "unexpected processing conditions". 1154 * 1155 * @throws JAXBException if anything else goes wrong during the 1156 * transformation. 1157 */ 1158 public static Project fromXml(Reader reader) 1159 throws JAXBException, XMLStreamException 1160 { 1161 Project newProj = JaxbUtility.getSharedInstance() 1162 .readObjectAsXmlFrom(reader, Project.class, null); 1163 1164 newProj.testScansForResourceFromJaxb(); 1165 1166 return newProj; 1167 } 1168 1169 /** 1170 * @throws JAXBException 1171 * if the any scan of this project has 1172 * no resource and the useResourceOfPriorScan flag is false. 1173 */ 1174 private void testScansForResourceFromJaxb() throws JAXBException 1175 { 1176 for (ProgramBlock pb : progBlocks) 1177 pb.testScansForResourceFromJaxb(); 1178 } 1179 1180 //============================================================================ 1181 // 1182 //============================================================================ 1183 1184 /** 1185 * Returns a project that is a copy of this one. 1186 * <p> 1187 * For the most part this project is deeply cloned. For example, the 1188 * clone and this project will have their own lists of program blocks, 1189 * and the program blocks in the clone will be clones of those in this 1190 * project. There are, however, a few exceptions to the deep copy 1191 * philosophy:</p> 1192 * <ol> 1193 * <li>The ID will be set to 1194 * {@link Identifiable#UNIDENTIFIED}.</li> 1195 * <li>The proposal will refer to the same proposal that this project 1196 * points to.</li> 1197 * <li>The createdOn and lastUpdatedOn attributes will be set to the 1198 * current system time.</li> 1199 * </ol> 1200 * <p> 1201 * If anything goes wrong during the cloning procedure, 1202 * a {@code RuntimeException} will be thrown.</p> 1203 */ 1204 public Project clone() 1205 { 1206 Project clone = null; 1207 1208 try 1209 { 1210 //This line takes care of the primitive fields properly 1211 clone = (Project)super.clone(); 1212 1213 //We do NOT want the clone to have the same ID as the original. 1214 //The ID is here for the persistence layer; it is in charge of 1215 //setting IDs. To help it, we put the clone's ID in the uninitialized 1216 //state. 1217 clone.id = Identifiable.UNIDENTIFIED; 1218 1219 clone.createdOn = new Date(); 1220 clone.lastUpdatedOn = clone.createdOn; 1221 1222 //Clone the collection and partially clone the contained elements 1223 clone.progBlocks = new ArrayList<ProgramBlock>(); 1224 for (ProgramBlock pb : this.progBlocks) 1225 clone.addProgramBlock(pb.cloneWithoutPrerequisites()); 1226 1227 //Recreate for the cloned SBs analogous prerequisite trees 1228 fixClonesSchedBlockPrereqs(clone); 1229 } 1230 catch (Exception ex) 1231 { 1232 throw new RuntimeException(ex); 1233 } 1234 1235 return clone; 1236 } 1237 1238 /** Helps the clone method get the clone's PB relationships straight. */ 1239 private void fixClonesSchedBlockPrereqs(Project clonedProj) 1240 { 1241 //STRATEGY 1242 //Loop through all of our own PBs. For each PB that has 1243 //direct prerequisites, note the positions in our list of 1244 //the prereqs. The clone's PB at the same position as the 1245 //current PB should receive as direct prereqs the PBs at 1246 //the indices we found. 1247 1248 List<ProgramBlock> pbsOfClone = clonedProj.getProgramBlocks(); 1249 1250 int pbCount = progBlocks.size(); 1251 1252 for (int p=0; p < pbCount; p++) 1253 { 1254 ProgramBlock myPb = progBlocks.get(p); 1255 ProgramBlock myPbClone = pbsOfClone.get(p); 1256 1257 for (ProgramBlock prereqOfMyPb : myPb.getDirectPrerequisites()) 1258 { 1259 int prereqIndex = getProgBlockIndex(prereqOfMyPb); 1260 1261 if (prereqIndex >= 0) 1262 myPbClone.addPrerequisite(pbsOfClone.get(prereqIndex)); 1263 } 1264 } 1265 } 1266 1267 /** 1268 * Returns <i>true</i> if {@code o} is equal to this project. 1269 * <p> 1270 * In order to be equal to this project, {@code o} must be non-null 1271 * and of the same class as this project. Equality is determined by examining 1272 * the equality of corresponding attributes, with the following exceptions, 1273 * which are ignored when assessing equality: 1274 * <ol> 1275 * <li>id</li> 1276 * <li>proposal 1277 * (but <i>not</i> proposal code, which is <i>not</i> ignored)</li> 1278 * <li>createdOn</li> 1279 * <li>createdBy</li> 1280 * <li>lastUpdatedOn</li> 1281 * <li>lastUpdatedBy</li> 1282 * </ol></p> 1283 */ 1284 @Override 1285 public boolean equals(Object o) 1286 { 1287 //Quick exit if o is null 1288 if (o == null) 1289 return false; 1290 1291 //Quick exit if o is this 1292 if (o == this) 1293 return true; 1294 1295 //Quick exit if classes are different 1296 if (!o.getClass().equals(this.getClass())) 1297 return false; 1298 1299 Project other = (Project)o; 1300 1301 //Attributes that we INTENTIONALLY DO NOT COMPARE: 1302 // id, 1303 // proposal, 1304 // createdOn, createdBy, lastUpdatedOn, lastUpdatedBy 1305 // proposalProvider 1306 1307 if (!other.projectCode.equals(this.projectCode) || 1308 other.isTest != this.isTest || 1309 !other.title.equals(this.title) || 1310 !other.editor.equals(this.editor) || 1311 !other.proposalCode.equals(this.proposalCode) || 1312 !other.projectType.equals(this.projectType) || 1313 !other.getExecutionStatus().equals(this.getExecutionStatus()) || 1314 !other.telescope.equals(this.telescope) || 1315 !other.scientificPriority.equals(this.scientificPriority) || 1316 !other.comments.equals(this.comments) || 1317 other.allocatedTime != this.allocatedTime || 1318 other.timeUsedSoFar != this.timeUsedSoFar) 1319 return false; 1320 1321 if (!other.progBlocks.equals(this.progBlocks)) 1322 return false; 1323 1324 //No differences found 1325 return true; 1326 } 1327 1328 /* (non-Javadoc) 1329 * @see java.lang.Object#hashCode() 1330 */ 1331 @Override 1332 public int hashCode() 1333 { 1334 //Taken from the Effective Java book by Joshua Bloch. 1335 //The constants 17 & 37 are arbitrary & carry no meaning. 1336 int result = 17; 1337 1338 //You MUST keep this method in sync w/ the equals method 1339 result = 37 * result + projectCode.hashCode(); 1340 result = 37 * result + Boolean.valueOf(isTest).hashCode(); 1341 result = 37 * result + title.hashCode(); 1342 result = 37 * result + editor.hashCode(); 1343 result = 37 * result + proposalCode.hashCode(); 1344 result = 37 * result + projectType.hashCode(); 1345 result = 37 * result + getExecutionStatus().hashCode(); 1346 result = 37 * result + telescope.hashCode(); 1347 result = 37 * result + scientificPriority.hashCode(); 1348 result = 37 * result + comments.hashCode(); 1349 result = 37 * result + new Double(allocatedTime).hashCode(); 1350 result = 37 * result + new Double(timeUsedSoFar).hashCode(); 1351 1352 result = 37 * result + progBlocks.hashCode(); 1353 1354 return result; 1355 } 1356 1357 //============================================================================ 1358 // 1359 //============================================================================ 1360 1361 //This is here for quick manual testing 1362 /* 1363 public static void main(String[] args) 1364 { 1365 ProjectBuilder builder = new ProjectBuilder(); 1366 builder.setIdentifiers(true); 1367 1368 Project proj = builder.makeProject(null); 1369 1370 try 1371 { 1372 proj.writeAsXmlTo(new java.io.PrintWriter(System.out)); 1373 } 1374 catch (JAXBException ex) 1375 { 1376 System.out.println("Trouble w/ proj.toXml. Msg:"); 1377 System.out.println(ex.getMessage()); 1378 ex.printStackTrace(); 1379 1380 System.out.println("Attempting to write XML w/out schema verification:"); 1381 JaxbUtility.getSharedInstance().setLookForDefaultSchema(false); 1382 try 1383 { 1384 proj.writeAsXmlTo(new java.io.PrintWriter(System.out)); 1385 } 1386 catch (JAXBException ex2) 1387 { 1388 System.out.println("Still had trouble w/ proj.toXml. Msg:"); 1389 System.out.println(ex.getMessage()); 1390 ex.printStackTrace(); 1391 } 1392 } 1393 try 1394 { 1395 java.io.FileWriter writer = 1396 new java.io.FileWriter("/export/home/calmer/dharland/JUNK/project.xml"); 1397 proj.writeAsXmlTo(writer); 1398 } 1399 catch (Exception ex3) 1400 { 1401 System.out.println(ex3.getMessage()); 1402 ex3.printStackTrace(); 1403 } 1404 } 1405 */ 1406 /* 1407 public static void main(String... args) throws Exception 1408 { 1409 java.io.FileReader reader = 1410 new java.io.FileReader("/export/home/calmer/dharland/JUNK/Project.xml"); 1411 1412 Project proj = Project.fromXml(reader); 1413 1414 java.io.FileWriter writer = 1415 new java.io.FileWriter("/export/home/calmer/dharland/JUNK/Project-OUT.xml"); 1416 proj.writeAsXmlTo(writer); 1417 } 1418 */ 1419 /* 1420 public static void main(String[] args) 1421 { 1422 Project sender = new Project(); 1423 Project receiver = new Project(); 1424 1425 ProjectBuilder builder = new ProjectBuilder(); 1426 1427 ProgramBlock postReq = builder.makeProgramBlock(); 1428 postReq.setLongName("Holder of Prereq"); 1429 postReq.setShortName("postReq"); 1430 1431 ProgramBlock preReq = builder.makeProgramBlock(); 1432 preReq.setLongName("Prerequisite"); 1433 preReq.setShortName("preReq"); 1434 1435 postReq.addPrerequisite(preReq); 1436 sender.addProgramBlock(postReq); 1437 1438 ProgramBlock postReqClone = postReq.clone(); 1439 receiver.addProgramBlock(postReqClone); 1440 1441 List<ProgramBlock> senderPbs = sender.getProgramBlocks(); 1442 List<ProgramBlock> receiverPbs = receiver.getProgramBlocks(); 1443 1444 for (int p=0; p < senderPbs.size(); p++) 1445 { 1446 System.out.println("p="+p+" value-equal? => " + senderPbs.get(p).equals(receiverPbs.get(p)) + 1447 ", reference-equal? => " + (senderPbs.get(p) == receiverPbs.get(p))); 1448 } 1449 1450 ProgramBlock receiverPreReq = null; 1451 for (ProgramBlock pb : receiverPbs.get(0).getDirectPrerequisites()) 1452 { 1453 receiverPreReq = pb; 1454 break; 1455 } 1456 1457 if (receiverPreReq == preReq) 1458 System.out.println("Crud, we have prereq PB from another project."); 1459 else 1460 System.out.println("Good, prereq PB is from same project."); 1461 } 1462 */ 1463 }