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