001 package edu.nrao.sss.model.resource; 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.List; 008 009 import javax.xml.bind.JAXBException; 010 import javax.xml.bind.annotation.XmlElement; 011 import javax.xml.bind.annotation.XmlElementWrapper; 012 import javax.xml.bind.annotation.XmlRootElement; 013 import javax.xml.stream.XMLStreamException; 014 015 import edu.nrao.sss.measure.FrequencyRange; 016 import edu.nrao.sss.measure.FrequencySpectrum; 017 import edu.nrao.sss.util.JaxbUtility; 018 019 /** 020 * A science view of the hardware needed for an observation. 021 * <p> 022 * <b>Version Info:</b> 023 * <table style="margin-left:2em"> 024 * <tr><td>$Revision: 851 $</td></tr> 025 * <tr><td>$Date: 2007-08-27 13:29:48 -0600 (Mon, 27 Aug 2007) $</td></tr> 026 * <tr><td>$Author: dharland $</td></tr> 027 * </table></p> 028 * 029 * @author David M. Harland 030 * @since 2006-09-08 031 */ 032 @XmlRootElement 033 public class ResourceSpecification 034 implements Cloneable 035 { 036 private List<SkyFrequencySpecification> skyFrequencySpecs; 037 private List<SpectralLineSpecification> spectralLineSpecs; 038 private List<PulsarSpecification> pulsarSpecs; 039 040 /** Creates a new instance. */ 041 public ResourceSpecification() 042 { 043 skyFrequencySpecs = new ArrayList<SkyFrequencySpecification>(); 044 spectralLineSpecs = new ArrayList<SpectralLineSpecification>(); 045 pulsarSpecs = new ArrayList<PulsarSpecification>(); 046 } 047 048 /** 049 * Creates a new instance based on the given specifications. 050 * 051 * @param specs an entry in this specification's list of spectral 052 * specifications. 053 * 054 * @return a new instance based on the given specifications. 055 */ 056 public static ResourceSpecification makeFrom(SkyFrequencySpecification specs) 057 { 058 ResourceSpecification result = new ResourceSpecification(); 059 060 if (specs != null) 061 result.skyFrequencySpecs.add(specs); 062 063 return result; 064 } 065 066 /** 067 * Creates a new instance based on the given specifications. 068 * 069 * @param specs an entry in this specification's list of spectral 070 * specifications. 071 * 072 * @return a new instance based on the given specifications. 073 */ 074 public static ResourceSpecification makeFrom(SpectralLineSpecification specs) 075 { 076 ResourceSpecification result = new ResourceSpecification(); 077 078 if (specs != null) 079 result.spectralLineSpecs.add(specs); 080 081 return result; 082 } 083 084 /** 085 * Creates a new instance based on the given specifications. 086 * 087 * @param specs an entry in this specification's list of pulsar 088 * specifications. 089 * 090 * @return a new instance based on the given specifications. 091 */ 092 public static ResourceSpecification makeFrom(PulsarSpecification specs) 093 { 094 ResourceSpecification result = new ResourceSpecification(); 095 096 if (specs != null) 097 result.pulsarSpecs.add(specs); 098 099 return result; 100 } 101 102 //============================================================================ 103 // 104 //============================================================================ 105 106 /** 107 * Resets the identifier of the subspecifications of this specification 108 * to a value that represents the unidentified state. 109 * <p> 110 * This method is useful for preparing a specification for storage in a database. 111 * The ID property (as of now, though this may change in the future) is 112 * used by our persistence mechanism to identify objects. If you are 113 * persisting this object for the first time, you may need to call 114 * this method before performing a save. This is especially true if 115 * you have created this object from XML, as the XML unmarshalling 116 * brings along the ID property.</p> 117 */ 118 void clearId() 119 { 120 for (SkyFrequencySpecification sfs : skyFrequencySpecs) 121 sfs.clearId(); 122 123 for (SpectralLineSpecification sls: spectralLineSpecs) 124 sls.clearId(); 125 126 for (PulsarSpecification ps : pulsarSpecs) 127 ps.clearId(); 128 } 129 130 //============================================================================ 131 // 132 //============================================================================ 133 134 /** 135 * Sets a new list of sky frequency specifications. 136 * If {@code newSpecs} is <i>null</i>, it will be interpreted 137 * as an empty list. 138 * <p> 139 * Note that this specification will hold an internal reference to 140 * {@code newSpecs} (unless it is <i>null</i>). This means that any 141 * changes made to {@code newSpecs} after this call <i>will</i> be 142 * reflected in this object.</p> 143 * 144 * @param newSpecs a list of sky frequency specifications. 145 */ 146 public void setSkyFrequencySpecs(List<SkyFrequencySpecification> newSpecs) 147 { 148 skyFrequencySpecs = 149 newSpecs == null ? new ArrayList<SkyFrequencySpecification>() : newSpecs; 150 } 151 152 /** 153 * Returns this specification's list of sky frequency specifications. 154 * The returned list may be empty, but will never be <i>null</i>. 155 * <p> 156 * Note that the returned list is the one held internally by this 157 * specification. This means that any 158 * changes made to the returned list after this call <i>will</i> be 159 * reflected in this object.</p> 160 * 161 * @return this specification's list of sky frequency specifications. 162 */ 163 @XmlElementWrapper 164 @XmlElement(name="skyFrequencySpecification") 165 public List<SkyFrequencySpecification> getSkyFrequencySpecs() 166 { 167 return skyFrequencySpecs; 168 } 169 170 /** 171 * Sets a new list of spectral line specifications. 172 * If {@code newSpecs} is <i>null</i>, it will be interpreted 173 * as an empty list. 174 * <p> 175 * Note that this specification will hold an internal reference to 176 * {@code newSpecs} (unless it is <i>null</i>). This means that any 177 * changes made to {@code newSpecs} after this call <i>will</i> be 178 * reflected in this object.</p> 179 * 180 * @param newSpecs a list of spectral line specifications. 181 */ 182 public void setSpectralLineSpecs(List<SpectralLineSpecification> newSpecs) 183 { 184 spectralLineSpecs = 185 newSpecs == null ? new ArrayList<SpectralLineSpecification>() : newSpecs; 186 } 187 188 /** 189 * Returns this specification's list of spectral line specifications. 190 * The returned list may be empty, but will never be <i>null</i>. 191 * <p> 192 * Note that the returned list is the one held internally by this 193 * specification. This means that any 194 * changes made to the returned list after this call <i>will</i> be 195 * reflected in this object.</p> 196 * 197 * @return this specification's list of spectral line specifications. 198 */ 199 @XmlElementWrapper 200 @XmlElement(name="spectralLineSpecification") 201 public List<SpectralLineSpecification> getSpectralLineSpecs() 202 { 203 return spectralLineSpecs; 204 } 205 206 /** 207 * Sets a new list of pulsar specifications. 208 * If {@code newSpecs} is <i>null</i>, it will be interpreted 209 * as an empty list. 210 * <p> 211 * Note that this specification will hold an internal reference to 212 * {@code newSpecs} (unless it is <i>null</i>). This means that any 213 * changes made to {@code newSpecs} after this call <i>will</i> be 214 * reflected in this object.</p> 215 * 216 * @param newSpecs a list of pulsar specifications. 217 */ 218 public void setPulsarSpecs(List<PulsarSpecification> newSpecs) 219 { 220 pulsarSpecs = 221 (newSpecs == null) ? new ArrayList<PulsarSpecification>() : newSpecs; 222 } 223 224 /** 225 * Returns this specification's list of pulsar specifications. 226 * The returned list may be empty, but will never be <i>null</i>. 227 * <p> 228 * Note that the returned list is the one held internally by this 229 * specification. This means that any 230 * changes made to the returned list after this call <i>will</i> be 231 * reflected in this object.</p> 232 * 233 * @return this specification's list of pulsar specifications. 234 */ 235 @XmlElementWrapper 236 @XmlElement(name="pulsarSpecification") 237 public List<PulsarSpecification> getPulsarSpecs() 238 { 239 return pulsarSpecs; 240 } 241 242 //============================================================================ 243 // 244 //============================================================================ 245 246 /** 247 * Returns the portions of the frequency spectrum covered by these 248 * specifications. 249 * 250 * @return the portions of the frequency spectrum covered by these 251 * specifications. 252 */ 253 public FrequencySpectrum getSpectrum() 254 { 255 FrequencySpectrum result = new FrequencySpectrum(); 256 257 for (SkyFrequencySpecification sfs : skyFrequencySpecs) 258 result.addCoveredRange(sfs.getFrequencyRange()); 259 260 for (SpectralLineSpecification sls : spectralLineSpecs) 261 result.addCoveredRange(sls.toSkyFrequencySpecification() 262 .getFrequencyRange()); 263 264 //TODO 265 //for (PulsarSpecification ps : pulsarSpecs) 266 // result.addCoveredRange(ps.getFrequencyRange()); 267 268 return result; 269 } 270 271 //============================================================================ 272 // MODIFYING THIS SPECIFICATION WITH A FREQUENCY RANGE 273 //============================================================================ 274 275 /** 276 * Modifies this resource specification to fit the given frequency range. 277 * <p> 278 * Any frequency ranges held by this specification that have no overlap with 279 * {@code targetRange} are deleted. Those ranges that do overlap the target 280 * range are trimmed to the overlapping portions. The net effect is that the 281 * frequency ranges of this specification after the modification are the 282 * result of intersecting the current ranges with {@code targetRange}.</p> 283 * 284 * @param targetRange the frequency range used to modify this specification. 285 * Only the portions of the ranges held by this 286 * specification that intersect this parameter are 287 * retained. 288 * 289 * @return this specification, after modification. 290 */ 291 public ResourceSpecification modifyToFit(FrequencyRange targetRange) 292 { 293 modifySkyToFit (targetRange); 294 modifySpectralToFit(targetRange); 295 modifyPulsarToFit (targetRange); 296 297 return this; 298 } 299 300 /** 301 * Modifies this specification's list of sky frequency specifications. 302 * Specifications whose frequency ranges do not overlap targetRange 303 * are removed. 304 * Specifications whose frequency ranges overlap targetRange 305 * are trimmed to the overlapping portion of their ranges. 306 */ 307 private void modifySkyToFit(FrequencyRange targetRange) 308 { 309 List<SkyFrequencySpecification> nonOverlappingRanges = 310 new ArrayList<SkyFrequencySpecification>(); 311 312 for (SkyFrequencySpecification sfs : skyFrequencySpecs) 313 { 314 FrequencyRange specRange = sfs.getFrequencyRange(); 315 316 if (specRange.contains(targetRange)) 317 { 318 ; //do nothing 319 } 320 if (specRange.overlaps(targetRange)) 321 { 322 specRange.intersectWith(targetRange); 323 } 324 else //no overlap 325 { 326 nonOverlappingRanges.add(sfs); 327 } 328 } 329 330 skyFrequencySpecs.removeAll(nonOverlappingRanges); 331 } 332 333 /** 334 * Modifies this specification's list of spectral line specifications. 335 * Specifications whose frequency ranges do not overlap targetRange 336 * are removed. 337 * Specifications whose frequency ranges overlap targetRange 338 * are trimmed to the overlapping portion of their ranges. 339 */ 340 private void modifySpectralToFit(FrequencyRange targetRange) 341 { 342 List<SpectralLineSpecification> nonOverlappingRanges = 343 new ArrayList<SpectralLineSpecification>(); 344 345 for (SpectralLineSpecification sls : spectralLineSpecs) 346 { 347 FrequencyRange skySpecRange = 348 sls.toSkyFrequencySpecification().getFrequencyRange(); 349 350 if (skySpecRange.contains(targetRange)) 351 { 352 ; //do nothing 353 } 354 else if (skySpecRange.overlaps(targetRange)) 355 { 356 skySpecRange.intersectWith(targetRange); 357 sls.adjustVelocityForSkyFrequencyOf(skySpecRange); 358 } 359 else //no overlap 360 { 361 nonOverlappingRanges.add(sls); 362 } 363 } 364 365 spectralLineSpecs.removeAll(nonOverlappingRanges); 366 } 367 368 /** 369 * Modifies the list of pulsar specifications. 370 * Specifications whose frequency ranges do not overlap targetRange 371 * are removed. 372 * Specifications whose frequency ranges overlap targetRange 373 * are trimmed to the overlapping portion of their ranges. 374 */ 375 private void modifyPulsarToFit(FrequencyRange targetRange) 376 { 377 //TODO 378 379 /* 380 List<PulsarSpecification> nonOverlappingRanges = 381 new ArrayList<PulsarSpecification>(); 382 383 for (PulsarSpecification ps : pulsarSpecs) 384 { 385 FrequencyRange specRange = ps.getFrequencyRange(); 386 387 if (specRange.overlaps(targetRange)) 388 { 389 ps.getFrequencyRange().intersectWith(targetRange)); 390 } 391 else //no overlap 392 { 393 nonOverlappingRanges.add(ps); 394 } 395 } 396 397 pulsarSpecs.removeAll(nonOverlappingRanges); 398 */ 399 } 400 401 //============================================================================ 402 // MODIFYING THIS SPECIFICATION WITH A FREQUENCY SPECTRUM 403 //============================================================================ 404 405 /** 406 * Modifies this resource specification to fit the given frequency spectrum. 407 * <p> 408 * Any frequency ranges held by this specification that have no overlap with 409 * {@code targetSpectrum} are deleted. Those ranges that do overlap the 410 * target spectrum are trimmed to the overlapping portions. The net effect 411 * is that the frequency ranges of this specification after the modification 412 * are the result of intersecting the current ranges with 413 * {@code targetSpectrum}.</p> 414 * <p> 415 * <b>Note:</b> one consequence of this method is that the internal lists 416 * of subspecifications are replaced with new lists.</p> 417 * 418 * @param targetSpectrum the frequency speumctr used to modify this 419 * specification. Only the portions of the ranges held 420 * by this specification that intersect this parameter 421 * are retained. 422 * 423 * @return this specification, after modification. 424 */ 425 public ResourceSpecification modifyToFit(FrequencySpectrum targetSpectrum) 426 { 427 modifySkyToFit (targetSpectrum); 428 modifySpectralToFit(targetSpectrum); 429 modifyPulsarToFit (targetSpectrum); 430 431 return this; 432 } 433 434 /** 435 * Replaces the list of sky frequency specifications with a new list. 436 * New sky frequency specifications are formed from the overlap of each 437 * current specification with the target frequency spectrum. 438 */ 439 private void modifySkyToFit(FrequencySpectrum targetSpectrum) 440 { 441 List<SkyFrequencySpecification> newSpecs = 442 new ArrayList<SkyFrequencySpecification>(); 443 444 //For each sky freq spec, find the overlap of its frequency range 445 //with the target spectrum. Note that there could be any number 446 //of overlapping regions, not just zero-or-one. 447 for (SkyFrequencySpecification sfs : skyFrequencySpecs) 448 { 449 FrequencySpectrum fs = 450 targetSpectrum.clone().intersectWith(sfs.getFrequencyRange()); 451 452 //For each overlapping region, create a new sky freq spec from the 453 //current one and set its frequency range to the overlapping region. 454 for (FrequencyRange coveredRange : fs.getCoveredRanges()) 455 { 456 SkyFrequencySpecification newSpec = sfs.clone(); 457 newSpec.setFrequencyRange(coveredRange); 458 newSpecs.add(newSpec); 459 } 460 } 461 462 //Replace current list with list of new specs 463 skyFrequencySpecs = newSpecs; 464 } 465 466 /** 467 * Replaces the list of spectral line specifications with a new list. 468 * New spectral line specifications are formed from the overlap of each 469 * current specification with the target frequency spectrum. 470 */ 471 private void modifySpectralToFit(FrequencySpectrum targetSpectrum) 472 { 473 List<SpectralLineSpecification> newSpecs = 474 new ArrayList<SpectralLineSpecification>(); 475 476 //For each spectral line spec, find the overlap of the frequency range 477 //of its derived sky frequency specification with the target spectrum. 478 //Note that there could be any number of overlapping regions, 479 //not just zero-or-one. 480 for (SpectralLineSpecification sls : spectralLineSpecs) 481 { 482 SkyFrequencySpecification skyFreqSpec = sls.toSkyFrequencySpecification(); 483 484 FrequencySpectrum fs = 485 targetSpectrum.clone().intersectWith(skyFreqSpec.getFrequencyRange()); 486 487 //For each overlapping region, create a new spectral line spec from the 488 //current one and adjust its velocities to match the covered sky freqs. 489 for (FrequencyRange coveredRange : fs.getCoveredRanges()) 490 { 491 SpectralLineSpecification newSpec = sls.clone(); 492 newSpec.adjustVelocityForSkyFrequencyOf(coveredRange); 493 newSpecs.add(newSpec); 494 } 495 } 496 497 spectralLineSpecs = newSpecs; 498 } 499 500 /** 501 * Replaces the list of pulsar specifications with a new list. 502 * New pulsar specifications are formed from the overlap of each 503 * current specification with the target frequency spectrum. 504 */ 505 private void modifyPulsarToFit(FrequencySpectrum targetSpectrum) 506 { 507 //TODO 508 509 /* 510 List<PulsarSpecification> newSpecs = 511 new ArrayList<PulsarSpecification>(); 512 513 //For each pulsar spec, find the overlap of its frequency range 514 //with the target spectrum. Note that there could be any number 515 //of overlapping regions, not just zero-or-one. 516 for (PulsarSpecification ps : pulsarSpecs) 517 { 518 FrequencySpectrum fs = 519 targetSpectrum.clone().intersectWith(ps.getFrequencyRange()); 520 521 //For each overlapping region, create a new pulsar spec from the 522 //current one and set its frequency range to the overlapping region. 523 for (FrequencyRange coveredRange : fs.getCoveredRanges()) 524 { 525 PulsarSpecification newSpec = ps.clone(); 526 newSpec.setFrequencyRange(coveredRange); 527 newSpecs.add(newSpec); 528 } 529 } 530 531 //Replace current list with list of new specs 532 continuumSpecs = newSpecs; 533 */ 534 } 535 536 //============================================================================ 537 // XML 538 //============================================================================ 539 540 /** 541 * Returns an XML representation of this specification. 542 * @return an XML representation of this specification. 543 * @throws JAXBException if anything goes wrong during the conversion to XML. 544 * @see #writeAsXmlTo(Writer) 545 */ 546 public String toXml() throws JAXBException 547 { 548 return JaxbUtility.getSharedInstance().objectToXmlString(this); 549 } 550 551 /** 552 * Writes an XML representation of this specification to {@code writer}. 553 * @param writer the device to which XML is written. 554 * @throws JAXBException if anything goes wrong during the conversion to XML. 555 */ 556 public void writeAsXmlTo(Writer writer) throws JAXBException 557 { 558 JaxbUtility.getSharedInstance().writeObjectAsXmlTo(writer, this, null); 559 } 560 561 /** 562 * Creates a new specification from the XML data in the given file. 563 * 564 * @param xmlFile the name of an XML file. This method will attempt to locate 565 * the file by using {@link Class#getResource(String)}. 566 * 567 * @return a new specification from the XML data in the given file. 568 * 569 * @throws FileNotFoundException if the XML file cannot be found. 570 * 571 * @throws JAXBException if the schema file used (if any) is malformed, if 572 * the XML file cannot be read, or if the XML file is not 573 * schema-valid. 574 * 575 * @throws XMLStreamException if there is a problem opening the XML file, 576 * if the XML is not well-formed, or for some other 577 * "unexpected processing conditions". 578 */ 579 public static ResourceSpecification fromXml(String xmlFile) 580 throws JAXBException, XMLStreamException, FileNotFoundException 581 { 582 return JaxbUtility.getSharedInstance() 583 .xmlFileToObject(xmlFile, ResourceSpecification.class); 584 } 585 586 /** 587 * Creates a new specification based on the XML data read from {@code reader}. 588 * 589 * @param reader the specification of the XML data. 590 * If this value is <i>null</i>, <i>null</i> is returned. 591 * 592 * @return a new specification based on the XML data read from {@code reader}. 593 * 594 * @throws XMLStreamException if the XML is not well-formed, 595 * or for some other "unexpected processing conditions". 596 * 597 * @throws JAXBException if anything else goes wrong during the 598 * transformation. 599 */ 600 public static ResourceSpecification fromXml(Reader reader) 601 throws JAXBException, XMLStreamException 602 { 603 return JaxbUtility.getSharedInstance() 604 .readObjectAsXmlFrom(reader, 605 ResourceSpecification.class, null); 606 } 607 608 //============================================================================ 609 // 610 //============================================================================ 611 612 /** 613 * Returns a resource specification that is a copy of this one. 614 * <p> 615 * If anything goes wrong during the cloning procedure, 616 * a {@code RuntimeException} will be thrown.</p> 617 */ 618 @Override 619 public ResourceSpecification clone() 620 { 621 ResourceSpecification clone = null; 622 623 try 624 { 625 clone = (ResourceSpecification)super.clone(); 626 627 //Clone the collection AND the contained elements 628 clone.skyFrequencySpecs = new ArrayList<SkyFrequencySpecification>(); 629 for (SkyFrequencySpecification sfs : this.skyFrequencySpecs) 630 clone.skyFrequencySpecs.add(sfs.clone()); 631 632 clone.spectralLineSpecs = new ArrayList<SpectralLineSpecification>(); 633 for (SpectralLineSpecification sls : this.spectralLineSpecs) 634 clone.spectralLineSpecs.add(sls.clone()); 635 636 clone.pulsarSpecs = new ArrayList<PulsarSpecification>(); 637 for (PulsarSpecification ps : this.pulsarSpecs) 638 clone.pulsarSpecs.add(ps.clone()); 639 } 640 catch (Exception ex) 641 { 642 throw new RuntimeException(ex); 643 } 644 645 return clone; 646 } 647 648 /** Returns <i>true</i> if {@code o} is equal to this specification. */ 649 @Override 650 public boolean equals(Object o) 651 { 652 //Quick exit if o is this 653 if (o == this) 654 return true; 655 656 //Quick exit if o is null 657 if (o == null) 658 return false; 659 660 //Quick exit if classes are different 661 if (!o.getClass().equals(this.getClass())) 662 return false; 663 664 ResourceSpecification other = (ResourceSpecification)o; 665 666 return other.skyFrequencySpecs.equals(this.skyFrequencySpecs) && 667 other.spectralLineSpecs.equals(this.spectralLineSpecs) && 668 other.pulsarSpecs.equals(this.pulsarSpecs); 669 } 670 671 /** Returns a hash code value for this specification. */ 672 @Override 673 public int hashCode() 674 { 675 //Taken from the Effective Java book by Joshua Bloch. 676 //The constants 17 & 37 are arbitrary & carry no meaning. 677 int result = 17; 678 679 result = 37 * result + skyFrequencySpecs.hashCode(); 680 result = 37 * result + spectralLineSpecs.hashCode(); 681 result = 37 * result + pulsarSpecs.hashCode(); 682 683 return result; 684 } 685 686 //============================================================================ 687 // 688 //============================================================================ 689 /* 690 public static void main(String[] args) 691 { 692 ResourceBuilder builder = new ResourceBuilder(); 693 694 ResourceSpecification spec = builder.makeResourceSpecification(); 695 //JaxbUtility.getSharedInstance().setLookForDefaultSchema(false); 696 try { 697 System.out.println(spec.toXml()); 698 } 699 catch (JAXBException ex) { 700 System.out.println("Trouble w/ spec.toXml. Msg:"); 701 System.out.println(ex.getMessage()); 702 ex.printStackTrace(); 703 } 704 } 705 */ 706 }