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