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