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