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.net.URL;
007    import java.util.ArrayList;
008    import java.util.HashMap;
009    import java.util.List;
010    import java.util.Map;
011    
012    import static edu.nrao.sss.util.StringUtil.EOL;
013    
014    import edu.nrao.sss.model.source.Source;
015    import edu.nrao.sss.model.source.SourceCatalog;
016    
017    /**
018     * A parser of the
019     * <a href="http://www.vla.nrao.edu/astro/calib/manual/csource.html">
020     * VLA Calibrator Manual</a> HTML page.
021     * This parser turns the data on the above page into a
022     * {@link edu.nrao.sss.model.source.SourceCatalog}.
023     * <p>
024     * <b>Version Info:</b>
025     * <table style="margin-left:2em">
026     *   <tr><td>$Revision: 1324 $</td></tr>
027     *   <tr><td>$Date: 2008-06-04 16:42:24 -0600 (Wed, 04 Jun 2008) $</td></tr>
028     *   <tr><td>$Author: dharland $</td></tr>
029     * </table></p>
030     * 
031     * @author David M. Harland
032     * @since 2006-12-05
033     */
034    public class VlaCalibDbHtmlReader
035      extends AbstractSourceCatalogReader
036    {
037      private URL              baseUrlForLinks;
038      private Source           source;
039    
040      private VlaCalibNameLineReader nameReader = new VlaCalibNameLineReader();
041      private VlaCalibBandLineReader bandReader = new VlaCalibBandLineReader();
042    
043      private LineNumberReader dataReader;
044    
045      /** Creates a new instance. */
046      public VlaCalibDbHtmlReader()
047      {
048        this(null);
049      }
050    
051      /**
052       * Creates a new instance.
053       * @param parentUrlForLinks serves as the parent URL for any relative URLs
054       *                          found by this reader.
055       */
056      public VlaCalibDbHtmlReader(URL parentUrlForLinks)
057      {
058        baseUrlForLinks = parentUrlForLinks;
059    
060        nameReader = new VlaCalibNameLineReader();
061        bandReader = new VlaCalibBandLineReader();
062        
063        bandReader.baseUrlForLinks = baseUrlForLinks;
064      }
065      
066      /** Prepares this instance to read from a new source. */
067      private void reset(Reader in, SourceCatalog destination)
068      {
069        //leave baseUrlForLinks as is
070        catalog = (destination == null) ? new SourceCatalog() : destination;
071        readWasSuccessful = false;
072        errors.clear();
073        dataReader = new LineNumberReader(in);
074      }
075    
076      /**
077       * Sets the base URL for relative links found by this reader.
078       * @param parentUrlForLinks serves as the parent URL for any relative URLs
079       *                          found by this reader.
080       */
081      public void setBaseUrlForLinks(URL parentUrlForLinks)
082      {
083        baseUrlForLinks = parentUrlForLinks;
084        bandReader.baseUrlForLinks = baseUrlForLinks;
085      }
086    
087      /* (non-Javadoc)
088       * @see SourceCatalogReader#setPrefixForHistoricalRecords(String)
089       */
090      @Override
091      public void setPrefixForHistoricalRecords(String prefix)
092      {
093        super.setPrefixForHistoricalRecords(prefix);
094        
095        nameReader.histRecPrefix = "origin=" + histRecPrefix + ";";
096        bandReader.histRecPrefix = "origin=" + histRecPrefix + ";";
097      }
098    
099      /**
100             * NOTE: This method closes the incoming Reader!
101             *
102       * @see AbstractSourceCatalogReader#read(java.io.Reader,
103       *      edu.nrao.sss.model.source.SourceCatalog)
104       */
105      public boolean read(Reader in, SourceCatalog destination)
106      {
107        reset(in, destination);
108        
109        String line = getFirstDataLine();
110        
111        while (line != null) // && getErrorCount() < 10)
112        {
113          source = new Source();
114    
115          if (sourceInfoOrigin != null)
116            source.setOriginOfInformation(sourceInfoOrigin);
117    
118          catalog.addItem(source);
119          
120          //J2000 line
121          nameReader.parse(line, dataReader.getLineNumber()).fillSource(source);
122          errors.addAll(nameReader.errors);
123          
124          String posRefJ2000 = nameReader.posRef;
125    
126          line = getNextDataLine();
127    
128          //B1950 line
129          nameReader.parse(line, dataReader.getLineNumber()).fillSource(source);
130          errors.addAll(nameReader.errors);
131          
132          line = getNextDataLine();
133    
134          //Should be band lines.  Might not have any
135          if (dataLooksLikeNameLine(line))
136          {
137            putError(nameReader.iauName + ": Found no band data for source.");
138          }
139          else //we have at least one band line or something really bogus
140          {
141            while (lineLooksLikeData(line))
142            {
143              bandReader.parse(line, dataReader.getLineNumber(), nameReader.iauName);
144              bandReader.fillSource(source, posRefJ2000);
145              errors.addAll(bandReader.errors);
146              line = getNextLine();
147            }
148            
149            makeCalQualTable(source);
150            
151            //If not at end of file, assume next data line is J2000 line
152            if (line != null)
153              line = getNextDataLine();
154          }
155        }
156        
157        source = null;
158        
159                    try
160                    {
161                            this.dataReader.close();
162                    }
163    
164                    catch(IOException ioe)
165                    {
166                            putError(0, "Error closing Reader: " + ioe.getMessage());
167                    }
168    
169        readWasSuccessful = getErrorCount() == 0;
170        
171        return readWasSuccessful;
172      }
173      
174      private Map<String, String> nameValue = new HashMap<String, String>();
175      private List<String>        bandCodes = new ArrayList<String>();
176      
177      private static final String DELIM = " ";
178      
179      private void makeCalQualTable(Source src)
180      {
181        if (src.getHistoricalRecords().size() == 0)
182          return;
183    
184        nameValue.clear();
185        bandCodes.clear();
186        
187        //Parse the historical record into a map
188        String histRec = src.getHistoricalRecords().get(0);
189        for (String nv : histRec.split(";"))
190        {
191          String[] s = nv.split("=");
192          if (s[0].startsWith("BAND"))
193          {
194            nameValue.put(s[0], s.length == 1 ? "" : s[1]);
195            if (s[0].endsWith("WAVELENGTH"))
196              bandCodes.add(s[0].substring(5, 6)); //BAND.x.BLAH
197          }
198        }
199        
200        if (nameValue.size() == 0)
201          return;
202        
203        //Build the note
204        StringBuilder note = new StringBuilder("  BAND   A B C D");
205        note.append(EOL);
206        note.append("======== = = = =").append(EOL);
207        
208        for (String bandCode : bandCodes)
209        {
210          String prefix = "BAND."+bandCode+".";
211          note.append(bandCode).append(DELIM);
212          String wavelen = nameValue.get(prefix+"WAVELENGTH");
213          int pad = Math.max(0, 6-wavelen.length());
214          for (int p=1; p <= pad; p++)
215            note.append(' ');
216          note.append(wavelen).append(DELIM);
217          note.append(nameValue.get(prefix+"QUALITY.A")).append(DELIM);
218          note.append(nameValue.get(prefix+"QUALITY.B")).append(DELIM);
219          note.append(nameValue.get(prefix+"QUALITY.C")).append(DELIM);
220          note.append(nameValue.get(prefix+"QUALITY.D")).append(EOL);
221        }
222        
223        src.getNotes().add(note.toString());
224      }
225    
226      //============================================================================
227      // LINE READING
228      //============================================================================
229    
230      /** Returns the first data line in the reader, or null if EOF. */
231      private String getFirstDataLine()
232      {
233        String line = getNextLine();
234    
235        //First line of table starts with "IAU NAME"
236        while (!line.startsWith("IAU"))
237          line = getNextLine();
238        
239        return getNextDataLine();
240      }
241    
242      /** Returns the next data line, or null if EOF. */
243      private String getNextDataLine()
244      {
245        String line = getNextLine();
246        
247        while (line != null && !lineLooksLikeData(line))
248          line = getNextLine();
249        
250        return line;
251      }
252      
253      /** Reads lines, returning first one that does not cause an error. */
254      private String getNextLine()
255      {
256        int attempt = 0;
257        
258        while (++attempt <= 100)
259        {
260          try {
261            String line = dataReader.readLine();
262            return (line.contains("</PRE>") || line.contains("</pre>")) ? null
263                                                                        : line;
264          }
265          catch (IOException ex) {
266            putError("I/O Exception: " + ex.getMessage());
267          }
268        }
269        
270        throw new RuntimeException("Failed " + attempt +
271                                   " times while attempting to read a line.");
272      }
273    
274      /** Decides whether or not line contains data. */
275      private boolean lineLooksLikeData(String line)
276      {
277        return  line != null            &&             //End of file
278                line.length() > 0       &&             //Empty line
279               !line.startsWith("====") &&             //Separator
280               !line.startsWith("----") &&             //Separator
281               !line.startsWith("BAND") &&             //Separator
282                line.replaceAll(" ", "").length() > 0; //Blank line
283      }
284      
285      /** Returns true if it looks like line is a name line. */
286      private boolean dataLooksLikeNameLine(String line)
287      {
288        if (line == null)
289          return false;
290        
291        char signChar = line.charAt(4);
292        
293        return signChar == '+' || signChar == '-';
294      }
295      
296      /** Adds a new parsing error to our list. */
297      private void putError(String message)
298      {
299        putError(dataReader.getLineNumber(), message);
300      }
301    }