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