001 package edu.nrao.sss.model.project.scan; 002 003 import java.io.Writer; 004 import java.util.Date; 005 006 import javax.xml.bind.JAXBException; 007 import javax.xml.bind.annotation.XmlAttribute; 008 import javax.xml.bind.annotation.XmlTransient; 009 import javax.xml.bind.annotation.XmlType; 010 011 import edu.nrao.sss.model.UserAccountable; 012 import edu.nrao.sss.model.project.ProgramBlock; 013 import edu.nrao.sss.model.project.Project; 014 import edu.nrao.sss.model.project.SchedulingBlock; 015 import edu.nrao.sss.util.Identifiable; 016 import edu.nrao.sss.util.JaxbUtility; 017 018 /** 019 * The abstract parent of both {@link Scan} and {@link ScanLoop}. 020 * <p> 021 * Note: This class was originally an interface but became an abstract 022 * class due to some JAXB restrictions on marshalling and unmarshalling 023 * to/from XML for interface classes. I was successful with this 024 * class as an interface while going to XML, but not coming from XML.</p> 025 * <p> 026 * <b>Version Info:</b> 027 * <table style="margin-left:2em"> 028 * <tr><td>$Revision: 1911 $</td></tr> 029 * <tr><td>$Date: 2009-01-21 10:27:48 -0700 (Wed, 21 Jan 2009) $</td></tr> 030 * <tr><td>$Author: dharland $</td></tr> 031 * </table></p> 032 * 033 * @author David M. Harland 034 * @since 2006-07-31 035 */ 036 @XmlType(propOrder= {"name", 037 "createdBy","createdOn","lastUpdatedBy","lastUpdatedOn", 038 "comments"}) 039 public abstract class ScanLoopElement 040 implements Cloneable, Identifiable, UserAccountable 041 { 042 private static final String NO_COMMENTS = ""; 043 044 //IDENTIFICATION 045 private Long id; //A unique identifier for the persistence layer. 046 private String name; 047 048 //USER TRACKING 049 private Long createdBy; //This is a user ID 050 private Date createdOn; 051 private Long lastUpdatedBy; //This is a user ID 052 private Date lastUpdatedOn; 053 054 //CONTAINER (SchedulingBlock OR ExecutionBlock) 055 private SchedulingBlock schedulingBlock; 056 057 //OTHER 058 private String comments; 059 060 /** Helps create a new instance. */ 061 ScanLoopElement() 062 { 063 initialize(); 064 065 //Objects that may never be null 066 createdOn = new Date(); 067 lastUpdatedOn = new Date(); 068 } 069 070 /** Initializes the instance variables of this class. */ 071 private void initialize() 072 { 073 id = Identifiable.UNIDENTIFIED; 074 name = getDefaultName(); 075 createdBy = UserAccountable.NULL_USER_ID; 076 lastUpdatedBy = UserAccountable.NULL_USER_ID; 077 schedulingBlock = null; 078 comments = NO_COMMENTS; 079 } 080 081 /** 082 * Resets this element to its initial state. 083 * A reset scan loop element has the same state as a new element. 084 */ 085 public void reset() 086 { 087 initialize(); 088 } 089 090 //============================================================================ 091 // IDENTIFICATION 092 //============================================================================ 093 094 /** 095 * Do not use. 096 * This method is here for the persistence mechanism. 097 * Other clients should not set object identifiers. 098 */ 099 public void setId(Long id) 100 { 101 this.id = id; 102 } 103 104 /* (non-Javadoc) 105 * @see edu.nrao.sss.util.Identifiable#getId() 106 */ 107 @XmlAttribute 108 public Long getId() 109 { 110 return id; 111 } 112 113 /** 114 * Resets this scan loop element's id to UNIDENTIFIED. 115 */ 116 public void clearId() 117 { 118 id = Identifiable.UNIDENTIFIED; 119 } 120 121 /** 122 * Sets the name of this element. 123 * <p> 124 * If {@code newName} is <i>null</i> or the empty string 125 * (<tt>""</tt>), the request to change the name will be 126 * denied and the current name will remain in place.</p> 127 * 128 * @param newName the new name of this element. 129 */ 130 public void setName(String newName) 131 { 132 if (newName != name && newName.length() > 0) 133 name = newName; 134 } 135 136 /** Returns a default name for a scan loop element. */ 137 abstract String getDefaultName(); 138 139 /** 140 * Returns the name of this element. 141 * @return the name of this element. 142 */ 143 public String getName() 144 { 145 return name; 146 } 147 148 //============================================================================ 149 // CONTAINER (SchedulingBlock) 150 //============================================================================ 151 152 /** 153 * Sets the scheduling block to which this scan loop element belongs. 154 * <p> 155 * This method does <i>not</i> communicate back to {@code newSchedBlock} 156 * about its new scan loop element. This is because the scheduling block does 157 * not hold these elements directly, except for its single 158 * {@code scanSequence}, which is of type {@code ScanLoop}. 159 * This means that the model is not enforcing integrity in the 160 * container / contained relationship that exists between 161 * {@code SchedulingBlock} and {@code ScanLoopElement}.</p> 162 * <p> 163 * Passing this method a {@code schedulingBlock} of <i>null</i> has the 164 * effect of disconnecting this element from any scheduling block.</p> 165 * 166 * @param newSchedBlock the scheduling block to which this element belongs. 167 */ 168 public void setSchedulingBlock(SchedulingBlock newSchedBlock) 169 { 170 this.schedulingBlock = newSchedBlock; 171 } 172 173 /** 174 * Returns the scheduling block to which this scan loop element belongs, 175 * if any. If this element belongs to 176 * no scheduling block, the value returned is <i>null</i>.</p> 177 * 178 * @return the scheduling block that contains this scan loop element. 179 */ 180 @XmlTransient 181 public SchedulingBlock getSchedulingBlock() 182 { 183 return schedulingBlock; 184 } 185 186 /** 187 * Returns <i>true</i> if this scan loop element is connected, either directly 188 * or indirectly, to a non-null scheduling block. 189 * <p> 190 * Scans should normally be contained directly within either a 191 * scheduling block or an execution block. The execution block, 192 * in turn, is normally contained within a scheduling block. Therefore, 193 * a scan loop element normally has a link to a scheduling block. 194 * However, there are some situations 195 * where this method will return <i>false</i>: 196 * <ol> 197 * <li>This element has just been created and its 198 * scheduling block has not yet been set.</li> 199 * <li>A client removed this element from its scheduling block and 200 * did not place it in a new scheduling block.</li> 201 * <li>A client explicitly set this element's scheduling block to 202 * <i>null</i>.</li> 203 * </ol></p> 204 * 205 * @return <i>true</i> if this scan has a non-null scheduling block. 206 * Therefore a return value of <i>true</i> means that 207 * you can call {@link #getSchedulingBlock()} and know that 208 * it will return a non-null object. 209 */ 210 public boolean hasSchedulingBlock() 211 { 212 return (schedulingBlock != null); 213 } 214 215 /** 216 * Returns the program block to which this scan belongs. 217 * If this scan is not currently contained by a program block, 218 * the returned value will be <i>null</i>. 219 * 220 * @return the program block that contains this scan or <i>null</i>. 221 */ 222 public ProgramBlock getProgramBlock() 223 { 224 return hasSchedulingBlock() ? schedulingBlock.getProgramBlock() : null; 225 } 226 227 /** 228 * Returns the project to which this scan belongs. 229 * If this scan is not currently contained by a project, 230 * the returned value will be <i>null</i>. 231 * 232 * @return the project that contains this scan or <i>null</i>. 233 */ 234 public Project getProject() 235 { 236 return hasSchedulingBlock() ? schedulingBlock.getProject() : null; 237 } 238 239 //============================================================================ 240 // COMMENTS 241 //============================================================================ 242 243 /** 244 * Sets comments about this element. 245 * 246 * @param replacementComments 247 * free-form text about this element. 248 * These comments replace all previously set comments. 249 * A <i>null</i> value will be replaced by the empty string (<tt>""</tt>). 250 * 251 * @see #appendComments(String) 252 */ 253 public void setComments(String replacementComments) 254 { 255 comments = (replacementComments == null) ? NO_COMMENTS : replacementComments; 256 } 257 258 /** 259 * Adds additional comments to those already associated with this element. 260 * 261 * @param additionalComments 262 * new, additional, comments about this element. 263 * 264 * @see #setComments(String) 265 */ 266 public void appendComments(String additionalComments) 267 { 268 if ((additionalComments != null) && (additionalComments.length() > 0)) 269 { 270 if (!comments.equals(NO_COMMENTS)) 271 comments = comments + System.getProperty("line.separator"); 272 273 comments = comments + additionalComments; 274 } 275 } 276 277 /** 278 * Returns comments about this element. 279 * The value returned is guaranteed to be non-null. 280 * 281 * @return 282 * free-form text about this element. 283 * 284 * @see #appendComments(String) 285 * @see #setComments(String) 286 */ 287 public String getComments() 288 { 289 return comments; 290 } 291 292 //============================================================================ 293 // INTERFACE UserAccountable 294 //============================================================================ 295 296 public void setCreatedBy(Long userId) 297 { 298 createdBy = (userId == null) ? UserAccountable.NULL_USER_ID : userId; 299 } 300 301 public void setCreatedOn(Date d) 302 { 303 if (d != null) 304 createdOn = d; 305 } 306 307 public void setLastUpdatedBy(Long userId) 308 { 309 lastUpdatedBy = (userId == null) ? UserAccountable.NULL_USER_ID : userId; 310 } 311 312 public void setLastUpdatedOn(Date d) 313 { 314 if (d != null) 315 lastUpdatedOn = d; 316 } 317 318 public Long getCreatedBy() { return createdBy; } 319 public Date getCreatedOn() { return createdOn; } 320 public Long getLastUpdatedBy() { return lastUpdatedBy; } 321 public Date getLastUpdatedOn() { return lastUpdatedOn; } 322 323 //============================================================================ 324 // TEXT 325 //============================================================================ 326 327 /** 328 * Returns a text representation of this scan loop element. 329 * The default form of the text is XML. However, if anything goes wrong 330 * during the conversion to XML, an alternate, and much abbreviated, form 331 * will be returned. 332 * 333 * @return a text representation of this scan. 334 * 335 * @see #toSummaryString() 336 */ 337 public String toString() 338 { 339 try { 340 return toXml(); 341 } 342 catch (Exception ex) { 343 return toSummaryString(); 344 } 345 } 346 347 /** 348 * Returns a short textual description of this scan loop element. 349 * @return a short textual description of this scan loop element. 350 */ 351 public abstract String toSummaryString(); 352 353 /** 354 * Returns an XML representation of this scan loop element. 355 * @return an XML representation of this scan loop element. 356 * @throws JAXBException if anything goes wrong during the conversion to XML. 357 * @see #writeAsXmlTo(Writer) 358 */ 359 public String toXml() throws JAXBException 360 { 361 return JaxbUtility.getSharedInstance().objectToXmlString(this); 362 } 363 364 /** 365 * Writes an XML representation of this scan loop element to {@code writer}. 366 * @param writer the device to which XML is written. 367 * @throws JAXBException if anything goes wrong during the conversion to XML. 368 */ 369 public void writeAsXmlTo(Writer writer) throws JAXBException 370 { 371 JaxbUtility.getSharedInstance().writeObjectAsXmlTo(writer, this, null); 372 } 373 374 //============================================================================ 375 // 376 //============================================================================ 377 378 /** 379 * Returns a scan loop element that is a copy of this one. 380 * <p> 381 * The returned element is, for the most part, a deep copy of this one. 382 * However, there are a few exceptions: 383 * <ol> 384 * <li>The ID will be set to 385 * {@link Identifiable#UNIDENTIFIED}.</li> 386 * <li>The schedulingBlock will be <i>null</i>.</li> 387 * <li>The createdOn and lastUpdatedOn attributes will be set to the 388 * current system time.</li> 389 * </ol></p> 390 * <p> 391 * If anything goes wrong during the cloning procedure, 392 * a {@code RuntimeException} will be thrown.</p> 393 */ 394 @Override 395 public ScanLoopElement clone() 396 { 397 ScanLoopElement clone; 398 399 try 400 { 401 clone = (ScanLoopElement)super.clone(); 402 403 //We do NOT want the clone to have the same ID as the original. 404 //The ID is here for the persistence layer; it is in charge of 405 //setting IDs. To help it, we put the clone's ID in the uninitialized 406 //state. 407 clone.id = Identifiable.UNIDENTIFIED; 408 409 clone.createdOn = new Date(); 410 clone.lastUpdatedOn = clone.createdOn; 411 412 clone.schedulingBlock = null; 413 } 414 catch (Exception ex) 415 { 416 throw new RuntimeException(ex); 417 } 418 419 return clone; 420 } 421 422 /** 423 * Returns <i>true</i> if {@code o} is equal to this scan loop element. 424 * <p> 425 * In order to be equal to this element, {@code o} must be non-null and 426 * of the same class as this element. Equality is determined by examining 427 * the equality of corresponding attributes, with the following exceptions, 428 * which are ignored when assessing equality: 429 * <ol> 430 * <li>id</li> 431 * <li>schedulingBlock</li> 432 * <li>createdOn</li> 433 * <li>createdBy</li> 434 * <li>lastUpdatedOn</li> 435 * <li>lastUpdatedBy</li> 436 * </ol></p> 437 */ 438 @Override 439 public boolean equals(Object o) 440 { 441 //Quick exit if o is null 442 if (o == null) 443 return false; 444 445 //Quick exit if o is this 446 if (o == this) 447 return true; 448 449 //Quick exit if classes are different 450 if (!o.getClass().equals(this.getClass())) 451 return false; 452 453 //Attributes that we INTENTIONALLY DO NOT COMPARE: 454 // id, 455 // schedulingBlock, 456 // createdOn, createdBy, lastUpdatedOn, lastUpdatedBy 457 458 ScanLoopElement other = (ScanLoopElement)o; 459 460 return 461 other.name.equals(this.name) && 462 other.comments.equals(this.comments); 463 } 464 465 /** Returns a hash code value for this scan loop element. */ 466 @Override 467 public int hashCode() 468 { 469 //You MUST keep this method in sync w/ the equals method 470 471 //Taken from the Effective Java book by Joshua Bloch. 472 //The constants 17 & 37 are arbitrary & carry no meaning. 473 int result = 17; 474 475 result = 37 * result + name.hashCode(); 476 result = 37 * result + comments.hashCode(); 477 478 return result; 479 } 480 }