FileParse object can be re-used to read different files concatenated together
[jalview.git] / src / jalview / io / FileParse.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2007 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     if (dataIn==null)
208     {
209       // pass up the reason why we have no source to read from
210       throw new IOException("Failed to read data from source:\n"+errormessage);
211     }
212     error=false;
213     dataIn.mark(READAHEAD_LIMIT);
214   }
215   /**
216    * mark the current position in the source as start
217    * for the purposes of it being analysed by IdentifyFile().identify
218    * @throws IOException
219    */
220   public void mark() throws IOException
221   {
222     if (dataIn!=null)
223     {
224       dataIn.mark(READAHEAD_LIMIT);
225     } else {
226       throw new IOException("Unitialised Source Stream");
227     }
228   }
229   public String nextLine()
230       throws IOException
231   {
232     if (!error)
233       return dataIn.readLine();
234     throw new IOException("Invalid Source Stream:"+errormessage);
235   }
236
237   public boolean isValid()
238   {
239     return !error;
240   }
241   /**
242    * closes the datasource and tidies up.
243    * source will be left in an error state
244    */
245   public void close() throws IOException
246   {
247     errormessage="EXCEPTION ON CLOSE";
248     error=true;
249     dataIn.close();
250     dataIn=null;
251     errormessage="SOURCE IS CLOSED";
252   }
253   /**
254    * rewinds the datasource the beginning.
255    *
256    */
257   public void reset() throws IOException
258   {
259     if (dataIn!=null && !error) {
260       dataIn.reset();
261     } else {
262       throw new IOException("Implementation Error: Reset called for invalid source.");
263     }
264   }
265   /**
266    * 
267    * @return true if there is a warning for the user
268    */
269   public boolean hasWarningMessage() {
270     return (warningMessage!=null && warningMessage.length()>0);
271   }
272   /**
273    * 
274    * @return empty string or warning message about file that was just parsed.
275    */
276   public String getWarningMessage() {
277     return warningMessage;
278   }
279   public String getInFile()
280   {
281     if (inFile!=null)
282     {
283       return inFile.getAbsolutePath()+" ("+index+")";
284     }
285     else
286     {
287       return "From Paste + ("+index+")";
288     }
289   }
290 }