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