001    package edu.nrao.sss.model.source.parser;
002    
003    import java.io.IOException;
004    import java.io.LineNumberReader;
005    import java.io.Reader;
006    import java.util.ArrayList;
007    import java.util.List;
008    
009    import edu.nrao.sss.model.source.Source;
010    import edu.nrao.sss.model.source.SourceCatalog;
011    import edu.nrao.sss.util.StringUtil;
012    
013    /**
014     * A reader of source catalog text files in VLBA format.
015     * <p>
016     * <b><u>Format</u></b> (starting 2007-11-24 &amp; tweaked 2008-06-19)
017     * <pre>
018     *  Field    1:10   A10   IAU name (J2000)
019     *  Field   13:20   A8    IVS name (B1950)
020     *  Field   23:24   I2    Right ascension (hr)
021     *  Field   26:27   I2    Right ascension (min)
022     *  Field   29:37   F9.6  Right ascension (sec)
023     *  Field   40:42   I3    Declination (deg)
024     *  Field   44:45   I3    Declination (min)
025     *  Field   47:54   F8.5  Declination (sec)
026     *  Field   57:61   F5.2  Inflated E/W position error (mas) 
027     *  Field   63-67   F5.2  Inflated N/S position error (mas) 
028     *  Field   69-73   F5.2  X-band total flux density integrated over entire map (Jy) or -1.00 if no data are available
029     *  Field   75-79   F5.2  X-band unresolved flux density at long VLBA baselines (Jy) or -1.00 if no data are available
030     *  Field   81-85   F5.2  S-band total flux density integrated over entire map (Jy) or -1.00 if no data are available
031     *  Field   87:91   F5.2  S-band unresolved flux density at long VLBA baselines (Jy) or -1.00 if no data are available
032     *
033     *  Missing value: -1.0 (often below detection level)
034     *
035     *  Field   94:94   A1    Category: C (calibrator), N (non-calibrator), U (unreliable coordinates)
036     *                        C=acceptable calibrator to be used for most VLBA observations;
037     *                        N=Non-calibrator that may be too weak or resolved and should be tested before use;
038     *                        U=Non-calibrator with poor position determination.
039     *                        K=Acceptable at 23 GHz.
040     *  Field   96:99   A4    Original catalogue name: 
041     *                        VCS1,2,3,4,5:  Original VCS surveys
042     *                        ICRF: ICRF observers
043     *                        2007: Contains VCS6, polar cap survey
044     *                        2008: Latest additions, mostly in south
045     *                        VERA: Sources from VERA/VLBA galactic plane surveys
046     * </pre>
047     * <b><u>Example</u></b>
048     * <pre>
049     *  J0000+8141  2355+814  00h00m17.266431s +81d41'36.52552" 57.33 19.57 -1.00 -1.00 -1.00 -1.00  U-2007
050     *  J0000-3221  2357-326  00h00m20.399946s -32d21'01.23315"  0.52  1.14  0.21  0.09  0.50  0.34  C-VCS2
051     *  J0000+4054  2358+406  00h00m53.081556s +40d54'01.79452"  1.80  2.19  0.25  0.05  1.00  0.12  C-VCS1
052     *  J0001-1551  2358-161  00h01m05.328768s -15d51'07.07599"  0.51  0.96  0.28  0.12  0.43  0.25  C-VCS1
053     * </pre>
054     * <p>
055     * <b><u>Format</u></b> (prior to 2007-11-24)
056     * <pre>
057     *  Field    1:1    A1    Category: C (calibrator), N (non-calibrator), U (unreliable coordinates)
058     *                                  K (possible VERA 22 GHz calibrator)
059     *  Field    4:11   A8    IVS name (B1950)
060     *  Field   13:22   A10   IAU name (J2000.0)
061     *  Field   25:26   I2    Right ascension: hours
062     *  Field   28:29   I2    Right ascension: minutes
063     *  Field   31:39   F9.6  Right ascension: seconds
064     *  Field   41:43   I3    Declination: degrees
065     *  Field   45:46   I3    Declination: minutes
066     *  Field   48:57   F8.5  Declination: seconds
067     *  Field   58:63   F6.2  Inflated error in right ascension in mas
068     *  Field   65:70   F6.2  Inflated error in declination in mas
069     *  Field   73:78   F6.3  Correlation between right ascension and declination
070     *  Field   80:85   I6    Number of observations used
071     *  Field   88:88   A1    Blank or < or - for X-band total flux density integrated over entire map
072     *  Field   89:92   F4.2  X-band total flux density integrated over entire map, Jy or n/a if no data are available
073     *  Field   94:94   A1    Blank or < or - for X-band unresolved flux density at VLBA baselines, Jy
074     *  Field   95:98   F4.2  X-band unresolved flux density at long VLBA baselines, Jy or n/a if no data are available
075     *  Field  101:101  A1    Blank or < or - for S-band total flux density integrated over entire map
076     *  Field  102:105  F4.2  S-band total flux density integrated over entire map, Jy or n/a if no data are available
077     *  Field  107:107  A1    Blank or < or - for S-band unresolved flux density at VLBA baselines
078     *  Field  108:111  F4.2  S-band unresolved flux density at long VLBA baselines, Jy or n/a if no data are available
079     *  Field  114:116  A3    Used Band (type_: X, S, X/S. K
080     *  Field  119:122  A4    Catalogue name
081     *  Field  124:124  A1    Modification code: a=added in this version
082     *                                           r=remove from this version
083     *                                           m=modified in this version
084     *  Field  125:134  A8    Date of change (YYYY.MM.DD)</pre>
085     * <b><u>Example</u></b>
086     * <pre>
087     *  C  2357-326 J0000-3221  00 00 20.399949 -32 21 01.23320    0.60   1.11   0.028     40   0.14  0.08   0.48  0.35  X/S  VCS2          
088     *  C  2358+406 J0000+4054  00 00 53.081537 +40 54 01.79341    2.36   2.13  -0.162     22   0.25  0.05   1.00  0.12  X/S  VCS1          
089     *  C  2358-161 J0001-1551  00 01 05.328763 -15 51 07.07576    0.51   0.92  -0.757     58   0.28  0.12   0.43  0.25  X/S  VCS1          
090     *  C  1038+52B J1041+523B  10 41 48.897635 +52 33 55.60815    0.79   0.57  -0.355     84  -1.00 -1.00  -1.00 -1.00  X/S  ICRF r2007.06.14
091     * </pre>
092     * <p>
093     * <b>Version Info:</b>
094     * <table style="margin-left:2em">
095     *   <tr><td>$Revision: 1552 $</td></tr>
096     *   <tr><td>$Date: 2008-09-15 16:16:57 -0600 (Mon, 15 Sep 2008) $</td></tr>
097     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
098     * </table></p>
099     * 
100     * @author David M. Harland
101     * @since 2007-06-15
102     */
103    public class VlbaSourceCatalogReader
104      extends AbstractSourceCatalogReader
105    {
106      private VlbaSourceReader sourceReader;
107      private LineNumberReader dataReader;
108      
109      private List<Source> addedSources;
110      private List<Source> changedSources;
111      private List<Source> removedSources;
112      
113      /** Creates a new instance. */
114      public VlbaSourceCatalogReader()
115      {
116        sourceReader = new VlbaSourceReader();
117        
118        addedSources   = new ArrayList<Source>();
119        changedSources = new ArrayList<Source>();
120        removedSources = new ArrayList<Source>();
121      }
122      
123      /** Prepares this instance to read from a new source. */
124      private void reset(Reader in, SourceCatalog destination)
125      {
126        catalog = (destination == null) ? new SourceCatalog() : destination;
127    
128        readWasSuccessful = false;
129        errors.clear();
130    
131        dataReader = new LineNumberReader(in);
132        
133        addedSources.clear();
134        changedSources.clear();
135        removedSources.clear();
136      }
137      
138      /**
139       * NOTE: This method closes the incoming Reader!
140       *
141       * @see AbstractSourceCatalogReader#read(java.io.Reader,
142       *      edu.nrao.sss.model.source.SourceCatalog)
143       */
144      public boolean read(Reader in, SourceCatalog destination)
145      {
146        return read(in, destination, "1900.01.01");
147      }
148    
149      /**
150       * Reads data from {@code in} and uses it to add sources to
151       * {@code destination}.
152       * <p>
153       * Note that <i>all</i> sources in the reader are placed in the
154       * destination catalog, no matter what the modification code and
155       * date of change values are.  Some clients may want to work only
156       * with those sources with modification codes.  In that situation
157       * the following list methods may be used:
158       * {@link #getAddedSources()}, {@link #getModifiedSources()}, and
159       * {@link #getRemovedSources()}.</p>
160       * 
161       * @param in
162       *          the source of text that can be read and turned into
163       *          {@code Source} objects.  This method closes this
164       *          reader when it has finished with it.
165       *           
166       * @param destination
167       *          the catalog to which the sources should be added.  If this
168       *          parameter is <i>null</i>, a new catalog will be created.
169       *          
170       * @param earliestDate
171       *          text representation of a date in form <i>yyyymmdd</i>.
172       *          A source with a modification code and an effective date on or
173       *          after this date will be remembered and can be accessed via the
174       *          {@link #getAddedSources()}, {@link #getModifiedSources()}, and
175       *          {@link #getRemovedSources()} methods.
176       *                    
177       * @return <i>true</i> if nothing unexpected occurred while reading data.
178       */
179      public boolean read(Reader in, SourceCatalog destination, String earliestDate)
180      {
181        reset(in, destination);
182        
183        String line = readHeaderComments();
184        
185        while (line != null)
186        {
187          if (sourceReader.textCouldBeData(line))
188          {
189            if (!sourceReader.read(line, dataReader.getLineNumber(), histRecPrefix))
190              errors.addAll(sourceReader.errors);
191            
192            if (sourceReader.source != null)
193            {
194              if (sourceInfoOrigin != null)
195                sourceReader.source.setOriginOfInformation(sourceInfoOrigin);
196    
197              catalog.addItem(sourceReader.source);
198              
199              //Make note of additions, changes, and deletions
200              if (sourceReader.textFields.get(VlbaSourceReader.MOD_DATE).value
201                  .compareToIgnoreCase(earliestDate) >= 0)
202              {
203                char modCode =
204                  Character.toLowerCase(sourceReader.textFields.get(
205                                        VlbaSourceReader.MOD_CODE).value.charAt(0));
206                String notePrefix = null;
207                switch (modCode)
208                {
209                  case 'a':
210                    notePrefix = "Added to catalog on ";
211                    addedSources.add(sourceReader.source);
212                    break;
213                    
214                  case 'm':
215                    notePrefix = "Modified on ";
216                    changedSources.add(sourceReader.source);
217                    break;
218                    
219                  case 'r':
220                    notePrefix = "Removed from catalog on ";
221                    removedSources.add(sourceReader.source);
222                    break;
223    
224                  default:
225                    putError("Unrecognized modification code '" + modCode +
226                             ".  Expected 'a', 'm', or 'r'.");
227                    break;
228                }
229                
230                sourceReader.source.getNotes().add(notePrefix +
231                  sourceReader.textFields.get(VlbaSourceReader.MOD_DATE).value);
232              }
233            }
234          }
235          //Would handle comments that are embedded in data here
236          //else
237          //{
238          //}
239          
240          line = getNextLine();
241        }
242        
243        try
244        {
245          this.dataReader.close();
246        }
247        catch(IOException ioe)
248        {
249          putError(0, "Error closing Reader: " + ioe.getMessage());
250        }
251    
252        readWasSuccessful = getErrorCount() == 0;
253        
254        return readWasSuccessful;
255      }
256      
257      /** Turns the header comments into notes on the catalog. */
258      private String readHeaderComments()
259      {
260        final String EOL        = StringUtil.EOL;
261        final int    PREFIX_LEN = VlbaSourceReader.COMMENT_PREFIX.length();
262        
263        int blankLineCount = 0;
264        
265        StringBuilder buff = new StringBuilder();
266        
267        String line = getNextLine();
268    
269        while (line != null)
270        {
271          if (sourceReader.textCouldBeData(line))
272            break;
273          
274          line = line.trim();
275          line = line.substring(PREFIX_LEN);
276          
277          if (sourceReader.lineIsEmpty(line))
278          {
279            blankLineCount++;
280            
281            //2+ blank lines interpreted as start of new note
282            if (blankLineCount == 2)
283            {
284              catalog.getNotes().add(buff.toString());
285              buff = new StringBuilder();
286            }
287          }
288          else //text on this line
289          {
290            //Preserve single blank lines
291            if (blankLineCount == 1)
292              buff.append(EOL);
293           
294            buff.append(line).append(EOL);
295            blankLineCount = 0;
296          }
297          
298          line = getNextLine();
299        }
300        
301        //The final note
302        if (buff.length() > 0)
303          catalog.getNotes().add(buff.toString());
304        
305        return line;
306      }
307      
308      /**
309       * Returns a list of sources that were marked with the "add" modification
310       * code and whose effective dates were on or after that sent to the read
311       * method. The returned list is guaranteed not to be <i>null</i>, but it
312       * may be empty.
313       */
314      public List<Source> getAddedSources()
315      {
316        return new ArrayList<Source>(addedSources);
317      }
318      
319      /**
320       * Returns a list of sources that were marked with the "modified" modification
321       * code and whose effective dates were on or after that sent to the read
322       * method. The returned list is guaranteed not to be <i>null</i>, but it
323       * may be empty.
324       */
325      public List<Source> getModifiedSources()
326      {
327        return new ArrayList<Source>(changedSources);
328      }
329      
330      /**
331       * Returns a list of sources that were marked with the "removed" modification
332       * code and whose effective dates were on or after that sent to the read
333       * method. The returned list is guaranteed not to be <i>null</i>, but it
334       * may be empty.
335       */
336      public List<Source> getRemovedSources()
337      {
338        return new ArrayList<Source>(removedSources);
339      }
340      
341      //============================================================================
342      // 
343      //============================================================================
344      
345      /** Reads lines, returning first one that does not cause an error. */
346      private String getNextLine()
347      {
348        int attempt = 0;
349        
350        while (++attempt <= 100)
351        {
352          try {
353            return dataReader.readLine();
354          }
355          catch (IOException ex) {
356            putError("I/O Exception: " + ex.getMessage());
357          }
358        }
359        
360        throw new RuntimeException("Failed " + attempt +
361                                   " times while attempting to read a line.");
362      }
363      
364      /** Adds a new parsing error to our list. */
365      private void putError(String message)
366      {
367        putError(dataReader.getLineNumber(), message);
368      }
369    }