j2sNative references moved to Platform
[jalview.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.AlignExportSettingsI;
24 import jalview.api.AlignViewportI;
25 import jalview.api.AlignmentViewPanel;
26 import jalview.api.FeatureSettingsModelI;
27 import jalview.util.MessageManager;
28 import jalview.util.Platform;
29
30 import java.io.BufferedReader;
31 import java.io.ByteArrayInputStream;
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.FileReader;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.InputStreamReader;
38 import java.io.Reader;
39 import java.io.StringReader;
40 import java.net.MalformedURLException;
41 import java.net.URL;
42 import java.util.zip.GZIPInputStream;
43
44 /**
45  * implements a random access wrapper around a particular datasource, for
46  * passing to identifyFile and AlignFile objects.
47  */
48 public class FileParse
49 {
50   protected static final String SPACE = " ";
51
52   protected static final String TAB = "\t";
53
54   /**
55    * text specifying source of data. usually filename or url.
56    */
57   private String dataName = "unknown source";
58
59   public File inFile = null;
60
61   private byte[] bytes; // from JavaScript
62
63   public byte[] getBytes()
64   {
65     return bytes;
66   }
67   /**
68    * a viewport associated with the current file operation. May be null. May
69    * move to different object.
70    */
71   private AlignViewportI viewport;
72
73   /**
74    * specific settings for exporting data from the current context
75    */
76   private AlignExportSettingsI exportSettings;
77
78   /**
79    * sequence counter for FileParse object created from same data source
80    */
81   public int index = 1;
82
83   /**
84    * separator for extracting specific 'frame' of a datasource for formats that
85    * support multiple records (e.g. BLC, Stockholm, etc)
86    */
87   protected char suffixSeparator = '#';
88
89   /**
90    * character used to write newlines
91    */
92   protected String newline = System.getProperty("line.separator");
93
94   public void setNewlineString(String nl)
95   {
96     newline = nl;
97   }
98
99   public String getNewlineString()
100   {
101     return newline;
102   }
103
104   /**
105    * '#' separated string tagged on to end of filename or url that was clipped
106    * off to resolve to valid filename
107    */
108   protected String suffix = null;
109
110   protected DataSourceType dataSourceType = null;
111
112   protected BufferedReader dataIn = null;
113
114   protected String errormessage = "UNINITIALISED SOURCE";
115
116   protected boolean error = true;
117
118   protected String warningMessage = null;
119
120   /**
121    * size of readahead buffer used for when initial stream position is marked.
122    */
123   final int READAHEAD_LIMIT = 2048;
124
125   public FileParse()
126   {
127   }
128
129   /**
130    * Create a new FileParse instance reading from the same datasource starting
131    * at the current position. WARNING! Subsequent reads from either object will
132    * affect the read position of the other, but not the error state.
133    * 
134    * @param from
135    */
136   public FileParse(FileParse from) throws IOException
137   {
138     if (from == null)
139     {
140       throw new Error(MessageManager
141               .getString("error.implementation_error_null_fileparse"));
142     }
143     if (from == this)
144     {
145       return;
146     }
147     index = ++from.index;
148     inFile = from.inFile;
149     suffixSeparator = from.suffixSeparator;
150     suffix = from.suffix;
151     errormessage = from.errormessage; // inherit potential error messages
152     error = false; // reset any error condition.
153     dataSourceType = from.dataSourceType;
154     dataIn = from.dataIn;
155     if (dataIn != null)
156     {
157       mark();
158     }
159     dataName = from.dataName;
160   }
161
162   /**
163    * Attempt to open a file as a datasource. Sets error and errormessage if
164    * fileStr was invalid.
165    * 
166    * @param fileStr
167    * @return this.error (true if the source was invalid)
168    */
169   private boolean checkFileSource(String fileStr) throws IOException
170   {
171     error = false;
172     this.inFile = new File(fileStr);
173     // check to see if it's a Jar file in disguise.
174     if (!inFile.exists())
175     {
176       errormessage = "FILE NOT FOUND";
177       error = true;
178     }
179     if (!inFile.canRead())
180     {
181       errormessage = "FILE CANNOT BE OPENED FOR READING";
182       error = true;
183     }
184     if (inFile.isDirectory())
185     {
186       // this is really a 'complex' filetype - but we don't handle directory
187       // reads yet.
188       errormessage = "FILE IS A DIRECTORY";
189       error = true;
190     }
191     if (!error)
192     {
193       if (fileStr.toLowerCase().endsWith(".gz"))
194       {
195         try
196         {
197           dataIn = tryAsGzipSource(new FileInputStream(fileStr));
198           dataName = fileStr;
199           return error;
200         } catch (Exception x)
201         {
202           warningMessage = "Failed  to resolve as a GZ stream ("
203                   + x.getMessage() + ")";
204           // x.printStackTrace();
205         }
206         ;
207       }
208
209       dataIn = new BufferedReader(new FileReader(fileStr));
210       dataName = fileStr;
211     }
212     return error;
213   }
214
215   private BufferedReader tryAsGzipSource(InputStream inputStream)
216           throws Exception
217   {
218     BufferedReader inData = new BufferedReader(
219             new InputStreamReader(new GZIPInputStream(inputStream)));
220     inData.mark(2048);
221     inData.read();
222     inData.reset();
223     return inData;
224   }
225
226   private boolean checkURLSource(String fileStr)
227           throws IOException, MalformedURLException
228   {
229     errormessage = "URL NOT FOUND";
230     URL url = new URL(fileStr);
231     //
232     // GZIPInputStream code borrowed from Aquaria (soon to be open sourced) via
233     // Kenny Sabir
234     Exception e = null;
235     if (fileStr.toLowerCase().endsWith(".gz"))
236     {
237       try
238       {
239         InputStream inputStream = url.openStream();
240         dataIn = tryAsGzipSource(inputStream);
241         dataName = fileStr;
242         return false;
243       } catch (Exception ex)
244       {
245         e = ex;
246       }
247     }
248
249     try
250     {
251       dataIn = new BufferedReader(new InputStreamReader(url.openStream()));
252     } catch (IOException q)
253     {
254       if (e != null)
255       {
256         throw new IOException(MessageManager
257                 .getString("exception.failed_to_resolve_gzip_stream"), e);
258       }
259       throw q;
260     }
261     // record URL as name of datasource.
262     dataName = fileStr;
263     return false;
264   }
265
266   /**
267    * sets the suffix string (if any) and returns remainder (if suffix was
268    * detected)
269    * 
270    * @param fileStr
271    * @return truncated fileStr or null
272    */
273   private String extractSuffix(String fileStr)
274   {
275     // first check that there wasn't a suffix string tagged on.
276     int sfpos = fileStr.lastIndexOf(suffixSeparator);
277     if (sfpos > -1 && sfpos < fileStr.length() - 1)
278     {
279       suffix = fileStr.substring(sfpos + 1);
280       // System.err.println("DEBUG: Found Suffix:"+suffix);
281       return fileStr.substring(0, sfpos);
282     }
283     return null;
284   }
285
286   /**
287    * not for general use, creates a fileParse object for an existing reader with
288    * configurable values for the origin and the type of the source
289    */
290   public FileParse(BufferedReader source, String originString,
291           DataSourceType sourceType)
292   {
293     dataSourceType = sourceType;
294     error = false;
295     inFile = null;
296     dataName = originString;
297     dataIn = source;
298     try
299     {
300       if (dataIn.markSupported())
301       {
302         dataIn.mark(READAHEAD_LIMIT);
303       }
304     } catch (IOException q)
305     {
306
307     }
308   }
309
310   /**
311    * Create a datasource for input to Jalview. See AppletFormatAdapter for the
312    * types of sources that are handled.
313    * 
314    * @param file
315    *          - datasource locator/content as File or String
316    * @param sourceType
317    *          - protocol of source
318    * @throws MalformedURLException
319    * @throws IOException
320    */
321   public FileParse(Object file, DataSourceType sourceType)
322           throws MalformedURLException, IOException
323   {
324     if (file instanceof File)
325     {
326       parse((File) file, ((File) file).getPath(), sourceType, true);
327     }
328     else
329     {
330       parse(null, file.toString(), sourceType, false);
331     }
332   }
333
334   private void parse(File file, String fileStr, DataSourceType sourceType,
335           boolean isFileObject) throws MalformedURLException, IOException
336   {
337         bytes = Platform.getFileBytes(file);
338     this.dataSourceType = sourceType;
339     error = false;
340
341     if (sourceType == DataSourceType.FILE)
342     {
343
344       if (bytes != null)
345       {
346         // this will be from JavaScript
347         inFile = file;
348         dataIn = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes)));
349         dataName = fileStr;
350       }
351       else if (checkFileSource(fileStr))
352       {
353         String suffixLess = extractSuffix(fileStr);
354         if (suffixLess != null)
355         {
356           if (checkFileSource(suffixLess))
357           {
358             throw new IOException(MessageManager.formatMessage(
359                     "exception.problem_opening_file_also_tried",
360                     new String[]
361                     { inFile.getName(), suffixLess, errormessage }));
362           }
363         }
364         else
365         {
366           throw new IOException(MessageManager.formatMessage(
367                   "exception.problem_opening_file", new String[]
368                   { inFile.getName(), errormessage }));
369         }
370       }
371     }
372     else if (sourceType == DataSourceType.RELATIVE_URL)
373     {
374       bytes = Platform.getFileAsBytes(fileStr);
375       dataIn = new BufferedReader(new java.io.InputStreamReader(new ByteArrayInputStream(bytes)));      
376       dataName = fileStr;
377
378     }
379     else if (sourceType == DataSourceType.URL)
380     {
381       try
382       {
383         try
384         {
385           checkURLSource(fileStr);
386           if (suffixSeparator == '#')
387           {
388             extractSuffix(fileStr); // URL lref is stored for later reference.
389           }
390         } catch (IOException e)
391         {
392           String suffixLess = extractSuffix(fileStr);
393           if (suffixLess == null)
394           {
395             throw (e);
396           }
397           else
398           {
399             try
400             {
401               checkURLSource(suffixLess);
402             } catch (IOException e2)
403             {
404               errormessage = "BAD URL WITH OR WITHOUT SUFFIX";
405               throw (e); // just pass back original - everything was wrong.
406             }
407           }
408         }
409       } catch (Exception e)
410       {
411         errormessage = "CANNOT ACCESS DATA AT URL '" + fileStr + "' ("
412                 + e.getMessage() + ")";
413         error = true;
414       }
415     }
416     else if (sourceType == DataSourceType.PASTE)
417     {
418       errormessage = "PASTE INACCESSIBLE!";
419       dataIn = new BufferedReader(new StringReader(fileStr));
420       dataName = "Paste";
421     }
422     else if (sourceType == DataSourceType.CLASSLOADER)
423     {
424       errormessage = "RESOURCE CANNOT BE LOCATED";
425       java.io.InputStream is = getClass()
426               .getResourceAsStream("/" + fileStr);
427       if (is == null)
428       {
429         String suffixLess = extractSuffix(fileStr);
430         if (suffixLess != null)
431         {
432           is = getClass().getResourceAsStream("/" + suffixLess);
433         }
434       }
435       if (is != null)
436       {
437         dataIn = new BufferedReader(new java.io.InputStreamReader(is));
438         dataName = fileStr;
439       }
440       else
441       {
442         error = true;
443       }
444     }
445     else
446     {
447       errormessage = "PROBABLE IMPLEMENTATION ERROR : Datasource Type given as '"
448               + (sourceType != null ? sourceType : "null") + "'";
449       error = true;
450     }
451     if (dataIn == null || error)
452     {
453       // pass up the reason why we have no source to read from
454       throw new IOException(MessageManager.formatMessage(
455               "exception.failed_to_read_data_from_source",
456               new String[]
457               { errormessage }));
458     }
459     error = false;
460     dataIn.mark(READAHEAD_LIMIT);
461   }
462
463   /**
464    * mark the current position in the source as start for the purposes of it
465    * being analysed by IdentifyFile().identify
466    * 
467    * @throws IOException
468    */
469   public void mark() throws IOException
470   {
471     if (dataIn != null)
472     {
473       dataIn.mark(READAHEAD_LIMIT);
474     }
475     else
476     {
477       throw new IOException(
478               MessageManager.getString("exception.no_init_source_stream"));
479     }
480   }
481
482   public String nextLine() throws IOException
483   {
484     if (!error)
485     {
486       return dataIn.readLine();
487     }
488     throw new IOException(MessageManager
489             .formatMessage("exception.invalid_source_stream", new String[]
490             { errormessage }));
491   }
492
493   /**
494    * 
495    * @return true if this FileParse is configured for Export only
496    */
497   public boolean isExporting()
498   {
499     return !error && dataIn == null;
500   }
501
502   /**
503    * 
504    * @return true if the data source is valid
505    */
506   public boolean isValid()
507   {
508     return !error;
509   }
510
511   /**
512    * closes the datasource and tidies up. source will be left in an error state
513    */
514   public void close() throws IOException
515   {
516     errormessage = "EXCEPTION ON CLOSE";
517     error = true;
518     dataIn.close();
519     dataIn = null;
520     errormessage = "SOURCE IS CLOSED";
521   }
522
523   /**
524    * Rewinds the datasource to the marked point if possible
525    * 
526    * @param bytesRead
527    * 
528    */
529   public void reset(int bytesRead) throws IOException
530   {
531     if (bytesRead >= READAHEAD_LIMIT)
532     {
533       System.err.println(String.format(
534               "File reset error: read %d bytes but reset limit is %d",
535               bytesRead, READAHEAD_LIMIT));
536     }
537     if (dataIn != null && !error)
538     {
539       dataIn.reset();
540     }
541     else
542     {
543       throw new IOException(MessageManager.getString(
544               "error.implementation_error_reset_called_for_invalid_source"));
545     }
546   }
547
548   /**
549    * 
550    * @return true if there is a warning for the user
551    */
552   public boolean hasWarningMessage()
553   {
554     return (warningMessage != null && warningMessage.length() > 0);
555   }
556
557   /**
558    * 
559    * @return empty string or warning message about file that was just parsed.
560    */
561   public String getWarningMessage()
562   {
563     return warningMessage;
564   }
565
566   public String getInFile()
567   {
568     if (inFile != null)
569     {
570       return inFile.getAbsolutePath() + " (" + index + ")";
571     }
572     else
573     {
574       return "From Paste + (" + index + ")";
575     }
576   }
577
578   /**
579    * @return the dataName
580    */
581   public String getDataName()
582   {
583     return dataName;
584   }
585
586   /**
587    * set the (human readable) name or URI for this datasource
588    * 
589    * @param dataname
590    */
591   protected void setDataName(String dataname)
592   {
593     dataName = dataname;
594   }
595
596   /**
597    * get the underlying bufferedReader for this data source.
598    * 
599    * @return null if no reader available
600    * @throws IOException
601    */
602   public Reader getReader()
603   {
604     if (dataIn != null) // Probably don't need to test for readiness &&
605                         // dataIn.ready())
606     {
607       return dataIn;
608     }
609     return null;
610   }
611
612   public AlignViewportI getViewport()
613   {
614     return viewport;
615   }
616
617   public void setViewport(AlignViewportI viewport)
618   {
619     this.viewport = viewport;
620   }
621
622   /**
623    * @return the currently configured exportSettings for writing data.
624    */
625   public AlignExportSettingsI getExportSettings()
626   {
627     return exportSettings;
628   }
629
630   /**
631    * Set configuration for export of data.
632    * 
633    * @param exportSettings
634    *          the exportSettings to set
635    */
636   public void setExportSettings(AlignExportSettingsI exportSettings)
637   {
638     this.exportSettings = exportSettings;
639   }
640
641   /**
642    * method overridden by complex file exporter/importers which support
643    * exporting visualisation and layout settings for a view
644    * 
645    * @param avpanel
646    */
647   public void configureForView(AlignmentViewPanel avpanel)
648   {
649     if (avpanel != null)
650     {
651       setViewport(avpanel.getAlignViewport());
652     }
653     // could also set export/import settings
654   }
655
656   /**
657    * Returns the preferred feature colour configuration if there is one, else
658    * null
659    * 
660    * @return
661    */
662   public FeatureSettingsModelI getFeatureColourScheme()
663   {
664     return null;
665   }
666
667   public DataSourceType getDataSourceType()
668   {
669     return dataSourceType;
670   }
671 }