Jalview 2.8 Source Header
[jalview.git] / src / jalview / io / FileParse.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3  * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10  *  
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.io;
19
20 import java.io.*;
21 import java.net.*;
22
23 /**
24  * implements a random access wrapper around a particular datasource, for
25  * passing to identifyFile and AlignFile objects.
26  */
27 public class FileParse
28 {
29   /**
30    * text specifying source of data. usually filename or url.
31    */
32   private String dataName = "unknown source";
33
34   public File inFile = null;
35
36   public int index = 1; // sequence counter for FileParse object created from
37
38   // same data source
39
40   protected char suffixSeparator = '#';
41
42   /**
43    * character used to write newlines
44    */
45   protected String newline = System.getProperty("line.separator");
46
47   public void setNewlineString(String nl)
48   {
49     newline = nl;
50   }
51
52   public String getNewlineString()
53   {
54     return newline;
55   }
56
57   /**
58    * '#' separated string tagged on to end of filename or url that was clipped
59    * off to resolve to valid filename
60    */
61   protected String suffix = null;
62
63   protected String type = null;
64
65   protected BufferedReader dataIn = null;
66
67   protected String errormessage = "UNITIALISED SOURCE";
68
69   protected boolean error = true;
70
71   protected String warningMessage = null;
72
73   /**
74    * size of readahead buffer used for when initial stream position is marked.
75    */
76   final int READAHEAD_LIMIT = 2048;
77
78   public FileParse()
79   {
80   }
81
82   /**
83    * Create a new FileParse instance reading from the same datasource starting
84    * at the current position. WARNING! Subsequent reads from either object will
85    * affect the read position of the other, but not the error state.
86    * 
87    * @param from
88    */
89   public FileParse(FileParse from) throws IOException
90   {
91     if (from == null)
92     {
93       throw new Error(
94               "Implementation error. Null FileParse in copy constructor");
95     }
96     if (from == this)
97       return;
98     index = ++from.index;
99     inFile = from.inFile;
100     suffixSeparator = from.suffixSeparator;
101     suffix = from.suffix;
102     errormessage = from.errormessage; // inherit potential error messages
103     error = false; // reset any error condition.
104     type = from.type;
105     dataIn = from.dataIn;
106     if (dataIn != null)
107     {
108       mark();
109     }
110     dataName = from.dataName;
111   }
112
113   /**
114    * Attempt to open a file as a datasource. Sets error and errormessage if
115    * fileStr was invalid.
116    * 
117    * @param fileStr
118    * @return this.error (true if the source was invalid)
119    */
120   private boolean checkFileSource(String fileStr) throws IOException
121   {
122     error = false;
123     this.inFile = new File(fileStr);
124     // check to see if it's a Jar file in disguise.
125     if (!inFile.exists())
126     {
127       errormessage = "FILE NOT FOUND";
128       error = true;
129     }
130     if (!inFile.canRead())
131     {
132       errormessage = "FILE CANNOT BE OPENED FOR READING";
133       error = true;
134     }
135     if (inFile.isDirectory())
136     {
137       // this is really a 'complex' filetype - but we don't handle directory
138       // reads yet.
139       errormessage = "FILE IS A DIRECTORY";
140       error = true;
141     }
142     if (!error)
143     {
144       dataIn = new BufferedReader(new FileReader(fileStr));
145       dataName = fileStr;
146     }
147     return error;
148   }
149
150   private boolean checkURLSource(String fileStr) throws IOException,
151           MalformedURLException
152   {
153     errormessage = "URL NOT FOUND";
154     URL url = new URL(fileStr);
155     dataIn = new BufferedReader(new InputStreamReader(url.openStream()));
156     // record URL as name of datasource.
157     dataName = fileStr;
158     return false;
159   }
160
161   /**
162    * sets the suffix string (if any) and returns remainder (if suffix was
163    * detected)
164    * 
165    * @param fileStr
166    * @return truncated fileStr or null
167    */
168   private String extractSuffix(String fileStr)
169   {
170     // first check that there wasn't a suffix string tagged on.
171     int sfpos = fileStr.lastIndexOf(suffixSeparator);
172     if (sfpos > -1 && sfpos < fileStr.length() - 1)
173     {
174       suffix = fileStr.substring(sfpos + 1);
175       // System.err.println("DEBUG: Found Suffix:"+suffix);
176       return fileStr.substring(0, sfpos);
177     }
178     return null;
179   }
180
181   /**
182    * Create a datasource for input to Jalview. See AppletFormatAdapter for the
183    * types of sources that are handled.
184    * 
185    * @param fileStr
186    *          - datasource locator/content
187    * @param type
188    *          - protocol of source
189    * @throws MalformedURLException
190    * @throws IOException
191    */
192   public FileParse(String fileStr, String type)
193           throws MalformedURLException, IOException
194   {
195     this.type = type;
196     error = false;
197
198     if (type.equals(AppletFormatAdapter.FILE))
199     {
200       if (checkFileSource(fileStr))
201       {
202         String suffixLess = extractSuffix(fileStr);
203         if (suffixLess != null)
204         {
205           if (checkFileSource(suffixLess))
206           {
207             throw new IOException("Problem opening " + inFile
208                     + " (also tried " + suffixLess + ") : " + errormessage);
209           }
210         }
211         else
212         {
213           throw new IOException("Problem opening " + inFile + " : "
214                   + errormessage);
215         }
216       }
217     }
218     else if (type.equals(AppletFormatAdapter.URL))
219     {
220       try
221       {
222         try
223         {
224           checkURLSource(fileStr);
225           if (suffixSeparator == '#')
226             extractSuffix(fileStr); // URL lref is stored for later reference.
227         } catch (IOException e)
228         {
229           String suffixLess = extractSuffix(fileStr);
230           if (suffixLess == null)
231           {
232             throw (e);
233           }
234           else
235           {
236             try
237             {
238               checkURLSource(suffixLess);
239             } catch (IOException e2)
240             {
241               errormessage = "BAD URL WITH OR WITHOUT SUFFIX";
242               throw (e); // just pass back original - everything was wrong.
243             }
244           }
245         }
246       } catch (Exception e)
247       {
248         errormessage = "CANNOT ACCESS DATA AT URL '" + fileStr + "' ("
249                 + e.getMessage() + ")";
250         error = true;
251       }
252     }
253     else if (type.equals(AppletFormatAdapter.PASTE))
254     {
255       errormessage = "PASTE INACCESSIBLE!";
256       dataIn = new BufferedReader(new StringReader(fileStr));
257       dataName = "Paste";
258     }
259     else if (type.equals(AppletFormatAdapter.CLASSLOADER))
260     {
261       errormessage = "RESOURCE CANNOT BE LOCATED";
262       java.io.InputStream is = getClass()
263               .getResourceAsStream("/" + fileStr);
264       if (is == null)
265       {
266         String suffixLess = extractSuffix(fileStr);
267         if (suffixLess != null)
268           is = getClass().getResourceAsStream("/" + suffixLess);
269       }
270       if (is != null)
271       {
272         dataIn = new BufferedReader(new java.io.InputStreamReader(is));
273         dataName = fileStr;
274       }
275       else
276       {
277         error = true;
278       }
279     }
280     else
281     {
282       errormessage = "PROBABLE IMPLEMENTATION ERROR : Datasource Type given as '"
283               + (type != null ? type : "null") + "'";
284       error = true;
285     }
286     if (dataIn == null || error)
287     {
288       // pass up the reason why we have no source to read from
289       throw new IOException("Failed to read data from source:\n"
290               + errormessage);
291     }
292     error = false;
293     dataIn.mark(READAHEAD_LIMIT);
294   }
295
296   /**
297    * mark the current position in the source as start for the purposes of it
298    * being analysed by IdentifyFile().identify
299    * 
300    * @throws IOException
301    */
302   public void mark() throws IOException
303   {
304     if (dataIn != null)
305     {
306       dataIn.mark(READAHEAD_LIMIT);
307     }
308     else
309     {
310       throw new IOException("Unitialised Source Stream");
311     }
312   }
313
314   public String nextLine() throws IOException
315   {
316     if (!error)
317       return dataIn.readLine();
318     throw new IOException("Invalid Source Stream:" + errormessage);
319   }
320
321   public boolean isValid()
322   {
323     return !error;
324   }
325
326   /**
327    * closes the datasource and tidies up. source will be left in an error state
328    */
329   public void close() throws IOException
330   {
331     errormessage = "EXCEPTION ON CLOSE";
332     error = true;
333     dataIn.close();
334     dataIn = null;
335     errormessage = "SOURCE IS CLOSED";
336   }
337
338   /**
339    * rewinds the datasource the beginning.
340    * 
341    */
342   public void reset() throws IOException
343   {
344     if (dataIn != null && !error)
345     {
346       dataIn.reset();
347     }
348     else
349     {
350       throw new IOException(
351               "Implementation Error: Reset called for invalid source.");
352     }
353   }
354
355   /**
356    * 
357    * @return true if there is a warning for the user
358    */
359   public boolean hasWarningMessage()
360   {
361     return (warningMessage != null && warningMessage.length() > 0);
362   }
363
364   /**
365    * 
366    * @return empty string or warning message about file that was just parsed.
367    */
368   public String getWarningMessage()
369   {
370     return warningMessage;
371   }
372
373   public String getInFile()
374   {
375     if (inFile != null)
376     {
377       return inFile.getAbsolutePath() + " (" + index + ")";
378     }
379     else
380     {
381       return "From Paste + (" + index + ")";
382     }
383   }
384
385   /**
386    * @return the dataName
387    */
388   public String getDataName()
389   {
390     return dataName;
391   }
392
393   /**
394    * set the (human readable) name or URI for this datasource
395    * 
396    * @param dataname
397    */
398   protected void setDataName(String dataname)
399   {
400     dataName = dataname;
401   }
402
403   /**
404    * get the underlying bufferedReader for this data source.
405    * 
406    * @return null if no reader available
407    * @throws IOException
408    */
409   public Reader getReader()
410   {
411     if (dataIn != null) // Probably don't need to test for readiness &&
412                         // dataIn.ready())
413     {
414       return dataIn;
415     }
416     return null;
417   }
418 }