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