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