001 package edu.nrao.sss.model.project.scan; 002 003 import java.io.FileNotFoundException; 004 import java.io.Reader; 005 import java.util.Collection; 006 import java.util.Date; 007 import java.util.HashMap; 008 import java.util.HashSet; 009 import java.util.Map; 010 import java.util.Set; 011 012 import javax.xml.bind.JAXBException; 013 import javax.xml.bind.annotation.XmlAttribute; 014 import javax.xml.bind.annotation.XmlElement; 015 import javax.xml.bind.annotation.XmlElementWrapper; 016 import javax.xml.bind.annotation.XmlList; 017 import javax.xml.bind.annotation.XmlTransient; 018 import javax.xml.bind.annotation.XmlType; 019 import javax.xml.stream.XMLStreamException; 020 021 import edu.nrao.sss.astronomy.DopplerTracker; 022 import edu.nrao.sss.astronomy.SkyPosition; 023 import edu.nrao.sss.astronomy.VelocityConvention; 024 import edu.nrao.sss.astronomy.VelocityFrame; 025 import edu.nrao.sss.geom.EarthPosition; 026 import edu.nrao.sss.measure.Frequency; 027 import edu.nrao.sss.measure.LinearVelocity; 028 import edu.nrao.sss.measure.LinearVelocityUnits; 029 import edu.nrao.sss.model.resource.AntennaSelection; 030 import edu.nrao.sss.model.resource.Resource; 031 import edu.nrao.sss.model.resource.TelescopeType; 032 import edu.nrao.sss.model.source.Source; 033 import edu.nrao.sss.model.source.SourceCatalogEntry; 034 import edu.nrao.sss.model.source.SourceLookupTable; 035 import edu.nrao.sss.model.source.SourceVelocity; 036 import edu.nrao.sss.model.source.Subsource; 037 import edu.nrao.sss.util.Identifiable; 038 import edu.nrao.sss.util.JaxbUtility; 039 import edu.nrao.sss.util.StringUtil; 040 041 /** 042 * A sequence of one or more observations that share a single goal. 043 * A scan is made up of a target source, a calibrator, resources, 044 * timing information, and an observing mode. 045 * <p> 046 * A {@code Scan} is usually an observation of a single target source 047 * or calibrator, but may involve different pointing and focus 048 * patterns.</p> 049 * <p> 050 * <b>Version Info:</b> 051 * <table style="margin-left:2em"> 052 * <tr><td>$Revision: 2277 $</td> 053 * <tr><td>$Date: 2009-04-29 11:19:38 -0600 (Wed, 29 Apr 2009) $</td> 054 * <tr><td>$Author: dharland $</td> 055 * </table></p> 056 * 057 * @author David M. Harland 058 * @since 2006-02-24 059 */ 060 @XmlType( 061 propOrder={ 062 "mode", "timeSpec", 063 "source", "sourceLookupTable", 064 "useResourceOfPriorScan", "resource", 065 "intents", 066 "subarray", "referenceAntennas", 067 "applyLastPhase", "applyLastReferencePointing", 068 "applyLastReferenceFocus", "applyLastReferenceDelay", 069 "solarObserving", "allowOverTheTop", 070 "antennaWrap", "xmlDopplerSpecs" 071 } 072 ) 073 074 public abstract class Scan 075 extends ScanLoopElement 076 { 077 public static final String DEFAULT_NAME = "[New Scan]"; 078 079 //IDENTIFICATION 080 private ScanMode mode; 081 082 //TIMING 083 private ScanTimeSpecification timeSpec; 084 085 //SCIENCE - Special Note re: Sources 086 //We originally used a single instance variable, 087 //private SourceCatalogEntry sourceCatalogEntry, for the source. 088 //However, we ran into a Hibernate issue when ScanValidator was run. 089 //Specifically, the Hibernate proxy class threw an internal class-cast 090 //exception when the validator called scan.getSource(Date). The split 091 //of the variable into these two is a work-around for that problem. 092 //Note that we did not change the public API at all. 093 //The code herein ensures that these two can never simultaneously 094 //be non-null. 095 @XmlElement private Source source; 096 @XmlElement private SourceLookupTable sourceLookupTable; 097 098 //SCIENCE 099 private boolean useResourceOfPriorScan; 100 private Resource resource; 101 private Set<ScanIntent> intents; 102 private boolean applyLastPhase; 103 private boolean applyLastReferencePointing; 104 private boolean applyLastReferenceFocus; 105 private boolean applyLastReferenceDelay; 106 private boolean solarObserving; 107 private boolean allowOverTheTop; 108 private AntennaWrap antennaWrap; 109 110 private Map<String, ScanDopplerSpecs> dtSpecs; 111 112 @XmlElement private AntennaSelection subarray; 113 @XmlElement private AntennaSelection referenceAntennas; 114 115 //============================================================================ 116 // CREATION 117 //============================================================================ 118 119 /** 120 * A factory method for creating a new scan. 121 * The returned scan will be of a variety that is appropriate for the given 122 * observation mode. 123 * 124 * @param scanMode the observation mode for which a scan is desired. 125 * 126 * @return a new scan instance. 127 * 128 * @throws IllegalArgumentException if {@code scanMode} is <i>null</i>. 129 */ 130 public static Scan createFor(ScanMode scanMode) 131 { 132 if (scanMode == null) 133 throw new IllegalArgumentException("Cannot create scan for NULL scanMode."); 134 135 Scan newScan = null; 136 137 // Added the selection of a default ScanIntent where appropriate. Not done 138 // in constructor because the ScanMode is not passed into the constructor and 139 // some subclasses represent several modes. Also not done in the private 140 // setMode() method for fear of hosing JAXB or Hibernate. --BWT 141 switch (scanMode) 142 { 143 case AMPLITUDE_DELAY_CALIBRATING: 144 newScan = new DelayScan(); 145 newScan.getIntents().add(ScanIntent.CALIBRATE_DELAY_AMPLITUDE_STYLE); 146 break; 147 148 case REFERENCE_FOCUSING: 149 newScan = new FocusScan(); 150 newScan.getIntents().add(ScanIntent.CALIBRATE_FOCUS); 151 break; 152 153 case HOLOGRAPHY: 154 newScan = new PointingScan(); 155 newScan.getIntents().add(ScanIntent.MAP_ANTENNA_SURFACE); 156 break; 157 158 case INTERFEROMETRIC_POINTING: 159 newScan = new PointingScan(); 160 newScan.getIntents().add(ScanIntent.CALIBRATE_OFFSET_POINTING); 161 break; 162 163 case MOSAICKING: 164 newScan = new PointingScan(); 165 newScan.getIntents().add(ScanIntent.OBSERVE_TARGET); 166 break; 167 168 case SINGLE_DISH_POINTING: 169 newScan = new PointingScan(); 170 newScan.getIntents().add(ScanIntent.DETERMINE_SINGLE_DISH_POINTING); 171 break; 172 173 case FAST_SWITCHING: 174 newScan = new SwitchingScan(); 175 newScan.getIntents().add(ScanIntent.OBSERVE_TARGET); 176 break; 177 178 case TIPPING: 179 newScan = new TippingScan(); 180 newScan.getIntents().add(ScanIntent.DETERMINE_OPACITY_TIPPING_STYLE); 181 break; 182 183 case STANDARD_OBSERVING: 184 default: 185 newScan = new SimpleScan(); 186 newScan.getIntents().add(ScanIntent.OBSERVE_TARGET); 187 break; 188 } 189 190 newScan.setMode(scanMode); 191 192 return newScan; 193 } 194 195 /** Helps create a new instance. */ 196 Scan() 197 { 198 initialize(); 199 200 //Objects that may never be null 201 timeSpec = new ScanTimeSpecification(); 202 intents = new HashSet<ScanIntent>(); 203 referenceAntennas = new AntennaSelection(); 204 subarray = new AntennaSelection(); 205 dtSpecs = new HashMap<String, ScanDopplerSpecs>(); 206 } 207 208 /** Initializes the instance variables of this class. */ 209 private void initialize() 210 { 211 //mode = intentionally unset 212 source = null; 213 sourceLookupTable = null; 214 resource = null; 215 useResourceOfPriorScan = false; 216 applyLastPhase = false; 217 applyLastReferencePointing = false; 218 applyLastReferenceFocus = false; 219 applyLastReferenceDelay = false; 220 solarObserving = false; 221 allowOverTheTop = false; 222 antennaWrap = AntennaWrap.NO_PREFERENCE; 223 } 224 225 /** 226 * Resets this scan to its initial state. 227 * A reset scan has the same state as a new scan. 228 */ 229 @Override 230 public void reset() 231 { 232 super.reset(); 233 234 initialize(); 235 236 timeSpec.reset(); 237 intents.clear(); 238 referenceAntennas.reset(); 239 subarray.reset(); 240 } 241 242 //============================================================================ 243 // IDENTIFICATION 244 //============================================================================ 245 246 247 /** 248 * Resets this scan's id to UNIDENTIFIED, an similarly resets it's 249 * source catalog entry and resource id's. 250 */ 251 public void clearId() 252 { 253 super.clearId(); 254 Resource resrc = getResource(); 255 if (resrc != null) 256 resrc.clearId(); 257 258 SourceCatalogEntry entry = getSourceCatalogEntry(); 259 if (entry != null) 260 entry.clearId(); 261 } 262 263 String getDefaultName() { return Scan.DEFAULT_NAME; } 264 265 /** 266 * Returns <i>true</i> if we may change the name of this scan if it is 267 * given a new source. 268 */ 269 boolean nameMayBeAutoUpdated() 270 { 271 //Note that we're not defending against the situation where some one 272 //explicitly set the name to xxx and this just happens to also be 273 //the name of the current source. If that becomes important, we'll 274 //handle it at that time. 275 String currentName = getName(); 276 277 return 278 currentName.equals(getDefaultName()) || 279 (source != null && currentName.equals(source.getName())) || 280 (sourceLookupTable != null && 281 currentName.equals(sourceLookupTable.getName())); 282 } 283 284 @XmlTransient 285 @Deprecated 286 /** @deprecated Use {@link #getName()}. */ 287 public String getLongName() { return getName(); } 288 289 @Deprecated 290 /** @deprecated Use {@link #setName(String)}. */ 291 public void setLongName(String newName) { setName(newName); } 292 293 @XmlTransient 294 @Deprecated 295 /** @deprecated Use {@link #getName()}. */ 296 public String getShortName() { return getName(); } 297 298 @Deprecated 299 /** @deprecated Use {@link #setName(String)}. */ 300 public void setShortName(String newName) { setName(newName); } 301 302 /** 303 * Returns the mode of this scan. 304 * @return the mode of this scan. 305 */ 306 @XmlElement 307 public ScanMode getMode() 308 { 309 return mode; 310 } 311 312 private void setMode(ScanMode newMode) 313 { 314 mode = (newMode == null) ? ScanMode.getDefault() : newMode; 315 } 316 317 //============================================================================ 318 // TIMING 319 //============================================================================ 320 321 /** 322 * Returns the timing specification for this scan. 323 * The returned object will never be <i>null</i> and is the actual instance 324 * held by this scan, so changes made to it will be reflected herein. 325 * 326 * @return 327 * the timing specification for this scan. 328 */ 329 public ScanTimeSpecification getTimeSpec() 330 { 331 return timeSpec; 332 } 333 334 //This is here for persistence mechanisms, such as JAXB and Hibernate. 335 @XmlElement 336 @SuppressWarnings("unused") 337 private void setTimeSpec(ScanTimeSpecification newSpec) 338 { 339 if (newSpec != null) 340 timeSpec = newSpec; 341 } 342 343 //============================================================================ 344 // SOURCES 345 //============================================================================ 346 347 /** 348 * Returns the source to use at the current time. If this scan has no such 349 * source, <i>null</i> is returned. 350 * 351 * @return the source to use at the current 352 * time, or <i>null</i> if there is no such source. 353 */ 354 public Source getSource() 355 { 356 return getSource(new Date()); 357 } 358 359 /** 360 * Returns the source to use at the given time. If this scan has no such 361 * source, <i>null</i> is returned. 362 * 363 * @param dateTime the time for which the source is needed. 364 * 365 * @return the source to use at the given 366 * time, or <i>null</i> if there is no such source. 367 */ 368 public Source getSource(Date dateTime) 369 { 370 Source answer; 371 372 if (source != null) 373 answer = source; 374 else if (sourceLookupTable != null) 375 answer = sourceLookupTable.get(dateTime); 376 else 377 answer = null; 378 379 return answer; 380 } 381 382 /** 383 * Sets either the {@code Source} or {@code SourceLookupTable} that is the 384 * focus of this scan. 385 * 386 * @param sourceOrTable the {@code Source} or {@code SourceLookupTable} to 387 * use for this scan. 388 */ 389 public void setSourceCatalogEntry(SourceCatalogEntry sourceOrTable) 390 { 391 if (sourceOrTable == null) 392 { 393 source = null; 394 sourceLookupTable = null; 395 } 396 397 else 398 { 399 // changing the name BEFORE updating the source because 400 // nameMayBeAutoUpdated() checks to see if the current name matches the 401 // current source's name. (Which isn't a useful check if we've already 402 // changed the source). 403 if (nameMayBeAutoUpdated()) 404 setName(sourceOrTable.getName()); 405 406 if (sourceOrTable instanceof Source) 407 { 408 source = (Source)sourceOrTable; 409 sourceLookupTable = null; 410 } 411 else if (sourceOrTable instanceof SourceLookupTable) 412 { 413 source = null; 414 sourceLookupTable = (SourceLookupTable)sourceOrTable; 415 } 416 else 417 { 418 throw new RuntimeException("Unknown type of SourceCatalogEntry found (" + 419 sourceOrTable.getClass().getName() + ")"); 420 } 421 } 422 } 423 424 /** 425 * Returns either the {@code Source} or {@code SourceLookupTable} that is the 426 * focus of this scan. The returned value may be <i>null</i>. 427 * 428 * @return the {@code Source} or {@code SourceLookupTable} used for this scan, 429 * or <i>null</i> if this scan has neither. 430 */ 431 @XmlTransient 432 public SourceCatalogEntry getSourceCatalogEntry() 433 { 434 return (source == null) ? sourceLookupTable : source; 435 } 436 437 //============================================================================ 438 // RESOURCES 439 //============================================================================ 440 441 /** 442 * Indicates whether this scan should use its own hardware configuration or 443 * that of the prior scan. 444 * <p> 445 * Calling this method has no impact on the value of the 446 * {@link #setResource(Resource) resource} property.</p> 447 * 448 * @param usePriorResource 449 * <i>true</i> if this scan should the hardware configuration of the 450 * prior scan, <i>false</i> if it should use its own configuration. 451 * 452 * @see #setResource(Resource) 453 */ 454 public void setUseResourceOfPriorScan(boolean usePriorResource) 455 { 456 useResourceOfPriorScan = usePriorResource; 457 } 458 459 /** 460 * Indicates whether this scan should use its own hardware configuration or 461 * that of the prior scan. 462 * 463 * @return 464 * <i>true</i> if this scan should the hardware configuration of the 465 * prior scan, <i>false</i> if it should use its own configuration. 466 * 467 * @see #getResource() 468 */ 469 public boolean getUseResourceOfPriorScan() 470 { 471 return useResourceOfPriorScan; 472 } 473 474 /** 475 * Sets the resource to be used for this scan. 476 * This method will accept a value of <i>null</i>. 477 * <p> 478 * The <tt>newResource</tt> should be used only if the value returned 479 * by {@link #getUseResourceOfPriorScan()} is <i>true</i>.</p> 480 * <p> 481 * Calling this method has no impact on the value of the 482 * {@link #setUseResourceOfPriorScan(boolean) use-resource-of-prior-scan} 483 * property.</p> 484 * 485 * @param newResource the resource to be used for this scan. 486 * 487 * @see #setUseResourceOfPriorScan(boolean) 488 */ 489 public void setResource(Resource newResource) 490 { 491 //No special coding for null parameter 492 resource = newResource; 493 } 494 495 /** 496 * Returns the resource to use for this scan, or <i>null</i> if one cannot 497 * be found. Most clients will first want to call 498 * {@link #getUseResourceOfPriorScan()} and then call this method only if 499 * the value returned by that method is <i>true</i>. 500 * <p> 501 * Note that this method may return a non-null resource even when 502 * <tt>getUseResourceOfPriorScan</tt> returns false.</p> 503 * 504 * @return 505 * the resource to use for this scan, or <i>null</i> if one cannot be found. 506 */ 507 public Resource getResource() 508 { 509 return resource; 510 } 511 512 /** 513 * Returns the antennas selected for a subarray. 514 * @return the antennas selected for a subarray. 515 */ 516 public AntennaSelection getSubarray() 517 { 518 return subarray; 519 } 520 521 /** 522 * Returns a set of antennas to be used as references for this scan. 523 * @return a set of antennas to be used as references for this scan. 524 */ 525 public AntennaSelection getReferenceAntennas() 526 { 527 return referenceAntennas; 528 } 529 530 //---------------------------------------------------------------------------- 531 // Doppler Tracking 532 //---------------------------------------------------------------------------- 533 534 /** 535 * Saves Doppler tracking specifications using the given key. 536 * 537 * @param key 538 * the key for retrieving {@code dopplerInfo} from this scan. 539 * See the description of the {@code key} parameter to the 540 * {@link #getDopplerTracker(String, Date, Frequency)} method 541 * for important information. 542 * 543 * @param dopplerInfo 544 * information regarding doppler tracking for this scan. 545 * 546 * @since 2009-09-10 547 */ 548 public void setDopplerSpecs(String key, ScanDopplerSpecs dopplerInfo) 549 { 550 dtSpecs.put(key, dopplerInfo); 551 } 552 553 /** 554 * Removes the Doppler tracking information stored previously with the 555 * given key. 556 * 557 * @param key 558 * a key used previously in {@link #setDopplerSpecs(String, ScanDopplerSpecs)} 559 * for storing Doppler tracking information. 560 * See the description of the {@code key} parameter to the 561 * {@link #getDopplerTracker(String, Date, Frequency)} method 562 * for important information. 563 * 564 * @since 2009-09-10 565 */ 566 public void removeDopplerSpecs(String key) 567 { 568 dtSpecs.remove(key); 569 } 570 571 /** 572 * Returns the Doppler tracking information associated with {@code key}, 573 * if any. 574 * 575 * @param key 576 * a key used previously in {@link #setDopplerSpecs(String, ScanDopplerSpecs)} 577 * for storing Doppler tracking information. 578 * See the description of the {@code key} parameter to the 579 * {@link #getDopplerTracker(String, Date, Frequency)} method 580 * for important information. 581 * 582 * @return 583 * the Doppler tracking information associated with {@code key}, if any. 584 * If no Doppler information exists for {@code key}, <i>null</i> is returned. 585 * 586 * @since 2008-09-22 587 */ 588 public ScanDopplerSpecs getDopplerSpecs(String key) 589 { 590 return dtSpecs.get(key); 591 } 592 593 /** 594 * Returns a copy of this scan's map of Doppler tracking information. 595 * <p> 596 * The returned map is not reference by this scan, so changes made to 597 * it will <i>not</i> be reflected herein. The returned map might be 598 * empty but will never be <i>null</i>.</p> 599 * 600 * @return 601 * a copy of this scan's map of Doppler tracking information. 602 * 603 * @since 2008-09-22 604 */ 605 public Map<String, ScanDopplerSpecs> getDopplerSpecs() 606 { 607 return new HashMap<String, ScanDopplerSpecs>(dtSpecs); 608 } 609 610 /** 611 * Returns a new Doppler tracker based on the given parameters and the 612 * properties of this scan. 613 * <p> 614 * This method first looks for a <tt>ScanDopplerSpecs</tt> object associated 615 * with {@code key}. If one is found, it is queried for source position and 616 * velocity information. If the found specification is missing position or 617 * velocity information, that information is sought from this scan's source. 618 * If no Doppler specs are found for {@code key}, this method will again 619 * use this scan's source for the needed information. The 620 * <tt>EarthPosition</tt> used in the returned <tt>DopplerTracker</tt> 621 * comes from the telescope used by this scan's <tt>Resource</tt>. 622 * If this scan's resource is <i>null</i>, the <tt>DopplerTracker</tt> 623 * will use a default position.</p> 624 * <p> 625 * It is important to note that this method makes no determination about 626 * whether or not Doppler tracking should be used; that decision is up to 627 * the client. This method will never return a <i>null</i> tracker.</p> 628 * 629 * @param key 630 * a key used previously for 631 * {@link #setDopplerSpecs(String, ScanDopplerSpecs) storing Doppler 632 * specifications}. 633 * At this point the key can be any arbitrary text chosen by clients. 634 * We would like in the future, though, to use the name of a signal 635 * as the key. By "signal" we mean one of the outputs from the 636 * antenna electronics of this scan's resource. If we were to 637 * adopt this convention, we could eliminate the 638 * {@code restFrequency} parameter (see below). 639 * The hardware configuration code is not yet ready for this, 640 * so clients currently have the inconvenience of furnishing 641 * a rest frequency. 642 * 643 * @param dateTime 644 * used to fetch the source from this scan. 645 * If this parameter is <i>null</i> the current system time is used. 646 * 647 * @param restFrequency 648 * an optional parameter that is used only if this method needs to fetch 649 * velocity information from this scan's source. If this scan has 650 * Doppler tracking information for {@code signalName}, and if that 651 * object has velocity information, this parameter will not be used. 652 * <p/> 653 * Ideally this parameter should not be needed, and if we enhance 654 * the <tt>Resource</tt> class or one of its components, we should 655 * be able to eliminate it. If the {@code key} parameter is 656 * truly the name of a signal, this method should be able to talk to 657 * its resource, get the named signal, and fetch the central frequency 658 * from it. 659 * 660 * @return 661 * a new Doppler tracker based on properties of this scan. 662 * 663 * @since 2009-09-10 664 */ 665 public DopplerTracker getDopplerTracker(String key, 666 Date dateTime, 667 Frequency restFrequency) 668 { 669 if (dateTime == null) 670 dateTime = new Date(); 671 672 ScanDopplerSpecs dopplerInfo = dtSpecs.get(key); 673 boolean haveDoppler = (dopplerInfo != null); 674 675 Source src = getSource(dateTime); 676 Subsource ss = (src == null) ? null : src.getCentralSubsource(); 677 SourceVelocity sv = ( ss == null) ? null : ss.getVelocity(restFrequency); 678 679 EarthPosition observer; 680 if (resource != null) 681 observer = resource.getTelescope().getLocation(); 682 else 683 observer = TelescopeType.EVLA.getLocation(); 684 685 SkyPosition srcPos = haveDoppler ? dopplerInfo.getPosition() : null; 686 if (srcPos == null && ss != null) 687 srcPos = ss.getPosition(); 688 689 LinearVelocity srcVel = haveDoppler ? dopplerInfo.getVelocity() : null; 690 if (srcVel == null && sv != null) 691 srcVel = sv.getRadialVelocity(); 692 693 VelocityFrame frame = haveDoppler ? dopplerInfo.getRestFrame() : null; 694 if (frame == null && sv != null) 695 frame = sv.getRestFrame(); 696 697 VelocityConvention velConv = haveDoppler ? dopplerInfo.getVelocityConvention() : null; 698 if (velConv == null && sv != null) 699 velConv = sv.getConvention(); 700 701 //Downstream of the OPT only conventions optical and radio are 702 //supported. For redshift, we will convert Z to km/s and use 703 //optical. What about relativistic? Arbitrarily going w/ radio. 704 // --DMH 2009-02-03 & 2009-02-26, JIRA EVL-794 & 799 705 if (VelocityConvention.REDSHIFT.equals(velConv) || 706 (srcVel != null && srcVel.getUnits().equals(LinearVelocityUnits.Z))) 707 velConv = VelocityConvention.OPTICAL; 708 else if (VelocityConvention.RELATIVISTIC.equals(velConv)) 709 velConv = VelocityConvention.RADIO; 710 711 return new DopplerTracker(observer, srcPos, srcVel, frame, velConv); 712 } 713 714 //---------------------------------------------------------------------------- 715 // Special JAXB code for handling map of Doppler specs 716 //---------------------------------------------------------------------------- 717 718 private static class XmlScanDopSpec extends ScanDopplerSpecs 719 { 720 @XmlAttribute String signalName; 721 722 XmlScanDopSpec() { super(); } 723 724 XmlScanDopSpec(SkyPosition pos, LinearVelocity vel, 725 VelocityFrame frame, VelocityConvention conv) 726 { 727 super(pos, vel, frame, conv); 728 } 729 } 730 731 @XmlElementWrapper(name="dopplerTracking") 732 @XmlElement(name="dopplerSpecs") 733 @SuppressWarnings("unused") 734 private XmlScanDopSpec[] getXmlDopplerSpecs() 735 { 736 int specCount = dtSpecs.size(); 737 738 if (specCount == 0) 739 return null; 740 741 int s = 0; 742 XmlScanDopSpec[] specs = new XmlScanDopSpec[specCount]; 743 744 for (String key : dtSpecs.keySet()) 745 { 746 ScanDopplerSpecs spec = dtSpecs.get(key); 747 748 XmlScanDopSpec xmlSpec = 749 new XmlScanDopSpec(spec.getPosition(), spec.getVelocity(), 750 spec.getRestFrame(), spec.getVelocityConvention()); 751 752 xmlSpec.signalName = key; 753 754 specs[s++] = xmlSpec; 755 } 756 757 return specs; 758 } 759 760 @SuppressWarnings("unused") 761 private void setXmlDopplerSpecs(XmlScanDopSpec[] newSpecs) 762 { 763 dtSpecs.clear(); 764 765 for (XmlScanDopSpec xmlSpec : newSpecs) 766 { 767 ScanDopplerSpecs spec = 768 new ScanDopplerSpecs(xmlSpec.getPosition(), xmlSpec.getVelocity(), 769 xmlSpec.getRestFrame(), xmlSpec.getVelocityConvention()); 770 771 dtSpecs.put(xmlSpec.signalName, spec); 772 } 773 } 774 775 //============================================================================ 776 // SCIENCE 777 //============================================================================ 778 779 /** 780 * Sets the purposes for which this scan is intended. 781 * If {@code replacementSet} is <i>null</i>, it will be intrepreted as 782 * a new, empty, set. 783 * <p> 784 * This scan will hold a reference to {@code replacementSet} 785 * (unless it is <i>null</i>), so any changes made to the set 786 * after calling this method will be reflected in this object.</p> 787 * 788 * @param replacementSet a set of the purposes for which this scan is intended. 789 */ 790 public void setIntents(Set<ScanIntent> replacementSet) 791 { 792 intents = (replacementSet == null) ? new HashSet<ScanIntent>() 793 : replacementSet; 794 } 795 796 /** 797 * Returns a set of the purposes for which this scan is intended. 798 * <p> 799 * The returned set is the actual set held by this scan, 800 * so changes made to the set will be reflected in this 801 * object.</p> 802 * 803 * @return a set of the purposes for which this scan is intended. 804 */ 805 @XmlList 806 public Set<ScanIntent> getIntents() 807 { 808 return intents; 809 } 810 811 /** 812 * Tells this scan whether or not it should apply the most recent set of 813 * phase offsets. 814 * 815 * @param apply <i>true</i> if this scan should apply the most recent set of 816 * phase offsets. 817 */ 818 public void setApplyLastPhase(boolean apply) { applyLastPhase = apply; } 819 820 /** 821 * Returns <i>true</i> if this scan should apply the most recent set of 822 * phase offsets. 823 * 824 * @return <i>true</i> if this scan should apply the most recent set of 825 * phase offsets. 826 */ 827 public boolean getApplyLastPhase() { return applyLastPhase; } 828 829 /** 830 * Tells this scan whether or not is should apply the most recent set of 831 * reference pointing offsets. 832 * 833 * @param apply <i>true</i> if this scan should apply the most recent set of 834 * reference pointing offsets. 835 */ 836 public void setApplyLastReferencePointing(boolean apply) 837 { 838 applyLastReferencePointing = apply; 839 } 840 841 /** 842 * Returns <i>true</i> if this scan should apply the most recent set of 843 * reference pointing offsets. 844 * 845 * @return <i>true</i> if this scan should apply the most recent set of 846 * reference pointing offsets. 847 */ 848 public boolean getApplyLastReferencePointing() 849 { 850 return applyLastReferencePointing; 851 } 852 853 /** 854 * Tells this scan whether or not is should apply the most recent set of 855 * reference focus offsets. 856 * 857 * @param apply <i>true</i> if this scan should apply the most recent set of 858 * reference focus offsets. 859 */ 860 public void setApplyLastReferenceFocus(boolean apply) 861 { 862 applyLastReferenceFocus = apply; 863 } 864 865 /** 866 * Returns <i>true</i> if this scan should apply the most recent set of 867 * reference focus offsets. 868 * 869 * @return <i>true</i> if this scan should apply the most recent set of 870 * reference focus offsets. 871 */ 872 public boolean getApplyLastReferenceFocus() 873 { 874 return applyLastReferenceFocus; 875 } 876 877 /** 878 * Tells this scan whether or not is should apply the most recent set of 879 * reference delays. 880 * 881 * @param apply <i>true</i> if this scan should apply the most recent set of 882 * reference delays. 883 */ 884 public void setApplyLastReferenceDelay(boolean apply) 885 { 886 applyLastReferenceDelay = apply; 887 } 888 889 /** 890 * Returns <i>true</i> if this scan should apply the most recent set of 891 * reference delays. 892 * 893 * @return <i>true</i> if this scan should apply the most recent set of 894 * reference delays. 895 */ 896 public boolean getApplyLastReferenceDelay() 897 { 898 return applyLastReferenceDelay; 899 } 900 901 /** 902 * Indicates whether or not this scan is for solar observing. 903 * 904 * @param solar <i>true</i> if this scan is for solar observing. 905 */ 906 public void setSolarObserving(boolean solar) { solarObserving = solar; } 907 908 /** 909 * Returns <i>true</i> if this scan is for solar observing. 910 * 911 * @return <i>true</i> if this scan is for solar observing. 912 */ 913 public boolean getSolarObserving() { return solarObserving; } 914 915 /** 916 * Indicates whether or not this scan will allow the telescope to tip beyond 917 * the zenith. 918 * 919 * @param allow <i>true</i> if this scan allows the telescope to tip beyond 920 * the zenith. 921 */ 922 public void setAllowOverTheTop(boolean allow) { allowOverTheTop = allow; } 923 924 /** 925 * Returns <i>true</i> if this scan allows the telescope to tip beyond 926 * the zenith. 927 * 928 * @return <i>true</i> if this scan allows the telescope to tip beyond 929 * the zenith. 930 */ 931 public boolean getAllowOverTheTop() { return allowOverTheTop; } 932 933 /** 934 * Sets the antenna wrapping direction for this scan. 935 * 936 * @param wrap the antenna wrapping direction for this scan. 937 */ 938 public void setAntennaWrap(AntennaWrap wrap) 939 { 940 antennaWrap = (wrap == null) ? AntennaWrap.NO_PREFERENCE : wrap; 941 } 942 943 /** 944 * Returns the antenna wrapping direction for this scan. 945 * 946 * @return the antenna wrapping direction for this scan. 947 */ 948 public AntennaWrap getAntennaWrap() { return antennaWrap; } 949 950 //============================================================================ 951 // TEXT 952 //============================================================================ 953 954 /* (non-Javadoc) 955 * @see ScanLoopElement#toSummaryString() 956 */ 957 public String toSummaryString() 958 { 959 StringBuilder buff = new StringBuilder(); 960 961 buff.append("name=").append(getName()); 962 buff.append(", id=").append(getId()); 963 buff.append(", mode=").append(mode); 964 965 return buff.toString(); 966 } 967 968 /** 969 * Creates a new scan from the XML data in the given file. 970 * <p> 971 * Sample usage:<pre> 972 * FocusScan myScan = Scan.fromXml(FocusScan.class, myFile);</pre> 973 * 974 * @param <T> the particular subclass of {@code Scan} returned. 975 * 976 * @param scanType the {@code Class} of an object that extends {@code Scan}. 977 * 978 * @param xmlFile the name of an XML file. This method will attempt to locate 979 * the file by using {@link Class#getResource(String)}. 980 * 981 * @return a new scan from the XML data in the given file. 982 * 983 * @throws FileNotFoundException if the XML file cannot be found. 984 * 985 * @throws JAXBException if the schema file used (if any) is malformed, if 986 * the XML file cannot be read, or if the XML file is not 987 * schema-valid. 988 * 989 * @throws XMLStreamException if there is a problem opening the XML file, 990 * if the XML is not well-formed, or for some other 991 * "unexpected processing conditions". 992 */ 993 public static <T extends Scan> T fromXml(Class<T> scanType, String xmlFile) 994 throws JAXBException, XMLStreamException, FileNotFoundException 995 { 996 T newScan = JaxbUtility.getSharedInstance().xmlFileToObject(xmlFile, scanType); 997 998 newScan.testForResourceFromJaxb(); 999 1000 return newScan; 1001 } 1002 1003 1004 /** 1005 * Creates a new scan based on the XML data read from {@code reader}. 1006 * <p> 1007 * Sample usage:<pre> 1008 * DelayScan myScan = Scan.fromXml(DelayScan.class, myReader);</pre> 1009 * 1010 * @param <T> the particular subclass of {@code Scan} returned. 1011 * 1012 * @param scanType the {@code Class} of an object that extends {@code Scan}. 1013 * 1014 * @param reader the source of the XML data. 1015 * If this value is <i>null</i>, <i>null</i> is returned. 1016 * 1017 * @return a new scan based on the XML data read from {@code reader}. 1018 * 1019 * @throws XMLStreamException if the XML is not well-formed, 1020 * or for some other "unexpected processing conditions". 1021 * 1022 * @throws JAXBException if anything else goes wrong during the 1023 * transformation. 1024 */ 1025 public static <T extends Scan> T fromXml(Class<T> scanType, Reader reader) 1026 throws JAXBException, XMLStreamException 1027 { 1028 T newScan = JaxbUtility.getSharedInstance() 1029 .readObjectAsXmlFrom(reader, scanType, null); 1030 1031 newScan.testForResourceFromJaxb(); 1032 1033 return newScan; 1034 } 1035 1036 /** 1037 * Meant for use by containers of scans; most clients should not use this method. 1038 * @throws JAXBException 1039 * if the scan has no resource and the useResourceOfPriorScan flag is false. 1040 */ 1041 void testForResourceFromJaxb() throws JAXBException 1042 { 1043 if (!useResourceOfPriorScan && resource == null) 1044 throw new JAXBException( 1045 "When useResourceOfPriorScan is false, the resource element must be provided. Scan "+ 1046 getName()+"."); 1047 } 1048 1049 //============================================================================ 1050 // PERSISTENCE HELPERS 1051 //============================================================================ 1052 1053 //This pair of methods converts Set<ScanIntent> to/from single string 1054 @SuppressWarnings("unused") 1055 private String getPersistentIntents() 1056 { 1057 Collection<String> strings = new HashSet<String>(); 1058 1059 for (ScanIntent intent : intents) 1060 strings.add(intent.name()); 1061 1062 return StringUtil.getInstance().fromCollection(strings, " | "); 1063 } 1064 1065 @SuppressWarnings("unused") 1066 private void setPersistentIntents(String text) 1067 { 1068 intents.clear(); 1069 1070 if (text != null) 1071 { 1072 Collection<String> intentNames = 1073 StringUtil.getInstance().toCollection(text, " | ", null); 1074 1075 for (String intentName : intentNames) 1076 intents.add(ScanIntent.fromString(intentName)); 1077 } 1078 } 1079 1080 //============================================================================ 1081 // 1082 //============================================================================ 1083 1084 /** 1085 * Returns a scan that is a copy of this one. 1086 * <p> 1087 * The returned scan is, for the most part, a deep copy of this one. 1088 * However, there are a few exceptions: 1089 * <ol> 1090 * <li>The ID will be set to 1091 * {@link Identifiable#UNIDENTIFIED}.</li> 1092 * <li>The schedulingBlock will be <i>null</i>.</li> 1093 * <li>The createdOn and lastUpdatedOn attributes will be set to the 1094 * current system time.</li> 1095 * </ol></p> 1096 * <p> 1097 * If anything goes wrong during the cloning procedure, 1098 * a {@code RuntimeException} will be thrown.</p> 1099 */ 1100 public Scan clone() 1101 { 1102 Scan clone = null; 1103 1104 try 1105 { 1106 //This line takes care of the primitive & immutable fields properly 1107 clone = (Scan)super.clone(); 1108 1109 clone.timeSpec = this.timeSpec.clone(); 1110 1111 //Need to clone set. 1112 //Do not need to clone elements because they are immutable. 1113 clone.intents = new HashSet<ScanIntent>(); 1114 clone.intents.addAll(this.intents); 1115 1116 clone.referenceAntennas = this.referenceAntennas.clone(); 1117 clone.subarray = this.subarray.clone(); 1118 1119 if (this.source != null) 1120 clone.source = this.source.clone(); 1121 1122 if (this.sourceLookupTable != null) 1123 clone.sourceLookupTable = this.sourceLookupTable.clone(); 1124 1125 if (this.resource != null) 1126 clone.resource = this.resource.clone(); 1127 1128 //Clone map and each of its elements 1129 clone.dtSpecs = new HashMap<String, ScanDopplerSpecs>(); 1130 for (String key : this.dtSpecs.keySet()) 1131 clone.dtSpecs.put(key, this.dtSpecs.get(key).clone()); 1132 } 1133 catch (Exception ex) 1134 { 1135 throw new RuntimeException(ex); 1136 } 1137 1138 return clone; 1139 } 1140 1141 /** 1142 * Returns <i>true</i> if {@code o} is equal to this scan. 1143 * <p> 1144 * In order to be equal to this element, {@code o} must be non-null and 1145 * of the same class as this element. Equality is determined by examining 1146 * the equality of corresponding attributes, with the following exceptions, 1147 * which are ignored when assessing equality: 1148 * <ol> 1149 * <li>id</li> 1150 * <li>schedulingBlock</li> 1151 * <li>createdOn</li> 1152 * <li>createdBy</li> 1153 * <li>lastUpdatedOn</li> 1154 * <li>lastUpdatedBy</li> 1155 * </ol></p> 1156 */ 1157 public boolean equals(Object o) 1158 { 1159 //Quick exit if o is this 1160 if (o == this) 1161 return true; 1162 1163 //Quick exit if parent class says objects not equal 1164 if (!super.equals(o)) 1165 return false; 1166 1167 //A safe cast because super class ensures classes are same 1168 Scan other = (Scan)o; 1169 1170 //Compare most important attributes 1171 if (!other.mode.equals(this.mode) || 1172 !other.intents.equals(this.intents)) 1173 return false; 1174 1175 //Compare timing info 1176 if (!other.timeSpec.equals(this.timeSpec)) 1177 return false; 1178 1179 //Compare source & resource 1180 if (!objectsAreEqual(this.source, other.source)) 1181 return false; 1182 1183 if (!objectsAreEqual(this.sourceLookupTable, other.sourceLookupTable)) 1184 return false; 1185 1186 //When a scan is using resource of previous scan, we will NOT compare 1187 //the cached resources that this and other scan may or may not have. 1188 if (other.useResourceOfPriorScan) 1189 { 1190 if (!this.useResourceOfPriorScan) 1191 return false; //not equal if one uses prior & one does not 1192 } 1193 else //other is using its own resource 1194 { 1195 if (this.useResourceOfPriorScan) 1196 return false; //not equal if one uses prior & one does not 1197 1198 //Neither this nor other is using rsrc of prev scan; compare their resources 1199 if (!objectsAreEqual(this.resource, other.resource)) 1200 return false; 1201 } 1202 1203 //Compare remaining attributes 1204 if (!other.referenceAntennas.equals(this.referenceAntennas) || 1205 !other.subarray.equals(this.subarray) || 1206 other.applyLastPhase != this.applyLastPhase || 1207 other.applyLastReferencePointing != this.applyLastReferencePointing || 1208 other.applyLastReferenceFocus != this.applyLastReferenceFocus || 1209 other.applyLastReferenceDelay != this.applyLastReferenceDelay || 1210 other.solarObserving != this.solarObserving || 1211 other.allowOverTheTop != this.allowOverTheTop || 1212 !other.antennaWrap.equals(this.antennaWrap) || 1213 !other.dtSpecs.equals(this.dtSpecs)) 1214 return false; 1215 1216 //No differences found 1217 return true; 1218 } 1219 1220 private boolean objectsAreEqual(Object thisOne, Object thatOne) 1221 { 1222 return (thisOne == null) ? (thatOne == null) : thisOne.equals(thatOne); 1223 } 1224 1225 /* (non-Javadoc) 1226 * @see ScanLoopElement#hashCode() 1227 */ 1228 public int hashCode() 1229 { 1230 //Taken from the Effective Java book by Joshua Bloch. 1231 //The constants 17 & 37 are arbitrary & carry no meaning. 1232 int result = super.hashCode(); 1233 1234 //You MUST keep this method in sync w/ the equals method 1235 1236 result = 37 * result + mode.hashCode(); 1237 result = 37 * result + intents.hashCode(); 1238 1239 result = 37 * result + timeSpec.hashCode(); 1240 1241 if (source != null) 1242 result = 37 * result + source.hashCode(); 1243 1244 if (sourceLookupTable != null) 1245 result = 37 * result + sourceLookupTable.hashCode(); 1246 1247 if (useResourceOfPriorScan) 1248 result = 37 * result + Boolean.TRUE.hashCode(); 1249 else if (resource != null) 1250 result = 37 * result + resource.hashCode(); 1251 1252 result = 37 * result + referenceAntennas.hashCode(); 1253 result = 37 * result + subarray.hashCode(); 1254 result = 37 * result + Boolean.valueOf(applyLastPhase).hashCode(); 1255 result = 37 * result + Boolean.valueOf(applyLastReferencePointing).hashCode(); 1256 result = 37 * result + Boolean.valueOf(applyLastReferenceFocus).hashCode(); 1257 result = 37 * result + Boolean.valueOf(applyLastReferenceDelay).hashCode(); 1258 result = 37 * result + Boolean.valueOf(solarObserving).hashCode(); 1259 result = 37 * result + Boolean.valueOf(allowOverTheTop).hashCode(); 1260 result = 37 * result + antennaWrap.hashCode(); 1261 result = 37 * result + dtSpecs.hashCode(); 1262 1263 return result; 1264 } 1265 1266 //============================================================================ 1267 // 1268 //============================================================================ 1269 1270 //This is here for quick manual testing 1271 /* 1272 public static void main(String[] args) 1273 { 1274 ScanBuilder builder = new ScanBuilder(); 1275 builder.setIdentifiers(true); 1276 1277 Scan scan = builder.makeScan(); 1278 1279 try { 1280 System.out.println(scan.toXml()); 1281 } 1282 catch (JAXBException ex) { 1283 System.out.println("Trouble w/ Scan.toXml. Msg:"); 1284 System.out.println(ex.getMessage()); 1285 ex.printStackTrace(); 1286 1287 System.out.println("Attempting to write XML w/out schema verification:"); 1288 JaxbUtility.getSharedInstance().setLookForDefaultSchema(false); 1289 try 1290 { 1291 System.out.println(scan.toXml()); 1292 } 1293 catch (JAXBException ex2) 1294 { 1295 System.out.println("Still had trouble w/ Scan.toXml. Msg:"); 1296 System.out.println(ex.getMessage()); 1297 ex.printStackTrace(); 1298 } 1299 } 1300 } 1301 */ 1302 /* 1303 public static void main(String[] args) 1304 { 1305 ValidationManager mgr = new ValidationManager(); 1306 1307 ScanBuilder builder = new ScanBuilder(); 1308 SwitchingScan scan = builder.makeSwitchingScanFor(new ScanLoop()); 1309 1310 Validator val = mgr.getValidator(scan.getClass()); 1311 System.out.println(val.getClass().getName()); 1312 1313 //scan.getSwitchSettings().clear(); 1314 //scan.getSwitchSettings().add(new SwitchSetting()); 1315 1316 List<ValidationFailure> failures = 1317 val.validate(scan, ValidationPurpose.CERTIFY_READY_TO_USE); 1318 1319 for (ValidationFailure failure : failures) 1320 { 1321 System.out.println(failure.getDisplayMessage()); 1322 } 1323 } 1324 */ 1325 }