JAL-2909 JAL-3894 hack in support to import regions of an unindexed SAM file.
[jalview.git] / src / jalview / io / FileLoader.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 java.io.File;
24 import java.io.IOException;
25 import java.util.StringTokenizer;
26 import java.util.Vector;
27
28 import javax.swing.SwingUtilities;
29
30 import jalview.api.ComplexAlignFile;
31 import jalview.api.FeatureSettingsModelI;
32 import jalview.api.FeaturesDisplayedI;
33 import jalview.api.FeaturesSourceI;
34 import jalview.bin.Cache;
35 import jalview.bin.Jalview;
36 import jalview.datamodel.AlignmentI;
37 import jalview.datamodel.HiddenColumns;
38 import jalview.datamodel.PDBEntry;
39 import jalview.datamodel.SequenceI;
40 import jalview.gui.AlignFrame;
41 import jalview.gui.AlignViewport;
42 import jalview.gui.BamFileOptionsChooser;
43 import jalview.gui.Desktop;
44 import jalview.gui.JvOptionPane;
45 import jalview.json.binding.biojson.v1.ColourSchemeMapper;
46 import jalview.project.Jalview2XML;
47 import jalview.schemes.ColourSchemeI;
48 import jalview.structure.StructureSelectionManager;
49 import jalview.util.MessageManager;
50 import jalview.ws.utils.UrlDownloadClient;
51
52 public class FileLoader implements Runnable
53 {
54   String file;
55
56   DataSourceType protocol;
57
58   FileFormatI format;
59
60   AlignmentFileReaderI source = null; // alternative specification of where data
61                                       // comes
62
63   // from
64
65   AlignViewport viewport;
66
67   AlignFrame alignFrame;
68
69   long loadtime;
70
71   long memused;
72
73   boolean raiseGUI = true;
74
75   private File selectedFile;
76
77   /**
78    * default constructor always raised errors in GUI dialog boxes
79    */
80   public FileLoader()
81   {
82     this(true);
83   }
84
85   /**
86    * construct a Fileloader that may raise errors non-interactively
87    * 
88    * @param raiseGUI
89    *          true if errors are to be raised as GUI dialog boxes
90    */
91   public FileLoader(boolean raiseGUI)
92   {
93     this.raiseGUI = raiseGUI;
94   }
95
96   public void LoadFile(AlignViewport viewport, Object file,
97           DataSourceType protocol, FileFormatI format)
98   {
99     this.viewport = viewport;
100     if (file instanceof File) {
101       this.selectedFile = (File) file;
102       file = selectedFile.getPath();
103     }
104     LoadFile(file.toString(), protocol, format);
105   }
106
107   public void LoadFile(String file, DataSourceType protocol,
108           FileFormatI format)
109   {
110     this.file = file;
111     this.protocol = protocol;
112     this.format = format;
113
114     final Thread loader = new Thread(this);
115
116     SwingUtilities.invokeLater(new Runnable()
117     {
118       @Override
119       public void run()
120       {
121         loader.start();
122       }
123     });
124   }
125
126   /**
127    * Load a (file, protocol) source of unknown type
128    * 
129    * @param file
130    * @param protocol
131    */
132   public void LoadFile(String file, DataSourceType protocol)
133   {
134     LoadFile(file, protocol, null);
135   }
136
137   /**
138    * Load alignment from (file, protocol) and wait till loaded
139    * 
140    * @param file
141    * @param sourceType
142    * @return alignFrame constructed from file contents
143    */
144   public AlignFrame LoadFileWaitTillLoaded(String file,
145           DataSourceType sourceType)
146   {
147     return LoadFileWaitTillLoaded(file, sourceType, null);
148   }
149
150   /**
151    * Load alignment from (file, protocol) of type format and wait till loaded
152    * 
153    * @param file
154    * @param sourceType
155    * @param format
156    * @return alignFrame constructed from file contents
157    */
158   public AlignFrame LoadFileWaitTillLoaded(String file,
159           DataSourceType sourceType, FileFormatI format)
160   {
161     this.file = file;
162     this.protocol = sourceType;
163     this.format = format;
164     return _LoadFileWaitTillLoaded();
165   }
166
167   /**
168    * Load alignment from (file, protocol) of type format and wait till loaded
169    * 
170    * @param file
171    * @param sourceType
172    * @param format
173    * @return alignFrame constructed from file contents
174    */
175   public AlignFrame LoadFileWaitTillLoaded(File file,
176           DataSourceType sourceType, FileFormatI format)
177   {
178     this.selectedFile = file;
179     this.file = file.getPath();
180     this.protocol = sourceType;
181     this.format = format;
182     return _LoadFileWaitTillLoaded();
183   }
184
185   /**
186    * Load alignment from FileParse source of type format and wait till loaded
187    * 
188    * @param source
189    * @param format
190    * @return alignFrame constructed from file contents
191    */
192   public AlignFrame LoadFileWaitTillLoaded(AlignmentFileReaderI source,
193           FileFormatI format)
194   {
195     this.source = source;
196
197     file = source.getInFile();
198     protocol = source.getDataSourceType();
199     this.format = format;
200     return _LoadFileWaitTillLoaded();
201   }
202
203   /**
204    * runs the 'run' method (in this thread), then return the alignFrame that's
205    * (hopefully) been read
206    * 
207    * @return
208    */
209   protected AlignFrame _LoadFileWaitTillLoaded()
210   {
211     this.run();
212
213     return alignFrame;
214   }
215
216   public void updateRecentlyOpened()
217   {
218     Vector<String> recent = new Vector<>();
219     if (protocol == DataSourceType.PASTE)
220     {
221       // do nothing if the file was pasted in as text... there is no filename to
222       // refer to it as.
223       return;
224     }
225     if (file != null
226             && file.indexOf(System.getProperty("java.io.tmpdir")) > -1)
227     {
228       // ignore files loaded from the system's temporary directory
229       return;
230     }
231     String type = protocol == DataSourceType.FILE ? "RECENT_FILE"
232             : "RECENT_URL";
233
234     String historyItems = Cache.getProperty(type);
235
236     StringTokenizer st;
237
238     if (historyItems != null)
239     {
240       st = new StringTokenizer(historyItems, "\t");
241
242       while (st.hasMoreTokens())
243       {
244         recent.addElement(st.nextToken().trim());
245       }
246     }
247
248     if (recent.contains(file))
249     {
250       recent.remove(file);
251     }
252
253     StringBuffer newHistory = new StringBuffer(file);
254     for (int i = 0; i < recent.size() && i < 10; i++)
255     {
256       newHistory.append("\t");
257       newHistory.append(recent.elementAt(i));
258     }
259
260     Cache.setProperty(type, newHistory.toString());
261
262     if (protocol == DataSourceType.FILE)
263     {
264       Cache.setProperty("DEFAULT_FILE_FORMAT", format.getName());
265     }
266   }
267
268   @Override
269   public void run()
270   {
271     String title = protocol == DataSourceType.PASTE
272             ? "Copied From Clipboard"
273             : file;
274     Runtime rt = Runtime.getRuntime();
275     try
276     {
277       if (Desktop.instance != null)
278       {
279         Desktop.instance.startLoading(file);
280       }
281       if (format == null)
282       {
283         // just in case the caller didn't identify the file for us
284         if (source != null)
285         {
286           format = new IdentifyFile().identify(source, false);
287           // identify stream and rewind rather than close
288         }
289         else if (selectedFile != null) {
290           format = new IdentifyFile().identify(selectedFile, protocol);
291         }
292         else
293         {
294           format = new IdentifyFile().identify(file, protocol);
295         }
296
297       }
298
299       if (format == null)
300       {
301         Desktop.instance.stopLoading();
302         System.err.println("The input file \"" + file
303                 + "\" has null or unidentifiable data content!");
304         if (!Jalview.isHeadlessMode())
305         {
306           JvOptionPane.showInternalMessageDialog(Desktop.desktop,
307                   MessageManager.getString("label.couldnt_read_data")
308                           + " in " + file + "\n"
309                           + AppletFormatAdapter.getSupportedFormats(),
310                   MessageManager.getString("label.couldnt_read_data"),
311                   JvOptionPane.WARNING_MESSAGE);
312         }
313         return;
314       }
315       // TODO: cache any stream datasources as a temporary file (eg. PDBs
316       // retrieved via URL)
317       if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
318       {
319         System.gc();
320         memused = (rt.maxMemory() - rt.totalMemory() + rt.freeMemory()); // free
321         // memory
322         // before
323         // load
324       }
325       loadtime = -System.currentTimeMillis();
326       AlignmentI al = null;
327
328       if (FileFormat.Bam.equals(format))
329       {
330         FormatAdapter fa = new FormatAdapter();
331         if (source == null)
332         {
333           fa.prepareFileReader(null, file, protocol, format);
334           source = fa.getAlignFile();
335         }
336         if (!((BamFile) source).parseSuffix())
337         {
338           // configure a window
339           BamFileOptionsChooser bamoptions = new BamFileOptionsChooser(
340                   source);
341           // ask the user which bit of the bam they want to load
342           int confirm = JvOptionPane.showConfirmDialog(null, bamoptions,
343                   MessageManager.getString("label.bam_file_options"),
344                   JvOptionPane.OK_CANCEL_OPTION,
345                   JvOptionPane.PLAIN_MESSAGE);
346
347           if (confirm == JvOptionPane.CANCEL_OPTION
348                   || confirm == JvOptionPane.CLOSED_OPTION)
349           {
350             Desktop.instance.stopLoading();
351             return;
352           }
353           else
354           {
355             bamoptions.update(source);
356             if (file.indexOf("#") == -1)
357             {
358               file = file + "#" + ((BamFile) source).suffix;
359             }
360           }
361         }
362         al = fa.readFile(file, protocol, format);
363       }
364
365       if (FileFormat.Jalview.equals(format))
366       {
367         if (source != null)
368         {
369           // Tell the user (developer?) that this is going to cause a problem
370           System.err.println(
371                   "IMPLEMENTATION ERROR: Cannot read consecutive Jalview XML projects from a stream.");
372           // We read the data anyway - it might make sense.
373         }
374         // BH 2018 switch to File object here instead of filename
375         alignFrame = new Jalview2XML(raiseGUI).loadJalviewAlign(selectedFile == null ? file : selectedFile);
376       }
377       else
378       {
379         String error = AppletFormatAdapter.getSupportedFormats();
380         try
381         {
382           if (source != null)
383           {
384             // read from the provided source
385             al = new FormatAdapter().readFromFile(source, format);
386           }
387           else
388           {
389
390             // open a new source and read from it
391             FormatAdapter fa = new FormatAdapter();
392             boolean downloadStructureFile = format.isStructureFile()
393                     && protocol.equals(DataSourceType.URL);
394             if (downloadStructureFile)
395             {
396               String structExt = format.getExtensions().split(",")[0];
397               String urlLeafName = file.substring(
398                       file.lastIndexOf(
399                               System.getProperty("file.separator")),
400                       file.lastIndexOf("."));
401               String tempStructureFileStr = createNamedJvTempFile(
402                       urlLeafName, structExt);
403               
404               // BH - switching to File object here so as to hold
405               // ._bytes array directly
406               File tempFile = new File(tempStructureFileStr);
407               UrlDownloadClient.download(file, tempFile);
408               
409               al = fa.readFile(tempFile, DataSourceType.FILE,
410                       format);
411               source = fa.getAlignFile();
412             }
413             else
414             {
415               if (selectedFile == null) {
416                 al = fa.readFile(file, protocol, format);
417                 
418               } else {
419                 al = fa.readFile(selectedFile, protocol, format);
420                              }
421               source = fa.getAlignFile(); // keep reference for later if              
422                                           // necessary.
423             }
424           }
425         } catch (java.io.IOException ex)
426         {
427           error = ex.getMessage();
428         }
429
430         if ((al != null) && (al.getHeight() > 0) && al.hasValidSequence())
431         {
432           // construct and register dataset sequences
433           for (SequenceI sq : al.getSequences())
434           {
435             while (sq.getDatasetSequence() != null)
436             {
437               sq = sq.getDatasetSequence();
438             }
439             if (sq.getAllPDBEntries() != null)
440             {
441               for (PDBEntry pdbe : sq.getAllPDBEntries())
442               {
443                 // register PDB entries with desktop's structure selection
444                 // manager
445                 StructureSelectionManager
446                         .getStructureSelectionManager(Desktop.instance)
447                         .registerPDBEntry(pdbe);
448               }
449             }
450           }
451
452           FeatureSettingsModelI proxyColourScheme = source
453                   .getFeatureColourScheme();
454           if (viewport != null)
455           {
456             // append to existing alignment
457             viewport.addAlignment(al, title);
458             viewport.applyFeaturesStyle(proxyColourScheme);
459           }
460           else
461           {
462             // otherwise construct the alignFrame
463
464             if (source instanceof ComplexAlignFile)
465             {
466               HiddenColumns colSel = ((ComplexAlignFile) source)
467                       .getHiddenColumns();
468               SequenceI[] hiddenSeqs = ((ComplexAlignFile) source)
469                       .getHiddenSequences();
470               String colourSchemeName = ((ComplexAlignFile) source)
471                       .getGlobalColourScheme();
472               FeaturesDisplayedI fd = ((ComplexAlignFile) source)
473                       .getDisplayedFeatures();
474               alignFrame = new AlignFrame(al, hiddenSeqs, colSel,
475                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
476               alignFrame.getViewport().setFeaturesDisplayed(fd);
477               alignFrame.getViewport().setShowSequenceFeatures(
478                       ((ComplexAlignFile) source).isShowSeqFeatures());
479               ColourSchemeI cs = ColourSchemeMapper
480                       .getJalviewColourScheme(colourSchemeName, al);
481               if (cs != null)
482               {
483                 alignFrame.changeColour(cs);
484               }
485             }
486             else
487             {
488               alignFrame = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
489                       AlignFrame.DEFAULT_HEIGHT);
490               if (source instanceof FeaturesSourceI)
491               {
492                 alignFrame.getViewport().setShowSequenceFeatures(true);
493               }
494             }
495             // add metadata and update ui
496             if (!(protocol == DataSourceType.PASTE))
497             {
498               alignFrame.setFileName(file, format);
499               alignFrame.setFileObject(selectedFile); // BH 2018 SwingJS
500             }
501             if (proxyColourScheme != null)
502             {
503               alignFrame.getViewport()
504                       .applyFeaturesStyle(proxyColourScheme);
505             }
506             alignFrame.setStatus(MessageManager.formatMessage(
507                     "label.successfully_loaded_file", new String[]
508                     { title }));
509
510             if (raiseGUI)
511             {
512               // add the window to the GUI
513               // note - this actually should happen regardless of raiseGUI
514               // status in Jalview 3
515               // TODO: define 'virtual desktop' for benefit of headless scripts
516               // that perform queries to find the 'current working alignment'
517               Desktop.addInternalFrame(alignFrame, title,
518                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
519             }
520
521             try
522             {
523               alignFrame.setMaximum(jalview.bin.Cache
524                       .getDefault("SHOW_FULLSCREEN", false));
525             } catch (java.beans.PropertyVetoException ex)
526             {
527             }
528           }
529         }
530         else
531         {
532           if (Desktop.instance != null)
533           {
534             Desktop.instance.stopLoading();
535           }
536
537           final String errorMessage = MessageManager.getString(
538                   "label.couldnt_load_file") + " " + title + "\n" + error;
539           // TODO: refactor FileLoader to be independent of Desktop / Applet GUI
540           // bits ?
541           if (raiseGUI && Desktop.desktop != null)
542           {
543             javax.swing.SwingUtilities.invokeLater(new Runnable()
544             {
545               @Override
546               public void run()
547               {
548                 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
549                         errorMessage,
550                         MessageManager
551                                 .getString("label.error_loading_file"),
552                         JvOptionPane.WARNING_MESSAGE);
553               }
554             });
555           }
556           else
557           {
558             System.err.println(errorMessage);
559           }
560         }
561       }
562
563       updateRecentlyOpened();
564
565     } catch (Exception er)
566     {
567       System.err.println("Exception whilst opening file '" + file);
568       er.printStackTrace();
569       if (raiseGUI)
570       {
571         javax.swing.SwingUtilities.invokeLater(new Runnable()
572         {
573           @Override
574           public void run()
575           {
576             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
577                     MessageManager.formatMessage(
578                             "label.problems_opening_file", new String[]
579                             { file }),
580                     MessageManager.getString("label.file_open_error"),
581                     JvOptionPane.WARNING_MESSAGE);
582           }
583         });
584       }
585       alignFrame = null;
586     } catch (OutOfMemoryError er)
587     {
588
589       er.printStackTrace();
590       alignFrame = null;
591       if (raiseGUI)
592       {
593         javax.swing.SwingUtilities.invokeLater(new Runnable()
594         {
595           @Override
596           public void run()
597           {
598             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
599                     MessageManager.formatMessage(
600                             "warn.out_of_memory_loading_file", new String[]
601                             { file }),
602                     MessageManager.getString("label.out_of_memory"),
603                     JvOptionPane.WARNING_MESSAGE);
604           }
605         });
606       }
607       System.err.println("Out of memory loading file " + file + "!!");
608
609     }
610     loadtime += System.currentTimeMillis();
611     // TODO: Estimate percentage of memory used by a newly loaded alignment -
612     // warn if more memory will be needed to work with it
613     // System.gc();
614     memused = memused
615             - (rt.maxMemory() - rt.totalMemory() + rt.freeMemory()); // difference
616     // in free
617     // memory
618     // after
619     // load
620     if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
621     {
622       if (alignFrame != null)
623       {
624         AlignmentI al = alignFrame.getViewport().getAlignment();
625
626         System.out.println("Loaded '" + title + "' in "
627                 + (loadtime / 1000.0) + "s, took an additional "
628                 + (1.0 * memused / (1024.0 * 1024.0)) + " MB ("
629                 + al.getHeight() + " seqs by " + al.getWidth() + " cols)");
630       }
631       else
632       {
633         // report that we didn't load anything probably due to an out of memory
634         // error
635         System.out.println("Failed to load '" + title + "' in "
636                 + (loadtime / 1000.0) + "s, took an additional "
637                 + (1.0 * memused / (1024.0 * 1024.0))
638                 + " MB (alignment is null)");
639       }
640     }
641     // remove the visual delay indicator
642     if (Desktop.instance != null)
643     {
644       Desktop.instance.stopLoading();
645     }
646
647   }
648
649   /**
650    * This method creates the file -
651    * {tmpdir}/jalview/{current_timestamp}/fileName.exetnsion using the supplied
652    * file name and extension
653    * 
654    * @param fileName
655    *          the name of the temp file to be created
656    * @param extension
657    *          the extension of the temp file to be created
658    * @return
659    */
660   private static String createNamedJvTempFile(String fileName,
661           String extension) throws IOException
662   {
663     String seprator = System.getProperty("file.separator");
664     String jvTempDir = System.getProperty("java.io.tmpdir") + "jalview"
665             + seprator + System.currentTimeMillis();
666     File tempStructFile = new File(
667             jvTempDir + seprator + fileName + "." + extension);
668     tempStructFile.mkdirs();
669     return tempStructFile.toString();
670   }
671
672 }