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