JAL-629 Now showing temp fac and secondary structure for File opened structures and...
[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     String title = protocol == DataSourceType.PASTE
292             ? "Copied From Clipboard"
293             : file;
294     Runtime rt = Runtime.getRuntime();
295
296     try
297     {
298       if (Desktop.instance != null)
299       {
300         Desktop.instance.startLoading(file);
301       }
302       if (format == null)
303       {
304         // just in case the caller didn't identify the file for us
305         if (source != null)
306         {
307           format = new IdentifyFile().identify(source, false);
308           // identify stream and rewind rather than close
309         }
310         else if (selectedFile != null)
311         {
312           format = new IdentifyFile().identify(selectedFile, protocol);
313         }
314         else
315         {
316           format = new IdentifyFile().identify(file, protocol);
317         }
318
319       }
320
321       if (format == null)
322       {
323         Desktop.instance.stopLoading();
324         System.err.println("The input file \"" + file
325                 + "\" has null or unidentifiable data content!");
326         if (!Jalview.isHeadlessMode())
327         {
328           JvOptionPane.showInternalMessageDialog(Desktop.desktop,
329                   MessageManager.getString("label.couldnt_read_data")
330                           + " in " + file + "\n"
331                           + AppletFormatAdapter.getSupportedFormats(),
332                   MessageManager.getString("label.couldnt_read_data"),
333                   JvOptionPane.WARNING_MESSAGE);
334         }
335         // don't set shouldBeSaved if didn't load anything
336         // this.setShouldBeSaved();
337         return;
338       }
339       // TODO: cache any stream datasources as a temporary file (eg. PDBs
340       // retrieved via URL)
341       if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
342       {
343         System.gc();
344         memused = (rt.maxMemory() - rt.totalMemory() + rt.freeMemory()); // free
345         // memory
346         // before
347         // load
348       }
349       loadtime = -System.currentTimeMillis();
350       AlignmentI al = null;
351
352       if (FileFormat.Jalview.equals(format))
353       {
354         if (source != null)
355         {
356           // Tell the user (developer?) that this is going to cause a problem
357           System.err.println(
358                   "IMPLEMENTATION ERROR: Cannot read consecutive Jalview XML projects from a stream.");
359           // We read the data anyway - it might make sense.
360         }
361         // BH 2018 switch to File object here instead of filename
362         alignFrame = new Jalview2XML(raiseGUI).loadJalviewAlign(
363                 selectedFile == null ? file : selectedFile);
364       }
365       else
366       {
367         String error = AppletFormatAdapter.getSupportedFormats();
368         try
369         {
370           if (source != null)
371           {
372             // read from the provided source
373             al = new FormatAdapter().readFromFile(source, format);
374           }
375           else
376           {
377
378             // open a new source and read from it
379             FormatAdapter fa = new FormatAdapter();
380             boolean downloadStructureFile = format.isStructureFile()
381                     && protocol.equals(DataSourceType.URL);
382             if (downloadStructureFile)
383             {
384               String structExt = format.getExtensions().split(",")[0];
385               String urlLeafName = file.substring(
386                       file.lastIndexOf(
387                               System.getProperty("file.separator")),
388                       file.lastIndexOf("."));
389               String tempStructureFileStr = createNamedJvTempFile(
390                       urlLeafName, structExt);
391
392               // BH - switching to File object here so as to hold
393               // ._bytes array directly
394               File tempFile = new File(tempStructureFileStr);
395               UrlDownloadClient.download(file, tempFile);
396
397               al = fa.readFile(tempFile, DataSourceType.FILE, format);
398               source = fa.getAlignFile();
399             }
400             else
401             {
402               if (selectedFile == null)
403               {
404                 al = fa.readFile(null, file, protocol, format);
405
406               }
407               else
408               {
409                 al = fa.readFile(selectedFile, null, protocol, format);
410               }
411               source = fa.getAlignFile(); // keep reference for later if
412
413               // necessary.
414             }
415           }
416         } catch (java.io.IOException ex)
417         {
418           error = ex.getMessage();
419         }
420
421         if ((al != null) && (al.getHeight() > 0) && al.hasValidSequence())
422         {
423           // construct and register dataset sequences
424           for (SequenceI sq : al.getSequences())
425           {
426             while (sq.getDatasetSequence() != null)
427             {
428               sq = sq.getDatasetSequence();
429             }
430             if (sq.getAllPDBEntries() != null)
431             {
432               for (PDBEntry pdbe : sq.getAllPDBEntries())
433               {
434                 // register PDB entries with desktop's structure selection
435                 // manager
436                 StructureSelectionManager
437                         .getStructureSelectionManager(Desktop.instance)
438                         .registerPDBEntry(pdbe);
439               }
440             }
441           }
442
443           FeatureSettingsModelI proxyColourScheme = source
444                   .getFeatureColourScheme();
445           if (viewport != null)
446           {
447             // append to existing alignment
448             viewport.addAlignment(al, title);
449             viewport.applyFeaturesStyle(proxyColourScheme);
450           }
451           else
452           {
453             // otherwise construct the alignFrame
454
455             if (source instanceof ComplexAlignFile)
456             {
457               HiddenColumns colSel = ((ComplexAlignFile) source)
458                       .getHiddenColumns();
459               SequenceI[] hiddenSeqs = ((ComplexAlignFile) source)
460                       .getHiddenSequences();
461               String colourSchemeName = ((ComplexAlignFile) source)
462                       .getGlobalColourScheme();
463               FeaturesDisplayedI fd = ((ComplexAlignFile) source)
464                       .getDisplayedFeatures();
465               alignFrame = new AlignFrame(al, hiddenSeqs, colSel,
466                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
467               alignFrame.getViewport().setFeaturesDisplayed(fd);
468               alignFrame.getViewport().setShowSequenceFeatures(
469                       ((ComplexAlignFile) source).isShowSeqFeatures());
470               ColourSchemeI cs = ColourSchemeMapper
471                       .getJalviewColourScheme(colourSchemeName, al);
472               if (cs != null)
473               {
474                 alignFrame.changeColour(cs);
475               }
476             }
477             else
478             {
479               alignFrame = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
480                       AlignFrame.DEFAULT_HEIGHT);
481               if (source instanceof FeaturesSourceI)
482               {
483                 alignFrame.getViewport().setShowSequenceFeatures(true);
484               }
485             }
486             // add metadata and update ui
487             if (!(protocol == DataSourceType.PASTE))
488             {
489               alignFrame.setFileName(file, format);
490               alignFrame.setFileObject(selectedFile); // BH 2018 SwingJS
491             }
492             if (proxyColourScheme != null)
493             {
494               alignFrame.getViewport()
495                       .applyFeaturesStyle(proxyColourScheme);
496             }
497             alignFrame.setStatus(MessageManager.formatMessage(
498                     "label.successfully_loaded_file", new String[]
499                     { title }));
500
501             if (raiseGUI)
502             {
503               // add the window to the GUI
504               // note - this actually should happen regardless of raiseGUI
505               // status in Jalview 3
506               // TODO: define 'virtual desktop' for benefit of headless scripts
507               // that perform queries to find the 'current working alignment'
508               Desktop.addInternalFrame(alignFrame, title,
509                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
510
511               /*
512                * for an Overview automatically opened with alignment,
513                * set its title now alignFrame title has been set
514                */
515               alignFrame.alignPanel.setOverviewTitle(alignFrame);
516             }
517
518             try
519             {
520               alignFrame.setMaximum(
521                       Cache.getDefault("SHOW_FULLSCREEN", false));
522             } catch (java.beans.PropertyVetoException ex)
523             {
524             }
525           }
526         }
527         else
528         {
529           if (Desktop.instance != null)
530           {
531             Desktop.instance.stopLoading();
532           }
533
534           final String errorMessage = MessageManager.getString(
535                   "label.couldnt_load_file") + " " + title + "\n" + error;
536           // TODO: refactor FileLoader to be independent of Desktop / Applet GUI
537           // bits ?
538           if (raiseGUI && Desktop.desktop != null)
539           {
540             javax.swing.SwingUtilities.invokeLater(new Runnable()
541             {
542               @Override
543               public void run()
544               {
545                 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
546                         errorMessage,
547                         MessageManager
548                                 .getString("label.error_loading_file"),
549                         JvOptionPane.WARNING_MESSAGE);
550               }
551             });
552           }
553           else
554           {
555             System.err.println(errorMessage);
556           }
557         }
558       }
559
560       updateRecentlyOpened();
561
562     } catch (Exception er)
563     {
564       System.err.println("Exception whilst opening file '" + file);
565       er.printStackTrace();
566       if (raiseGUI)
567       {
568         javax.swing.SwingUtilities.invokeLater(new Runnable()
569         {
570           @Override
571           public void run()
572           {
573             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
574                     MessageManager.formatMessage(
575                             "label.problems_opening_file", new String[]
576                             { file }),
577                     MessageManager.getString("label.file_open_error"),
578                     JvOptionPane.WARNING_MESSAGE);
579           }
580         });
581       }
582       alignFrame = null;
583     } catch (OutOfMemoryError er)
584     {
585
586       er.printStackTrace();
587       alignFrame = null;
588       if (raiseGUI)
589       {
590         javax.swing.SwingUtilities.invokeLater(new Runnable()
591         {
592           @Override
593           public void run()
594           {
595             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
596                     MessageManager.formatMessage(
597                             "warn.out_of_memory_loading_file", new String[]
598                             { file }),
599                     MessageManager.getString("label.out_of_memory"),
600                     JvOptionPane.WARNING_MESSAGE);
601           }
602         });
603       }
604       System.err.println("Out of memory loading file " + file + "!!");
605
606     }
607     loadtime += System.currentTimeMillis();
608     // TODO: Estimate percentage of memory used by a newly loaded alignment -
609     // warn if more memory will be needed to work with it
610     // System.gc();
611     memused = memused
612             - (rt.maxMemory() - rt.totalMemory() + rt.freeMemory()); // difference
613     // in free
614     // memory
615     // after
616     // load
617     if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
618     {
619       if (alignFrame != null)
620       {
621         AlignmentI al = alignFrame.getViewport().getAlignment();
622
623         System.out.println("Loaded '" + title + "' in "
624                 + (loadtime / 1000.0) + "s, took an additional "
625                 + (1.0 * memused / (1024.0 * 1024.0)) + " MB ("
626                 + al.getHeight() + " seqs by " + al.getWidth() + " cols)");
627       }
628       else
629       {
630         // report that we didn't load anything probably due to an out of memory
631         // error
632         System.out.println("Failed to load '" + title + "' in "
633                 + (loadtime / 1000.0) + "s, took an additional "
634                 + (1.0 * memused / (1024.0 * 1024.0))
635                 + " MB (alignment is null)");
636       }
637     }
638     // remove the visual delay indicator
639     if (Desktop.instance != null)
640     {
641       Desktop.instance.stopLoading();
642     }
643
644     this.setShouldBeSaved();
645   }
646
647   /**
648    * This method creates the file -
649    * {tmpdir}/jalview/{current_timestamp}/fileName.exetnsion using the supplied
650    * file name and extension
651    * 
652    * @param fileName
653    *          the name of the temp file to be created
654    * @param extension
655    *          the extension of the temp file to be created
656    * @return
657    */
658   private static String createNamedJvTempFile(String fileName,
659           String extension) throws IOException
660   {
661     String seprator = System.getProperty("file.separator");
662     String jvTempDir = System.getProperty("java.io.tmpdir") + "jalview"
663             + seprator + System.currentTimeMillis();
664     File tempStructFile = new File(
665             jvTempDir + seprator + fileName + "." + extension);
666     tempStructFile.mkdirs();
667     return tempStructFile.toString();
668   }
669
670   /*
671    * set whether quit should ask to save when just loaded this source
672    */
673   private void setShouldBeSaved()
674   {
675     if (protocol == null)
676       return;
677     AlignFrame af = this.alignFrame;
678     if (af == null)
679       return;
680     AlignViewport avp = af.getViewport();
681     if (avp == null)
682       return;
683     avp.setSavedUpToDate(!protocol.isDynamic(),
684             QuitHandler.Message.UNSAVED_ALIGNMENTS);
685   }
686
687 }