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.HashMap;
007    import java.util.HashSet;
008    import java.util.Map;
009    import java.util.Set;
010    import java.util.SortedMap;
011    import java.util.TreeMap;
012    
013    import javax.xml.bind.JAXBException;
014    import javax.xml.bind.annotation.XmlAttribute;
015    import javax.xml.bind.annotation.XmlElement;
016    import javax.xml.bind.annotation.XmlList;
017    import javax.xml.bind.annotation.XmlRootElement;
018    import javax.xml.stream.XMLStreamException;
019    
020    import edu.nrao.sss.electronics.SignalTransferSwitch;
021    import edu.nrao.sss.measure.Frequency;
022    import edu.nrao.sss.util.JaxbUtility;
023    
024    /**
025     * A generalized representation of the configuration of antenna electronics.
026     * Configuring the electronics system of an antenna usually means
027     * setting switch positions, tuning local oscillators, and setting
028     * the number of bits per sample for the output signals.
029     * <p>
030     * This class is meant to be able to represent the configuration information
031     * for a wide variety of antenna types.  Instances of this class may be
032     * obtained from the {@link AntennaElectronics} class, which can return
033     * its current configuration in this form.  Clients that will merely query
034     * a configuration need have no special knowledge of the specific
035     * electronics with which they are dealing.  Clients that need to
036     * alter a configuration, however, will likely need to know something
037     * about the targeted electronics and about the conventions suggested
038     * by this class.</p>
039     * <h3 style="font-variant:small-caps">
040     * <u>Suggested Conventions for Users of this Class</u></h3>
041     * <div style="margin-left:1.5em; margin-right:2em; text-align:justify;">
042     * <p>
043     * In order to remain a fairly generic piece of code, this class makes
044     * heavy use of maps of name-value pairs.  The name is always a string
045     * and is meant to be the name of a device, such as a local oscillator
046     * or switch.  Concrete implementations of {@code AntennaElectronics}
047     * are expected to name all those LOs and switches that can be configured
048     * by an external user via this class.  The value portion of a name-value
049     * pair varies depending on the device involved.  Each device type will be
050     * covered below.  First, though, we suggest a convention for embedded
051     * devices.</p>
052     * <p>
053     * <i><b>Convention for Naming Devices Held in Other Devices</b></i><br/>
054     * Sometimes a device such as a switch is held in an internal component
055     * of the overall antenna electronics.  An internal component might be
056     * an up-converter, a down-converter, or some other device.  In order to
057     * distinguish devices held directly by {@code AntennaElectronics} from
058     * those it holds indirectly via its internal components, we suggest
059     * using dot notation.  For example, if the electronics wishes to
060     * expose a switch named <i>output</i> in its internal component named
061     * <i>downConverterX</i>, the name it should use when populating
062     * this class is <i>downConverterX.output</i>.</p>
063     * <p>
064     * <i><b>Value Objects for Local Oscillator Settings</b></i><br/>
065     * The {@link #getLoTunings()} method returns a map whose keys are
066     * the names of local oscillators.  The values in the maps are the
067     * tuning frequencies for those local oscillators.</p>
068     * <p>
069     * <a name="conventionSwitchSettings"/>
070     * <i><b>Value Objects for Switch Settings</b></i><br/>
071     * The {@link #getSwitchPositions()} method returns a map whose keys
072     * are the names of switches.  The values are themselves maps that
073     * give the active input and active output poles for the switch.
074     * The value map will either be empty, have one entry, or have two entries.
075     * It will never be <i>null</i>. 
076     * The two valid keys for the value map are <tt>input</tt> and
077     * <tt>output</tt>, and the values of these keys represent the
078     * selection of a given input or output pole as the <i>active</i>
079     * input or output pole of that switch.</p>
080     * <p>
081     * <i><b>Value Objects for Transfer Switch Settings</b></i><br/>
082     * The {@link #getTransferSwitchPositions()} method returns a map whose keys
083     * are the names of {@link SignalTransferSwitch transfer switches}.
084     * The values in the maps are also strings and the convention is to use
085     * one of these two values: <i>clockwise</i> or <i>counterclockwise</i>.
086     * Also by convention, these values are <i>not</i> case sensitive.</p>
087     * <p>
088     * <i><b>Value Objects for Bits per Digital Samples</b></i><br/>
089     * The {@link #getBitsPerSample()} method returns a map whose keys are
090     * the names of digital samplers.  The values in the maps are the
091     * number of bits per sample.</p>
092     * </div>
093     * <p>
094     * <b>Version Info:</b>
095     * <table style="margin-left:2em">
096     *   <tr><td>$Revision: 1709 $</td></tr>
097     *   <tr><td>$Date: 2008-11-14 11:22:37 -0700 (Fri, 14 Nov 2008) $</td></tr>
098     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
099     * </table></p>
100     * 
101     * @author David M. Harland
102     * @since 2008-01-09
103     */
104    @XmlRootElement
105    public class AntennaElectronicsConfiguration
106      implements Cloneable
107    {
108      @XmlElement private TelescopeType telescope;
109      
110      private Set<ReceiverBand> bands;
111      
112      private SortedMap<String, Frequency>           loTunings;
113      private SortedMap<String, Map<String, String>> switchPositions;
114      private SortedMap<String, String>              transferSwitchPositions;
115      private SortedMap<String, Integer>             bitsPerSample;
116      
117      /**
118       * Creates a new empty configuration for the given telescope.
119       */
120      public AntennaElectronicsConfiguration(TelescopeType telescope)
121      {
122        this();
123        this.telescope = telescope;
124      }
125      
126      /**
127       * This no-arg constructor is here primarily for use by persistence
128       * mechanisms such as JAXB and Hibernate.
129       */
130      private AntennaElectronicsConfiguration()
131      {
132        bands                   = new HashSet<ReceiverBand>();
133        loTunings               = new TreeMap<String, Frequency>();
134        switchPositions         = new TreeMap<String, Map<String, String>>();
135        transferSwitchPositions = new TreeMap<String, String>();
136        bitsPerSample           = new TreeMap<String, Integer>();
137      }
138      
139      /**
140       * Returns the telescope for which this configuration is intended.
141       * @return the telescope for which this configuration is intended.
142       */
143      public TelescopeType getTelescope()
144      {
145        return telescope;
146      }
147      
148      /**
149       * Returns the receiver bands for this configuration.
150       * <p>
151       * The returned set is the one held internally by this configuration,
152       * so changes made to it will have an effect on this object.
153       * The returned set is guaranteed to be non-<i>null</i>, but it
154       * may be empty.  The most common situation is for there to be
155       * one element in the returned set.</p>
156       * 
157       * @return the receiver bands for this configuration.
158       */
159      @XmlList
160      public Set<ReceiverBand> getBands()
161      {
162        return bands;
163      }
164      //For JAXB
165      @SuppressWarnings("unused")
166      private void setBands(Set<ReceiverBand> replacementSet)
167      {
168        if (replacementSet != null)
169          bands = replacementSet;
170        else //null set -- use empty set instead
171          bands.clear();
172      }
173      
174      /**
175       * Returns a map of local oscillator tunings.
176       * The keys are the names of local oscillators and the values
177       * are their tunings.
178       * <p>
179       * The returned map is the one held internally by this configuration,
180       * so changes made to it will have an effect on this object.
181       * The returned map is guaranteed to be non-<i>null</i>, but it
182       * may be empty.</p>
183       * 
184       * @return a map of local oscillator tunings.
185       */
186      public SortedMap<String, Frequency> getLoTunings()
187      {
188        return loTunings;
189      }
190      
191      /**
192       * Returns a map of switch positions.
193       * The keys are the names of switches and the values are themselves maps.
194       * The value map will either be empty, hold one entry, or hold two entries.
195       * It will never be <i>null</i>.
196       * The two valid keys for the value map are <tt>input</tt> and
197       * <tt>output</tt>, and the values of these keys represent the
198       * selection of a given input or output pole as the <i>active</i>
199       * input or output pole of that switch.
200       * <p>
201       * The returned map is the one held internally by this configuration,
202       * so changes made to it will have an effect on this object.
203       * The returned map is guaranteed to be non-<i>null</i>, but it
204       * may be empty.</p>
205       * 
206       * @return a map of switch positions.
207       */
208      public SortedMap<String, Map<String, String>> getSwitchPositions()
209      {
210        return switchPositions;
211      }
212      
213      /**
214       * Returns a map of {@link SignalTransferSwitch transfer switch} positions.
215       * The keys are the names of switches and the values
216       * are their settings.  A setting may be either the string
217       * <i>clockwise</i> or the string <i>counterclockwise</i>.
218       * Implementations of {@link AntennaElectronics} are to treat these values
219       * as case insensitive.
220       * <p>
221       * The returned map is the one held internally by this configuration,
222       * so changes made to it will have an effect on this object.
223       * The returned map is guaranteed to be non-<i>null</i>, but it
224       * may be empty.</p>
225       * 
226       * @return a map of transfer switch positions.
227       */
228      public SortedMap<String, String> getTransferSwitchPositions()
229      {
230        return transferSwitchPositions;
231      }
232      
233      /**
234       * Returns a map of digital sampler settings.
235       * The keys are the names of digital samplers and the values
236       * are the number of bits per sample
237       * <p>
238       * The returned map is the one held internally by this configuration,
239       * so changes made to it will have an effect on this object.
240       * The returned map is guaranteed to be non-<i>null</i>, but it
241       * may be empty.</p>
242       * 
243       * @return a map of digital sampler settings
244       */
245      public SortedMap<String, Integer> getBitsPerSample()
246      {
247        return bitsPerSample;
248      }
249    
250      //============================================================================
251      // XML
252      //============================================================================
253    
254      /**
255       * Returns an XML representation of this configuration.
256       * @return an XML representation of this configuration.
257       * @throws JAXBException if anything goes wrong during the conversion to XML.
258       * @see #writeAsXmlTo(Writer)
259       */
260      public String toXml() throws JAXBException
261      {
262        return JaxbUtility.getSharedInstance().objectToXmlString(this);
263      }
264      
265      /**
266       * Writes an XML representation of this configuration to {@code writer}.
267       * @param writer the device to which XML is written.
268       * @throws JAXBException if anything goes wrong during the conversion to XML.
269       */
270      public void writeAsXmlTo(Writer writer) throws JAXBException
271      {
272        JaxbUtility.getSharedInstance().writeObjectAsXmlTo(writer, this, null);
273      }
274      
275      /**
276       * Creates a new configuration from the XML data in the given file.
277       * 
278       * @param xmlFile the name of an XML file.  This method will attempt to locate
279       *                the file by using {@link Class#getResource(String)}.
280       *                
281       * @return a new configuration from the XML data in the given file.
282       * 
283       * @throws FileNotFoundException if the XML file cannot be found.
284       * 
285       * @throws JAXBException if the schema file used (if any) is malformed, if
286       *           the XML file cannot be read, or if the XML file is not
287       *           schema-valid.
288       * 
289       * @throws XMLStreamException if there is a problem opening the XML file,
290       *           if the XML is not well-formed, or for some other
291       *           "unexpected processing conditions".
292       */
293      public static AntennaElectronicsConfiguration fromXml(String xmlFile)
294        throws JAXBException, XMLStreamException, FileNotFoundException
295      {
296        return
297          JaxbUtility.getSharedInstance()
298                     .xmlFileToObject(xmlFile,
299                                      AntennaElectronicsConfiguration.class);
300      }
301      
302      /**
303       * Creates a new configuration based on the XML data read from
304       * {@code reader}.
305       * 
306       * @param reader the source of the XML data.
307       *               If this value is <i>null</i>, <i>null</i> is returned.
308       *               
309       * @return a new configuration based on the XML data read from
310       *         {@code reader}.
311       * 
312       * @throws XMLStreamException if the XML is not well-formed,
313       *           or for some other "unexpected processing conditions".
314       *           
315       * @throws JAXBException if anything else goes wrong during the
316       *           transformation.
317       */
318      public static AntennaElectronicsConfiguration fromXml(Reader reader)
319        throws JAXBException, XMLStreamException
320      {
321        return
322          JaxbUtility.getSharedInstance()
323                     .readObjectAsXmlFrom(reader,
324                                          AntennaElectronicsConfiguration.class,
325                                          null);
326      }
327    
328      //To deal with maps in JAXB one has to jump through a few hoops.
329      //The code below does just that.  The basic strategy is to present
330      //the maps to JAXB as an array of objects.  These objects contain
331      //the keys and values of the map entries.  That is, if we have
332      //a variable of type Map<K,V>, we create a new object, X, that
333      //has properties of type K and V.  We then make methods that
334      //get and set X[].
335      
336      //----------------------------------------------------------------------------
337      // Transfer switches
338      //----------------------------------------------------------------------------
339      
340      @XmlElement(name="transferSwitch")
341      @SuppressWarnings("unused")
342      private TransferSwitch[] getXmlTransferSwitch()
343      {
344        TransferSwitch[] xmlEntries = new TransferSwitch[transferSwitchPositions.size()];
345        
346        int i=0;
347        for (String switchName : transferSwitchPositions.keySet())
348        {
349          xmlEntries[i++] =
350            new TransferSwitch(switchName,
351                              transferSwitchPositions.get(switchName));
352        }
353        
354        return xmlEntries;
355      }
356      
357      @SuppressWarnings("unused")
358      private void setXmlTransferSwitch(TransferSwitch[] xmlEntries)
359      {
360        //TODO: Are we guaranteed by JAXB to get all the transferSwitch elements at once?
361        //      If not, clearing the list is not going to work.
362        //      We could create a wrapper element called "tranferSwitches" that
363        //      has a sequence of transferSwitch elements.  That should guarantee
364        //      all elements coming at once.
365        transferSwitchPositions.clear();
366        
367        for (TransferSwitch ts : xmlEntries)
368        {
369          transferSwitchPositions.put(ts.name, ts.orientation);
370        }
371      }
372      
373      private static class TransferSwitch
374      {
375        @XmlAttribute String orientation;
376        @XmlAttribute String name;
377        
378        TransferSwitch()  { }
379        TransferSwitch(String n, String v)  { name=n; orientation=v; }
380      }
381      
382      //----------------------------------------------------------------------------
383      // Plain switches
384      //----------------------------------------------------------------------------
385      
386      @XmlElement(name="switch")
387      @SuppressWarnings("unused")
388      private NameStringValue[] getXmlSwitch()
389      {
390        NameStringValue[] xmlEntries = new NameStringValue[switchPositions.size()];
391        
392        int i=0;
393        for (String switchName : switchPositions.keySet())
394        {
395          xmlEntries[i++] =
396            new NameStringValue(switchName,
397                                switchPositions.get(switchName).get("input"),
398                                switchPositions.get(switchName).get("output"));
399        }
400        
401        return xmlEntries;
402      }
403      
404      @SuppressWarnings("unused")
405      private void setXmlSwitch(NameStringValue[] xmlEntries)
406      {
407        //TODO: See TODO in setXmlTransferSwitch.
408        switchPositions.clear();
409        
410        for (NameStringValue s : xmlEntries)
411        {
412          Map<String, String> value = new HashMap<String, String>();
413    
414          if (s.inputPole != null)
415            value.put("input",  s.inputPole);
416          
417          if (s.outputPole != null)
418            value.put("output", s.outputPole);
419          
420          switchPositions.put(s.name, value);
421        }
422      }
423      
424      private static class NameStringValue
425      {
426        @XmlAttribute String outputPole;
427        @XmlAttribute String inputPole;
428        @XmlAttribute String name;
429        
430        NameStringValue()  { }
431        NameStringValue(String n, String in, String out)
432        {
433          name=n; inputPole=in; outputPole=out;
434        }
435      }
436    
437      //----------------------------------------------------------------------------
438      // Digital samplers
439      //----------------------------------------------------------------------------
440      
441      @XmlElement(name="sampler")
442      @SuppressWarnings("unused")
443      private Sampler[] getXmlSampler()
444      {
445        Sampler[] xmlEntries = new Sampler[bitsPerSample.size()];
446        
447        int i=0;
448        for (String samplerName : bitsPerSample.keySet())
449        {
450          xmlEntries[i++] = new Sampler(samplerName,bitsPerSample.get(samplerName));
451        }
452        
453        return xmlEntries;
454      }
455      
456      @SuppressWarnings("unused")
457      private void setXmlSampler(Sampler[] xmlEntries)
458      {
459        //TODO: See TODO in setXmlTransferSwitch.
460        bitsPerSample.clear();
461        
462        for (Sampler s : xmlEntries)
463        {
464          bitsPerSample.put(s.name, s.bitsPerSample);
465        }
466      }
467    
468      //@XmlType(name="sampler")
469      private static class Sampler
470      {
471        @XmlAttribute Integer bitsPerSample;
472        @XmlAttribute String  name;
473        
474        Sampler()  { }
475        Sampler(String n, Integer b)  { name=n; bitsPerSample=b; }
476      }
477      
478      //----------------------------------------------------------------------------
479      // Local oscillators
480      //----------------------------------------------------------------------------
481      
482      @XmlElement(name="oscillator")
483      @SuppressWarnings("unused")
484      private Oscillator[] getXmlOscillator()
485      {
486        Oscillator[] xmlEntries = new Oscillator[loTunings.size()];
487        
488        int i=0;
489        for (String loName : loTunings.keySet())
490        {
491          xmlEntries[i++] = new Oscillator(loName,loTunings.get(loName).toString());
492        }
493        
494        return xmlEntries;
495      }
496      
497      @SuppressWarnings("unused")
498      private void setXmlOscillator(Oscillator[] xmlEntries)
499      {
500        //TODO: See TODO in setXmlTransferSwitch.
501        loTunings.clear();
502        
503        for (Oscillator lo : xmlEntries)
504        {
505          loTunings.put(lo.name, Frequency.parse(lo.tuning));
506        }
507      }
508    
509      private static class Oscillator
510      {
511        @XmlAttribute String tuning;
512        @XmlAttribute String name;
513        
514        Oscillator()  { }
515        Oscillator(String n, String t)  { name=n; tuning=t; }
516      }
517    
518      //============================================================================
519      // 
520      //============================================================================
521      
522      /**
523       * Returns a copy of this configuration.
524       * <p>
525       * If anything goes wrong during the cloning procedure,
526       * a {@code RuntimeException} will be thrown.</p>
527       */
528      @Override
529      public AntennaElectronicsConfiguration clone()
530      {
531        AntennaElectronicsConfiguration clone = null;
532        
533        try
534        {
535          //This line takes care of the primitive & immutable fields properly
536          clone = (AntennaElectronicsConfiguration)super.clone();
537    
538          clone.bands = new HashSet<ReceiverBand>(this.bands);
539          clone.loTunings = new TreeMap<String, Frequency>(this.loTunings);
540          clone.switchPositions = new TreeMap<String, Map<String,String>>(this.switchPositions);
541          clone.transferSwitchPositions = new TreeMap<String, String>(this.transferSwitchPositions);
542          clone.bitsPerSample = new TreeMap<String, Integer>(this.bitsPerSample);
543    
544        }
545        catch (Exception ex)
546        {
547          throw new RuntimeException(ex);
548        }
549        
550        return clone;
551      }
552      
553      /** Returns <i>true</i> if {@code o} is equal to this configuration. */
554      @Override
555      public boolean equals(Object o)
556      {
557        //Quick exit if o is this
558        if (o == this)
559          return true;
560        
561        //Quick exit if o is null
562        if (o == null)
563          return false;
564       
565        //Quick exit if classes are different
566        if (!o.getClass().equals(this.getClass()))
567          return false;
568       
569        //A safe cast if we got this far
570        AntennaElectronicsConfiguration other = (AntennaElectronicsConfiguration)o;
571    
572        return
573          other.telescope.equals(              this.telescope              ) &&
574          other.bands.equals(                  this.bands                  ) &&
575          other.loTunings.equals(              this.loTunings              ) &&
576          other.switchPositions.equals(        this.switchPositions        ) &&
577          other.transferSwitchPositions.equals(this.transferSwitchPositions) &&
578          other.bitsPerSample.equals(          this.bitsPerSample          );
579    
580        //Note the comparison of the bands Set is OK because
581        //the elements are immutable.
582      }
583    
584      /** Returns a hash code value for this configuration. */
585      @Override
586      public int hashCode()
587      {
588        //Taken from the Effective Java book by Joshua Bloch.
589        //The constants 17 & 37 are arbitrary & carry no meaning.
590        int result = 17;
591        
592        //You MUST keep this method in sync w/ the equals method
593    
594        result = 37 * result + telescope.hashCode();
595        result = 37 * result + bands.hashCode();
596        result = 37 * result + loTunings.hashCode();
597        result = 37 * result + switchPositions.hashCode();
598        result = 37 * result + transferSwitchPositions.hashCode();
599        result = 37 * result + bitsPerSample.hashCode();
600    
601        return result;
602      }
603      
604      //============================================================================
605      // 
606      //============================================================================
607      /*
608      //This is here for quick & dirty testing
609      public static void main(String[] args)
610      {
611        edu.nrao.sss.model.resource.evla.EvlaAntennaElectronics evla =
612          new edu.nrao.sss.model.resource.evla.EvlaAntennaElectronics();
613    
614        evla.configureFor(ReceiverBand.EVLA_X);
615        
616        AntennaElectronicsConfiguration config = evla.getConfiguration();
617    
618        try
619        {
620          config.writeAsXmlTo(new java.io.PrintWriter(System.out));
621        }
622        catch (JAXBException ex)
623        {
624          System.out.println("Trouble w/ config.toXml.  Msg:");
625          System.out.println(ex.getMessage());
626          ex.printStackTrace();
627          
628          System.out.println("Attempting to write XML w/out schema verification:");
629          JaxbUtility.getSharedInstance().setLookForDefaultSchema(false);
630          try
631          {
632            config.writeAsXmlTo(new java.io.PrintWriter(System.out));
633          }
634          catch (JAXBException ex2)
635          {
636            System.out.println("Still had trouble w/ config.toXml.  Msg:");
637            System.out.println(ex.getMessage());
638            ex.printStackTrace();
639          }
640        }
641      }
642      */
643    }