001    package edu.nrao.sss.model.resource.evla;
002    
003    import java.io.Writer;
004    import java.math.BigDecimal;
005    import java.util.ArrayList;
006    import java.util.Arrays;
007    import java.util.List;
008    import java.util.SortedSet;
009    import java.util.TreeSet;
010    
011    import javax.xml.bind.JAXBException;
012    import javax.xml.bind.annotation.XmlElement;
013    import javax.xml.bind.annotation.XmlElementWrapper;
014    import javax.xml.bind.annotation.XmlTransient;
015    import javax.xml.bind.annotation.XmlType;
016    
017    import static ca.nrc.widar.jaxb.vci.UpperLowerType.LOWER;
018    import static ca.nrc.widar.jaxb.vci.UpperLowerType.UPPER;
019    import static ca.nrc.widar.jaxb.vci.YesNoType.NO;
020    import static ca.nrc.widar.jaxb.vci.YesNoType.YES;
021    
022    import static edu.nrao.sss.measure.FrequencyUnits.HERTZ;
023    import static edu.nrao.sss.measure.FrequencyUnits.KILOHERTZ;
024    import static edu.nrao.sss.measure.FrequencyUnits.MEGAHERTZ;
025    import static edu.nrao.sss.measure.TimeUnits.SECOND;
026    
027    import ca.nrc.widar.jaxb.vci.BaseBand;
028    import ca.nrc.widar.jaxb.vci.BbParams;
029    import ca.nrc.widar.jaxb.vci.InputStateCounts;
030    import ca.nrc.widar.jaxb.vci.PulsarGating;
031    import ca.nrc.widar.jaxb.vci.StbSourceType;
032    import ca.nrc.widar.jaxb.vci.SubBand;
033    import ca.nrc.widar.jaxb.vci.WideBandCorrelator;
034    
035    import edu.nrao.sss.astronomy.PolarizationType;
036    import edu.nrao.sss.electronics.DigitalSignal;
037    import edu.nrao.sss.electronics.Signal;
038    import edu.nrao.sss.measure.Frequency;
039    import edu.nrao.sss.measure.FrequencyRange;
040    import edu.nrao.sss.measure.FrequencyUnits;
041    import edu.nrao.sss.measure.TimeDuration;
042    import edu.nrao.sss.model.resource.CorrelatorBasebandAbs;
043    import edu.nrao.sss.model.resource.CorrelatorConfiguration;
044    import edu.nrao.sss.model.resource.CorrelatorSubband;
045    import edu.nrao.sss.util.JaxbUtility;
046    
047    /**
048     * Common parent for the WIDAR baseband singlet and pair classes.
049     * <p>
050     * <b>Version Info:</b>
051     * <table style="margin-left:2em">
052     *   <tr><td>$Revision: 2296 $</td></tr>
053     *   <tr><td>$Date: 2009-05-12 14:50:22 -0600 (Tue, 12 May 2009) $</td></tr>
054     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
055     * </table></p>
056     * 
057     * @author David M. Harland
058     * @since 2008-06-25
059     */
060    @XmlType(propOrder={"delayModelLifespan","pulsarGating","singlePhaseCenter",
061                        "wideBandCorrelator", "widarSubbands"
062                        })
063    public abstract class WidarBaseband
064      extends CorrelatorBasebandAbs
065    {
066      private static final Frequency MIN_BW       = new Frequency(     "31250.0", HERTZ);
067      private static final Frequency MAX_BW_3_BIT = new Frequency("2048000000.0", HERTZ);
068      private static final Frequency MAX_BW_8_BIT = new Frequency("1024000000.0", HERTZ);
069    
070      private EvlaWidarConfiguration container;
071      
072      /** Helps create a new WIDAR baseband. */
073      WidarBaseband()
074      {
075        super();
076        setVciDefaults();
077      }
078    
079      //============================================================================
080      // CONTAINER
081      //============================================================================
082    
083      /**
084       * Returns the correlator configuration that holds this baseband.
085       * It is possible for the returned value to be <i>null</i>.
086       * If not <i>null</i>, the returned object will always be of type
087       * {@link EvlaWidarConfiguration}.
088       */
089      protected CorrelatorConfiguration getContainer()
090      {
091        return container;
092      }
093      
094      /**
095       * Convenience method that returns the same object as
096       * {@link #getContainer()}.
097       */
098      EvlaWidarConfiguration getEvlaWidarConfiguration()
099      {
100        return container;
101      }
102      
103      /**
104       * Sets the correlator configuration to which this baseband belongs.
105       * The {@code newContainer} is allowed to be <i>null</i>.
106       * If not <i>null</i>, the type of {@code newContainer} must be
107       * {@link EvlaWidarConfiguration}.
108       */
109      protected void setContainer(CorrelatorConfiguration newContainer)
110      {
111        container = (EvlaWidarConfiguration)newContainer;
112      }
113    
114      /**
115       * Convenience method that performs the same function as
116       * {@link #setContainer(CorrelatorConfiguration)}.
117       */
118      void setEvlaWidarConfiguration(EvlaWidarConfiguration newCfg)
119      {
120        //Because of the way we have the Hibernate mappings configured, we must
121        //call the method below and not simply set the variable.
122        setContainer(newCfg);
123      }
124      
125      //============================================================================
126      // FREQUENCY
127      //
128      //  In our initial implementation we assumed, based on documentation, that
129      //  clients were permitted to choose their own bandwidth.  The client selected
130      //  bandwidth was constrained to be a value from a set of discrete legal
131      //  values.  On 2008-Aug-29 Michael Rupen informed us that the bandwidth
132      //  had to be exactly equal to the bandwidth of the incoming IF signal(s).
133      //  The section of the VCI documentation that shows multiple possible values
134      //  for the bandwidth was meant to accommodate VLBI situations where a single
135      //  baseband was composed of signals from many antennas arranged sequentially
136      //  every so many Hertz.  It was not meant to imply client choice for
137      //  baseband bandwidths.
138      //
139      //  Some of the original code has been removed, but could be recovered from
140      //  subversion if need be.  Much of what remains, though, is still influenced
141      //  by the original assumption.
142      //============================================================================
143      
144      boolean proxiedRangeIsReversed(Signal signal)
145      {
146        return signal.proxiedRangeIsReversed();
147      }
148    
149      /* (non-Javadoc)
150       * @see edu.nrao.sss.model.resource.CorrelatorBaseband#getMinimumBandwidth()
151       */
152      public Frequency getMinimumBandwidth()
153      {
154        return MIN_BW.clone();
155      }
156      
157      /* (non-Javadoc)
158       * @see edu.nrao.sss.model.resource.CorrelatorBaseband#getMaximumBandwidth()
159       */
160      public Frequency getMaximumBandwidth()
161      {
162        Frequency max = (getInitialQuantization() == 8) ? MAX_BW_8_BIT
163                                                        : MAX_BW_3_BIT;
164        Frequency inputBw = getInputBandwidth();
165        
166        return
167          (inputBw != null && inputBw.compareTo(max) <= 0) ? inputBw : max.clone();
168      }
169      
170      /** Returns the bandwidth of this baseband's input signal. */
171      abstract Frequency getInputBandwidth();
172      
173      /* (non-Javadoc)
174       * @see edu.nrao.sss.model.resource.CorrelatorBaseband#hasDiscreteBandwidths()
175       */
176      public boolean hasDiscreteBandwidths()
177      {
178        return true;
179      }
180      
181      /* (non-Javadoc)
182       * @see edu.nrao.sss.model.resource.CorrelatorBaseband#getAllowableBandwidths()
183       */
184      public SortedSet<Frequency> getAllowableBandwidths()
185      {
186        SortedSet<Frequency> bws = new TreeSet<Frequency>();
187        
188        double minHz =
189          getMinimumBandwidth().toUnits(HERTZ).doubleValue();
190        
191        double maxHz =
192          getMaximumBandwidth().toUnits(HERTZ).doubleValue();
193        
194        //Only non-negative powers-of-two multiples of min are allowed
195        for (double hertz = minHz; hertz <= maxHz; hertz *= 2.0)
196        {
197          bws.add(new Frequency(BigDecimal.valueOf(hertz), HERTZ).normalize());
198        }
199        
200        return bws;
201      }
202    
203      /**
204       * Does nothing.  The bandwidth of a WIDAR baseband is completely
205       * determined by the input signal(s).
206       */
207      @Override
208      public void setBandwidth(Frequency newWidth)  { }
209      
210      //============================================================================
211      // QUANTIZATION & POLARIZATION
212      //============================================================================
213    
214      int getInitialQuantization(DigitalSignal signal)
215      {
216        return signal == null ? 3 : signal.getBitDepth();
217      }
218    
219      List<PolarizationType> getPolarizations(Signal... signals)
220      {
221        List<PolarizationType> answer = new ArrayList<PolarizationType>();
222        for (Signal s : signals)
223          answer.add(s.getPolarization());
224        return answer;
225      }
226    
227      //============================================================================
228      // PARTNERED BASEBANDS
229      //============================================================================
230    
231      /**
232       * Returns <i>true</i> if <tt>other</tt> is loosely partnered with this
233       * baseband.
234       * In general baseband pairs do not have partners while baseband
235       * singlets do.
236       * 
237       * @param other
238       *   another WIDAR baseband that may or may not be a partner of this one.
239       *   
240       * @return
241       *   <i>true</i> if <tt>other</tt> is this baseband's partner.
242       */
243      public abstract boolean isPartnerOf(WidarBaseband other);
244      
245      /**
246       * Returns <i>true</i> if this baseband has a partner.
247       * In general baseband pairs do not have partners while baseband
248       * singlets do.
249       * 
250       * @return
251       *   <i>true</i> if this baseband has a partner.
252       */
253      public abstract boolean hasPartner();
254      
255      /**
256       * Returns the partner of this baseband, if any.
257       * In general baseband pairs do not have partners while baseband
258       * singlets do.
259       * <p>
260       * Even unpaired basebands have some coupling to one other baseband, due to
261       * the correlator hardware.</p>
262       * 
263       * @return
264       *   the partner of this baseband or <i>null</i> if this baseband has no
265       *   partner.
266       */
267      public abstract WidarBaseband getPartner();
268      
269      //============================================================================
270      // SUBBANDS
271      //============================================================================
272    
273      /* (non-Javadoc)
274       * @see edu.nrao.sss.model.resource.CorrelatorBaseband#makeNewSubband()
275       */
276      public WidarSubband makeNewSubband()
277      {
278        return new WidarSubband();
279      }
280    
281      /* (non-Javadoc)
282       * @see edu.nrao.sss.model.resource.CorrelatorBaseband#getMaxSubbandCount()
283       */
284      public int getMaxSubbandCount()
285      {
286        return getInitialQuantization() == 8 ? 32 : 16;
287      }
288    
289      /**
290       * WIDAR basebands have an array of slots for subbands.  A subband
291       * must lie entirely within a single slot.  This constant gives
292       * the width of these slots.
293       */
294      static final Frequency SB_GRID_WIDTH = new Frequency("128.0", MEGAHERTZ);
295      
296      /* (non-Javadoc)
297       * @see edu.nrao.sss.model.resource.CorrelatorBaseband#getSubbandGrid()
298       */
299      public SortedSet<Frequency> getSubbandGrid()
300      {
301        SortedSet<Frequency> grid = new TreeSet<Frequency>();
302        
303        Frequency increment = SB_GRID_WIDTH;
304        Frequency gridLine  = new Frequency("0.0", SB_GRID_WIDTH.getUnits());
305        Frequency bbBw      = getBandwidth();
306        
307        while (gridLine.compareTo(bbBw) < 0)
308        {
309          grid.add(gridLine.clone().normalize());
310          gridLine.add(increment);
311        }
312        
313        //Add high frequency of this baseband, even if it's not a natural point
314        grid.add(bbBw.normalize());
315        
316        return grid;
317      }
318      
319      /**
320       * Returns a frequency range that represents the grid cell of this baseband
321       * that contains the given frequency.  This baseband contains boundary lines
322       * that break it up into a group of cells.  No subband may straddle a boundary
323       * line and occupy multiple cells.
324       * <p>
325       * If the given frequency is below the lowest frequency of this baseband,
326       * the lowest grid cell is returned.  If it is above the highest frequency,
327       * the highest cell is returned.  If the given frequency is exactly equal
328       * to one of the inner boundaries, the higher of the two cells that use
329       * that boundary is returned.</p>
330       * 
331       * @param point
332       *   the frequency used to find a grid cell.
333       *   
334       * @return
335       *   a frequency range that represents one subband grid cell of this baseband.
336       */
337      FrequencyRange getGridCellContaining(Frequency point)
338      {
339        Frequency[] boundaries = getSubbandGrid().toArray(new Frequency[0]);
340    
341        //Any frequency beyond grid will be said to be in highest cell of grid.
342        int highIndex = boundaries.length - 1;
343    
344        //Any frequency below grid will be said to be in lowest cell of grid,
345        //thus i=1 and not i=0 as loop start.
346        for (int i=1; i < boundaries.length; i++)
347        {
348          if (point.compareTo(boundaries[i]) < 0)
349          {
350            highIndex = i;
351            break;
352          }
353        }
354    
355        int lowIndex = Math.max(0, highIndex - 1);
356        
357        return new FrequencyRange(boundaries[lowIndex], boundaries[highIndex]);
358      }
359    
360      public void addSubband(CorrelatorSubband newSubband)
361      {
362        WidarSubband sb = (WidarSubband)newSubband;
363        
364        addNewSubband(sb);
365        
366        //TODO sb's bw and center freq might not be legal.  Probably want:
367        //sb.setCentralFrequency(sb.getCentralFrequency());
368        //sb.setBandwidth(sb.getBandwidth());
369        
370        setSubbandId(sb);
371      }
372    
373      @Override
374      public boolean removeSubband(CorrelatorSubband unwantedSubband)
375      {
376        boolean removed = super.removeSubband(unwantedSubband);
377        
378        if (removed)
379          freeSubbandId((WidarSubband)unwantedSubband);
380        
381        return removed;
382      }
383      
384      @XmlElementWrapper(name="subbands")
385      @XmlElement(name="widarSubband")
386      @SuppressWarnings("unused")  //JAXB use
387      private void setWidarSubbands(WidarSubband[] replacements)
388      {
389        subbands.clear();
390        freeAllSubbandIds();
391        
392        for (WidarSubband subband : replacements)
393        {
394          subband.setBaseband(this);
395          subbands.add(subband);
396          
397          //Here we're assuming xml schema guarantees unique, valid, values
398          sbIdInUse[subband.getSbId()] = true;
399        }
400      }
401    
402      @SuppressWarnings("unused")  //JAXB use
403      private WidarSubband[] getWidarSubbands()
404      {
405        return subbands.toArray(new WidarSubband[subbands.size()]);
406      }
407    
408      //----------------------------------------------------------------------------
409      // Managing SB ID
410      //----------------------------------------------------------------------------
411      
412      //TODO need to get straight: 18 or 16?
413      private boolean[] sbIdInUse = new boolean[18];
414      
415      private void setSubbandId(WidarSubband sb)
416      {
417        //Give 1st available.  If found, exit
418        for (int i=0; i < sbIdInUse.length; i++)
419        {
420          if (!sbIdInUse[i])
421          {
422            sb.setSbId(i);
423            sbIdInUse[i] = true;
424            return;
425          }
426        }
427        
428        //If we got this far we have problems
429        throwSbIdSettingError();
430      }
431      
432      private void freeSubbandId(WidarSubband sb)
433      {
434        sbIdInUse[sb.getSbId()] = false;
435      }
436      
437      private void freeAllSubbandIds()
438      {
439        for (int i=0; i < sbIdInUse.length; i++)
440          sbIdInUse[i] = false;
441      }
442      
443      private void throwSbIdSettingError()
444      {
445        //If we got this far there are no available IDs.  Since we expected calling
446        //methods to check for max SBs in this BB, we shouldn't get here.  However,
447        //we need to notify if we did.
448        if (subbands.size() <= getMaxSubbandCount())
449        {
450          throw new RuntimeException(
451            "PROGRAMMER ERROR: Ran out of subband IDs.  Subband count = " +
452            subbands.size() + ", max SB count = " + getMaxSubbandCount());
453        }
454        else //too many subbands
455        {
456          throw new RuntimeException(
457            "PROGRAMMER ERROR: ID-setting logic found too many subbands.  SB count = " +
458            subbands.size() + ", max SB count = " + getMaxSubbandCount());
459        }
460      }
461      
462      //============================================================================
463      // VCI ELEMENTS
464      //============================================================================
465    
466      //VCI element BaseBand
467      //          bbA     - see subclasses
468      //          bbB     - see subclasses
469      //          bw      - part of this class already
470      private TimeDuration       delayModelsValid;
471      //          inQuant - part of this class already
472      //          name    - using name of this instance
473      private PulsarGating       pulsarGating;
474      //          sid     - an optional attribute that we won't use, at least for now
475      private boolean            singlePhaseCenter;
476      //          subband - part of this class already
477      private WideBandCorrelator wideBandCorrelator;
478      
479      //VCI element BbParams - model in this class?
480      //  Probably not.  We might invent a non-public class to handle this.
481      //  Any exposure to public that might be needed will probably be done
482      //  in EvlaWidarConfiguration.
483      //    --DMH, 2009-02-10
484      
485      //VCI element BaseBandHw - model in this class?
486      //  No.  In fact, this element will not be handled by SSS software.
487      //    --DMH, 2009-02-10
488      
489      private void setVciDefaults()
490      {
491        singlePhaseCenter = true;
492        
493        overridePulsarGating = false;
494        pulsarGating         = null;
495        pulsarGatingCache    = VciJaxbUtil.makePulsarGating();
496        
497        overrideWideBandCorr = false;
498        wideBandCorrelator   = null;
499        wideBandCorrCache    = new WideBandCorrelator();
500    
501        overrideDelayModels = false;
502        delayModelsValid    = null;
503        delayModelsCache    = new TimeDuration("1.0", SECOND);
504      }
505      
506      /**
507       * Makes copies of the VCI elements of this baseband for <tt>other</tt> baseband.
508       */
509      private void copyVciElementsInto(WidarBaseband other)
510      {
511        other.singlePhaseCenter = this.singlePhaseCenter;
512        
513        other.overridePulsarGating = this.overridePulsarGating;
514        other.pulsarGating         = VciJaxbUtil.clone(this.pulsarGating);
515        other.pulsarGatingCache    = VciJaxbUtil.clone(this.pulsarGatingCache);
516        
517        other.overrideWideBandCorr = this.overrideWideBandCorr;
518        other.wideBandCorrelator   = VciJaxbUtil.clone(this.wideBandCorrelator);
519        other.wideBandCorrCache    = VciJaxbUtil.clone(this.wideBandCorrCache);
520        
521        other.overrideDelayModels = this.overrideDelayModels;
522        other.delayModelsValid    = (this.delayModelsValid == null) ? null : this.delayModelsValid.clone();
523        other.delayModelsCache    = (this.delayModelsCache == null) ? null : this.delayModelsCache.clone();
524      }
525      
526      /**
527       * Expresses this baseband as either one or two VCI <tt>BaseBand</tt> objects.
528       * If the initial quantization of this baseband is 8, the returned array will
529       * be of length two (unless this baseband has only one subband).
530       * If the initial quantization of this baseband is 3, the returned array will
531       * be of length one.
532       * 
533       * @return
534       *   an array containing one or two VCI <tt>BaseBand</tt> objects.
535       */
536      public BaseBand[] toVci()
537      {
538        //If this BB's IQ is 8-bit we'll split the subbands evenly over 2 WIDAR BBs
539        boolean needTwoBBs = (getInitialQuantization() == 8 && subbands.size() > 1);
540        
541        //Create array of 1 or 2 elements
542        BaseBand[] vciBB = new BaseBand[needTwoBBs ? 2 : 1];
543        vciBB[0] = new BaseBand();
544        if (needTwoBBs)
545          vciBB[1] = new BaseBand();
546    
547        for (int i=0; i < vciBB.length; i++)
548        {
549          //Attributes modeled directly as VCI elements
550          if (delayModelsValid != null)
551            vciBB[i].setDelayModelsValid(delayModelsValid.toUnits(SECOND).intValue());
552          
553          vciBB[i].setSinglePhaseCenter(singlePhaseCenter ? YES : NO);
554    
555          vciBB[i].setPulsarGating      (VciJaxbUtil.clone(pulsarGating));
556          vciBB[i].setWideBandCorrelator(VciJaxbUtil.clone(wideBandCorrelator));
557    
558          //Mapped from SSS objects
559          vciBB[i].setBw(bandwidth.toUnits(HERTZ).stripTrailingZeros().toPlainString());
560          vciBB[i].setInQuant(getInitialQuantization());
561          vciBB[i].setName(getName());
562          
563          setWidarBbIds(vciBB[i], i == 0);
564        }
565        
566        int sbCount    = subbands.size();
567        int sbCountBb1 = needTwoBBs ? sbCount / 2 : sbCount;
568    
569        //Put subbands into first BB
570        List<SubBand> vciSubbands = vciBB[0].getSubBand();
571        for (int s = 0; s < sbCountBb1; s++)
572          vciSubbands.add(((WidarSubband)subbands.get(s)).toVci()); //see note below
573    
574        //Put subbands into second BB, if applicable
575        if (needTwoBBs)
576        {
577          vciSubbands = vciBB[1].getSubBand();
578          for (int s = sbCountBb1; s < sbCount; s++)
579            vciSubbands.add(((WidarSubband)subbands.get(s)).toVci()); //see note below
580        }
581        
582        return vciBB;
583    
584        //Note: this cast should be safe because this class is in control of
585        //making new subband objects for itself.
586      }
587      
588      /** Sets bbA, and sometimes bbB, of widarBB. */
589      abstract void setWidarBbIds(BaseBand widarBB, boolean primary);
590      
591      /** Creates one or two BbParam objects and adds to list. */
592      abstract void addBbParams(List<BbParams> bbParamList, int stationId);
593      
594      /** Creates one or two BbParam objects and adds to list. */
595      void addBbParams(List<BbParams> bbParamList, int stationId, DigitalSignal inputSignal)
596      {
597        //A0, B1, C2, etc.
598        String signalName = inputSignal.getName();
599        
600        BigDecimal sslo   = inputSignal.getMappingIntercept().toUnits(MEGAHERTZ);
601        BigDecimal offset = calcLocOscOffset(stationId).toUnits(KILOHERTZ);
602        int[]      bbIds  = getVciBbIds(signalName);
603        
604        //The 8-bit IQ signals will lead to two VCI BBs; the 3-bit to one
605        BbParams bbParams1 = new BbParams();
606        
607        bbParams1.setBbid(bbIds[0]);
608        bbParams1.setFshiftKHz(offset.doubleValue());
609        bbParams1.setLoOMHz(sslo.doubleValue());
610        bbParams1.setPolarization(VciJaxbUtil.getPolarization(inputSignal.getPolarization()));
611        bbParams1.setSideband(proxiedRangeIsReversed() ? LOWER : UPPER);
612        bbParams1.setSourceId(getVciBbParamSrcId(signalName));
613        bbParams1.setSourceType(StbSourceType.FORM);
614        
615        bbParamList.add(bbParams1);
616        
617        //When the 8-bit IQ signals lead to two VCI BBs, the VCI BBs are identical
618        //with the exception of their ID numbers.
619        if (bbIds.length == 2)
620        {
621          BbParams bbParams2 = VciJaxbUtil.clone(bbParams1);
622          bbParams2.setBbid(bbIds[1]);
623          bbParamList.add(bbParams2);
624        }
625      }
626    
627      /** Calculates the LO offset for this baseband for the given antenna. */
628      Frequency calcLocOscOffset(int antennaId)
629      {
630        return new Frequency("0", FrequencyUnits.KILOHERTZ); //TODO code me
631      }
632      
633      /** Returns a VCI baseband ID for one of our IF signals. */
634      int[] getVciBbIds(String signalName)
635      {
636        //See Section 4.2 of EVLA Correlator System Numbering Plan
637        if      (signalName.equals("A0"))  return new int[] {0, 1};
638        else if (signalName.equals("C0"))  return new int[] {2, 3};
639        else if (signalName.equals("B0"))  return new int[] {4, 5};
640        else if (signalName.equals("D0"))  return new int[] {6, 7};
641        else if (signalName.equals("A1"))  return new int[] {0};
642        else if (signalName.equals("C1"))  return new int[] {1};
643        else if (signalName.equals("A2"))  return new int[] {2};
644        else if (signalName.equals("C2"))  return new int[] {3};
645        else if (signalName.equals("B1"))  return new int[] {4};
646        else if (signalName.equals("D1"))  return new int[] {5};
647        else if (signalName.equals("B2"))  return new int[] {6};
648        else if (signalName.equals("D2"))  return new int[] {7};
649        else
650          throw new RuntimeException("Cannot map unexpected signal name, '" +
651                                     signalName + "' to a WIDAR BB ID.");
652      }
653    
654      /** Picks a sourceId for bbParams element based on signalName. */
655      private int getVciBbParamSrcId(String signalName)
656      {
657        //This code is based on Figures 5-3 and 5-4 of vers. 3.5 of the VCI
658        //specification.  These figures are only examples; will need to
659        //check these assumptions w/ someone.
660        
661        //8-bit IQ
662        if (signalName.endsWith("0")) return 0;
663        
664        //3-bit IQ
665        if (signalName.startsWith("A")) return 0;
666        if (signalName.startsWith("B")) return 0;
667        if (signalName.startsWith("C")) return 1;
668        if (signalName.startsWith("D")) return 1;
669        
670        throw new RuntimeException("Cannot map unexpected signal name, '" +
671                                   signalName + "' to a WIDAR BB Source ID.");
672      }
673      
674      //----------------------------------------------------------------------------
675      // Pulsar Gating
676      //----------------------------------------------------------------------------
677    
678      private boolean      overridePulsarGating;
679      private PulsarGating pulsarGatingCache;
680    
681      /**
682       * Tells this baseband to override, or restore, pulsar gating defaults.
683       * 
684       * @param override
685       *   instruction to override, or restore, pulsar gating defaults.
686       *   
687       * @return
688       *   <i>null</i> if {@code override} is <i>false</i>.
689       *   If {@code override} is <i>true</i>, returns the <tt>PulsarGating</tt>
690       *   held internally by this baseband.  This means clients can operate
691       *   directly on the returned object and impact this baseband.
692       */
693      public PulsarGating overrideDefaultPulsarGating(boolean override)
694      {
695        //Changing state?
696        if (override != overridePulsarGating)
697        {
698          overridePulsarGating = override;
699          
700          if (override) //user-specified
701          {
702            pulsarGating = pulsarGatingCache;
703          }
704          else //default setting
705          {
706            pulsarGatingCache = VciJaxbUtil.clone(pulsarGating);
707            pulsarGating      = null;
708          }
709        }
710        
711        return pulsarGating;
712      }
713    
714      /**
715       * Returns the pulsar gating configuration, if any.
716       * The default value of this property is <i>null</i>.
717       * <p>
718       * This is a VCI property.
719       * The returned object was generated from VCI XML schema elements and
720       * is the actual instance held internally by this baseband.</p>
721       * 
722       * @return the pulsar gating configuration, if any.
723       * 
724       * @see #overrideDefaultPulsarGating(boolean)
725       */
726      @XmlElement(namespace="http://www.nrc.ca/namespaces/widar")
727      public PulsarGating getPulsarGating()  { return pulsarGating; }
728      
729      //This method is for persistence frameworks such as JAXB & Hibernate
730      @SuppressWarnings("unused")
731      private void setPulsarGating(PulsarGating newCfg)
732      {
733        pulsarGating = newCfg;
734        overridePulsarGating = (newCfg != null);
735      }
736      
737      //----------------------------------------------------------------------------
738      // Wide Band Correlator
739      //----------------------------------------------------------------------------
740      
741      private boolean            overrideWideBandCorr;
742      private WideBandCorrelator wideBandCorrCache;
743    
744      /**
745       * Tells this baseband to override, or restore, wide band correlator defaults.
746       * 
747       * @param override
748       *   instruction to override, or restore, wide band correlator defaults.
749       *   
750       * @return
751       *   <i>null</i> if {@code override} is <i>false</i>.
752       *   If {@code override} is <i>true</i>, returns the <tt>WideBandCorrelator</tt>
753       *   held internally by this baseband.  This means clients can operate
754       *   directly on the returned object and impact this baseband.
755       */
756      public WideBandCorrelator overrideDefaultWideBandCorrelator(boolean override)
757      {
758        //Changing state?
759        if (override != overrideWideBandCorr)
760        {
761          overrideWideBandCorr = override;
762    
763          if (override) //user-specified
764          {
765            wideBandCorrelator = wideBandCorrCache;
766          }
767          else //default setting
768          {
769            wideBandCorrCache  = VciJaxbUtil.clone(wideBandCorrelator);
770            wideBandCorrelator = null;
771          }
772        }
773        
774        return wideBandCorrelator;
775      }
776      
777      /**
778       * Returns the wide band correlation configuration, if any.
779       * The default value of this property is <i>null</i>.
780       * <p>
781       * This is a VCI property.
782       * The returned object was generated from VCI XML schema elements and
783       * is the actual instance held internally by this baseband.</p>
784       * 
785       * @return the wide band correlation configuration, if any.
786       * 
787       * @see #overrideDefaultWideBandCorrelator(boolean)
788       */
789      @XmlElement(namespace="http://www.nrc.ca/namespaces/widar")
790      public WideBandCorrelator getWideBandCorrelator()  { return wideBandCorrelator; }
791      
792      //This method is for persistence frameworks such as JAXB & Hibernate
793      @SuppressWarnings("unused")
794      private void setWideBandCorrelator(WideBandCorrelator newCfg)
795      {
796        wideBandCorrelator = newCfg;
797        overrideWideBandCorr = (newCfg != null);
798      }
799      
800      //----------------------------------------------------------------------------
801      // Delay Models
802      //----------------------------------------------------------------------------
803      
804      private boolean      overrideDelayModels;
805      private TimeDuration delayModelsCache;
806    
807      /**
808       * Tells this baseband to override, or restore, the default value of the delay
809       * models' lifetime.
810       * 
811       * @param override
812       *   instruction to override, or restore, the default value of the delay
813       *   models' lifetime.
814       *   
815       * @return
816       *   <i>null</i> if {@code override} is <i>false</i>.
817       *   If {@code override} is <i>true</i>, returns the <tt>WideBandCorrelator</tt>
818       *   held internally by this baseband.  This means clients can operate
819       *   directly on the returned object and impact this baseband.
820       */
821      public TimeDuration overrideDefaultDelayModelLifespan(boolean override)
822      {
823        //Changing state?
824        if (override != overrideDelayModels)
825        {
826          overrideDelayModels = override;
827    
828          if (override) //user-specified
829          {
830            delayModelsValid = delayModelsCache;
831          }
832          else //default setting
833          {
834            delayModelsCache = delayModelsValid.clone();
835            delayModelsValid = null;
836          }
837        }
838        
839        return delayModelsValid;
840      }
841    
842      /**
843       * Returns the length of time for which this baseband's delay models
844       * may remain valid.
845       * The default value of this property is <i>null</i>.
846       * <p>
847       * This is a VCI property.
848       * The returned object 
849       * is the actual instance held internally by this baseband.</p>
850       * 
851       * @return
852       *   the length of time for which this baseband's delay models
853       *   may remain valid.
854       */
855      @XmlElement
856      public TimeDuration getDelayModelLifespan()  { return delayModelsValid;  }
857      
858      //This method is for persistence frameworks such as JAXB & Hibernate
859      @SuppressWarnings("unused")
860      private void setDelayModelLifespan(TimeDuration newSpan)
861      {
862        delayModelsValid = newSpan;
863        overrideDelayModels = (newSpan != null);
864      }
865    
866      //----------------------------------------------------------------------------
867      // Other
868      //----------------------------------------------------------------------------
869      
870      /**
871       * Determines whether or not all the subbands of this baseband are required
872       * to have the same phase center.
873       * <p>
874       * This is a VCI property.  The default value is <i>true</i> or "yes".</p>
875       */
876      public void setSinglePhaseCenter(boolean singleCenter)
877      {
878        singlePhaseCenter = singleCenter;
879      }
880      
881      /**
882       * Returns <i>true</i> if all subbands of this baseband are required
883       * to have the same phase center.
884       * <p>
885       * This is a VCI property.</p>
886       * 
887       * @return
888       *   <i>true</i> if all subbands of this baseband are required
889       *   to have the same phase center.
890       */
891      public boolean getSinglePhaseCenter()  { return singlePhaseCenter; }
892    
893      //----------------------------------------------------------------------------
894      // Deprecated
895      //----------------------------------------------------------------------------
896    
897      /** @deprecated Always returns <i>null</i>. */
898      @Deprecated public InputStateCounts getInputStateCounts()  { return null; }
899      
900      /** @deprecated does nothing */
901      @XmlTransient
902      @Deprecated public void setFringeRotation(boolean turnOn)  { }
903      
904      /** @deprecated Always returns <i>false</i>. */
905      @Deprecated public boolean getFringeRotation()  { return false; }
906      
907      //============================================================================
908      // 
909      //============================================================================
910    
911      /**
912       * Returns an XML representation of this baseband.
913       * @return an XML representation of this baseband.
914       * @throws JAXBException if anything goes wrong during the conversion to XML.
915       * @see #writeAsXmlTo(Writer)
916       */
917      public String toXml() throws JAXBException
918      {
919        return JaxbUtility.getSharedInstance().objectToXmlString(this);
920      }
921    
922      /**
923       * Writes an XML representation of this baseband to {@code writer}.
924       * @param writer the device to which XML is written.
925       * @throws JAXBException if anything goes wrong during the conversion to XML.
926       */
927      public void writeAsXmlTo(Writer writer) throws JAXBException
928      {
929        JaxbUtility.getSharedInstance().writeObjectAsXmlTo(writer, this, null);
930      }
931    
932      //============================================================================
933      // 
934      //============================================================================
935    
936      /**
937       * Returns a copy of this baseband.
938       * <p>
939       * If anything goes wrong during the cloning procedure,
940       * a {@code RuntimeException} will be thrown.</p>
941       */
942      @Override
943      public WidarBaseband clone()
944      {
945        WidarBaseband clone = null;
946        
947        try
948        {
949          clone = (WidarBaseband)super.clone();
950          
951          clone.sbIdInUse = Arrays.copyOf(this.sbIdInUse, this.sbIdInUse.length);
952          
953          copyVciElementsInto(clone);
954        }
955        catch (Exception ex)
956        {
957          throw new RuntimeException(ex);
958        }
959    
960        return clone;
961      }
962      
963      /** Returns <i>true</i> if {@code o} is equal to this baseband. */
964      @Override
965      public boolean equals(Object o)
966      {
967        //Quick exit if super class says not equal
968        if (!super.equals(o))
969          return false;
970       
971        //A safe cast if we got this far
972        WidarBaseband other = (WidarBaseband)o;
973    
974        //VCI Objects
975        return
976          other.singlePhaseCenter == this.singlePhaseCenter &&
977          
978          objectsAreEqual(other.delayModelsValid, this.delayModelsValid) &&
979          
980          VciJaxbUtil.testEquality(other.pulsarGating,       this.pulsarGating) &&
981          VciJaxbUtil.testEquality(other.wideBandCorrelator, this.wideBandCorrelator);
982      }
983      
984      private boolean objectsAreEqual(Object thisOne, Object thatOne)
985      {
986        return (thisOne == null) ? (thatOne == null) : thisOne.equals(thatOne);
987      }
988      
989      @Override
990      public int hashCode()
991      {
992        //Taken from the Effective Java book by Joshua Bloch.
993        //The constants 17 & 37 are arbitrary & carry no meaning.
994        int result = super.hashCode();
995        
996        //You MUST keep this method in sync w/ the equals method
997        
998        result = 37 * result + (singlePhaseCenter ? 37 : 17);
999    
1000        if (delayModelsValid != null)
1001          result = 37 * result + delayModelsValid.hashCode();
1002        
1003        result = 37 * result + VciJaxbUtil.makeHashCode(pulsarGating);
1004        result = 37 * result + VciJaxbUtil.makeHashCode(wideBandCorrelator);
1005    
1006        return result;
1007      }
1008      
1009      //============================================================================
1010      // 
1011      //============================================================================
1012      /*
1013      public static void main(String... args) throws Exception
1014      {
1015        FrequencyRange freqRange = new FrequencyRange(new Frequency("0.0"), new Frequency("2.048"));
1016        DigitalSignal input = new DigitalSignal(freqRange, PolarizationType.L, new Frequency("2.0"), 2);
1017        WidarBaseband bb = new WidarBasebandSinglet(input);
1018        
1019        for (Frequency g : bb.getSubbandGrid())
1020          System.out.println(g);
1021      }
1022      */
1023    }