JAL-1807 Bob's JalviewJS prototype first commit
[jalviewjs.git] / src / jalview / io / FileParse.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
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
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.io;
22
23 import jalview.api.AlignExportSettingI;
24 import jalview.api.AlignViewportI;
25 import jalview.api.AlignmentViewPanel;
26 import jalview.util.MessageManager;
27
28 import java.io.BufferedReader;
29 import java.io.File;
30 import java.io.FileInputStream;
31 import java.io.FileReader;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.InputStreamReader;
35 import java.io.Reader;
36 import java.io.StringReader;
37 import java.net.MalformedURLException;
38 import java.net.URL;
39 import java.util.zip.GZIPInputStream;
40
41 import javajs.J2SIgnoreImport;
42
43 /**
44  * implements a random access wrapper around a particular datasource, for
45  * passing to identifyFile and AlignFile objects.
46  */
47 @J2SIgnoreImport({java.io.File.class, java.io.FileReader.class, java.io.FileInputStream.class })
48 public class FileParse
49 {
50   /**
51    * text specifying source of data. usually filename or url.
52    */
53   private String dataName = "unknown source";
54
55   public File inFile = null;
56
57   /**
58    * a viewport associated with the current file operation. May be null. May
59    * move to different object.
60    */
61   private AlignViewportI viewport;
62
63   /**
64    * specific settings for exporting data from the current context
65    */
66   private AlignExportSettingI exportSettings;
67
68   /**
69    * sequence counter for FileParse object created from same data source
70    */
71   public int index = 1;
72
73   /**
74    * separator for extracting specific 'frame' of a datasource for formats that
75    * support multiple records (e.g. BLC, Stockholm, etc)
76    */
77   protected char suffixSeparator = '#';
78
79   /**
80    * character used to write newlines
81    */
82   protected String newline = System.getProperty("line.separator");
83
84   public void setNewlineString(String nl)
85   {
86       newline = nl;
87   }
88
89   public String getNewlineString()
90   {
91     return newline;
92   }
93
94   /**
95    * '#' separated string tagged on to end of filename or url that was clipped
96    * off to resolve to valid filename
97    */
98   protected String suffix = null;
99
100   protected String type = null;
101
102   protected BufferedReader dataIn = null;
103
104   protected String errormessage = "UNITIALISED SOURCE";
105
106   protected boolean error = true;
107
108   protected String warningMessage = null;
109
110   /**
111    * size of readahead buffer used for when initial stream position is marked.
112    */
113   final int READAHEAD_LIMIT = 2048;
114
115   public FileParse()
116   {
117   }
118
119   /**
120    * Create a new FileParse instance reading from the same datasource starting
121    * at the current position. WARNING! Subsequent reads from either object will
122    * affect the read position of the other, but not the error state.
123    * 
124    * @param from
125    */
126   public FileParse(FileParse from) throws IOException
127   {
128     if (from == null)
129     {
130       throw new Error(MessageManager.getString("error.implementation_error_null_fileparse"));
131     }
132     if (from == this)
133     {
134       return;
135     }
136     index = ++from.index;
137     inFile = from.inFile;
138     suffixSeparator = from.suffixSeparator;
139     suffix = from.suffix;
140     errormessage = from.errormessage; // inherit potential error messages
141     error = false; // reset any error condition.
142     type = from.type;
143     dataIn = from.dataIn;
144     if (dataIn != null)
145     {
146       mark();
147     }
148     dataName = from.dataName;
149   }
150
151   /**
152    * Attempt to open a file as a datasource. Sets error and errormessage if
153    * fileStr was invalid.
154    * 
155    * Not used by applet
156    * 
157    * @j2sIgnore
158    * 
159    * @param fileStr
160    * @return this.error (true if the source was invalid)
161    */
162   private boolean checkFileSource(String fileStr) throws IOException
163   {
164     error = false;
165     this.inFile = new File(fileStr);
166     // check to see if it's a Jar file in disguise.
167     if (!inFile.exists())
168     {
169       errormessage = "FILE NOT FOUND";
170       error = true;
171     }
172     if (!inFile.canRead())
173     {
174       errormessage = "FILE CANNOT BE OPENED FOR READING";
175       error = true;
176     }
177     if (inFile.isDirectory())
178     {
179       // this is really a 'complex' filetype - but we don't handle directory
180       // reads yet.
181       errormessage = "FILE IS A DIRECTORY";
182       error = true;
183     }
184     if (!error)
185     {
186       if (fileStr.toLowerCase().endsWith(".gz"))
187       {
188         try
189         {
190           dataIn = tryAsGzipSource(new FileInputStream(fileStr));
191           dataName = fileStr;
192           return error;
193         } catch (Exception x)
194         {
195           warningMessage = "Failed  to resolve as a GZ stream ("
196                   + x.getMessage() + ")";
197           // x.printStackTrace();
198         }
199         ;
200       }
201       dataIn = new BufferedReader(new FileReader(fileStr));
202       dataName = fileStr;
203     }
204     return error;
205   }
206
207   /**
208    * 
209    * @param inputStream
210    * @return
211    * @throws Exception
212    */
213   private BufferedReader tryAsGzipSource(InputStream inputStream)
214           throws Exception
215   {
216     BufferedReader inData = new BufferedReader(new InputStreamReader(
217             new GZIPInputStream(inputStream)));
218     inData.mark(2048);
219     inData.read();
220     inData.reset();
221     return inData;
222   }
223
224   private boolean checkURLSource(String fileStr) throws IOException,
225           MalformedURLException
226   {
227     errormessage = "URL NOT FOUND";
228     URL url = new URL(fileStr);
229     //
230     // GZIPInputStream code borrowed from Aquaria (soon to be open sourced) via
231     // Kenny Sabir
232     Exception e = null;
233     if (fileStr.toLowerCase().endsWith(".gz"))
234     {
235       try
236       {
237         InputStream inputStream = url.openStream();
238         dataIn = tryAsGzipSource(inputStream);
239         dataName = fileStr;
240         return false;
241       } catch (Exception ex)
242       {
243         e = ex;
244       }
245     }
246
247     try
248     {
249       dataIn = new BufferedReader(new InputStreamReader(url.openStream()));
250     } catch (IOException q)
251     {
252       if (e != null)
253       {
254         throw new IOException(MessageManager.getString("exception.failed_to_resolve_gzip_stream"), e);
255       }
256       throw q;
257     }
258     // record URL as name of datasource.
259     dataName = fileStr;
260     return false;
261   }
262
263   /**
264    * sets the suffix string (if any) and returns remainder (if suffix was
265    * detected)
266    * 
267    * @param fileStr
268    * @return truncated fileStr or null
269    */
270   private String extractSuffix(String fileStr)
271   {
272     // first check that there wasn't a suffix string tagged on.
273     int sfpos = fileStr.lastIndexOf(suffixSeparator);
274     if (sfpos > -1 && sfpos < fileStr.length() - 1)
275     {
276       suffix = fileStr.substring(sfpos + 1);
277       // System.err.println("DEBUG: Found Suffix:"+suffix);
278       return fileStr.substring(0, sfpos);
279     }
280     return null;
281   }
282
283   /**
284    * Create a datasource for input to Jalview. See AppletFormatAdapter for the
285    * types of sources that are handled.
286    * 
287    * @param fileStr
288    *          - datasource locator/content
289    * @param type
290    *          - protocol of source
291    * @throws MalformedURLException
292    * @throws IOException
293    */
294   public FileParse(String fileStr, String type) throws MalformedURLException, IOException {
295                 this.type = type;
296                 error = false;
297
298                 if (type.equals(AppletFormatAdapter.FILE)) {
299                         /**
300                          * @j2sIgnore
301                          * 
302                          */
303                         {
304                                 if (checkFileSource(fileStr)) {
305                                         String suffixLess = extractSuffix(fileStr);
306                                         if (suffixLess != null) {
307                                                 if (checkFileSource(suffixLess)) {
308                                                         throw new IOException(MessageManager.formatMessage(
309                                                                         "exception.problem_opening_file_also_tried", new String[] {
310                                                                                         inFile.getName(), suffixLess, errormessage }));
311                                                 }
312                                         } else {
313                                                 throw new IOException(MessageManager.formatMessage(
314                                                                 "exception.problem_opening_file",
315                                                                 new String[] { inFile.getName(), errormessage }));
316                                         }
317                                 }
318                         }
319                 } else if (type.equals(AppletFormatAdapter.URL)) {
320                         try {
321                                 try {
322                                         checkURLSource(fileStr);
323                                         if (suffixSeparator == '#') {
324                                                 extractSuffix(fileStr); // URL lref is stored for later reference.
325                                         }
326                                 } catch (IOException e) {
327                                         String suffixLess = extractSuffix(fileStr);
328                                         if (suffixLess == null) {
329                                                 throw (e);
330                                         } else {
331                                                 try {
332                                                         checkURLSource(suffixLess);
333                                                 } catch (IOException e2) {
334                                                         errormessage = "BAD URL WITH OR WITHOUT SUFFIX";
335                                                         throw (e); // just pass back original - everything was wrong.
336                                                 }
337                                         }
338                                 }
339                         } catch (Exception e) {
340                                 errormessage = "CANNOT ACCESS DATA AT URL '" + fileStr + "' ("
341                                                 + e.getMessage() + ")";
342                                 error = true;
343                         }
344                 } else if (type.equals(AppletFormatAdapter.PASTE)) {
345                         errormessage = "PASTE INACCESSIBLE!";
346                         dataIn = new BufferedReader(new StringReader(fileStr));
347                         dataName = "Paste";
348                 } else if (type.equals(AppletFormatAdapter.CLASSLOADER)) {
349                         errormessage = "RESOURCE CANNOT BE LOCATED";
350                         java.io.InputStream is = getClass().getResourceAsStream("/" + fileStr);
351                         if (is == null) {
352                                 String suffixLess = extractSuffix(fileStr);
353                                 if (suffixLess != null) {
354                                         is = getClass().getResourceAsStream("/" + suffixLess);
355                                 }
356                         }
357                         if (is != null) {
358                                 dataIn = new BufferedReader(new java.io.InputStreamReader(is));
359                                 dataName = fileStr;
360                         } else {
361                                 error = true;
362                         }
363                 } else {
364                         errormessage = "PROBABLE IMPLEMENTATION ERROR : Datasource Type given as '"
365                                         + (type != null ? type : "null") + "'";
366                         error = true;
367                 }
368                 if (dataIn == null || error) {
369                         // pass up the reason why we have no source to read from
370                         throw new IOException(MessageManager.formatMessage(
371                                         "exception.failed_to_read_data_from_source",
372                                         new String[] { errormessage }));
373                 }
374                 error = false;
375                 dataIn.mark(READAHEAD_LIMIT);
376         }
377
378   /**
379    * mark the current position in the source as start for the purposes of it
380    * being analysed by IdentifyFile().identify
381    * 
382    * @throws IOException
383    */
384   public void mark() throws IOException
385   {
386     if (dataIn != null)
387     {
388       dataIn.mark(READAHEAD_LIMIT);
389     }
390     else
391     {
392       throw new IOException(MessageManager.getString("exception.no_init_source_stream"));
393     }
394   }
395
396   public String nextLine() throws IOException
397   {
398     if (!error)
399     {
400       return dataIn.readLine();
401     }
402     throw new IOException(MessageManager.formatMessage("exception.invalid_source_stream", new String[]{errormessage}));
403   }
404
405   /**
406    * 
407    * @return true if this FileParse is configured for Export only
408    */
409   public boolean isExporting()
410   {
411     return !error && dataIn == null;
412   }
413
414   /**
415    * 
416    * @return true if the data source is valid
417    */
418   public boolean isValid()
419   {
420     return !error;
421   }
422
423   /**
424    * closes the datasource and tidies up. source will be left in an error state
425    */
426   public void close() throws IOException
427   {
428     errormessage = "EXCEPTION ON CLOSE";
429     error = true;
430     dataIn.close();
431     dataIn = null;
432     errormessage = "SOURCE IS CLOSED";
433   }
434
435   /**
436    * rewinds the datasource the beginning.
437    * 
438    */
439   public void reset() throws IOException
440   {
441     if (dataIn != null && !error)
442     {
443       dataIn.reset();
444     }
445     else
446     {
447       throw new IOException(MessageManager.getString("error.implementation_error_reset_called_for_invalid_source"));
448     }
449   }
450
451   /**
452    * 
453    * @return true if there is a warning for the user
454    */
455   public boolean hasWarningMessage()
456   {
457     return (warningMessage != null && warningMessage.length() > 0);
458   }
459
460   /**
461    * 
462    * @return empty string or warning message about file that was just parsed.
463    */
464   public String getWarningMessage()
465   {
466     return warningMessage;
467   }
468
469   public String getInFile()
470   {
471     if (inFile != null)
472     {
473       return inFile.getAbsolutePath() + " (" + index + ")";
474     }
475     else
476     {
477       return "From Paste + (" + index + ")";
478     }
479   }
480
481   /**
482    * @return the dataName
483    */
484   public String getDataName()
485   {
486     return dataName;
487   }
488
489   /**
490    * set the (human readable) name or URI for this datasource
491    * 
492    * @param dataname
493    */
494   protected void setDataName(String dataname)
495   {
496     dataName = dataname;
497   }
498
499   /**
500    * get the underlying bufferedReader for this data source.
501    * 
502    * @return null if no reader available
503    * @throws IOException
504    */
505   public Reader getReader()
506   {
507     if (dataIn != null) // Probably don't need to test for readiness &&
508                         // dataIn.ready())
509     {
510       return dataIn;
511     }
512     return null;
513   }
514
515   public AlignViewportI getViewport()
516   {
517     return viewport;
518   }
519
520   public void setViewport(AlignViewportI viewport)
521   {
522     this.viewport = viewport;
523   }
524
525   /**
526    * @return the currently configured exportSettings for writing data.
527    */
528   public AlignExportSettingI getExportSettings()
529   {
530     return exportSettings;
531   }
532
533   /**
534    * Set configuration for export of data.
535    * 
536    * @param exportSettings
537    *          the exportSettings to set
538    */
539   public void setExportSettings(AlignExportSettingI exportSettings)
540   {
541     this.exportSettings = exportSettings;
542   }
543
544   /**
545    * method overridden by complex file exporter/importers which support
546    * exporting visualisation and layout settings for a view
547    * 
548    * @param avpanel
549    */
550   public void configureForView(AlignmentViewPanel avpanel)
551   {
552     if (avpanel!=null) {
553       setViewport(avpanel.getAlignViewport());
554     }
555     // could also set export/import settings
556   }
557 }