001    package edu.nrao.sss.model.resource;
002    
003    import java.awt.event.ActionListener;
004    import java.util.ArrayList;
005    import java.util.HashMap;
006    import java.util.List;
007    import java.util.Map;
008    
009    import javax.xml.bind.annotation.XmlElementRef;
010    import javax.xml.bind.annotation.XmlElementRefs;
011    import javax.xml.bind.annotation.XmlElementWrapper;
012    import javax.xml.bind.annotation.XmlTransient;
013    
014    import edu.nrao.sss.electronics.DigitalSignal;
015    import edu.nrao.sss.model.resource.evla.EvlaWidarConfiguration;
016    import edu.nrao.sss.model.resource.vla.VlaConfiguration;
017    import edu.nrao.sss.util.Identifiable;
018    
019    import org.apache.log4j.Logger;
020    
021    /**
022     * Configuration information for a correlator of a radio telescope.
023     * 
024     * <h3 style="font-variant:small-caps"><u>How To Use This Class</u></h3>
025     * <div style="margin-left:1.5em; margin-right:2em; text-align:justify;">
026     * <p>
027     * <i><b>Getting an Instance</b></i><br/>
028     * Clients are discouraged from creating new instances of this type by using the
029     * constructors of concrete subclasses. The preferred way to get an instance of
030     * this type is to use the static factory method,
031     * {@link #makeFor(CorrelatorName, AntennaElectronics)}.
032     * The static method {@link #getSupportedCorrelators()} lists the
033     * correlator supported by this configuration class.</p>
034     * <p>
035     * <i><b>Working with Basebands and Baseband Pairs</b></i><br/>
036     * This configuration now has only one method for fetching basebands:
037     * {@link #getBasebands()}.  The method <tt>getBasebandPairs()</tt> has been
038     * eliminated, but the {@link CorrelatorBaseband} now has methods to determine
039     * whether or not it is part of pair and, if so, who its parter is.
040     * The basebands held by this configuration were
041     * originally constructed based on the output signals of the
042     * {@link AntennaElectronics} provided to the factory method.</p>
043     * <p>
044     * <i><b>Listening for Changes</b></i><br/>
045     * This configuration gives clients the ability to listen for changes in
046     * the <i>collection</i> of basebands in this configuration.  Clients
047     * may register by using the
048     * {@link #addBasebandCollectionListener(BasebandCollectionListener)
049     * addBasebandCollectionListener} method.
050     * Registered users will be notified when a baseband(s) is added to the
051     * configuration, removed from the configuration, or both at once.
052     * Note that a listener's
053     * {@link BasebandCollectionListener#basebandCollectionChanged(BasebandCollectionEvent)
054     * basebandCollectionChanged} method is called only when the collection itself
055     * changes, <i>not</i> when properties of the contained basebands change.</p> 
056     * </div>
057     * <p>
058     * <b>Version Info:</b>
059     * <table style="margin-left:2em">
060     *   <tr><td>$Revision: 2287 $</td></tr>
061     *   <tr><td>$Date: 2009-05-07 13:54:58 -0600 (Thu, 07 May 2009) $</td></tr>
062     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
063     * </table></p>
064     * 
065     * @author David M. Harland
066     * @since 2008-02-04
067     */
068    public abstract class CorrelatorConfiguration
069      implements TelescopeBackend, ActionListener
070    {
071      private static final Logger LOG = Logger.getLogger(CorrelatorConfiguration.class);
072      
073      private Long id; //A unique identifier for the persistence layer.
074    
075      /** The basebands of this configuration in a map keyed by name. */
076      //protected Map<String, CorrelatorBaseband> activeBBs;
077      
078      /** The basebands of this configuration. */
079      protected List<CorrelatorBasebandAbs> activeBBs;
080    
081      /**
082       * Basebands that had been, but are not currently, active.
083       * Subclasses do not have to work with this variable.
084       * The intention behind it is to help with a single-level
085       * "redo" feature.  The targeted use case goes something like this:
086       * <ol>
087       *   <li>A user has set up the antenna electronics that feeds this
088       *       configuration in such a way that eight basebands are produced.</li>
089       *   <li>The user completely configures all basebands, including
090       *       breaking them down into subbands.</li>
091       *   <li>The user then either intentionally or accidentally modifies
092       *       the antenna electronics in such a way that two of the fully
093       *       configured basebands are replaced by two new basebands.</li>
094       *   <li>The user puts the antenna electronics back into its
095       *       original state.  This means that the original eight basebands
096       *       are again active.</li>
097       * </ol>
098       * This variable can be used to restore all the work the user had
099       * done on the disappearing basebands when they reappear.
100       */
101      protected Map<String, CorrelatorBasebandAbs> inactiveBBs;
102    
103      /** The provider of digital signals for the basebands of this configuration.*/
104      protected AntennaElectronics signalSource;
105    
106      /** Objects interested in changes to our collection of basebands. */
107      protected List<BasebandCollectionListener> bbListeners;
108    
109      /**
110       * Creates and returns a new configuration for the given correlator that is
111       * initialized from {@code signalSrc}.
112       * 
113       * @param correlator
114       *   the name of a known correlator.
115       *   
116       * @param signalSrc
117       *   the antenna electronics that provide the input signals for the basebands
118       *   of this configuration.
119       *   
120       * @return
121       *   a new configuration for the given correlator whose basebands are
122       *   initialized based on the given antenna electronics.
123       *   
124       * @throws IllegalArgumentException
125       *   if {@code correlator} is not one of the supported types.
126       */
127      public static CorrelatorConfiguration makeFor(CorrelatorName     correlator,
128                                                    AntennaElectronics signalSrc)
129      {
130        switch(correlator)
131        {
132          case VLA:   return new VlaConfiguration(signalSrc);
133          case WIDAR: return new EvlaWidarConfiguration(signalSrc);
134          
135          default:
136            throw new IllegalArgumentException("The " + correlator +
137              " correlator is not yet supported.");
138        }
139      }
140    
141      /**
142       * Returns a list of the correlators that are supported by the
143       * {@link #makeFor(CorrelatorName, AntennaElectronics) factory
144       * method} of this class.
145       * 
146       * @return
147       *   the correlators for which this class can construct configurations.
148       */
149      public static List<CorrelatorName> getSupportedCorrelators()
150      {
151        List<CorrelatorName> list = new ArrayList<CorrelatorName>();
152        
153        list.add(CorrelatorName.WIDAR);
154        list.add(CorrelatorName.VLA);
155        
156        return list;
157      }
158    
159      /**
160       * Constructs a new configuration that is initialized from {@code signalSrc}.
161       * 
162       * @param signalSrc
163       *   the antenna electronics that provide the input signals for the basebands
164       *   of this configuration.
165       *   
166       * @throws IllegalArgumentException
167       *   if {@code signalSrc} is <i>null</i>.
168       */
169      protected CorrelatorConfiguration(AntennaElectronics signalSrc)
170      {
171        if (signalSrc == null)
172          throw new IllegalArgumentException("You may not create a new correlator" +
173            " configuration with NULL AntennaElectronics.");
174    
175        init(signalSrc);
176      }
177    
178      //This is here only for persistence mechanisms
179      protected CorrelatorConfiguration()
180      {
181        init(null);
182      }
183    
184      //All constructors should lead to a call to this method
185      private void init(AntennaElectronics signalSrc)
186      {
187        id = Identifiable.UNIDENTIFIED;
188        
189        activeBBs   = new ArrayList<CorrelatorBasebandAbs>();
190        inactiveBBs = new HashMap<String, CorrelatorBasebandAbs>();
191        bbListeners = new ArrayList<BasebandCollectionListener>();
192    
193        setSigSrc(signalSrc);
194      }
195    
196      //============================================================================
197      // IDENTIFICATION
198      //============================================================================
199    
200      /* (non-Javadoc)
201       * @see edu.nrao.sss.util.Identifiable#getId()
202       */
203      public Long getId() { return id; }
204    
205      @XmlTransient public void setId(Long id)  { this.id = id; }
206    
207      /* (non-Javadoc)
208       * @see edu.nrao.sss.model.resource.TelescopeBackend#clearId()
209       */
210      public void clearId()
211      {
212        id = Identifiable.UNIDENTIFIED;
213        
214        for (CorrelatorBaseband bb : activeBBs)
215          bb.clearId();
216        
217        //This is probably not necessary (not persisting inactiveBBs)
218        for (CorrelatorBaseband bb : inactiveBBs.values())
219          bb.clearId();
220      }
221    
222      //============================================================================
223      // SOURCE OF SIGNAL
224      //============================================================================
225    
226      //Used by constructor and by public method
227      private void setSigSrc(AntennaElectronics newSource)
228      {
229        //Quick exit if attempt to set to what we already have
230        if (newSource == signalSource)
231          return;
232        
233        //Stop listening to old signal source
234        if (signalSource != null)
235          signalSource.removeExecutionListener(this);
236        
237        //Save the new source, even if it's null
238        signalSource = newSource;
239        
240        if (signalSource != null)
241        {
242          //When the antenna electronics is executed, we will be notified.
243          //That execution could cause us to change the number of active
244          //basebands, or just retune where the current basebands begin.
245          signalSource.addExecutionListener(this);
246          
247          //Synchronize this configuration with the current state of the
248          //antenna electronics
249          //NOTE from DMH 2008-05-21
250          //  I deactivated this call so that the pathway that starts at either
251          //  the database or an XML file, winds its way through
252          //  Resource.connectBackendsToAntElec(), and then finds itself here
253          //  does not blow away the backend data we just fetched from the DB
254          //  or XML.
255          //  The reason it gets blown away is that the antenna electronics
256          //  have not been executed and have null signals.  The act of syncing
257          //  the front end w/ the backend results in null signal in the back.
258          //  There are other ways to deal w/ this.  One would be to execute
259          //  the electronics when fetching from DB/XML.  We'd need to beware,
260          //  though, of signals produced by the execute that don't match the
261          //  stored backend signals.  Another would be to go into the
262          //  actionPerformed methods of the backends and not replace the signals
263          //  under some conditions (eg, prior sig src was null and new one is not,
264          //  action.equals("New source"), or some such).
265          //
266          //  I'm a little worried that fixing the use-case at hand may muck up
267          //  others, hence the deactivation -- rather than removal -- of the line
268          //  below and this note.  If you find you want to RE-activate the line
269          //  below, be aware of the DB / XML issues.
270          //actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
271          //                                "New source"));
272        }
273      }
274    
275      /**
276       * Sets the source of input signals for this configuration.
277       * Most clients will not need to use this method, since it is expected that
278       * they created this configuration with a valid signal source in the first
279       * place.
280       * 
281       * @param newSource
282       *   a new source of input signals for this configuration.
283       *   A value of <i>null</i> is accepted here.
284       */
285      @XmlTransient
286      public void setSignalSource(AntennaElectronics newSource)
287      {
288        setSigSrc(newSource);
289      }
290    
291      /**
292       * Returns the source of input signals for this configuration, if any.
293       * If no signal source has been provided, the returned value will be
294       * <i>null</i>.
295       * 
296       * @return the source of input signals for this configuration.
297       */
298      public AntennaElectronics getSignalSource()
299      {
300        return signalSource;
301      }
302    
303      //============================================================================
304      // BASEBANDS
305      //============================================================================
306    
307      /**
308       * Allows concrete subclasses to be in charge of the ordering of the basebands
309       * returned by {@link #getBasebands()}.
310       * This default implementation returns a copy of the <tt>activeBBs</tt> list
311       * without any reordering
312       */
313      protected List<CorrelatorBaseband> getOrderedBasebands()
314      {
315        return new ArrayList<CorrelatorBaseband>(activeBBs);
316      }
317      
318      /**
319       * Creates and initializes the active basebands for this configuration
320       * based on the antenna electronics that were sent to the constructor.
321       * This method is called from the constructor after the
322       * <tt>signalSource</tt> variable was set.
323       *
324      protected void initializeBasebands()
325      {
326        activeBBs.clear();
327        inactiveBBs.clear();
328        
329        //Create new basebands from the input signals and save as active BBs
330        for (List<DigitalSignal> inputPair : signalSource.getSignalPairs())
331        {
332          CorrelatorBaseband bb0 = makeBasebandFrom(inputPair.get(0));
333          CorrelatorBaseband bb1 = makeBasebandFrom(inputPair.get(1));
334          
335          if (bb0 != null)
336            activeBBs.put(bb0.getName(), bb0);
337          
338          if (bb1 != null)
339            activeBBs.put(bb1.getName(), bb0);
340        }
341      }*/
342    
343      public BackendType getType()  { return BackendType.CORRELATOR;}
344    
345      /**
346       * Creates a new baseband from the given digital signal.
347       * Subclasses of this one decide which implementation of a
348       * <tt>CorrelatorBaseband</tt> to construct. 
349       * <p>
350       * This method is expected to produce a <tt>CorrelatorBaseband</tt>
351       * whose {@link CorrelatorBaseband#isSinglet()} property is
352       * <i>true</i>.</p>
353       * 
354       * @param ds
355       *   the digital signal that serves as the signal source for the
356       *   newly created baseband.
357       *   
358       * @return
359       *   a new baseband, or <i>null</i> if {@code ds} is <i>null</i>.
360       */
361      protected abstract CorrelatorBaseband makeBasebandFrom(DigitalSignal ds);
362    
363      /**
364       * Creates a new baseband from the given digital signals.
365       * Subclasses of this one decide which implementation of a
366       * <tt>CorrelatorBaseband</tt> to construct. 
367       * <p>
368       * This method is expected to produce a <tt>CorrelatorBaseband</tt>
369       * whose {@link CorrelatorBaseband#isPair()} property is
370       * <i>true</i>.</p>
371       * 
372       * @param ds1
373       *   one of two digital signals that serve as the signal sources for the
374       *   newly created baseband.
375       *   
376       * @param ds2
377       *   one of two digital signals that serve as the signal sources for the
378       *   newly created baseband.
379       *   
380       * @return
381       *   a new baseband, or <i>null</i> if either {@code ds1} or {@code ds2}
382       *   is <i>null</i>.
383       */
384      protected abstract CorrelatorBaseband makeBasebandFrom(DigitalSignal ds1,
385                                                             DigitalSignal ds2);
386    
387      /**
388       * Returns the basebands of this correlator configuration.
389       * <p>
390       * While the basebands in the returned list are the actual basebands
391       * held internally by this configuration, the list itself is a new
392       * list created at the time this method is called and not referenced
393       * by this configuration.  The returned list may be empty, but will
394       * never be <i>null</i>.</p>
395       * 
396       * @return the basebands of this correlator configuration.
397       */
398      public List<CorrelatorBaseband> getBasebands()
399      {
400        return getOrderedBasebands();
401      }
402    
403      //----------------------------------------------------------------------------
404      // Persistence Helpers
405      //----------------------------------------------------------------------------
406    
407      //For the list of baseband pairs we have one set of logic for Hibernate &
408      //one for JAXB.  In XML we're making basebandPairs an optional element, but
409      //for Hibernate it cannot be null (or else Hibernate throws an exception).
410      @XmlElementWrapper(name="basebands")
411      @XmlElementRefs
412      (
413        {
414          @XmlElementRef(type=edu.nrao.sss.model.resource.evla.WidarBasebandSinglet.class),
415          @XmlElementRef(type=edu.nrao.sss.model.resource.evla.WidarBasebandPair.class),
416          @XmlElementRef(type=edu.nrao.sss.model.resource.vla.VlaBasebandPair.class)
417        }
418      )
419      @SuppressWarnings("unused")
420      private void setXmlBasebandList(CorrelatorBasebandAbs[] replacementList)
421      {
422        if (replacementList == null)
423        {
424          activeBBs = new ArrayList<CorrelatorBasebandAbs>();
425        }
426        else
427        {
428          ArrayList<CorrelatorBasebandAbs> newList =
429            new ArrayList<CorrelatorBasebandAbs>();
430          
431          for (CorrelatorBasebandAbs cbp : replacementList)
432            newList.add(cbp);
433          
434          activeBBs = newList;
435        }
436        
437        createdBasebandsFromPersistentStore();
438      }
439      @SuppressWarnings("unused")
440      private CorrelatorBasebandAbs[] getXmlBasebandList()
441      {
442        return activeBBs.size() == 0 ?
443          null : activeBBs.toArray(new CorrelatorBasebandAbs[activeBBs.size()]);
444      }
445    
446      @SuppressWarnings("unused")  //Used by Hibernate
447      private void setBasebandList(List<CorrelatorBasebandAbs> replacementList)
448      {
449        LOG.warn("Setting baseband list. Size = "+(replacementList==null?0:replacementList.size()));
450        
451        activeBBs =
452          replacementList == null ? new ArrayList<CorrelatorBasebandAbs>()
453                                  : replacementList;
454    
455        createdBasebandsFromPersistentStore();
456      }
457      @SuppressWarnings("unused")  //Used by Hibernate
458      private List<CorrelatorBasebandAbs> getBasebandList()
459      {
460        LOG.warn("Getting baseband list; activeBBs.size = "+activeBBs.size());
461        return activeBBs;
462      }
463      
464      /**
465       * Called after basebands were created from a persistent store,
466       * such as a database or XMl file.  This method is here so that
467       * subclasses may override it.  This default implementation does nothing.
468       */
469      protected void createdBasebandsFromPersistentStore()
470      {
471        //Default do-nothing implementation
472      }
473    
474      //============================================================================
475      // LISTENERS
476      //============================================================================
477    
478      /**
479       * Registers a new listener that will be notified whenever the collection
480       * of basebands in this configuration is changed.  Note that "changed" means
481       * that there are new basebands in the collection, that some basebands
482       * formerly in the collection no longer are, or both of the preceding.
483       * For the purpose of the <tt>BasebandCollectionListener</tt>, a change to
484       * a baseband already in the collection is <i>not</i> considered an event.
485       * Only a modification of the collection itself causes notification.
486       * 
487       * @param newListener
488       *   an object that will be notified when the collection of basebands held
489       *   by this configuration changes.  A value of <i>null</i> will be ignored.
490       */
491      public void addBasebandCollectionListener(BasebandCollectionListener newListener)
492      {
493        if (newListener != null)
494          bbListeners.add(newListener);
495      }
496    
497      /**
498       * Removes {@code formerListener} from this configuration's list of baseband
499       * collection listeners.  If {@code formerListener} is not currently a
500       * registered baseband collection listener, this method does nothing.
501       * 
502       * @param formerListener
503       *   an object that no longer wishes to be notified about changes in the
504       *   collection of basebands held by this configuration.
505       */
506      public void removeBasebandCollectionListener(BasebandCollectionListener formerListener)
507      {
508        bbListeners.remove(formerListener);
509      }
510    
511      //============================================================================
512      // 
513      //============================================================================
514    
515      /**
516       *  Returns a copy of this configuration.
517       *  The returned object is a deep copy of this one, with the following
518       *  exceptions:
519       *  <ol>
520       *    <li>Both configurations are using the same signal source.</li>
521       *    <li>While each configuration has its own list of listeners,
522       *        the same listeners are present in each list.</li> 
523       *  </ol>
524       *  <p>
525       *  If anything goes wrong during the cloning procedure,
526       *  a {@code RuntimeException} will be thrown.</p>
527       */
528      @Override
529      public CorrelatorConfiguration clone()
530      {
531        CorrelatorConfiguration clone = null;
532        
533        try
534        {
535          //This line takes care of the primitive & immutable fields properly
536          clone = (CorrelatorConfiguration)super.clone();
537          
538          //We do NOT want the clone to have the same ID as the original.
539          //The ID is here for the persistence layer; it is in charge of
540          //setting IDs.  To help it, we put the clone's ID in the uninitialized
541          //state.
542          clone.id = Identifiable.UNIDENTIFIED;
543    
544          //Clone the collection AND the elements
545          clone.activeBBs = new ArrayList<CorrelatorBasebandAbs>();
546          for (CorrelatorBasebandAbs thisBB : this.activeBBs)
547            clone.activeBBs.add(thisBB.clone());
548          
549          //Clone the collection AND the elements
550          clone.inactiveBBs = new HashMap<String, CorrelatorBasebandAbs>();
551          for (String bbName : this.inactiveBBs.keySet())
552            clone.inactiveBBs.put(bbName, this.inactiveBBs.get(bbName).clone());
553    
554          //We clone the listener collection, but NOT the listeners themselves
555          clone.bbListeners =
556            new ArrayList<BasebandCollectionListener>(this.bbListeners);
557        }
558        catch (Exception ex)
559        {
560          throw new RuntimeException(ex);
561        }
562    
563        return clone;
564      }
565    
566      /** Returns <i>true</i> if {@code o} is equal to this configuration. */
567      @Override
568      public boolean equals(Object o)
569      {
570        //Quick exit if o is this
571        if (o == this)
572          return true;
573        
574        //Quick exit if o is null
575        if (o == null)
576          return false;
577       
578        //Quick exit if classes are different
579        if (!o.getClass().equals(this.getClass()))
580          return false;
581       
582        //A safe cast if we got this far
583        CorrelatorConfiguration other = (CorrelatorConfiguration)o;
584    
585        //We're intentionally not comparing:
586        //1. inactiveBBs because
587        //   a. we don't persist these
588        //   b. we don't care about them in this context
589        //2. the listeners
590        //3. the signal source
591        return other.activeBBs.equals(this.activeBBs);
592      }
593    
594      /** Returns a hash code value for this configuration. */
595      @Override
596      public int hashCode()
597      {
598        //Taken from the Effective Java book by Joshua Bloch.
599        //The constants 17 & 37 are arbitrary & carry no meaning.
600        int result = 17;
601        
602        //You MUST keep this method in sync w/ the equals method
603        
604        result = 37 * result + activeBBs.hashCode();
605    
606        return result;
607      }
608    }