001    package edu.nrao.sss.model.resource;
002    
003    import java.util.ArrayList;
004    import java.util.List;
005    
006    import javax.xml.bind.annotation.XmlAttribute;
007    import javax.xml.bind.annotation.XmlElement;
008    import javax.xml.bind.annotation.XmlTransient;
009    import javax.xml.bind.annotation.XmlType;
010    
011    import org.apache.log4j.Logger;
012    
013    import edu.nrao.sss.math.MathUtil;
014    import edu.nrao.sss.measure.Frequency;
015    import edu.nrao.sss.measure.FrequencyRange;
016    import edu.nrao.sss.util.Identifiable;
017    
018    /**
019     * A partial implementation of a {@link CorrelatorSubband}.
020     * <p>
021     * <b>Version Info:</b>
022     * <table style="margin-left:2em">
023     *   <tr><td>$Revision: 2289 $</td></tr>
024     *   <tr><td>$Date: 2009-05-07 16:13:41 -0600 (Thu, 07 May 2009) $</td></tr>
025     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
026     * </table></p>
027     * 
028     * @author David M. Harland
029     * @since 2008-03-11
030     */
031    @XmlType(propOrder={"name", "freqRange","requantizationBits"
032                        })
033    public abstract class CorrelatorSubbandAbs
034      implements CorrelatorSubband
035    {
036      private static final Logger LOG = Logger.getLogger(CorrelatorSubbandAbs.class);
037      
038      private Long id; //A unique identifier for the persistence layer.
039    
040      private CorrelatorBasebandAbs baseband;
041      
042      @XmlAttribute(required=false)
043      protected String name;
044      
045      @XmlElement(name="frequencyRange")
046      protected FrequencyRange freqRange;
047      
048      @XmlAttribute(required=true)
049      protected int requantizationBits;
050      
051      protected List<CorrelationProductGroupAbs> productGroups;
052      
053      private BandwidthHelper bwHelper;
054      
055      /** Helps create a new correlator subband. */
056      protected CorrelatorSubbandAbs()
057      {
058        id            = Identifiable.UNIDENTIFIED;
059        name          = null;
060        freqRange     = new FrequencyRange();
061        baseband      = null;
062        productGroups = new ArrayList<CorrelationProductGroupAbs>();
063        bwHelper      = new BandwidthHelper(this);
064      }
065    
066      //============================================================================
067      // IDENTIFICATION
068      //============================================================================
069    
070      /* (non-Javadoc)
071       * @see edu.nrao.sss.util.Identifiable#getId()
072       */
073      public Long getId() { return id; }
074    
075      @SuppressWarnings("unused")
076      private void setId(Long id)  { this.id = id; }
077      
078      public void clearId()
079      {
080        id = Identifiable.UNIDENTIFIED;
081        
082        for (CorrelationProductGroupAbs group : productGroups)
083          group.clearId();
084      }
085      
086      @XmlTransient
087      public void setName(String newName)
088      {
089        name = newName; //null is a permitted value
090      }
091      
092      public String getName()
093      {
094        return name == null ? "" : name;
095      }
096    
097      //============================================================================
098      // FREQUENCY
099      //============================================================================
100      
101      /* (non-Javadoc)
102       * @see CorrelatorBaseband#getAllowableBandwidthFor(Frequency)
103       */
104      public Frequency getAllowableBandwidthFor(Frequency frequency)
105      {
106        return bwHelper.getAllowableBandwidthFor(frequency);
107      }
108      
109      /* (non-Javadoc)
110       * @see CorrelatorBaseband#getAllowableBandwidthClosestTo(Frequency)
111       */
112      public Frequency getAllowableBandwidthClosestTo(Frequency frequency)
113      {
114        return bwHelper.getAllowableBandwidthClosestTo(frequency);
115      }
116    
117      /* (non-Javadoc)
118       * @see CorrelatorSubband#setBandwidth(Frequency)
119       */
120      @XmlTransient
121      public void setBandwidth(Frequency newWidth)
122      {
123        if (newWidth == null)
124          throw new IllegalArgumentException(
125            "Cannot set bandwidth of subband to NULL.");
126    
127        //Force to a legal value
128        newWidth = getAllowableBandwidthClosestTo(newWidth);
129    
130        //The frequency range class will normally put the center where requested.
131        //However, if the low frequency of the range would be negative, the
132        //freq range class will move the low freq to zero and the center to BW/2.
133        freqRange.setCenterAndWidth(freqRange.getCenterFrequency(), newWidth);
134        
135        //If the current center and old width had the high end of this subband
136        //at or near upper edge of baseband, we may need to move the center
137        //to accommodate the new bw so that we don't spill over upper BB freq.
138        Frequency maxCenter = getMaximumCentralFrequency();
139        if (getCentralFrequency().compareTo(maxCenter) > 0)
140          freqRange.setCenterAndWidth(maxCenter, newWidth);
141      }
142      
143      /* (non-Javadoc)
144       * @see edu.nrao.sss.model.resource.HasBandwidth#getBandwidth()
145       */
146      public Frequency getBandwidth()  { return freqRange.getWidth(); }
147    
148      /* (non-Javadoc)
149       * @see CorrelatorSubband#setCentralFrequency(Frequency)
150       */
151      @XmlTransient
152      public void setCentralFrequency(Frequency newCenter)
153      {
154        if (newCenter == null)
155          throw new IllegalArgumentException(
156            "Cannot set center of subband to NULL.");
157        
158        //Cap new center at max
159        Frequency maxCenter = getMaximumCentralFrequency();
160        if (newCenter.compareTo(maxCenter) > 0)
161          newCenter = maxCenter;
162    
163        //Don't need to test against min because the freq range class
164        //will guard against a negative low freq by moving the center
165        //up to BW/2.
166        freqRange.setCenterAndWidth(newCenter, freqRange.getWidth());
167      }
168      
169      /* (non-Javadoc)
170       * @see CorrelatorSubband#getCentralFrequency()
171       */
172      public Frequency getCentralFrequency()
173      {
174        return freqRange.getCenterFrequency();
175      }
176    
177      public Frequency getMinimumCentralFrequency()
178      {
179        return freqRange.getWidth().divideBy("2.0");
180      }
181    
182      public Frequency getMaximumCentralFrequency()
183      {
184        Frequency maxCenter = new Frequency(MathUtil.POSITIVE_INFINITY);
185        
186        if (baseband != null)
187        {
188          Frequency halfBw = freqRange.getWidth().divideBy("2.0");
189          maxCenter = baseband.getBandwidth().subtract(halfBw);
190        }
191        
192        return maxCenter;
193      }
194      
195      /* (non-Javadoc)
196       * @see CorrelatorSubband#getFrequencyRange()
197       */
198      public FrequencyRange getFrequencyRange()  { return freqRange.clone(); }
199      
200      /* (non-Javadoc)
201       * @see edu.nrao.sss.model.resource.CorrelatorSubband#getProxiedRange()
202       */
203      public FrequencyRange getProxiedRange()
204      {
205        FrequencyRange answer;
206        
207        //If we have a baseband, use the subband's location in the baseband
208        //and the baseband's representation of the original frequency range
209        //to determine what part of that range this subband represents.
210        if (baseband != null)
211        {
212          FrequencyRange bbProxiedRange = baseband.getProxiedRange();
213          
214          Frequency sbProxyCenter = freqRange.getCenterFrequency();
215          
216          //We need to know whether increasing baseband frequencies represent
217          //increasing or decreasing proxied frequencies.
218          if (baseband.proxiedRangeIsReversed())
219            sbProxyCenter = bbProxiedRange.getHighFrequency().subtract(sbProxyCenter);
220          else
221            sbProxyCenter.add(bbProxiedRange.getLowFrequency());
222          
223          answer = new FrequencyRange().setCenterAndWidth(sbProxyCenter,
224                                                          freqRange.getWidth());
225        }
226        else //no baseband, just use subband's nominal range
227        {
228          answer = freqRange.clone();
229        }
230        
231        return answer;
232      }
233    
234      /* (non-Javadoc)
235       * @see CorrelatorSubband#setCentralProxiedFrequency(Frequency)
236       */
237      //@Override
238      public void setCentralProxiedFrequency(Frequency newSkyCenter)
239      {
240        Frequency center;
241        
242        if (baseband != null)
243        {
244          FrequencyRange bbProxiedRange = baseband.getProxiedRange();
245          
246          //We need to know whether increasing baseband frequencies represent
247          //increasing or decreasing proxied frequencies.
248          if (baseband.proxiedRangeIsReversed())
249            center = bbProxiedRange.getHighFrequency().subtract(newSkyCenter);
250          else
251            center = newSkyCenter.clone().subtract(bbProxiedRange.getLowFrequency());
252        }
253        else
254        {
255          center = newSkyCenter;
256        }
257        
258        setCentralFrequency(center);
259      }
260      
261      //============================================================================
262      // QUANTIZATION
263      //============================================================================
264      
265      /* (non-Javadoc)
266       * @see CorrelatorSubband#getRequantization()
267       */
268      public int getRequantization()  { return requantizationBits; }
269      
270      //============================================================================
271      // CORRELATION PRODUCT GROUPS
272      //============================================================================
273      
274      /**
275       * Creates and returns a new product group that could be used with this
276       * subband.
277       */
278      abstract protected CorrelationProductGroupAbs makeCorrelationProductGroup();
279      
280      /**
281       * Returns this subband's collection of correlation product groups.
282       * <p>
283       * The returned collection is guaranteed to be non-null, but it may be empty.
284       * The returned collection is a copy of the one held internally, so
285       * changes to the structure of the collection by clients will not impact
286       * this object.  The members of the collection, however, are those referenced
287       * by this subband, so changes to them will be reflected herein.</p>
288       * 
289       * @return this subband's collection of correlation product groups.
290       */
291      public List<CorrelationProductGroup> getCorrelationProductGroups()
292      {
293        return new ArrayList<CorrelationProductGroup>(productGroups);
294      }
295      
296      /**
297       * Creates a new correlation product group, adds it to this subband, and
298       * returns it.
299       * 
300       * @return a new correlation product group of this subband.
301       */
302      public CorrelationProductGroupAbs addNewCorrelationProductGroup()
303      {
304        CorrelationProductGroupAbs newGroup = makeCorrelationProductGroup();
305        
306        productGroups.add(newGroup);
307        
308        return newGroup;
309      }
310      
311      /**
312       * Removes a correlation product group from this subband.
313       * 
314       * @param unwantedGroup
315       *   a correlation product group to be removed from this subband.
316       */
317      public void removeCorrelationProductGroup(CorrelationProductGroup unwantedGroup)
318      {
319        productGroups.remove(unwantedGroup);
320      }
321      
322      @SuppressWarnings("unused")  //Used by Hibernate
323      private void setProdGrpList(List<CorrelationProductGroupAbs> replacementList)
324      {
325        LOG.debug("Setting productGroup list. Size = "+(replacementList==null?0:replacementList.size()));
326    
327        productGroups =
328          replacementList == null ? new ArrayList<CorrelationProductGroupAbs>()
329                                  : replacementList;
330      }
331      @SuppressWarnings("unused")  //Used by Hibernate
332      private List<CorrelationProductGroupAbs> getProdGrpList()
333      {
334        LOG.debug("Getting productGroup list; productGroups.size = "+productGroups.size());
335        return productGroups;
336      }
337      
338      /**
339       * Called after product groups were created from a persistent store,
340       * such as a database or XMl file.
341       */
342      protected void createdProdGrpsFromPersistentStore()
343      {
344        LOG.debug("Setting container of productGroups; productGroups.size = "+productGroups.size());
345    
346        //Because of the way we have the Hibernate mappings configured, we must
347        //call the method below and not simply set the variable.
348        for (CorrelationProductGroupAbs p : productGroups)
349          p.setContainer(this);
350      }
351    
352      //============================================================================
353      // BASEBAND
354      //============================================================================
355    
356      /* (non-Javadoc)
357       * @see CorrelatorSubband#getBaseband()
358       */
359      public CorrelatorBaseband getBaseband()  { return baseband; }
360    
361      /** Primarily for use by persistence mechanism. */
362      protected void setContainer(CorrelatorBasebandAbs newContainer)
363      {
364        baseband = newContainer;
365      }
366      
367      /** Primarily for use by persistence mechanism. */
368      protected CorrelatorBasebandAbs getContainer()
369      {
370        return baseband;
371      }
372      
373      //============================================================================
374      // VALIDATION
375      //============================================================================
376    
377      //============================================================================
378      // 
379      //============================================================================
380      
381      /**
382       *  Returns a copy of this subband.
383       *  <p>
384       *  The returned subband is <i>nearly</i>, but not quite, a deep copy of this
385       *  subband.  Properties that are not copied:</p>
386       *  <ol>
387       *    <li><b>id</b> - this is left in the <tt>UNDEFINED</tt> state.</li>
388       *    <li><b>baseband</b> - this value will be <i>null</i>.</li>
389       *  </ol>
390       *  <p>
391       *  If anything goes wrong during the cloning procedure,
392       *  a {@code RuntimeException} will be thrown.</p>
393       */
394      @Override
395      public CorrelatorSubbandAbs clone()
396      {
397        CorrelatorSubbandAbs clone = null;
398        
399        try
400        {
401          //This line takes care of the primitive & immutable fields properly
402          clone = (CorrelatorSubbandAbs)super.clone();
403          
404          //We do NOT want the clone to have the same ID as the original.
405          //The ID is here for the persistence layer; it is in charge of
406          //setting IDs.  To help it, we put the clone's ID in the uninitialized
407          //state.
408          clone.id = Identifiable.UNIDENTIFIED;
409          
410          clone.baseband  = null;
411          clone.freqRange = this.freqRange.clone();
412          
413          clone.productGroups = new ArrayList<CorrelationProductGroupAbs>();
414          for (CorrelationProductGroupAbs thisGroup : this.productGroups)
415          {
416            CorrelationProductGroupAbs newGroup = makeCorrelationProductGroup();
417            thisGroup.copyInto(newGroup);
418            clone.productGroups.add(newGroup);
419          }
420        }
421        catch (Exception ex)
422        {
423          throw new RuntimeException(ex);
424        }
425    
426        return clone;
427      }
428      
429      /**
430       * Returns <i>true</i> if <tt>o</tt> is equal to this subband.
431       * <p>
432       * Most, but not all, public attributes take place in the comparison
433       * Those that do not are:</p>
434       * <ol>
435       *   <li>The ID.</li>
436       *   <li>The containing baseband.</li>
437       * </ol>
438       */
439      @Override
440      public boolean equals(Object o)
441      {
442        //Quick exit if o is this
443        if (o == this)
444          return true;
445        
446        //Quick exit if o is null
447        if (o == null)
448          return false;
449       
450        //Quick exit if classes are different
451        if (!o.getClass().equals(this.getClass()))
452          return false;
453       
454        //A safe cast if we got this far
455        CorrelatorSubbandAbs other = (CorrelatorSubbandAbs)o;
456        
457        //Attributes that we INTENTIONALLY DO NOT COMPARE:
458        //  id, baseband, bwHelper
459        
460        //Simple properties
461        if ((other.name == null && this.name != null) ||
462            (other.name != null && this.name == null))
463          return false;
464        
465        if (other.name != null && !other.name.equals(this.name))
466          return false;
467        
468        if (other.requantizationBits != this.requantizationBits ||
469            !other.freqRange.equals(this.freqRange))
470          return false;
471    
472        //Correlation products.  This is an expensive test, so save for last.
473        if (other.productGroups.size() != this.productGroups.size())
474        {
475          return false;
476        }
477        else //same # of product groups
478        {
479          for (CorrelationProductGroupAbs thisGroup : this.productGroups)
480          {
481            boolean found = false;
482            
483            for (CorrelationProductGroupAbs otherGroup : other.productGroups)
484            {
485              if (otherGroup.equals(thisGroup))
486              {
487                found = true;
488                break;
489              }
490            }
491            
492            if (!found)
493              return false;
494          }
495        }
496    
497        //No differences found
498        return true;
499      }
500    
501      /** Returns a hash code value for this subband. */
502      @Override
503      public int hashCode()
504      {
505        //Taken from the Effective Java book by Joshua Bloch.
506        //The constants 17 & 37 are arbitrary & carry no meaning.
507        int result = 17;
508        
509        //You MUST keep this method in sync w/ the equals method
510        
511        if (name != null)
512          result = 37 * result + name.hashCode();
513    
514        result = 37 * result + requantizationBits;
515        result = 37 * result + freqRange.hashCode();
516        result = 37 * result + productGroups.hashCode();
517        
518        return result;
519      }
520    }