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.HashMap;
007    import java.util.Map;
008    
009    import edu.nrao.sss.model.source.Source;
010    import edu.nrao.sss.model.source.SourceCatalog;
011    import edu.nrao.sss.model.source.SourceGroup;
012    
013    /**
014     * A parser of source catalog text files in the format used by the
015     * GBT / VLA Proposal Submission Tool (PST).
016     * <p> 
017     * The following are the specifications for source catalog text files
018     * found in the PST User Manual:</p>
019     * <div style="color:rgb(0,0,100)">
020     * This ASCII file must be formatted as follows:
021     * <ul>
022     *   <li>Each line of the ASCII file should contain the information for a single
023     *       source, as a comma-separated list in the following order: source name; 
024     *       right ascension; declination; epoch; velocity; redshift; and (optional)
025     *       group name(s), with each group name separated by a comma.</li>
026     *   <li>Either, but not both of, the velocity or the redshift must be specified
027     *       for every source.</li>
028     *   <li>If any group name is given, the source will appear only as a member of
029     *       the indicated group or groups: it will not appear as an ``ungrouped''
030     *       (individual) source, and will not be selectable as an individual source
031     *       when creating source/resource pairs on the Sessions Tab Page (see &sect;[*]
032     *       and &sect;[*]). To enter a source both as an individual and as part of a
033     *       group, simply put the same source in two lines, one listing groups and
034     *       one not listing groups.</li>
035     *   <li>A valid source line must have a minimum of six commas.</li>
036     *   <li>Comment lines begin with the character ``#''.</li>
037     *   <li>You can use tab as the delimiter in place of comma. However, commas and
038     *       tabs cannot be mixed in the same source data line.</li>
039     *   <li>Embedded blank lines are fine.</li>
040     * </ul>
041     * </div>
042     * <p>
043     * <b>Version Info:</b>
044     * <table style="margin-left:2em">
045     *   <tr><td>$Revision: 1251 $</td></tr>
046     *   <tr><td>$Date: 2008-04-28 16:28:37 -0600 (Mon, 28 Apr 2008) $</td></tr>
047     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
048     * </table></p>
049     * 
050     * @author David M. Harland
051     * @since 2007-08-09
052     */
053    public class PstSourceCatalogReader
054      extends AbstractSourceCatalogReader
055    {
056      private PstSourceReader  sourceReader;
057      private LineNumberReader dataReader;
058      
059      private Map<String, SourceGroup> catalogGroups;
060    
061      /** Creates a new instance. */
062      public PstSourceCatalogReader()
063      {
064        sourceReader = new PstSourceReader();
065      }
066      
067      /** Prepares this instance to read from a new source. */
068      private void reset(Reader in, SourceCatalog destination)
069      {
070        catalog = (destination == null) ? new SourceCatalog() : destination;
071    
072        catalogGroups = new HashMap<String, SourceGroup>();
073        
074        for (SourceGroup g : catalog.getGroups())
075          catalogGroups.put(g.getName(), g);
076        
077        readWasSuccessful = false;
078        errors.clear();
079    
080        dataReader = new LineNumberReader(in);
081      }
082    
083      //============================================================================
084      // INTERFACE SourceCatalogReader
085      //============================================================================
086    
087      /**
088       * Reads data from {@code in} and uses it to add sources to
089       * {@code destination}.
090       * <p>
091       * <b>Note:</b> this method closes the reader when it is done.</p>
092       * 
093       * @param in the source of text that can be read and turned into
094       *           {@code Source} objects.
095       *           
096       * @param destination the catalog to which the sources should be
097       *                    added.  If this parameter is <i>null</i>,
098       *                    a new catalog will be created.
099       *                    
100       * @return <i>true</i> if nothing unexpected occurred while reading data.
101       * 
102       * @see #getCatalog()
103       * @see #getErrors()
104       */
105      public boolean read(Reader in, SourceCatalog destination)
106      {
107        reset(in, destination);
108        
109        String line = getNextLine();
110    
111        while (line != null)
112        {
113          if (sourceReader.textCouldBeData(line))
114          {
115            //Read text, create & fill source, add to catalog.
116            if (!sourceReader.read(line, dataReader.getLineNumber()))
117              errors.addAll(sourceReader.errors);
118            
119            Source source = new Source();
120            
121            sourceReader.fillSource(source);
122            
123            catalog.addItem(source);
124            
125            //Add source to groups, creating new ones if necessary
126            for (String groupName : sourceReader.groups)
127            {
128              SourceGroup group = catalogGroups.get(groupName);
129              
130              if (group == null)
131              {
132                //Creates new group and adds it to catalog
133                group = new SourceGroup(catalog, groupName);
134                catalogGroups.put(groupName, group);
135              }
136              
137              group.add(source);
138            }
139          }
140          
141          line = getNextLine();
142        }
143        
144        try
145        {
146          dataReader.close();
147        }
148        catch(IOException ioe)
149        {
150          putError(0, "Error closing Reader: " + ioe.getMessage());
151        }
152    
153        readWasSuccessful = getErrorCount() == 0;
154        
155        return readWasSuccessful;
156      }
157      
158      //============================================================================
159      // 
160      //============================================================================
161      
162      /** Reads lines, returning first one that does not cause an error. */
163      private String getNextLine()
164      {
165        int attempt = 0;
166        
167        while (++attempt <= 100)
168        {
169          try {
170            return dataReader.readLine();
171          }
172          catch (IOException ex) {
173            putError(dataReader.getLineNumber(),
174                     "I/O Exception: " + ex.getMessage());
175          }
176        }
177        
178        throw new RuntimeException("Failed " + attempt +
179                                   " times while attempting to read a line.");
180      }
181      
182      //============================================================================
183      // 
184      //============================================================================
185      
186      //Here for quick testing
187      /*
188      public static void main(String[] args) throws Exception
189      {
190        if (args.length == 0)
191        {
192          System.out.println("Specify an input file on the command line.");
193          System.exit(9);
194        }
195        
196        java.io.FileReader input = new java.io.FileReader(args[0]);
197        PstSourceCatalogReader reader = new PstSourceCatalogReader();
198        
199        System.out.println("Read was flawless? => " + reader.read(input, null));
200        
201        System.out.println("Catalog.toXml:");
202        
203        reader.getCatalog().writeAsXmlTo(new java.io.PrintWriter(System.out));
204      }
205      */
206    }