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