001 package edu.nrao.sss.model.source; 002 003 import java.io.FileNotFoundException; 004 import java.io.Reader; 005 import java.io.Writer; 006 import java.net.URL; 007 import java.util.ArrayList; 008 import java.util.Date; 009 import java.util.HashSet; 010 import java.util.List; 011 import java.util.Map; 012 import java.util.SortedMap; 013 import java.util.SortedSet; 014 import java.util.TreeMap; 015 import java.util.TreeSet; 016 import java.util.UUID; 017 018 import javax.xml.bind.JAXBException; 019 import javax.xml.bind.annotation.XmlAttribute; 020 import javax.xml.bind.annotation.XmlElement; 021 import javax.xml.bind.annotation.XmlElementWrapper; 022 import javax.xml.bind.annotation.XmlID; 023 import javax.xml.bind.annotation.XmlRootElement; 024 import javax.xml.bind.annotation.XmlType; 025 import javax.xml.stream.XMLStreamException; 026 027 import edu.nrao.sss.astronomy.CoordinateConversionException; 028 import edu.nrao.sss.astronomy.SkyPosition; 029 import edu.nrao.sss.geom.EarthPosition; 030 import edu.nrao.sss.measure.Frequency; 031 import edu.nrao.sss.measure.LinearVelocity; 032 import edu.nrao.sss.model.UserAccountable; 033 import edu.nrao.sss.model.resource.ReceiverBand; 034 import edu.nrao.sss.model.resource.TelescopeConfiguration; 035 import edu.nrao.sss.util.Identifiable; 036 import edu.nrao.sss.util.JaxbUtility; 037 import edu.nrao.sss.util.StringUtil; 038 039 /** 040 * An astronomical emitter of radio waves. 041 * <p> 042 * <b>Version Info:</b> 043 * <table style="margin-left:2em"> 044 * <tr><td>$Revision: 1709 $</td></tr> 045 * <tr><td>$Date: 2008-11-14 11:22:37 -0700 (Fri, 14 Nov 2008) $</td></tr> 046 * <tr><td>$Author: dharland $ (last person to modify)</td></tr> 047 * </table></p> 048 * 049 * @author David M. Harland 050 * @since 2006-02-24 051 */ 052 @XmlRootElement 053 @XmlType(propOrder={"name", 054 "createdBy","createdOn","lastUpdatedBy","lastUpdatedOn", 055 "originOfInformation", 056 "aliases", "xmlUserDefinedValues", "imageLinks", 057 "historicalRecords", "notes", "links", 058 "subsources"}) 059 public class Source 060 implements SourceCatalogEntry 061 { 062 private static final String NO_ORIGIN = ""; 063 private static final String NO_NAME = "[NEW]"; 064 065 public static final String CENTER_POSITION_NAME = "CENTER"; 066 067 //IDENTIFICATION 068 private long id; 069 private String name; 070 071 @XmlAttribute(required=true) 072 @XmlID 073 String xmlId; 074 075 //USER TRACKING 076 private Long createdBy; //This is a user ID 077 private Date createdOn; 078 private Long lastUpdatedBy; //This is a user ID 079 private Date lastUpdatedOn; 080 081 //COMPONENT SOURCES 082 private SortedSet<Subsource> subsources; 083 084 //OTHER PROPERTIES 085 private String originOfInformation; 086 private SortedMap<String, String> userDefinedValues; 087 private List<SourceImageLink> imageLinks; 088 private List<String> historicalRecords; 089 private List<String> notes; 090 private List<URL> links; 091 092 @XmlElement(name="alias") 093 private List<String> aliases; 094 095 /** 096 * Creates a new unnamed source. 097 * The new source will have one subsource. The name of that 098 * subsource will be {@link #CENTER_POSITION_NAME}. 099 */ 100 public Source() 101 { 102 subsources = new TreeSet<Subsource>(); 103 aliases = new ArrayList<String>(); 104 userDefinedValues = new TreeMap<String, String>(); 105 imageLinks = new ArrayList<SourceImageLink>(); 106 historicalRecords = new ArrayList<String>(); 107 notes = new ArrayList<String>(); 108 links = new ArrayList<URL>(); 109 110 initialize(); 111 112 addSubsource(new Subsource(CENTER_POSITION_NAME)); 113 } 114 115 /** 116 * Creates a new instance with the given name. 117 * The new source will have one subsource. The name of that 118 * subsource will be {@link #CENTER_POSITION_NAME}. 119 */ 120 public Source(String name) 121 { 122 this(); 123 setName(name); 124 } 125 126 /** Initializes the instance variables of this class. */ 127 private void initialize() 128 { 129 id = Identifiable.UNIDENTIFIED; 130 name = NO_NAME; 131 xmlId = "source-" + UUID.randomUUID().toString(); 132 133 createdBy = UserAccountable.NULL_USER_ID; 134 createdOn = new Date(); 135 lastUpdatedBy = UserAccountable.NULL_USER_ID; 136 lastUpdatedOn = new Date(); 137 138 originOfInformation = NO_ORIGIN; 139 } 140 141 /** 142 * Resets this source to its initial state. A reset source has the same 143 * state as a new source. 144 */ 145 public void reset() 146 { 147 aliases.clear(); 148 subsources.clear(); 149 userDefinedValues.clear(); 150 imageLinks.clear(); 151 historicalRecords.clear(); 152 notes.clear(); 153 links.clear(); 154 155 initialize(); 156 } 157 158 /* (non-Javadoc) 159 * @see edu.nrao.sss.model.source.SourceCatalogEntry#get(java.util.Date) 160 */ 161 public Source get(Date pointInTime) { return this; } 162 163 /** 164 * Returns <i>true</i> if the position of this source at time T 165 * could be different than its position at time U ≠ T. 166 * <p> 167 * The determination of motion will be made with respect to the 168 * coordinate system in which the position of this source is expressed. 169 * For example, a position that is expressed in the equatorial system, 170 * and that is holding steady at its position in that system, will said 171 * to be not moving, even though in other coordinate systems it may be 172 * moving quite rapidly.</p> 173 * <p> 174 * This is a convenience method that is equivalent to calling 175 * <tt>getCentralSubsource().isMoving()</tt>.</p> 176 * 177 * @return <i>true</i> if this source is moving. 178 */ 179 public boolean isMoving() { return getCentralSubsource().isMoving(); } 180 181 //============================================================================ 182 // IDENTIFICATION 183 //============================================================================ 184 185 /* (non-Javadoc) 186 * @see edu.nrao.sss.model.util.Identifiable#getId() 187 */ 188 @XmlAttribute 189 public Long getId() 190 { 191 return id; 192 } 193 194 /* (non-Javadoc) 195 * @see SourceCatalogEntry#setId(java.lang.Long) 196 */ 197 public void setId(Long id) 198 { 199 this.id = id; 200 } 201 202 /* (non-Javadoc) 203 * @see edu.nrao.sss.model.source.SourceCatalogEntry#clearId() 204 */ 205 public void clearId() 206 { 207 this.id = Identifiable.UNIDENTIFIED; 208 209 for (Subsource ss : subsources) 210 ss.clearId(); 211 } 212 213 /** 214 * Sets the name of this source. 215 * <p> 216 * If {@code newName} is <i>null</i> or the empty string 217 * (<tt>""</tt>), the request to change the name will be 218 * denied and the current name will remain in place.</p> 219 * 220 * @param newName the new name for this source. 221 */ 222 public void setName(String newName) 223 { 224 if (newName != name && newName.length() > 0) 225 name = newName; 226 } 227 228 /** 229 * Returns the name of this source. 230 * @return the name of this source. 231 */ 232 public String getName() 233 { 234 return name; 235 } 236 237 //============================================================================ 238 // USER-DEFINED-VALUES 239 //============================================================================ 240 241 /** 242 * Returns a collection of user-defined key/value pairs. 243 * The returned map may be empty, but is guaranteed to be non-null. 244 * <p> 245 * Note that the returned map is the one actually held by this object. 246 * This means that any changes made to the returned map <i>will</i> 247 * be reflected in this source. It also means that to add, delete, or change 248 * a keyword mapping, clients call this method and then operate directly on 249 * the returned map.</p> 250 * 251 * @return a collection of user-defined key/value pairs. 252 */ 253 public SortedMap<String, String> getUserDefinedValues() 254 { 255 return userDefinedValues; 256 } 257 258 /** 259 * Returns text that can be used as a key for the {@link #getUserDefinedValues() 260 * user defined values map}. 261 * The returned key is used to get a VLA calibrator quality code and 262 * consists of some boiler plate text, a code for the receiver 263 * band, and a code for the telescope configuration. 264 * 265 * @param band 266 * the receiver band for which a key is desired. 267 * 268 * @param cfg 269 * the telescope configuration for which a key is desired. 270 * 271 * @return 272 * text that can be used as a user defined value key. 273 * 274 * @since 2008-07-25 275 */ 276 public static String makeUdbKeyVlaCalibQuality(ReceiverBand band, 277 TelescopeConfiguration cfg) 278 { 279 return makeUdbKeyVlaCalibQuality(band.getDisplayName(), cfg.getCode()); 280 } 281 282 /** 283 * Returns text that can be used as a key for the {@link #getUserDefinedValues() 284 * user defined values map}. 285 * The returned key is used to get a VLA calibrator quality code and 286 * consists of some boiler plate text, a code for the receiver 287 * band, and a code for the telescope configuration. 288 * 289 * @param bandCode 290 * a code for a receiver band. By convention this should be a value returned 291 * by the {@link ReceiverBand#getDisplayName() getDisplayName() method} of 292 * {@link ReceiverBand}. 293 * 294 * @param teleCfgCode 295 * a code for a telescope configuration. By convention this should be a value 296 * returned by the {@link TelescopeConfiguration#getCode() getCode method} of 297 * {@link TelescopeConfiguration}. 298 * 299 * @return 300 * text that can be used as a user defined value key. 301 * 302 * @since 2008-07-25 303 */ 304 public static String makeUdbKeyVlaCalibQuality(String bandCode, 305 String teleCfgCode) 306 { 307 StringBuilder buff = new StringBuilder("Quality."); 308 buff.append(bandCode).append("-band."); 309 buff.append(teleCfgCode).append("-cfg"); 310 return buff.toString(); 311 } 312 313 /** 314 * Returns text that can be used as a key for the {@link #getUserDefinedValues() 315 * user defined values map}. 316 * The returned key is used to get a UV<sub>max</sub> value and 317 * consists of some boiler plate text and a code for the receiver band. 318 * 319 * @param band 320 * the receiver band for which a key is desired. 321 * 322 * @return 323 * text that can be used as a user defined value key. 324 * 325 * @since 2008-07-25 326 */ 327 public static String makeUdbKeyVlaUvMax(ReceiverBand band) 328 { 329 return makeUdbKeyVlaUvMax(band.getDisplayName()); 330 } 331 332 /** 333 * Returns text that can be used as a key for the {@link #getUserDefinedValues() 334 * user defined values map}. 335 * The returned key is used to get a UV<sub>max</sub> value and 336 * consists of some boiler plate text and a code for the receiver band. 337 * 338 * @param bandCode 339 * a code for a receiver band. By convention this should be a value returned 340 * by the {@link ReceiverBand#getDisplayName() getDisplayName() method} of 341 * {@link ReceiverBand}. 342 * 343 * @return 344 * text that can be used as a user defined value key. 345 * 346 * @since 2008-07-25 347 */ 348 public static String makeUdbKeyVlaUvMax(String bandCode) 349 { 350 return "UV max, " + bandCode + " band"; 351 } 352 353 /** 354 * Returns text that can be used as a key for the {@link #getUserDefinedValues() 355 * user defined values map}. 356 * The returned key is used to get a UV<sub>min</sub> value and 357 * consists of some boiler plate text and a code for the receiver band. 358 * 359 * @param band 360 * the receiver band for which a key is desired. 361 * 362 * @return 363 * text that can be used as a user defined value key. 364 * 365 * @since 2008-07-25 366 */ 367 public static String makeUdbKeyVlaUvMin(ReceiverBand band) 368 { 369 return makeUdbKeyVlaUvMin(band.getDisplayName()); 370 } 371 372 /** 373 * Returns text that can be used as a key for the {@link #getUserDefinedValues() 374 * user defined values map}. 375 * The returned key is used to get a UV<sub>min</sub> value and 376 * consists of some boiler plate text and a code for the receiver band. 377 * 378 * @param bandCode 379 * a code for a receiver band. By convention this should be a value returned 380 * by the {@link ReceiverBand#getDisplayName() getDisplayName() method} of 381 * {@link ReceiverBand}. 382 * 383 * @return 384 * text that can be used as a user defined value key. 385 * 386 * @since 2008-07-25 387 */ 388 public static String makeUdbKeyVlaUvMin(String bandCode) 389 { 390 return "UV min, " + bandCode + " band"; 391 } 392 393 //============================================================================ 394 // OTHER PROPERTIES 395 //============================================================================ 396 397 /** 398 * Sets the origin of this source's information. 399 * @param origin the origin of this source's information. A value of 400 * <i>null</i> will be replaced by a non-null default value. 401 */ 402 public void setOriginOfInformation(String origin) 403 { 404 originOfInformation = (origin == null) ? NO_ORIGIN : origin; 405 } 406 407 /** 408 * Returns the origin of this source's information. 409 * @return the origin of this source's information. 410 */ 411 public String getOriginOfInformation() 412 { 413 return originOfInformation; 414 } 415 416 /** 417 * Returns a list of other names by which this source is known. 418 * The returned list may be empty, but is guaranteed to be non-null. 419 * <p> 420 * Note that the returned list is the one actually held by this object. 421 * This means that any changes made to the returned list <i>will</i> 422 * be reflected in this source. It also means that to add or delete 423 * an alias, clients call this method and then operate directly on the 424 * returned list.</p> 425 * 426 * @return a list of other names by which this source is known. 427 */ 428 public List<String> getAliases() 429 { 430 return aliases; 431 } 432 433 /** 434 * Returns a collection of links to images of this source. 435 * The returned list may be empty, but is guaranteed to be non-null. 436 * <p> 437 * Note that the returned list is the one actually held by this object. 438 * This means that any changes made to the returned list <i>will</i> 439 * be reflected in this source. It also means that to add, delete, or change 440 * an image link, clients call this method and then operate directly on 441 * the returned list.</p> 442 * 443 * @return a collection of user-defined key/value pairs. 444 */ 445 @XmlElementWrapper 446 @XmlElement(name="imageLink") 447 public List<SourceImageLink> getImageLinks() 448 { 449 return imageLinks; 450 } 451 452 /** This is here for mechanisms that need setX/getX pairs, such as JAXB. */ 453 @SuppressWarnings("unused") 454 private void setImageLinks(List<SourceImageLink> replacementList) 455 { 456 imageLinks = (replacementList == null) ? new ArrayList<SourceImageLink>() 457 : replacementList; 458 } 459 460 /** 461 * Returns one element of text for each historical record held by this source. 462 * Each record is free-form text, but most clients are expected to structure 463 * the text in some way, perhaps as XML or as delimited key=value pairs. 464 * <p> 465 * This method returns the list actually held by this {@code Source}, so 466 * any list manipulations may be performed by first fetching the list and 467 * then operating on it.</p> 468 * 469 * @return a list of historical records for this source. 470 */ 471 @XmlElementWrapper 472 @XmlElement(name="historicalRecord") 473 public List<String> getHistoricalRecords() 474 { 475 return historicalRecords; 476 } 477 478 /** This is here for mechanisms that need setX/getX pairs, such as JAXB. */ 479 @SuppressWarnings("unused") 480 private void setHistoricalRecords(List<String> replacementList) 481 { 482 historicalRecords = (replacementList == null) ? new ArrayList<String>() 483 : replacementList; 484 } 485 486 /** 487 * Returns a list of notes about this source. 488 * Each note is free-form text with no particular structure. 489 * <p> 490 * This method returns the list actually held by this {@code Source}, so 491 * any list manipulations may be performed by first fetching the list and 492 * then operating on it.</p> 493 * 494 * @return a list of notes about this source. 495 */ 496 @XmlElementWrapper 497 @XmlElement(name="note") 498 public List<String> getNotes() 499 { 500 return notes; 501 } 502 503 /** This is here for mechanisms that need setX/getX pairs, such as JAXB. */ 504 @SuppressWarnings("unused") 505 private void setNotes(List<String> replacementList) 506 { 507 notes = (replacementList == null) ? new ArrayList<String>() 508 : replacementList; 509 } 510 511 /** 512 * Returns a list of URL links to more information about this source. 513 * <p> 514 * This method returns the list actually held by this {@code Source}, so 515 * any list manipulations may be performed by first fetching the list and 516 * then operating on it.</p> 517 * 518 * @return a list of links that refer to this source. 519 */ 520 @XmlElementWrapper 521 @XmlElement(name="link") 522 public List<URL> getLinks() 523 { 524 return links; 525 } 526 527 /** This is here for mechanisms that need setX/getX pairs, such as JAXB. */ 528 @SuppressWarnings("unused") 529 private void setLinks(List<URL> replacementList) 530 { 531 links = (replacementList == null) ? new ArrayList<URL>() 532 : replacementList; 533 } 534 535 //============================================================================ 536 // SUBSOURCES 537 //============================================================================ 538 539 /** 540 * Adds {@code newSubsource} to this source. 541 * If {@code newSubsource} is <i>null</i>, no action is taken. 542 * 543 * @param newSubsource the new subsource to be added to this source. 544 */ 545 public void addSubsource(Subsource newSubsource) 546 { 547 if (newSubsource != null) 548 subsources.add(newSubsource); 549 } 550 551 /** 552 * Removes {@code oldSubsource} from this source. 553 * <p> 554 * If this source is not holding {@code oldSubsource}, this method 555 * does nothing.</p> 556 * 557 * @param oldSubsource the subsource to be removed from this source. 558 */ 559 public void removeSubsource(Subsource oldSubsource) 560 { 561 if (oldSubsource != null) 562 subsources.remove(oldSubsource); 563 } 564 565 /** 566 * Removes all subsources from this source. 567 */ 568 public void removeAllSubsources() 569 { 570 subsources.clear(); 571 } 572 573 /** 574 * Replaces this source's collection of subsources with 575 * {@code replacementSet}. 576 * <p> 577 * Note that this source will hold a reference to {@code replacementSet} 578 * (unless it is <i>null</i>); it will not store a copy. This means 579 * that any changes a client makes to {@code replacementSet} <i>after</i> 580 * calling this method will be reflected in this source.</p> 581 * 582 * @param replacementSet a replacement set of subsources for this source. 583 * If {@code replacementSet} is <i>null</i>, it will 584 * be interpreted as an empty set. 585 */ 586 public void setSubsources(SortedSet<Subsource> replacementSet) 587 { 588 subsources = (replacementSet == null) ? new TreeSet<Subsource>() 589 : replacementSet; 590 } 591 592 /** 593 * Returns this source's collection of subsources. 594 * <p> 595 * Note that returned set is the one actually held by this source, 596 * not a clone thereof. That means that any changes that a client 597 * makes to the set will affect this source.</p> 598 * 599 * @return this source's collection of subsources. The value returned 600 * is guaranteed to be non-null, though it may be an empty set. 601 */ 602 @XmlElementWrapper 603 @XmlElement(name="subsource") 604 public SortedSet<Subsource> getSubsources() 605 { 606 return subsources; 607 } 608 609 /** 610 * Returns the subsource that represents the center position of this source. 611 * <p> 612 * This method will search this source's set of subsources for one whose 613 * name is {@link #CENTER_POSITION_NAME}. If no such subsource is found, 614 * an arbitrary subsource will be returned. If this source has no subsources 615 * the returned value will be <i>null</i>.</p> 616 * 617 * @return the central subsource, or <i>null</i>. 618 */ 619 public Subsource getCentralSubsource() 620 { 621 //Quick exit if no subsources 622 if ((subsources == null) || (subsources.size() < 1)) 623 return null; 624 625 //The code here originally took advantage of the fact that we're using a 626 //SortedSet. Unfortunately, something was going wrong in a Hibernate 627 //wrapper class (PersistentSortedSet), wherein Hibernate was using a 628 //HashSet to hold the subsources. 629 Subsource central = null; 630 631 for (Subsource ss : subsources) 632 { 633 if (central == null) 634 central = ss; //will return first ss if we never find match on name 635 636 if (ss.getName().equalsIgnoreCase(CENTER_POSITION_NAME)) 637 { 638 central = ss; 639 break; 640 } 641 } 642 643 //Return either the true central subsource, 644 //or the first in the list if no central subsource 645 return central; 646 } 647 648 //---------------------------------------------------------------------------- 649 // Delegation to Central Subsource 650 //---------------------------------------------------------------------------- 651 652 /** 653 * Returns the position of this source. 654 * This is a convenience method that is equivalent to calling 655 * {@code getCentralSubsource().getPosition()}. 656 * 657 * @return the position of this source. 658 */ 659 public SkyPosition getPosition() 660 { 661 return getCentralSubsource().getPosition(); 662 } 663 664 /** 665 * Returns the velocity of this source toward or away from the 666 * observer at the given point in time. 667 * 668 * @param observer 669 * a position on earth. The radial velocity of this source is 670 * calculated relative this observer. 671 * 672 * @param dateTime 673 * the point in time at which the velocity is calculated. 674 * 675 * @return 676 * the radial velocity of this source toward or away from a point 677 * on earth. 678 * 679 * @throws CoordinateConversionException 680 * if the position of this source cannot be converted to 681 * an equatorial RA / Dec position. 682 * 683 * @since 2008-07-29 684 */ 685 public LinearVelocity calcVelocityRelativeTo(EarthPosition observer, 686 Date dateTime) 687 throws CoordinateConversionException 688 { 689 return getCentralSubsource().calcVelocityRelativeTo(observer, dateTime); 690 } 691 692 /** 693 * Returns an observed frequency based on a rest frequency and the 694 * relative motion between this source and an earth-bound observer. 695 * 696 * @param restFreq 697 * the rest frequency for which an observed frequency is requested. 698 * 699 * @param observer 700 * a position on earth. The relative radial velocity between this 701 * observer and this source determines the amount by which the 702 * {@code restFreq} is shifted into an observed frequency. 703 * 704 * @param dateTime 705 * the time for which the velocity calculation is performed. 706 * 707 * @return 708 * an observed frequency based on {@code restFreq} and the relative 709 * motion between this source and the {@code observer}. 710 * 711 * @throws CoordinateConversionException 712 * if the position of this source cannot be converted to 713 * an equatorial RA / Dec position. 714 * 715 * @since 2008-07-29 716 */ 717 public Frequency calcShiftedFrequency(Frequency restFreq, 718 EarthPosition observer, 719 Date dateTime) 720 throws CoordinateConversionException 721 { 722 return getCentralSubsource().calcShiftedFrequency(restFreq, 723 observer, dateTime); 724 } 725 726 //============================================================================ 727 // INTERFACE UserAccountable 728 //============================================================================ 729 730 public void setCreatedBy(Long userId) 731 { 732 createdBy = (userId == null) ? UserAccountable.NULL_USER_ID : userId; 733 } 734 735 public void setCreatedOn(Date d) 736 { 737 if (d != null) 738 createdOn = d; 739 } 740 741 public void setLastUpdatedBy(Long userId) 742 { 743 lastUpdatedBy = (userId == null) ? UserAccountable.NULL_USER_ID : userId; 744 } 745 746 public void setLastUpdatedOn(Date d) 747 { 748 if (d != null) 749 lastUpdatedOn = d; 750 } 751 752 public Long getCreatedBy() { return createdBy; } 753 public Date getCreatedOn() { return createdOn; } 754 public Long getLastUpdatedBy() { return lastUpdatedBy; } 755 public Date getLastUpdatedOn() { return lastUpdatedOn; } 756 757 //============================================================================ 758 // PERSISTENCE HELPERS 759 //============================================================================ 760 761 //This pair of methods converts List<String> to/from single string 762 @SuppressWarnings("unused") 763 private String getPersistentAliases() 764 { 765 return StringUtil.getInstance().fromCollection(aliases, " | "); 766 } 767 768 @SuppressWarnings("unused") 769 private void setPersistentAliases(String text) 770 { 771 aliases.clear(); 772 StringUtil.getInstance().toCollection(text, " | ", aliases); 773 } 774 775 //============================================================================ 776 // TEXT 777 //============================================================================ 778 779 /** 780 * Returns a text representation of this source. 781 * The default form of the text is XML. However, if anything goes wrong 782 * during the conversion to XML, an alternate, and much abbreviated, form 783 * will be returned. 784 * 785 * @return a text representation of this source. 786 */ 787 public String toString() 788 { 789 try { 790 return toXml(); 791 } 792 catch (Exception ex) { 793 StringBuilder buff = new StringBuilder(); 794 795 buff.append("name=").append(name); 796 buff.append(", id=").append(id); 797 buff.append(", subsources=").append(subsources.size()); 798 799 return buff.toString(); 800 } 801 } 802 803 /** 804 * Returns an XML representation of this source. 805 * @return an XML representation of this source. 806 * @throws JAXBException if anything goes wrong during the conversion to XML. 807 * @see #writeAsXmlTo(Writer) 808 */ 809 public String toXml() throws JAXBException 810 { 811 return JaxbUtility.getSharedInstance().objectToXmlString(this); 812 } 813 814 /** 815 * Writes an XML representation of this source to {@code writer}. 816 * @param writer the device to which XML is written. 817 * @throws JAXBException if anything goes wrong during the conversion to XML. 818 */ 819 public void writeAsXmlTo(Writer writer) throws JAXBException 820 { 821 JaxbUtility.getSharedInstance().writeObjectAsXmlTo(writer, this, null); 822 } 823 824 /** 825 * Creates a new source from the XML data in the given file. 826 * 827 * @param xmlFile the name of an XML file. This method will attempt to locate 828 * the file by using {@link Class#getResource(String)}. 829 * 830 * @return a new source from the XML data in the given file. 831 * 832 * @throws FileNotFoundException if the XML file cannot be found. 833 * 834 * @throws JAXBException if the schema file used (if any) is malformed, if 835 * the XML file cannot be read, or if the XML file is not 836 * schema-valid. 837 * 838 * @throws XMLStreamException if there is a problem opening the XML file, 839 * if the XML is not well-formed, or for some other 840 * "unexpected processing conditions". 841 */ 842 public static Source fromXml(String xmlFile) 843 throws JAXBException, XMLStreamException, FileNotFoundException 844 { 845 return JaxbUtility.getSharedInstance() 846 .xmlFileToObject(xmlFile, Source.class); 847 } 848 849 /** 850 * Creates a new source based on the XML data read from {@code reader}. 851 * 852 * @param reader the source of the XML data. 853 * If this value is <i>null</i>, <i>null</i> is returned. 854 * 855 * @return a new source based on the XML data read from {@code reader}. 856 * 857 * @throws XMLStreamException if the XML is not well-formed, 858 * or for some other "unexpected processing conditions". 859 * 860 * @throws JAXBException if anything else goes wrong during the 861 * transformation. 862 */ 863 public static Source fromXml(Reader reader) 864 throws JAXBException, XMLStreamException 865 { 866 return JaxbUtility.getSharedInstance() 867 .readObjectAsXmlFrom(reader, Source.class, null); 868 } 869 870 //---------------------------------------------------------------------------- 871 // User-Defined Values 872 //---------------------------------------------------------------------------- 873 874 //Used only for JAXB. Helps deal w/ the underlying sorted map. 875 @XmlElementWrapper(name="userDefinedValues") 876 @XmlElement(name="userDefinedValue") 877 @SuppressWarnings("unused") 878 private KeyValue[] getXmlUserDefinedValues() 879 { 880 int valueCount = userDefinedValues.size(); 881 if (valueCount == 0) 882 return null; 883 884 KeyValue[] result = new KeyValue[valueCount]; 885 886 int e=0; 887 for (Map.Entry<String, String> udv : userDefinedValues.entrySet()) 888 result[e++] = new KeyValue(udv.getKey(), udv.getValue()); 889 890 return result; 891 } 892 893 //Used only for JAXB. Helps deal w/ the underlying sorted map. 894 @SuppressWarnings("unused") 895 private void setXmlUserDefinedValues(KeyValue[] yyy) 896 { 897 userDefinedValues.clear(); 898 for (KeyValue x : yyy) 899 userDefinedValues.put(x.key, x.value); 900 } 901 902 //Used only for JAXB. Helps deal w/ the underlying sorted map. 903 static class KeyValue 904 { 905 @XmlAttribute String key; 906 @XmlElement String value; 907 908 KeyValue() { this("",""); } 909 910 KeyValue(String key, String value) 911 { 912 this.key = key; 913 this.value = value; 914 } 915 } 916 917 //============================================================================ 918 // 919 //============================================================================ 920 921 /** 922 * Returns a source that is equal to this one. 923 * <p> 924 * If anything goes wrong during the cloning procedure, 925 * a {@code RuntimeException} will be thrown.</p> 926 */ 927 public Source clone() 928 { 929 Source clone = null; 930 931 try 932 { 933 //This line takes care of the primitive fields properly 934 clone = (Source)super.clone(); 935 936 //We do NOT want the clone to have the same ID as the original. 937 //The ID is here for the persistence layer; it is in charge of 938 //setting IDs. To help it, we put the clone's ID in the uninitialized 939 //state. 940 clone.id = Identifiable.UNIDENTIFIED; 941 942 clone.xmlId = "source-" + UUID.randomUUID().toString(); 943 944 clone.createdOn = new Date(); 945 clone.lastUpdatedOn = clone.createdOn; 946 947 //Clone list, but it's OK to share refs to the same immutable strings. 948 clone.aliases = new ArrayList<String>(this.aliases); 949 clone.historicalRecords = new ArrayList<String>(this.historicalRecords); 950 clone.notes = new ArrayList<String>(this.notes); 951 952 clone.links = new ArrayList<URL>(); 953 for (URL link : this.links) 954 clone.links.add(new URL(link.toString())); 955 956 clone.userDefinedValues = 957 new TreeMap<String, String>(this.userDefinedValues); 958 959 clone.imageLinks = new ArrayList<SourceImageLink>(); 960 for (SourceImageLink link : this.imageLinks) 961 clone.imageLinks.add(link.clone()); 962 963 clone.subsources = new TreeSet<Subsource>(); 964 for (Subsource ss : this.subsources) 965 clone.addSubsource(ss.clone()); 966 } 967 catch (Exception ex) 968 { 969 throw new RuntimeException(ex); 970 } 971 972 return clone; 973 } 974 975 /** Returns <i>true</i> if {@code o} is equal to this source. */ 976 public boolean equals(Object o) 977 { 978 //Quick exit if o is this 979 if (o == this) 980 return true; 981 982 //Quick exit if o is null 983 if (o == null) 984 return false; 985 986 //Quick exit if classes are different 987 if (!o.getClass().equals(this.getClass())) 988 return false; 989 990 Source other = (Source)o; 991 992 //NOTE 1: The absence of the id property here is intentional. 993 994 //NOTE 2: New philosophy: Base equality mainly on the science. 995 // For example, we will no longer compare the createdOn/By 996 // and lastUpdatedOn/By properties. 997 998 //NOTE 3: Absence of aliases, origin of information, user-defined values, 999 // image links, plain links, and historicalRecords 1000 // here is intentional. TODO Include any of these? 1001 1002 //Compare the attributes 1003 if (!other.name.equals(this.name)) 1004 return false; 1005 1006 //We can't rely on comparing the two sets directly because the 1007 //Set classes don't handle mutability of their elements very 1008 //well. (See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6579200) 1009 //If we make fresh sets, though, we're OK. 1010 //(In this particular case we have troubles of our own making as well. 1011 //We're using a SortedSet for subsources, but the Subsource.compareTo 1012 //method is not consistent w/ equals, which means Subsource is not a 1013 //good candidate for sorted sets.) 1014 HashSet<Subsource> theseSubs = new HashSet<Subsource>( this.subsources); 1015 HashSet<Subsource> thoseSubs = new HashSet<Subsource>(other.subsources); 1016 1017 return thoseSubs.equals(theseSubs); 1018 } 1019 1020 /** Returns a hash code value for this source. */ 1021 public int hashCode() 1022 { 1023 //Taken from the Effective Java book by Joshua Bloch. 1024 //The constants 17 & 37 are arbitrary & carry no meaning. 1025 int result = 17; 1026 1027 //NOTE: The absence of the id property here is intentional. 1028 // See other NOTES in equals method. 1029 1030 result = 37 * result + name.hashCode(); 1031 result = 37 * result + subsources.hashCode(); 1032 1033 return result; 1034 } 1035 1036 //============================================================================ 1037 // 1038 //============================================================================ 1039 /* 1040 public static void main(String[] args) 1041 { 1042 SourceBuilder builder = new SourceBuilder(); 1043 1044 Source src = builder.makeSource("Deep Throat"); 1045 1046 try 1047 { 1048 src.writeAsXmlTo(new java.io.PrintWriter(System.out)); 1049 } 1050 catch (Exception ex) 1051 { 1052 System.out.println("Trouble w/ src.toXml. Msg:"); 1053 System.out.println(ex.getMessage()); 1054 ex.printStackTrace(); 1055 1056 System.out.println("Attempting to write XML w/out schema verification:"); 1057 JaxbUtility.getSharedInstance().setLookForDefaultSchema(false); 1058 try 1059 { 1060 System.out.println(src.toXml()); 1061 } 1062 catch (JAXBException ex2) 1063 { 1064 System.out.println("Still had trouble w/ src.toXml. Msg:"); 1065 System.out.println(ex.getMessage()); 1066 ex.printStackTrace(); 1067 } 1068 } 1069 } 1070 */ 1071 /* 1072 public static void main(String... args) throws Exception 1073 { 1074 java.io.FileReader reader = 1075 new java.io.FileReader("/export/home/calmer/dharland/JUNK/Source.xml"); 1076 1077 Source src = Source.fromXml(reader); 1078 1079 java.io.FileWriter writer = 1080 new java.io.FileWriter("/export/home/calmer/dharland/JUNK/Source-OUT.xml"); 1081 src.writeAsXmlTo(writer); 1082 } 1083 */ 1084 /* 1085 public static void main(String... args) throws Exception 1086 { 1087 SourceBuilder sb = new SourceBuilder(); 1088 Subsource ss1=null; 1089 do 1090 { 1091 ss1 = sb.makeSubsource(ss1); 1092 }while (!(ss1.getPosition() instanceof SimpleSkyPosition)); 1093 1094 Source s1 = sb.makeSource("orig"); 1095 s1.addSubsource(ss1); 1096 Source s2 = s1.clone(); 1097 1098 System.out.println("s1 == s1.clone()? => " + s1.equals(s2)); 1099 1100 String ssName = ss1.getName(); 1101 Subsource ss2=null; 1102 for (Subsource subSrc : s2.getSubsources()) 1103 { 1104 if (subSrc.getName().equals(ssName)) 1105 { 1106 ss2=subSrc; 1107 SimpleSkyPosition pos = (SimpleSkyPosition)ss2.getPosition(); 1108 Distance d = pos.getDistance(); 1109 pos.setDistance(d.add(new Distance("1.0"))); 1110 System.out.println("new dist = " +pos.getDistance()); 1111 } 1112 } 1113 1114 boolean eq = s1.equals(s2); 1115 System.out.println(" s1 == s2? => " + eq); 1116 System.out.println("ss1 == ss2? => " + ss1.equals(ss2)); 1117 System.out.println("s1.hash = " + s1.hashCode()); 1118 System.out.println("s2.hash = " + s2.hashCode()); 1119 } 1120 */ 1121 /* 1122 public static void main(String... args) throws Exception 1123 { 1124 for (ReceiverBand rb : ReceiverBand.values()) 1125 { 1126 System.out.print(makeUdbKeyVlaUvMax(rb)+" | "); 1127 System.out.println(makeUdbKeyVlaUvMin(rb)); 1128 for (TelescopeConfiguration tc : TelescopeConfiguration.values()) 1129 { 1130 System.out.println(" " + makeUdbKeyVlaCalibQuality(rb, tc)); 1131 } 1132 } 1133 } 1134 */ 1135 }