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.XmlElement;
007    import javax.xml.bind.annotation.XmlTransient;
008    import javax.xml.bind.annotation.XmlType;
009    
010    import org.apache.log4j.Logger;
011    
012    import edu.nrao.sss.electronics.Signal;
013    import edu.nrao.sss.measure.Frequency;
014    import edu.nrao.sss.measure.FrequencyRange;
015    import edu.nrao.sss.util.Identifiable;
016    
017    /**
018     * A partial implementation of a {@link CorrelatorBaseband}.
019     * <p>
020     * <b>Version Info:</b>
021     * <table style="margin-left:2em">
022     *   <tr><td>$Revision: 2289 $</td></tr>
023     *   <tr><td>$Date: 2009-05-07 16:13:41 -0600 (Thu, 07 May 2009) $</td></tr>
024     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
025     * </table></p>
026     * 
027     * @author David M. Harland
028     * @since 2008-03-06
029     */
030    @XmlType(propOrder={"bandwidth"
031                        })
032    public abstract class CorrelatorBasebandAbs
033      implements CorrelatorBaseband
034    {
035      private static final Logger LOG = Logger.getLogger(CorrelatorBasebandAbs.class);
036      
037      private Long id; //A unique identifier for the persistence layer.
038    
039      @XmlElement protected Frequency bandwidth;
040      
041      private   BandwidthHelper            bwHelper;
042      protected List<CorrelatorSubbandAbs> subbands;
043      
044      /** Helps create a new correlator baseband. */
045      protected CorrelatorBasebandAbs()
046      {
047        id        = Identifiable.UNIDENTIFIED;
048        bandwidth = new Frequency("0.0");
049        bwHelper  = new BandwidthHelper(this);
050        subbands  = new ArrayList<CorrelatorSubbandAbs>();
051      }
052    
053      //============================================================================
054      // IDENTIFICATION
055      //============================================================================
056    
057      /* (non-Javadoc)
058       * @see edu.nrao.sss.util.Identifiable#getId()
059       */
060      public Long getId() { return id; }
061    
062      @SuppressWarnings("unused")
063      private void setId(Long id)  { this.id = id; }
064      
065      /* (non-Javadoc)
066       * @see edu.nrao.sss.model.resource.CorrelatorBaseband#clearId()
067       */
068      public void clearId()
069      {
070        id = Identifiable.UNIDENTIFIED;
071        
072        for (CorrelatorSubbandAbs sb : subbands)
073          sb.clearId();
074      }
075    
076      public boolean isPair()  { return !isSinglet(); }
077    
078      //============================================================================
079      // CONTAINER
080      //============================================================================
081    
082      /**
083       * Returns the correlator configuration that holds this baseband.
084       * It is possible for the returned value to be <i>null</i>.
085       */
086      protected abstract CorrelatorConfiguration getContainer();
087      
088      /**
089       * Sets the correlator configuration to which this baseband belongs.
090       * The {@code newContainer} is allowed to be <i>null</i>.
091       */
092      protected abstract void setContainer(CorrelatorConfiguration newContainer);
093      
094      //============================================================================
095      // FREQUENCY
096      //============================================================================
097      
098      /* (non-Javadoc)
099       * @see CorrelatorBaseband#getAllowableBandwidthFor(Frequency)
100       */
101      public Frequency getAllowableBandwidthFor(Frequency frequency)
102      {
103        return bwHelper.getAllowableBandwidthFor(frequency);
104      }
105      
106      /* (non-Javadoc)
107       * @see CorrelatorBaseband#getAllowableBandwidthClosestTo(Frequency)
108       */
109      public Frequency getAllowableBandwidthClosestTo(Frequency frequency)
110      {
111        return bwHelper.getAllowableBandwidthClosestTo(frequency);
112      }
113    
114      /* (non-Javadoc)
115       * @see CorrelatorBaseband#setBandwidth(Frequency)
116       */
117      public void setBandwidth(Frequency newWidth)
118      {
119        if (newWidth == null)
120          throw new IllegalArgumentException(
121            "Cannot set bandwidth of baseband to NULL.");
122    
123        bandwidth = getAllowableBandwidthClosestTo(newWidth);
124      }
125      
126      /**
127       * Returns a copy of the bandwidth of this baseband.
128       * @return a copy of the bandwidth of this baseband.
129       */
130      @XmlTransient
131      public Frequency getBandwidth()
132      {
133        return bandwidth.clone();
134      }
135    
136      /**
137       * Returns the original frequency range represented by {@code signal} and
138       * the smaller of the signal's bandwidth and the bandwidth of this baseband.
139       * 
140       * @param signal
141       *   a signal that may have been manipulated in such a way that its frequency
142       *   range is no longer the same as it had been when the signal was created.
143       *   
144       * @return
145       *   the original frequency range represented by {@code signal}. 
146       */
147      protected FrequencyRange getProxiedRange(Signal signal)
148      {
149        FrequencyRange proxied  = signal.getProxiedRange();
150        Frequency      signalBw = proxied.getWidth();
151        
152        //If the bandwidth of this baseband and the proxy signal are the
153        //same, just get the proxied range from the input.  Otherwise we
154        //need to construct the range ourself.
155        if (!bandwidth.equals(signalBw))
156        {
157          Frequency bw = (signalBw.compareTo(bandwidth) <= 0) ? signalBw : bandwidth;
158          
159          //We use the terms "left" and "right", below, because the proxied
160          //frequency range and the baseband may be flipped with respect to
161          //direction of increasing frequencies.  E.g., a baseband of 
162          //0 - 2 GHz might represent 44 - 42 GHz, in that order.  If we're
163          //using a baseband bandwidth != signal band width, we need to
164          //know whether the 0 GHz, or left, frequency of the BB represents the
165          //low or the high of the signal.
166          Frequency left, right;
167          
168          if (signal.proxiedRangeIsReversed())
169          {
170            left  = proxied.getHighFrequency();
171            right = left.clone().subtract(bw);
172          }
173          else //"normal"
174          {
175            left  = proxied.getLowFrequency();
176            right = left.clone().add(bw);
177          }
178    
179          proxied = new FrequencyRange(left, right);
180        }
181        
182        return proxied;
183      }
184    
185      //============================================================================
186      // SUBBAND
187      //============================================================================
188      
189      public int getSubbandCount()
190      {
191        return subbands.size();
192      }
193    
194      public List<CorrelatorSubband> getSubbands()
195      {
196        return new ArrayList<CorrelatorSubband>(subbands);
197      }
198      
199      /**
200       * The {@link CorrelatorBaseband#addSubband(CorrelatorSubband)} method
201       * of classes that extend this one should call this method.
202       * This method will:
203       * <ol>
204       *   <li>Ensure that this baseband does not yet hold its maximum number
205       *       of subbands.  If it does, an {@code IllegalArgumentException}
206       *       is thrown.</li>
207       *   <li>Remove {@code newSubband} from its former baseband, if any.</li>
208       *   <li>Inform {@code newSubband} that <i>this</i> is its new baseband.</li>
209       *   <li>Add {@code newSubband} to this baseband's collection of subbands.</li>
210       * </ol>
211       * The main duty of an extending class is to ensure that the type of subband
212       * passed to it is of an acceptable type.  For example, an <tt>XyzBaseband</tt>
213       * may want to accept only <tt>XyzSubband</tt> instances.
214       * 
215       * @param newSubband
216       *   a new subband for this baseband.
217       */
218      protected void addNewSubband(CorrelatorSubbandAbs newSubband)
219      {
220        if (getSubbandCount() >= getMaxSubbandCount())
221          throw new IllegalArgumentException("Baseband " + getName() +
222            " already has its maximum of " + getMaxSubbandCount() +
223            " subbands.  This subband was not added.");
224        
225        //If the incoming subband is associated with a different baseband,
226        //break that connection first.
227        CorrelatorBasebandAbs oldBb = newSubband.getContainer();
228        if (oldBb != null)
229          oldBb.removeSubband(newSubband);
230        
231        newSubband.setContainer(this);
232        
233        subbands.add(newSubband);
234      }
235      
236      public boolean removeSubband(CorrelatorSubband unwantedSubband)
237      {
238        if (unwantedSubband instanceof CorrelatorSubbandAbs)
239          return removeOldSubband((CorrelatorSubbandAbs)unwantedSubband);
240        else
241          return false;
242      }
243      
244      protected boolean removeOldSubband(CorrelatorSubbandAbs unwantedSubband)
245      {
246        boolean removed = subbands.remove(unwantedSubband);
247        
248        if (removed)
249          unwantedSubband.setContainer(null);
250        
251        return removed; 
252      }
253      
254      public CorrelatorSubbandAbs removeSubbandAt(int index)
255      {
256        CorrelatorSubbandAbs removedSubband = null;
257        
258        if (index >= 0 && index < subbands.size())
259        {
260          removedSubband = subbands.remove(index);
261          removedSubband.setContainer(null);
262        }
263        
264        return removedSubband;
265      }
266    
267      public int removeAllSubbands()
268      {
269        int oldCount = subbands.size();
270        
271        subbands.clear();
272     
273        return oldCount;
274      }
275      
276      @SuppressWarnings("unused")  //Used by Hibernate
277      private void setSubbandList(List<CorrelatorSubbandAbs> replacementList)
278      {
279        LOG.debug("Setting subband list. Size = "+(replacementList==null?0:replacementList.size()));
280    
281        subbands =
282          replacementList == null ? new ArrayList<CorrelatorSubbandAbs>()
283                                  : replacementList;
284      }
285      @SuppressWarnings("unused")  //Used by Hibernate
286      private List<CorrelatorSubbandAbs> getSubbandList()
287      {
288        LOG.debug("Getting subband list; subbands.size = "+subbands.size());
289        return subbands;
290      }
291      
292      /**
293       * Called after subbands were created from a persistent store,
294       * such as a database or XMl file.
295       */
296      protected void createdSubbandsFromPersistentStore()
297      {
298        LOG.debug("Setting container of subbands; subbands.size = "+subbands.size());
299    
300        //Because of the way we have the Hibernate mappings configured, we must
301        //call the method below and not simply set the variable.
302        for (CorrelatorSubbandAbs s : subbands)
303          s.setContainer(this);
304      }
305    
306      //============================================================================
307      // 
308      //============================================================================
309      
310      @Override
311      public String toString()
312      {
313        StringBuilder buff = new StringBuilder();
314        final String PAIR_SEP = "; ";
315        final String NV_SEP   = " = ";
316        
317        buff.append("name").append(NV_SEP).append(getName()).append(PAIR_SEP);
318        buff.append("polarizations").append(NV_SEP).append(getPolarizations()).append(PAIR_SEP);
319        buff.append("bb.BW").append(NV_SEP).append(getBandwidth()).append(PAIR_SEP);
320        buff.append("proxiedRange").append(NV_SEP).append(getProxiedRange()).append(PAIR_SEP);
321        
322        return buff.toString();
323      }
324      
325      /**
326       *  Returns a copy of this baseband.
327       *  <p>
328       *  If anything goes wrong during the cloning procedure,
329       *  a {@code RuntimeException} will be thrown.</p>
330       */
331      @Override
332      public CorrelatorBasebandAbs clone()
333      {
334        CorrelatorBasebandAbs clone = null;
335        
336        try
337        {
338          //This line takes care of the primitive & immutable fields properly
339          clone = (CorrelatorBasebandAbs)super.clone();
340          
341          //We do NOT want the clone to have the same ID as the original.
342          //The ID is here for the persistence layer; it is in charge of
343          //setting IDs.  To help it, we put the clone's ID in the uninitialized
344          //state.
345          clone.id = Identifiable.UNIDENTIFIED;
346    
347          clone.bandwidth = this.bandwidth.clone();
348          clone.bwHelper  = new BandwidthHelper(clone);
349          
350          //Clone individual subbands and use method to add to clone's list
351          clone.subbands = new ArrayList<CorrelatorSubbandAbs>();
352          for (CorrelatorSubbandAbs sb : this.subbands)
353            clone.addNewSubband(sb.clone());
354        }
355        catch (Exception ex)
356        {
357          throw new RuntimeException(ex);
358        }
359    
360        return clone;
361      }
362      
363      /**
364       * Returns <i>true</i> if <tt>o</tt> is equal to this baseband.
365       * <p>
366       * Most, but not all, public attributes take place in the comparison
367       * Those that do not are:</p>
368       * <ol>
369       *   <li>The ID.</li>
370       * </ol>
371       */
372      @Override
373      //TODO do we want value equality?
374      public boolean equals(Object o)
375      {
376        //Quick exit if o is this
377        if (o == this)
378          return true;
379        
380        //Quick exit if o is null
381        if (o == null)
382          return false;
383       
384        //Quick exit if classes are different
385        if (!o.getClass().equals(this.getClass()))
386          return false;
387       
388        //A safe cast if we got this far
389        CorrelatorBasebandAbs other = (CorrelatorBasebandAbs)o;
390        
391        //Attributes that we INTENTIONALLY DO NOT COMPARE:
392        //  id, bwHelper
393    
394        return
395          other.bandwidth.equals(this.bandwidth) &&
396          other.subbands.equals(this.subbands);
397      }
398    
399      /** Returns a hash code value for this baseband. */
400      @Override
401      //TODO remove?
402      public int hashCode()
403      {
404        //Taken from the Effective Java book by Joshua Bloch.
405        //The constants 17 & 37 are arbitrary & carry no meaning.
406        int result = 17;
407        
408        //You MUST keep this method in sync w/ the equals method
409        
410        result = 37 * result + bandwidth.hashCode();
411        result = 37 * result + subbands.hashCode();
412        
413        return result;
414      }
415    }