001    package edu.nrao.sss.model.resource;
002    
003    import java.util.ArrayList;
004    import java.util.Date;
005    import java.util.EnumSet;
006    import java.util.HashSet;
007    import java.util.List;
008    import java.util.Map;
009    import java.util.Set;
010    import java.util.TimeZone;
011    import java.util.TreeSet;
012    
013    import org.apache.log4j.Logger;
014    
015    import edu.nrao.sss.astronomy.CoordinateConversionException;
016    import edu.nrao.sss.geom.EarthPosition;
017    import edu.nrao.sss.measure.Distance;
018    import edu.nrao.sss.measure.DistanceUnits;
019    import edu.nrao.sss.measure.Frequency;
020    import edu.nrao.sss.measure.FrequencyRange;
021    import edu.nrao.sss.measure.Latitude;
022    import edu.nrao.sss.measure.LinearVelocity;
023    import edu.nrao.sss.measure.Longitude;
024    import edu.nrao.sss.model.resource.evla.EvlaAntennaElectronics;
025    import edu.nrao.sss.model.source.Source;
026    import edu.nrao.sss.util.EnumerationUtility;
027    import edu.nrao.sss.util.StringUtil;
028    
029    /**
030     * An enumeration of the NRAO telescopes.
031     * <p>
032     * <b>Version Info:</b>
033     * <table style="margin-left:2em">
034     *   <tr><td>$Revision: 1598 $</td></tr>
035     *   <tr><td>$Date: 2008-10-07 10:25:51 -0600 (Tue, 07 Oct 2008) $</td></tr>
036     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
037     * </table></p>
038     * 
039     * @author David M. Harland
040     * @since  2006-02-23
041     */
042    public enum TelescopeType
043      implements ReceiverProvider, ReceiverSelector
044    {
045      /** The Greenbank Telescope. */
046      GBT("G", "The Greenbank Telescope",
047          new EarthPosition(),
048          NullAntennaElectronics.class),
049      
050      /** The Very Large Array. */
051      //VLA position came from http://www.vla.nrao.edu/genpub/overview/
052      VLA("A", "The Very Large Array",
053          new EarthPosition(Longitude.parse("-107d 37' 03.819\""),
054                            Latitude.parse("34d 04' 43.497\""),
055                            new Distance("2124.0", DistanceUnits.METER),
056                            TimeZone.getTimeZone("America/Denver")),
057          NullAntennaElectronics.class,
058          CorrelatorName.VLA)
059      {
060        @Override
061        void initCombos()
062        {
063          TreeSet<ReceiverBand> combo1 = new TreeSet<ReceiverBand>();
064          TreeSet<ReceiverBand> combo2 = new TreeSet<ReceiverBand>();
065          TreeSet<ReceiverBand> combo3 = new TreeSet<ReceiverBand>();
066          
067          combo1.add(ReceiverBand.VLA_4);
068          combo1.add(ReceiverBand.VLA_P);
069          
070          combo2.add(ReceiverBand.VLA_4);
071          combo2.add(ReceiverBand.VLA_L);
072          
073          combo3.add(ReceiverBand.VLA_P);
074          combo3.add(ReceiverBand.VLA_L);
075          
076          telescope.addCombo(combo1);
077          telescope.addCombo(combo2);
078          telescope.addCombo(combo3);
079        }
080      },
081      
082      /** The Very Long Baseline Array. */
083      VLBA("B", "The Very Long Baseline Array",
084           new EarthPosition(),
085           NullAntennaElectronics.class),
086      
087      /** The Expanded Very Large Array. */
088      EVLA("E", "The Expanded Very Large Array",
089           new EarthPosition(Longitude.parse("-107d 37' 03.819\""),
090                             Latitude.parse("34d 04' 43.497\""),
091                             new Distance("2124.0", DistanceUnits.METER),
092                             TimeZone.getTimeZone("America/Denver")),
093           EvlaAntennaElectronics.class,
094           CorrelatorName.VLA,
095           CorrelatorName.WIDAR)
096      {
097        @Override
098        void initCombos()
099        {
100          TreeSet<ReceiverBand> combo1 = new TreeSet<ReceiverBand>();
101          TreeSet<ReceiverBand> combo2 = new TreeSet<ReceiverBand>();
102          TreeSet<ReceiverBand> combo3 = new TreeSet<ReceiverBand>();
103          
104          combo1.add(ReceiverBand.EVLA_4);
105          combo1.add(ReceiverBand.EVLA_P);
106          
107          combo2.add(ReceiverBand.EVLA_4);
108          combo2.add(ReceiverBand.EVLA_L);
109          
110          combo3.add(ReceiverBand.EVLA_P);
111          combo3.add(ReceiverBand.EVLA_L);
112          
113          telescope.addCombo(combo1);
114          telescope.addCombo(combo2);
115          telescope.addCombo(combo3);
116        }
117      },
118      
119      /** The Atacama Large Millimeter Array. */
120      ALMA("L", "The Atacama Large Millimeter Array",
121           new EarthPosition(),
122           NullAntennaElectronics.class),
123    
124      /** A telescope other than those listed herein. */
125      OTHER("O", "Other",
126            new EarthPosition(),
127            NullAntennaElectronics.class);
128    
129      private final String        abbrev;
130      private final String        longName;
131      private final EarthPosition location;
132      private List<CorrelatorName> backends;
133      
134      private final Class<? extends AntennaElectronics> antennaElectronicsClass;
135      
136      //Many of the methods delegate their work to this object
137      Telescope telescope;
138    
139      /**
140       * Constructs a new telescope type.
141       * 
142       * @param abbreviation a short name for the new telescope type.
143       */
144      private TelescopeType(String abbreviation, String longName,
145                            EarthPosition location,
146                            Class<? extends AntennaElectronics> aeClass,
147                            CorrelatorName... backends)
148      {
149        this.abbrev   = abbreviation;
150        this.longName = longName;
151        this.location = location;
152        
153        this.antennaElectronicsClass = aeClass;
154        
155        this.telescope = null;
156        
157        this.backends = new ArrayList<CorrelatorName>();
158        if (backends != null)
159          for (CorrelatorName backend : backends)
160            this.backends.add(backend);
161      }
162      
163      /** Returns the object that does much of the work for this one. */
164      private Telescope getDelegate()
165      {
166        if (telescope == null)
167          initTelescope();
168        
169        return telescope;
170      }
171      
172      /** Initializes the telescope instance variable. */
173      private void initTelescope()
174      {
175        telescope = new Telescope();
176        
177        for (ReceiverBand rb : ReceiverBand.values())
178          if (rb.getTelescope() == this)
179            telescope.addReceiver(rb);
180        
181        initCombos();
182      }
183      
184      /** Initializes valid receiver combinations. */
185      void initCombos()
186      {
187        //default implementation is to do nothing.
188      }
189    
190      /**
191       * Returns the historical abbreviation for this telescope.
192       * 
193       * @return a short name for this telescope.
194       */
195      public String getLegacyAbbreviation()
196      {
197        return abbrev;
198      }
199      
200      /**
201       * Returns a long name for this telescope.  For example, the long name of the
202       * VLA is "The Very Large Array".
203       * @return a long name for this telescope.
204       */
205      public String getLongName()
206      {
207        return longName;
208      }
209    
210      /**
211       * Returns a copy of the location of this telescope.
212       * @return a copy of the location of this telescope.
213       */
214      public EarthPosition getLocation()
215      {
216        //Even though EarthPosition looks immutable, it is not, because the
217        //objects it holds are themselves mutable.  Therefore, return a clone.
218        return location.clone();
219      }
220      
221      /**
222       * Returns the receiver bands that this type of telescope has.
223       * @return the receiver bands that this type of telescope has.
224       */
225      public Set<ReceiverBand> getReceivers()
226      {
227        return getDelegate().getReceivers();
228      }
229      
230      /**
231       * Returns a list of all the combinations of two or more receivers that
232       * may be used by this telescope simultaneously.
233       * <p>
234       * Note that the returned list, and the sets contained therein, are
235       * copies of those held by this telescope, so changes made to them
236       * will not be reflected in this object.</p>
237       * 
238       * @return a list of all the combinations of two or more receivers that
239       *         may be used by this telescope simultaneously.
240       */
241      public List<Set<ReceiverBand>> getValidReceiverCombinations()
242      {
243        return getDelegate().getValidReceiverCombinations();
244      }
245      
246      /**
247       * Returns a default tuning plan for the local oscillators of this
248       * telescope.  Each of this telescope's receivers gets a separate
249       * set of local oscillator tunings.
250       * <p>
251       * The returned map has a {@code ReceiverBand} as its key and another
252       * map as its value.  This second map has as its key the name of
253       * a local oscillator, and its value is the frequency to which that
254       * LO should be tuned.</p>
255       * 
256       * @return a default tuning plan for this telescope.
257       */
258      public Map<ReceiverBand, Map<String, Frequency>> getTuningPlan()
259      {
260        return TuningPlanLoader.getTuningPlanFor(this);
261      }
262      
263      /**
264       * Creates and returns antenna electronics for this telescope.
265       * If this telescope has no electronics, or if its electronics cannot be
266       * successfully built, a {@link NullAntennaElectronics null-like}
267       * electronics will be built and returned.
268       * 
269       * @return
270       *   new antenna electronics for this telescope.  The returned object will
271       *   never be <i>null</i>, but could be an implementation of the
272       *   <a href="http://en.wikipedia.org/wiki/Null_Object_pattern">
273       *   null-object-pattern</a>.
274       */
275      public AntennaElectronics makeElectronics()
276      {
277        AntennaElectronics electronics;
278        
279        if (antennaElectronicsClass == null)
280        {
281          electronics = null;
282        }
283        else if (antennaElectronicsClass.equals(NullAntennaElectronics.class))
284        {
285          electronics = new NullAntennaElectronics(this);
286        }
287        else
288        {
289          try {
290            //This is the hoped-for situation
291            electronics = antennaElectronicsClass.newInstance();
292          }
293          catch (InstantiationException ie) {
294            electronics = null;
295          }
296          catch (IllegalAccessException iae) {
297            electronics = null;
298          }
299        }
300        
301        if (electronics == null)
302          electronics = new NullAntennaElectronics(TelescopeType.OTHER);
303        
304        return electronics;
305      }
306    
307      /**
308       * Returns a list of the backend processors available on this telescope.
309       * The term "backend" encompasses correlators, spectrometers,
310       * and any other electronic devices that can take as input the digital
311       * signals output by this telescope's {@link AntennaElectronics antenna
312       * electronics}.
313       * 
314       * @return a list of the backend processors available on this telescope.
315       */
316      public List<CorrelatorName> getBackendTypes()
317      {
318        return new ArrayList<CorrelatorName>(backends);
319      }
320    
321      /**
322       * Returns the velocity of this telescope toward or away from an
323       * object in space at the given point in time.
324       * 
325       * @param source
326       *   an object in space.  The radial velocity of this object is
327       *   calculated relative to this telescope.
328       *   
329       * @param dateTime
330       *   the point in time at which the velocity is calculated.
331       *   
332       * @return
333       *   the radial velocity of this telescope toward or away from an
334       *   object in space.
335       *   
336       * @throws CoordinateConversionException
337       *   if the position of the source cannot be converted to
338       *   an equatorial RA / Dec position.
339       *   
340       * @since 2008-07-29
341       */
342      public LinearVelocity calcVelocityRelativeTo(Source source,
343                                                   Date   dateTime)
344        throws CoordinateConversionException
345      {
346        return source.calcVelocityRelativeTo(location, dateTime);
347      }
348    
349      /**
350       * Returns a default telescope type.
351       * @return a default telescope type.
352       */
353      public static TelescopeType getDefault()
354      {
355        return EVLA;
356      }
357      
358      @Override
359      public String toString()
360      {
361        return name();
362      }
363      
364      /**
365       * Returns the telescope type represented by {@code text}.
366       * <p>
367       * For details about the transformation, see
368       * {@link EnumerationUtility#enumFromString(Class, String)}.</p>
369       * 
370       * @param text a text representation of a telescope type.
371       * 
372       * @return the telescope type represented by {@code text}.
373       */
374      public static TelescopeType fromString(String text)
375      {
376        //Try standard approach first
377        TelescopeType result =
378          EnumerationUtility.getSharedInstance()
379                            .enumFromString(TelescopeType.class, text);
380        
381        //If no match found, search abbreviations
382        if (result == null)
383        {
384          text = StringUtil.getInstance().normalizeString(text);
385          for (TelescopeType tt : TelescopeType.values())
386          {
387            if (text.equalsIgnoreCase(tt.getLegacyAbbreviation()) ||
388                text.equalsIgnoreCase(tt.getLongName()))
389            {
390              result = tt;
391              break;
392            }
393          }
394        }
395        
396        return result;
397      }
398    
399      //============================================================================
400      // RECEIVER SELECTION
401      //============================================================================
402      
403      /**
404       * Sets the class to use as a receiver selector.
405       * 
406       * @param fullClassName the fully-qualified name of a class that implements
407       *                      {@link ReceiverSelector}.
408       */
409      public void setReceiverSelectorType(String fullClassName)
410      {
411        getDelegate().setReceiverSelectorType(fullClassName);
412      }
413      
414      /**
415       * Sets the class to use as a receiver selector.
416       * 
417       * @param type a class the implements {@link ReceiverSelector}.  If this
418       *             value is <i>null</i> a default class will be used.
419       */
420      public void setReceiverSelectorType(Class<? extends ReceiverSelector> type)
421      {
422        getDelegate().setReceiverSelectorType(type);
423      }
424    
425      /**
426       * Does nothing; this telescope will always be its own provider.
427       */
428      public void setProvider(ReceiverProvider dummy)  { }
429    
430      public List<ReceiverSelection> selectReceivers(ResourceSpecification specs)
431      {
432        return getDelegate().selectReceivers(specs);
433      }
434      
435      public List<ReceiverSelection> selectReceivers(SkyFrequencySpecification specs)
436      {
437        return getDelegate().selectReceivers(specs);
438      }
439      
440      public List<ReceiverSelection> selectReceivers(SpectralLineSpecification specs)
441      {
442        return getDelegate().selectReceivers(specs);
443      }
444      
445      public List<ReceiverSelection> selectReceivers(PulsarSpecification specs)
446      {
447        return getDelegate().selectReceivers(specs);
448      }
449    
450      public List<ReceiverSelection> selectReceivers(ObservingMode         mode,
451                                                     ResourceSpecification specs)
452      {
453        return getDelegate().selectReceivers(mode, specs);
454      }
455    
456      public ReceiverBand selectBestReceiverFor(FrequencyRange targetRange)
457      {
458        return getDelegate().selectBestReceiverFor(targetRange);
459      }
460    
461      //============================================================================
462      // 
463      //============================================================================
464      /*
465      public static void main(String... args) throws Exception
466      {
467        for (edu.nrao.sss.electronics.DigitalSignal ds :
468             TelescopeType.EVLA.makeElectronics().execute())
469          System.out.println(ds);
470      }
471      */
472      /*
473      public static void main(String... args) throws Exception
474      {
475        for (TelescopeType tt : TelescopeType.values())
476        {
477          System.out.print(tt.name() + " is located at ");
478          EarthPosition pos = tt.getLocation();
479          System.out.print(pos.getLongitude().toStringDms());
480          System.out.print(" longitude, ");
481          System.out.print(pos.getLatitude().toStringDms());
482          System.out.print(" latitude, ");
483          System.out.print(pos.getDistance());
484          System.out.print(" elevation, in the ");
485          System.out.print(pos.getTimeZone().getDisplayName());
486          System.out.println(" zone.");
487          System.out.print("Valid Backends: ");
488          for (CorrelatorName c : tt.getBackendTypes())
489            System.out.print(c+" ");
490          System.out.println();
491        }
492      }
493      */
494    }
495    
496    /** Helper class for TelescopeType; back-formed from older public class. */
497    class Telescope implements ReceiverSelector, ReceiverProvider
498    {
499      private static final Logger log = Logger.getLogger(Telescope.class);
500    
501      private Set<ReceiverBand>       receivers;
502      private List<Set<ReceiverBand>> validReceiverCombinations;
503    
504      //This variable is not directly mutable, but will be updated if a client
505      //updates the receiverSelectorClass variable.
506      private ReceiverSelector receiverSelector;
507      
508      private Class<? extends ReceiverSelector> receiverSelectorClass;
509    
510      private static final Class<? extends ReceiverSelector> DEFAULT_SELECTOR_CLASS =
511        SimpleReceiverSelector.class;
512    
513      /** Creates a new instance. */
514      Telescope()
515      {
516        receivers                 = new HashSet<ReceiverBand>();
517        validReceiverCombinations = new ArrayList<Set<ReceiverBand>>();
518        receiverSelectorClass     = DEFAULT_SELECTOR_CLASS;
519      }
520      
521      //============================================================================
522      // RECEIVERS
523      //============================================================================
524    
525      /** Does nothing; this telescope will always be its own provider. */
526      public void setProvider(ReceiverProvider dummy)  { }
527      
528      /**
529       * Returns the complete collection of receivers available on antennas
530       * of this telescope.
531       * The returned set represents the union of receivers across all the
532       * antennas of this telescope.  An individual antenna might not have all
533       * of the receivers in the returned set. 
534       * <p>
535       * Note that the returned set is a <i>copy</i> of the one held internally
536       * by this telescope.  This means that any changes made to the returned
537       * set will <i>not</i> be reflected in this object.</p>
538       *  
539       * @return the collection of receivers available on antennas of this
540       *         telescope.
541       */
542      public Set<ReceiverBand> getReceivers()
543      {
544        EnumSet<ReceiverBand> result = EnumSet.noneOf(ReceiverBand.class);
545        result.addAll(receivers);
546        return result;
547      }
548      
549      //Keep this non-public
550      void addReceiver(ReceiverBand newReceiver)
551      {
552        if (newReceiver != null)
553          receivers.add(newReceiver);
554      }
555      
556      /**
557       * Returns the receiver with the given name, or <i>null</i> if this telescope
558       * has no such receiver.
559       * <p>
560       * The name search is both case-insensitive and tolerant of extraneous
561       * whitespace.</p>
562       * 
563       * @param receiverName the name of the desired receiver.
564       * 
565       * @return the receiver with the given name, or <i>null</i> if this telescope
566       *         has no such receiver.
567       */
568      public ReceiverBand getReceiver(String receiverName)
569      {
570        //Use the enumeration class to convert string to object
571        ReceiverBand result = ReceiverBand.fromString(receiverName);
572        
573        if (result != null)
574        {
575          //If we don't hold that receiver, return null
576          if (!receivers.contains(result))
577            result = null;
578        }
579        
580        return result;
581      }
582      
583      /**
584       * Returns a list of all the combinations of two or more receivers that
585       * may be used by this telescope simultaneously.
586       * <p>
587       * Note that the returned list, and the sets contained therein, are
588       * copies of those held by this telescope, so changes made to them
589       * will not be reflected in this object.</p>
590       * 
591       * @return a list of all the combinations of two or more receivers that
592       *         may be used by this telescope simultaneously.
593       */
594      public List<Set<ReceiverBand>> getValidReceiverCombinations()
595      {
596        ArrayList<Set<ReceiverBand>> result = new ArrayList<Set<ReceiverBand>>();
597        
598        for (Set<ReceiverBand> validCombo : validReceiverCombinations)
599          result.add(new HashSet<ReceiverBand>(validCombo));
600        
601        return result;
602      }
603      
604      /**
605       * Returns <i>true</i> if the given set of receivers may be used
606       * simultaneously by one antenna of this telescope.
607       *  
608       * @param combination a set of receivers that a client would like to use
609       *                    simultaneously on one antenna.
610       *                    
611       * @return <i>true</i> if the given set of receivers may be used
612       *         simultaneously by one antenna of this telescope.
613       *         If {@code combination} is an empty set, the returned value
614       *         will be <i>false</i>.
615       */
616      public boolean isValidReceiverCombination(Set<ReceiverBand> combination)
617      {
618        for (Set<ReceiverBand> validCombo : validReceiverCombinations)
619          if (combination.equals(validCombo))
620            return true;
621        
622        return false;
623      }
624      
625      //Keep this non-public
626      void addCombo(Set<ReceiverBand> newCombination)
627      {
628        if (newCombination != null)
629          validReceiverCombinations.add(newCombination);
630      }
631    
632      //============================================================================
633      // RECEIVER SELECTION
634      //============================================================================
635      
636      /**
637       * Sets the class to use as a receiver selector.
638       * 
639       * @param fullClassName the fully-qualified name of a class that implements
640       *                      {@link ReceiverSelector}.
641       */
642      @SuppressWarnings("unchecked")
643      public void setReceiverSelectorType(String fullClassName)
644      {
645        Class selectorClass;
646        
647        try
648        {
649          selectorClass = Class.forName(fullClassName);
650        }
651        catch (Exception ex)
652        {
653          log.warn("Could not make ReceiverSelector class from " + fullClassName +
654                   ". Will use " + DEFAULT_SELECTOR_CLASS.getCanonicalName() +
655                   " instead.  Exception:", ex);
656          selectorClass = DEFAULT_SELECTOR_CLASS;
657        }
658        
659        //An unchecked (not type-safe) conversion
660        setReceiverSelectorType(selectorClass);
661      }
662      
663      /**
664       * Sets the class to use as a receiver selector.
665       * 
666       * @param type a class the implements {@link ReceiverSelector}.  If this
667       *             value is <i>null</i> a default class will be used.
668       */
669      public void setReceiverSelectorType(Class<? extends ReceiverSelector> type)
670      {
671        if (type != receiverSelectorClass)
672        {
673          receiverSelectorClass = (type == null) ? DEFAULT_SELECTOR_CLASS : type;
674          receiverSelector      = null;
675        }
676      }
677      
678      /** Returns the receiver selector to use with this telescope. */
679      ReceiverSelector getReceiverSelector()
680      {
681        if (receiverSelector == null)
682          receiverSelector = makeSelector();
683        
684        return receiverSelector;
685      }
686    
687      /** Uses receiverSelectorClass to make new instance. */
688      private ReceiverSelector makeSelector()
689      {
690        ReceiverSelector rs;
691       
692        try
693        {
694          rs = receiverSelectorClass.newInstance();
695        }
696        catch (Exception ex)
697        {
698          log.warn("Could not make new instance for ReceiverSelector class " +
699                   receiverSelectorClass.getCanonicalName() +
700                   ".  Will try class " + DEFAULT_SELECTOR_CLASS.getCanonicalName() +
701                   " instead.  Exception: ", ex);
702          try
703          {
704            rs = DEFAULT_SELECTOR_CLASS.newInstance();
705          }
706          catch (Exception ex2)
707          {
708            //Should never get here, but just in case...
709            log.error("Could not make new instance for ReceiverSelector class " +
710                      DEFAULT_SELECTOR_CLASS.getCanonicalName() +
711                      ".  Exception: ", ex2);
712            rs = null;
713          }
714        }
715        
716        rs.setProvider(this);
717        
718        return rs;
719      }
720      
721      public List<ReceiverSelection> selectReceivers(ResourceSpecification specs)
722      {
723        return getReceiverSelector().selectReceivers(specs);
724      }
725      
726      public List<ReceiverSelection> selectReceivers(SkyFrequencySpecification specs)
727      {
728        return getReceiverSelector().selectReceivers(specs);
729      }
730      
731      public List<ReceiverSelection> selectReceivers(SpectralLineSpecification specs)
732      {
733        return getReceiverSelector().selectReceivers(specs);
734      }
735      
736      public List<ReceiverSelection> selectReceivers(PulsarSpecification specs)
737      {
738        return getReceiverSelector().selectReceivers(specs);
739      }
740    
741      public List<ReceiverSelection> selectReceivers(ObservingMode         mode,
742                                                     ResourceSpecification specs)
743      {
744        return getReceiverSelector().selectReceivers(mode, specs);
745      }
746      
747      public ReceiverBand selectBestReceiverFor(FrequencyRange targetRange)
748      {
749        return getReceiverSelector().selectBestReceiverFor(targetRange);
750      }
751    }