001    package edu.nrao.sss.model.resource;
002    
003    import java.util.List;
004    import java.util.Map;
005    import java.util.Set;
006    import java.util.SortedMap;
007    import java.util.SortedSet;
008    import java.util.TreeMap;
009    import java.util.TreeSet;
010    import java.util.UUID;
011    
012    import javax.xml.bind.annotation.XmlAttribute;
013    import javax.xml.bind.annotation.XmlElement;
014    import javax.xml.bind.annotation.XmlElementWrapper;
015    import javax.xml.bind.annotation.XmlID;
016    
017    import edu.nrao.sss.astronomy.PolarizationType;
018    import edu.nrao.sss.astronomy.StokesParameter;
019    import edu.nrao.sss.util.Identifiable;
020    
021    /**
022     * Partial implementation of a
023     * {@link CorrelationProductGroup correlation product group}.
024     * <p>
025     * <b>Version Info:</b>
026     * <table style="margin-left:2em">
027     *   <tr><td>$Revision: 2289 $</td></tr>
028     *   <tr><td>$Date: 2009-05-07 16:13:41 -0600 (Thu, 07 May 2009) $</td></tr>
029     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
030     * </table></p>
031     * 
032     * @author David M. Harland
033     * @since 2008-11-25
034     */
035    public abstract class CorrelationProductGroupAbs
036      implements CorrelationProductGroup
037    {
038      private Long id; //A unique identifier for the persistence layer.
039      
040      @XmlAttribute(required=true)
041      @XmlID
042      private String uuid;
043    
044      /**
045       * The subband that holds this group.
046       * Under normal circumstances this value is not <i>null</i>.  However, most
047       * uses of this variable protect against <i>null</i> because when this object
048       * is first reconstructed from XML or a database, this variable may be
049       * <i>null</i> momentarily.
050       * <p>
051       * Subclass authors should try to prevent this value from being <i>null</i>.</p>
052       */
053      private CorrelatorSubbandAbs subband;
054    
055      /**
056       * A sorted map whose keys are correlation types and whose values are
057       * spectral channel counts. 
058       * Subclasses must not set this value to <i>null</i>.
059       */
060      protected SortedMap<StokesParameter, Integer> channels;
061      
062      /**
063       * Set to <i>true</i> when the previously calculated channels is a stale
064       * value.
065       */
066      protected boolean needToRecalcChannels;
067    
068      /** Helps create a new group. */
069      protected CorrelationProductGroupAbs()
070      {
071        id = Identifiable.UNIDENTIFIED;
072    
073        uuid = "CPG-"+UUID.randomUUID().toString();
074    
075        channels = new TreeMap<StokesParameter, Integer>();
076        
077        needToRecalcChannels = true;
078      }
079    
080      //============================================================================
081      // IDENTIFICATION
082      //============================================================================
083    
084      /* (non-Javadoc)
085       * @see edu.nrao.sss.util.Identifiable#getId()
086       */
087      public Long getId() { return id; }
088    
089      @SuppressWarnings("unused")
090      private void setId(Long id)  { this.id = id; }
091      
092      public void clearId()
093      {
094        id = Identifiable.UNIDENTIFIED;
095      }
096      
097      public String getUUID()  { return uuid; }
098    
099      //============================================================================
100      // SUBBAND
101      //============================================================================
102    
103      /**
104       * Primarily for use by persistence mechanism.
105       * Subclass authors should try to prevent this value from being <i>null</i>.
106       */
107      protected void setContainer(CorrelatorSubbandAbs newContainer)
108      {
109        subband = newContainer;
110      }
111      
112      /** Primarily for use by persistence mechanism. */
113      protected CorrelatorSubbandAbs getContainer()
114      {
115        return subband;
116      }
117    
118      //============================================================================
119      // CORRELATION PRODUCTS
120      //============================================================================
121    
122      /**
123       * Called when something that can influence the total number of channels,
124       * or the distribution of channels among products, has changed.
125       * The main job of this method is to update the <tt>channels</tt> map.
126       * Implementations should also set the variable <tt>needToRecalcChannels</tt>
127       * to <i>false</i> upon successful recalculation.
128       */
129      abstract protected void recalculateChannels();
130      
131      /**
132       * Removes from the <tt>channels</tt> map any entries for Stokes parameters
133       * that are not in the allowable set.
134       */
135      protected void removeDisallowedProducts()
136      {
137        SortedSet<StokesParameter> allowed = getAllowablePolarizationProducts();
138    
139        Set<StokesParameter> disallowed = new TreeSet<StokesParameter>();
140        
141        //Can't remove while in this loop or get ConcurrentModificationException
142        for (StokesParameter sp : channels.keySet())
143          if (!allowed.contains(sp))
144            disallowed.add(sp);
145        
146        for (StokesParameter sp : disallowed)
147          channels.remove(sp);
148      }
149      
150      /* (non-Javadoc)
151       * @see edu.nrao.sss.model.resource.CorrelatorSubband#getAllowablePolarizationProducts()
152       */
153      public SortedSet<StokesParameter> getAllowablePolarizationProducts()
154      {
155        SortedSet<StokesParameter> allowableProducts;
156        
157        CorrelatorBaseband bb = null;
158        
159        if (subband != null)
160          bb = subband.getBaseband();
161        
162        if (bb == null)
163        {
164          allowableProducts = new TreeSet<StokesParameter>();
165        }
166        else //Get the polarization type(s) from the baseband
167        {
168          List<PolarizationType> polarizations = bb.getPolarizations();
169          PolarizationType p1 = null, p2 = null;
170          int listSize = polarizations.size();
171          if (listSize >= 1)
172          {
173            p1 = polarizations.get(0);
174    
175            if (listSize >= 2)
176            {
177              p2 = polarizations.get(1);
178    
179              if (listSize > 2)
180                throw new IllegalStateException("PROGRAMMER ERROR: Found " +
181                  polarizations.size() +
182                  " polarizations in baseband. Expected either 1 or 2.");
183            }
184          }
185          //Turn the polarization type(s) into Stokes parameter(s)
186          allowableProducts = StokesParameter.getStokesFor(p1, p2);
187        }
188        
189        return allowableProducts;
190      }
191    
192      /* (non-Javadoc)
193       * @see CorrelationProductGroup#addPolarizationProduct(StokesParameter)
194       */
195      public boolean addPolarizationProduct(StokesParameter stokes)
196      {
197        //See if it's allowed
198        boolean success = getAllowablePolarizationProducts().contains(stokes);
199        
200        //If allowed, see if we already have it; if not, add it
201        if (success && !channels.keySet().contains(stokes))
202        {
203          channels.put(stokes, 0);
204          needToRecalcChannels = true;
205        }
206        
207        return success;
208      }
209    
210      /* (non-Javadoc)
211       * @see CorrelationProductGroup#removePolarizationProduct(StokesParameter)
212       */
213      public int removePolarizationProduct(StokesParameter stokes)
214      {
215        Integer channelsRemoved = channels.remove(stokes);
216        
217        int answer = (channelsRemoved == null) ? 0 : channelsRemoved.intValue();
218        
219        if (answer > 0)
220          needToRecalcChannels = true;
221        
222        return answer;
223      }
224      
225      /* (non-Javadoc)
226       * @see CorrelationProductGroup#removeAllPolarizationProducts()
227       */
228      public void removeAllPolarizationProducts()
229      {
230        channels.clear();
231        needToRecalcChannels = true;
232      }
233    
234      /* (non-Javadoc)
235       * @see CorrelationProductGroup#getSpectralChannels()
236       */
237      public int getSpectralChannels()
238      {
239        if (needToRecalcChannels)
240          recalculateChannels();
241        
242        int channelCount = 0;
243        for (int ch : channels.values())
244          channelCount += ch;
245        
246        return channelCount;
247      }
248    
249      /* (non-Javadoc)
250       * @see CorrelationProductGroup#getSpectralChannels(StokesParameter)
251       */
252      public int getSpectralChannels(StokesParameter stokes)
253      {
254        if (needToRecalcChannels)
255          recalculateChannels();
256        
257        Integer channelCount = channels.get(stokes);
258        
259        return channelCount == null ? 0 : channelCount.intValue();
260      }
261      
262      @XmlElementWrapper(name="correlationProducts")
263      @XmlElement(name="product")
264      @SuppressWarnings("unused")  //JAXB use.  Helps deal w/ the underlying sorted map.
265      private CorrProd[] getXmlCorrProds()
266      {
267        int count = channels.size();
268        
269        CorrProd[] array = new CorrProd[count];
270        
271        int i = 0;
272        
273        for (Map.Entry<StokesParameter, Integer> map : channels.entrySet())
274          array[i++] = new CorrProd(map.getKey(), map.getValue());
275        
276        return array;
277      }
278      
279      @SuppressWarnings("unused")  //JAXB use
280      private void setXmlCorrProds(CorrProd[] replacements)
281      {
282        channels.clear();
283    
284        for (CorrProd corrProd : replacements)
285          channels.put(corrProd.correlation, corrProd.channels);
286      }
287      
288      //Used only for JAXB.  Helps deal w/ the underlying sorted map.
289      static class CorrProd
290      {
291        @XmlAttribute StokesParameter correlation;
292        @XmlAttribute Integer         channels;
293        
294        CorrProd()  { this(null,null); }
295        
296        CorrProd(StokesParameter key, Integer value)
297        {
298          this.correlation = key;
299          this.channels    = value;
300        }
301      }
302    
303      //============================================================================
304      // UTILITY
305      //============================================================================
306      
307      /**
308       * Sets {@code other}'s internal variables to the same values as those of
309       * this group.  The exceptions are the ID, which is cleared, and the UUID.
310       *  
311       * @param other
312       *   a correlation product group whose values should be set to mimic the
313       *   values in this group.
314       */
315      protected void copyInto(CorrelationProductGroupAbs other)
316      {
317        other.channels = new TreeMap<StokesParameter, Integer>(this.channels);
318        
319        other.needToRecalcChannels = this.needToRecalcChannels;
320        
321        other.clearId();
322      }
323      
324      //NOTE TO PROGRAMMERS:
325      //
326      //  The equals and hashCode methods have been intentionally removed.
327      //  The reason centered around the channels inst var and the way
328      //  the WIDAR subclass handled these.  Do NOT reinsert these methods
329      //  unless you have a thorough understanding of the consequences
330      //  for the Widar, and any other, subclasses.  Be sure to run the
331      //  cloning and xmlTransformation unit tests for the Widar subclass
332      //  before committing any changes.
333      //                                 --DMH 2009-Apr-15
334      /**
335       * Returns <i>true</i> if <tt>o</tt> is equal to this group.
336       * <p>
337       * Items not compared in the equality tests:</p>
338       * <ol>
339       *   <li>ID</li>
340       *   <li>UUID</li>
341       *   <li>Subband</li>
342       *   <li>Number of channels</li>
343       * </ol>
344       * <p>
345       * Note that the number and type of correlation products is examined,
346       * but not the channels for each.  Subclasses may use such a check
347       * in their overrides of this method.</p>
348       */
349      @Override
350      public boolean equals(Object o)
351      {
352        //Quick exit if o is null
353        if (o == null)
354          return false;
355        
356        //Quick exit if o is this
357        if (o == this)
358          return true;
359        
360        //Quick exit if classes are different
361        if (!o.getClass().equals(this.getClass()))
362          return false;
363    
364        CorrelationProductGroupAbs other = (CorrelationProductGroupAbs)o;
365        
366        //Intentionally NOT comparing ID, uuid, subband, numbers of channels
367        
368        return other.channels.keySet().equals(this.channels.keySet());
369      }
370    
371      @Override
372      public int hashCode()
373      {
374        //Taken from the Effective Java book by Joshua Bloch.
375        //The constants 17 & 37 are arbitrary & carry no meaning.
376        int result = 17;
377        
378        //You MUST keep this method in sync w/ the equals method
379        
380        return 37 * result + this.channels.keySet().hashCode();
381      }
382    }