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.SortedMap; 007 import java.util.SortedSet; 008 import java.util.TreeMap; 009 import java.util.TreeSet; 010 011 import javax.xml.bind.JAXBException; 012 import javax.xml.bind.annotation.XmlList; 013 import javax.xml.bind.annotation.XmlTransient; 014 import javax.xml.bind.annotation.XmlType; 015 import javax.xml.stream.XMLStreamException; 016 017 import edu.nrao.sss.astronomy.StokesParameter; 018 import edu.nrao.sss.measure.Frequency; 019 import edu.nrao.sss.measure.FrequencyRange; 020 import edu.nrao.sss.measure.TimeDuration; 021 import edu.nrao.sss.measure.TimeUnits; 022 import edu.nrao.sss.util.Identifiable; 023 import edu.nrao.sss.util.JaxbUtility; 024 import edu.nrao.sss.util.StringUtil; 025 026 /** 027 * A specification for observing a portion of the electromagnetic spectrum. 028 * Instances of this class are used to help choose and configure instrumentation 029 * that can obtain data that conform to the encapsulated specifications. 030 * <p> 031 * <b>Version Info:</b> 032 * <table style="margin-left:2em"> 033 * <tr><td>$Revision: 1709 $</td></tr> 034 * <tr><td>$Date: 2008-11-14 11:22:37 -0700 (Fri, 14 Nov 2008) $</td></tr> 035 * <tr><td>$Author: dharland $</td></tr> 036 * </table></p> 037 * 038 * @author David M. Harland 039 * @since 2006-09-12 040 */ 041 @XmlType(propOrder={"frequencyRange", "frequencyResolution", "timeResolution", 042 "polarizationProducts"}) 043 public class SkyFrequencySpecification 044 implements Cloneable 045 { 046 //This ID field is here for the persistance layer 047 @SuppressWarnings("unused") 048 private Long id; 049 050 private FrequencyRange frequencyRange; 051 private Frequency frequencyResolution; 052 private TimeDuration timeResolution; 053 private SortedSet<StokesParameter> polarizations; 054 private SortedMap<String, String> additionalSpecifications; 055 056 /** Creates a new instance. */ 057 public SkyFrequencySpecification() 058 { 059 id = Identifiable.UNIDENTIFIED; 060 061 frequencyRange = null; 062 frequencyResolution = new Frequency(); 063 timeResolution = new TimeDuration("0.0", TimeUnits.MILLISECOND); 064 065 polarizations = new TreeSet<StokesParameter>(); 066 additionalSpecifications = new TreeMap<String, String>(); 067 } 068 069 /** Creates a new instance using the given range. */ 070 public SkyFrequencySpecification(FrequencyRange skyFrequencyRange) 071 { 072 this(); 073 setFrequencyRange(skyFrequencyRange); 074 } 075 076 /** 077 * Resets the identifier of this specification 078 * to a value that represents the unidentified state. 079 * <p> 080 * This method is useful for preparing a specification for storage in a 081 * database. 082 * The ID property (as of now, though this may change in the future) is 083 * used by our persistence mechanism to identify objects. If you are 084 * persisting this object for the first time, you may need to call 085 * this method before performing a save. This is especially true if 086 * you have created this object from XML, as the XML unmarshalling 087 * brings along the ID property.</p> 088 */ 089 void clearId() 090 { 091 this.id = Identifiable.UNIDENTIFIED; 092 } 093 094 //============================================================================ 095 // 096 //============================================================================ 097 098 /** 099 * Sets the range of sky frequencies to use for this specification. 100 * <p> 101 * Unless {@code newRange} is <i>null</i>, 102 * this object will hold a reference to the parameter, 103 * so any changes made to it will be reflected in this object. 104 * A value of <i>null</i> will be interpreted as a range that 105 * contains all positive frequencies.</p> 106 * 107 * @param newRange the range of sky frequencies to use for this specification. 108 */ 109 public void setFrequencyRange(FrequencyRange newRange) 110 { 111 frequencyRange = (newRange == null) ? new FrequencyRange() : newRange; 112 } 113 114 /** 115 * Returns the range of sky frequencies to use for this specification. 116 * <p> 117 * Note that the returned range is the one held by this specification, 118 * so any changes made to it will be reflected in this object. 119 * This value is guaranteed to be non-null.</p> 120 * 121 * @return the range of sky frequencies to use for this specification. 122 */ 123 public FrequencyRange getFrequencyRange() 124 { 125 return frequencyRange; 126 } 127 128 /** 129 * Sets the width of a single sky frequency channel. 130 * <p> 131 * Unless {@code width} is <i>null</i>, 132 * this object will hold a reference to the parameter, 133 * so any changes made to it will be reflected in this object. 134 * A value of <i>null</i> will be interpreted as a frequency 135 * of zero gigahertz.</p> 136 * 137 * @param width the width of a single sky frequency channel. 138 */ 139 public void setFrequencyResolution(Frequency width) 140 { 141 frequencyResolution = (width == null) ? new Frequency() : width; 142 } 143 144 /** 145 * Returns the width of a single sky frequency channel. 146 * <p> 147 * Note that the returned width is the one held by this specification, 148 * so any changes made to it will be reflected in this object. 149 * This value is guaranteed to be non-null.</p> 150 * 151 * @return the width of a single sky frequency channel. 152 */ 153 public Frequency getFrequencyResolution() 154 { 155 return frequencyResolution; 156 } 157 158 /** 159 * Returns the number of whole spectral channels in this specification. 160 * The number of channels is the bandwidth divided by frequency 161 * resolution. The amount returned is the integer portion of this division. 162 * That is, the floating point result is "floored" down to the greatest 163 * integer that is less than or equal to the floating point result. 164 * 165 * @return the number of whole spectral channels in this specification. 166 */ 167 public long getNumberOfSpectralChannels() 168 { 169 return 170 getFrequencyRange().getWidth().toUnits(frequencyResolution.getUnits()) 171 .divideToIntegralValue(frequencyResolution.getValue()) 172 .longValue(); 173 } 174 175 //============================================================================ 176 // 177 //============================================================================ 178 179 /** 180 * Sets the time resolution of this specification. 181 * <p> 182 * Unless {@code resolution} is <i>null</i>, 183 * this object will hold a reference to the parameter, 184 * so any changes made to it will be reflected in this object. 185 * A value of <i>null</i> will be interpreted as a duration 186 * of zero milliseconds.</p> 187 * 188 * @param resolution the time resolution of this specification. 189 */ 190 public void setTimeResolution(TimeDuration resolution) 191 { 192 timeResolution = 193 (resolution == null) ? new TimeDuration("0.0", TimeUnits.MILLISECOND) 194 : resolution; 195 } 196 197 /** 198 * Returns the time resolution of this specification. 199 * <p> 200 * Note that the returned duration is the one held by this specification, 201 * so any changes made to it will be reflected in this object. 202 * This value is guaranteed to be non-null.</p> 203 * 204 * @return the time resolution of this specification. 205 */ 206 public TimeDuration getTimeResolution() 207 { 208 return timeResolution; 209 } 210 211 //============================================================================ 212 // 213 //============================================================================ 214 215 /** 216 * Sets the polarization products requested by this specification. 217 * <p> 218 * Unless {@code newSet} is <i>null</i>, 219 * this object will hold a reference to the parameter, 220 * so any changes made to it will be reflected in this object. 221 * A value of <i>null</i> will be interpreted as a new empty set.</p> 222 * 223 * @param newSet the polarizations requested by this specification. 224 * If this value is <i>null</i>, it will be interpreted 225 * as an empty set. 226 */ 227 public void setPolarizationProducts(SortedSet<StokesParameter> newSet) 228 { 229 polarizations = (newSet == null) ? new TreeSet<StokesParameter>() 230 : newSet; 231 } 232 233 /** 234 * Returns the polarization products requested by this specification. 235 * <p> 236 * Note that the returned set is the one held by this specification, 237 * so any changes made to it will be reflected in this object. 238 * This set is guaranteed to be non-null, but it may be empty.</p> 239 * 240 * @return the polarizations product requested by this specification. 241 */ 242 @XmlList 243 public SortedSet<StokesParameter> getPolarizationProducts() 244 { 245 return polarizations; 246 } 247 248 //============================================================================ 249 // 250 //============================================================================ 251 252 /** 253 * Sets a map of key/value pairs that hold additional specifications. 254 * <p> 255 * Unless {@code newMap} is <i>null</i>, 256 * this object will hold a reference to the parameter, 257 * so any changes made to it will be reflected in this object. 258 * A value of <i>null</i> will be interpreted as a new empty map.</p> 259 * <p> 260 * See {@link #getAdditionalSpecifications()} for more information.</p> 261 * 262 * @param newMap a map of key/value pairs that hold additional specifications. 263 */ 264 public void setAdditionalSpecifications(SortedMap<String, String> newMap) 265 { 266 additionalSpecifications = (newMap == null) ? new TreeMap<String, String>() 267 : newMap; 268 } 269 270 /** 271 * Returns any additional specifications for this specification. 272 * <p> 273 * "Additional specifications" are key/value pairs that are typically 274 * targeted at particular pieces of hardware. For example, the WIDAR 275 * correlator has a concept called "recirculation", so it could define 276 * the keyword <i>recirculationFactor</i> that could be given a value 277 * here and passed on to the WIDAR correlator. If, however, this 278 * specification is passed to different hardware, then the fact that 279 * this specification is carrying a <i>recirculationFactor</i> value 280 * will be irrelevant (unless we have namespace collissions -- may want 281 * to prefix all keywords with hardware name).</p> 282 * <p> 283 * Note that the returned map is the one held by this specification, 284 * so any changes made to it will be reflected in this object. 285 * This set is guaranteed to be non-null, but it may be empty.</p> 286 * 287 * @return any additional specifications for this specification. 288 */ 289 //TODO allow into XML 290 @XmlTransient 291 public SortedMap<String, String> getAdditionalSpecifications() 292 { 293 return additionalSpecifications; 294 } 295 296 //============================================================================ 297 // TEXT 298 //============================================================================ 299 300 /** 301 * Returns a text representation of this specification. 302 * @return a text representation of this specification. 303 */ 304 public String toString() 305 { 306 StringBuilder buff = new StringBuilder(); 307 308 final String EOL = StringUtil.EOL; 309 310 buff.append("Frequency Range: ").append(frequencyRange).append(EOL); 311 buff.append("Frequency Res'n: ").append(frequencyResolution).append(EOL); 312 buff.append("Time Resolution: ").append(timeResolution).append(EOL); 313 buff.append("Polarizations: ").append(polarizations).append(EOL); 314 buff.append("Additional Specs: ").append(additionalSpecifications).append(EOL); 315 316 return buff.toString(); 317 } 318 319 /** 320 * Returns an XML representation of this specification. 321 * @return an XML representation of this specification. 322 * @throws JAXBException if anything goes wrong during the conversion to XML. 323 * @see #writeAsXmlTo(Writer) 324 */ 325 public String toXml() throws JAXBException 326 { 327 return JaxbUtility.getSharedInstance().objectToXmlString(this); 328 } 329 330 /** 331 * Writes an XML representation of this specification to {@code writer}. 332 * @param writer the device to which XML is written. 333 * @throws JAXBException if anything goes wrong during the conversion to XML. 334 */ 335 public void writeAsXmlTo(Writer writer) throws JAXBException 336 { 337 JaxbUtility.getSharedInstance().writeObjectAsXmlTo(writer, this, null); 338 } 339 340 /** 341 * Creates a new specification from the XML data in the given file. 342 * 343 * @param xmlFile the name of an XML file. This method will attempt to locate 344 * the file by using {@link Class#getResource(String)}. 345 * 346 * @return a new specification from the XML data in the given file. 347 * 348 * @throws FileNotFoundException if the XML file cannot be found. 349 * 350 * @throws JAXBException if the schema file used (if any) is malformed, if 351 * the XML file cannot be read, or if the XML file is not 352 * schema-valid. 353 * 354 * @throws XMLStreamException if there is a problem opening the XML file, 355 * if the XML is not well-formed, or for some other 356 * "unexpected processing conditions". 357 */ 358 public static SkyFrequencySpecification fromXml(String xmlFile) 359 throws JAXBException, XMLStreamException, FileNotFoundException 360 { 361 return JaxbUtility.getSharedInstance() 362 .xmlFileToObject(xmlFile, SkyFrequencySpecification.class); 363 } 364 365 /** 366 * Creates a new specification based on the XML data read from {@code reader}. 367 * 368 * @param reader the specification of the XML data. 369 * If this value is <i>null</i>, <i>null</i> is returned. 370 * 371 * @return a new specification based on the XML data read from {@code reader}. 372 * 373 * @throws XMLStreamException if the XML is not well-formed, 374 * or for some other "unexpected processing conditions". 375 * 376 * @throws JAXBException if anything else goes wrong during the 377 * transformation. 378 */ 379 public static SkyFrequencySpecification fromXml(Reader reader) 380 throws JAXBException, XMLStreamException 381 { 382 return JaxbUtility.getSharedInstance() 383 .readObjectAsXmlFrom(reader, 384 SkyFrequencySpecification.class, null); 385 } 386 387 //============================================================================ 388 // 389 //============================================================================ 390 391 /** 392 * Returns a specification that is a copy of this one. 393 * <p> 394 * If anything goes wrong during the cloning procedure, 395 * a {@code RuntimeException} will be thrown.</p> 396 */ 397 @Override 398 public SkyFrequencySpecification clone() 399 { 400 SkyFrequencySpecification clone = null; 401 402 try 403 { 404 clone = (SkyFrequencySpecification)super.clone(); 405 406 //We do NOT want the clone to have the same ID as the original. 407 //The ID is here for the persistence layer; it is in charge of 408 //setting IDs. To help it, we put the clone's ID in the uninitialized 409 //state. 410 clone.id = Identifiable.UNIDENTIFIED; 411 412 clone.frequencyRange = this.frequencyRange.clone(); 413 clone.frequencyResolution = this.frequencyResolution.clone(); 414 clone.timeResolution = this.timeResolution.clone(); 415 416 //These are collections of immutables, so we need clone only the collection 417 //variables, not the contents therein. 418 clone.polarizations = new TreeSet<StokesParameter>(this.polarizations); 419 420 clone.additionalSpecifications = 421 new TreeMap<String, String>(this.additionalSpecifications); 422 } 423 catch (Exception ex) 424 { 425 throw new RuntimeException(ex); 426 } 427 428 return clone; 429 } 430 431 /** Returns <i>true</i> if {@code o} is equal to this specification. */ 432 @Override 433 public boolean equals(Object o) 434 { 435 //Quick exit if o is this 436 if (o == this) 437 return true; 438 439 //Quick exit if o is null 440 if (o == null) 441 return false; 442 443 //Quick exit if classes are different 444 if (!o.getClass().equals(this.getClass())) 445 return false; 446 447 SkyFrequencySpecification other = (SkyFrequencySpecification)o; 448 449 //NOTE: absence of ID field is intentional 450 451 return other.frequencyRange.equals(this.frequencyRange) && 452 other.frequencyResolution.equals(this.frequencyResolution) && 453 other.timeResolution.equals(this.timeResolution) && 454 other.polarizations.equals(this.polarizations) && 455 other.additionalSpecifications.equals(this.additionalSpecifications); 456 457 //Note the comparison of the polarizations Set is OK because 458 //the elements are immutable. 459 } 460 461 /** Returns a hash code value for this specification. */ 462 @Override 463 public int hashCode() 464 { 465 //Taken from the Effective Java book by Joshua Bloch. 466 //The constants 17 & 37 are arbitrary & carry no meaning. 467 int result = 17; 468 469 result = 37 * result + frequencyRange.hashCode(); 470 result = 37 * result + frequencyResolution.hashCode(); 471 result = 37 * result + timeResolution.hashCode(); 472 result = 37 * result + polarizations.hashCode(); 473 result = 37 * result + additionalSpecifications.hashCode(); 474 475 return result; 476 } 477 478 //============================================================================ 479 // 480 //============================================================================ 481 /* 482 public static void main(String[] args) throws Exception 483 { 484 ResourceBuilder builder = new ResourceBuilder(); 485 SkyFrequencySpecification spec = builder.makeSkyFrequencySpecification(); 486 487 System.out.println(spec); 488 } 489 */ 490 }