Merge commit 'alpha/update_2_12_for_2_11_2_series_merge^2' into HEAD
[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.awt.Dimension;
24 import java.io.File;
25 import java.io.IOException;
26 import java.util.StringTokenizer;
27 import java.util.Vector;
28
29 import javax.swing.SwingUtilities;
30
31 import jalview.api.ComplexAlignFile;
32 import jalview.api.FeatureSettingsModelI;
33 import jalview.api.FeaturesDisplayedI;
34 import jalview.api.FeaturesSourceI;
35 import jalview.bin.Cache;
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.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.util.Platform;
51 import jalview.ws.utils.UrlDownloadClient;
52
53 import java.util.ArrayList;
54 import java.util.List;
55 public class FileLoader implements Runnable
56 {
57   private static final String TAB = "\t";
58   String file;
59
60   DataSourceType protocol;
61
62   FileFormatI format;
63
64   AlignmentFileReaderI source = null; // alternative specification of where data
65                                       // comes
66
67   // from
68
69   AlignViewport viewport;
70
71   AlignFrame alignFrame;
72
73   long loadtime;
74
75   long memused;
76
77   boolean raiseGUI = true;
78
79   private File selectedFile;
80
81   /**
82    * default constructor always raised errors in GUI dialog boxes
83    */
84   public FileLoader()
85   {
86     this(true);
87   }
88
89   /**
90    * construct a Fileloader that may raise errors non-interactively
91    * 
92    * @param raiseGUI
93    *          true if errors are to be raised as GUI dialog boxes
94    */
95   public FileLoader(boolean raiseGUI)
96   {
97     this.raiseGUI = raiseGUI;
98   }
99
100   public void LoadFile(AlignViewport viewport, Object file,
101           DataSourceType protocol, FileFormatI format)
102   {
103     this.viewport = viewport;
104     if (file instanceof File) {
105       this.selectedFile = (File) file;
106       file = selectedFile.getPath();
107     }
108     LoadFile(file.toString(), protocol, format);
109   }
110
111   public void LoadFile(String file, DataSourceType protocol,
112           FileFormatI format)
113   {
114     this.file = file;
115     this.protocol = protocol;
116     this.format = format;
117
118     final Thread loader = new Thread(this);
119
120     SwingUtilities.invokeLater(new Runnable()
121     {
122       @Override
123       public void run()
124       {
125         loader.start();
126       }
127     });
128   }
129
130   /**
131    * Load a (file, protocol) source of unknown type
132    * 
133    * @param file
134    * @param protocol
135    */
136   public void LoadFile(String file, DataSourceType protocol)
137   {
138     LoadFile(file, protocol, null);
139   }
140
141   /**
142    * Load alignment from (file, protocol) and wait till loaded
143    * 
144    * @param file
145    * @param sourceType
146    * @return alignFrame constructed from file contents
147    */
148   public AlignFrame LoadFileWaitTillLoaded(String file,
149           DataSourceType sourceType)
150   {
151     return LoadFileWaitTillLoaded(file, sourceType, null);
152   }
153
154   /**
155    * Load alignment from (file, protocol) of type format and wait till loaded
156    * 
157    * @param file
158    * @param sourceType
159    * @param format
160    * @return alignFrame constructed from file contents
161    */
162   public AlignFrame LoadFileWaitTillLoaded(String file,
163           DataSourceType sourceType, FileFormatI format)
164   {
165     this.file = file;
166     this.protocol = sourceType;
167     this.format = format;
168     return _LoadFileWaitTillLoaded();
169   }
170
171   /**
172    * Load alignment from (file, protocol) of type format and wait till loaded
173    * 
174    * @param file
175    * @param sourceType
176    * @param format
177    * @return alignFrame constructed from file contents
178    */
179   public AlignFrame LoadFileWaitTillLoaded(File file,
180           DataSourceType sourceType, FileFormatI format)
181   {
182     this.selectedFile = file;
183     this.file = file.getPath();
184     this.protocol = sourceType;
185     this.format = format;
186     return _LoadFileWaitTillLoaded();
187   }
188
189   /**
190    * Load alignment from FileParse source of type format and wait till loaded
191    * 
192    * @param source
193    * @param format
194    * @return alignFrame constructed from file contents
195    */
196   public AlignFrame LoadFileWaitTillLoaded(AlignmentFileReaderI source,
197           FileFormatI format)
198   {
199     this.source = source;
200
201     file = source.getInFile();
202     protocol = source.getDataSourceType();
203     this.format = format;
204     return _LoadFileWaitTillLoaded();
205   }
206
207   /**
208    * runs the 'run' method (in this thread), then return the alignFrame that's
209    * (hopefully) been read
210    * 
211    * @return
212    */
213   protected AlignFrame _LoadFileWaitTillLoaded()
214   {
215     this.run();
216
217     return alignFrame;
218   }
219
220   public void LoadFileOntoAlignmentWaitTillLoaded(AlignViewport viewport,
221           String file, DataSourceType sourceType, FileFormatI format)
222   {
223     Vector<String> recent = new Vector<>();
224     if (protocol == DataSourceType.PASTE)
225     this.viewport = viewport;
226     this.file = file;
227     this.protocol = sourceType;
228     this.format = format;
229     _LoadFileWaitTillLoaded();
230   }
231
232
233   /**
234    * Updates (or creates) the tab-separated list of recently opened files held
235    * under the given property name by inserting the filePath at the front of the
236    * list. Duplicates are removed, and the list is limited to 11 entries. The
237    * method returns the updated value of the property.
238    * 
239    * @param filePath
240    * @param sourceType
241    */
242   public static String updateRecentlyOpened(String filePath,
243           DataSourceType sourceType)
244   {
245     if (sourceType != DataSourceType.FILE
246             && sourceType != DataSourceType.URL)
247     {
248       return null;
249     }
250     String propertyName = sourceType == DataSourceType.FILE ? "RECENT_FILE"
251             : "RECENT_URL";
252     String historyItems = Cache.getProperty(propertyName);
253     if (filePath != null
254             && filePath.indexOf(System.getProperty("java.io.tmpdir")) > -1)
255     {
256       // ignore files loaded from the system's temporary directory
257       return null;
258     }
259
260     List<String> recent = new ArrayList<>();
261
262     if (historyItems != null)
263     {
264       StringTokenizer st = new StringTokenizer(historyItems, TAB);
265
266       while (st.hasMoreTokens())
267       {
268         String trimmed = st.nextToken().trim();
269         recent.add(trimmed);
270       }
271     }
272
273     /*
274      * if file was already in the list, it moves to the top
275      */
276     if (recent.contains(filePath))
277     {
278       recent.remove(filePath);
279     }
280
281     StringBuilder newHistory = new StringBuilder(filePath);
282     for (int i = 0; i < recent.size() && i < 10; i++)
283     {
284       newHistory.append(TAB);
285       newHistory.append(recent.get(i));
286     }
287
288     String newProperty = newHistory.toString();
289     Cache.setProperty(propertyName, newProperty);
290
291     return newProperty;
292   }
293
294   @Override
295   public void run()
296   {
297     String title = protocol == DataSourceType.PASTE
298             ? "Copied From Clipboard"
299             : file;
300     Runtime rt = Runtime.getRuntime();
301     try
302     {
303       if (Desktop.getInstance() != null)
304       {
305         Desktop.getInstance().startLoading(file);
306       }
307       if (format == null)
308       {
309         // just in case the caller didn't identify the file for us
310         if (source != null)
311         {
312           format = new IdentifyFile().identify(source, false);
313           // identify stream and rewind rather than close
314         }
315         else if (selectedFile != null) {
316           format = new IdentifyFile().identify(selectedFile, protocol);
317         }
318         else
319         {
320           format = new IdentifyFile().identify(file, protocol);
321         }
322
323       }
324
325       if (format == null)
326       {
327         Desktop.getInstance().stopLoading();
328         System.err.println("The input file \"" + file
329                 + "\" has null or unidentifiable data content!");
330         if (!Jalview.isHeadlessMode())
331         {
332           JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
333                   MessageManager.getString("label.couldnt_read_data")
334                           + " in " + file + "\n"
335                           + AppletFormatAdapter.getSupportedFormats(),
336                   MessageManager.getString("label.couldnt_read_data"),
337                   JvOptionPane.WARNING_MESSAGE);
338         }
339         return;
340       }
341       // TODO: cache any stream datasources as a temporary file (eg. PDBs
342       // retrieved via URL)
343       if (Desktop.getDesktopPane() != null && Desktop.getDesktopPane().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           System.err.println(
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).loadJalviewAlign(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,
399                       format);
400               source = fa.getAlignFile();
401             }
402             else
403             {
404               if (selectedFile == null) {
405                 al = fa.readFile(file, protocol, format);
406                 
407               } else {
408                 al = fa.readFile(selectedFile, protocol, format);
409                              }
410               source = fa.getAlignFile(); // keep reference for later if
411               
412                                           // necessary.
413             }
414           }
415         } catch (java.io.IOException ex)
416         {
417           error = ex.getMessage();
418         }
419
420         if ((al != null) && (al.getHeight() > 0) && al.hasValidSequence())
421         {
422           // construct and register dataset sequences
423           for (SequenceI sq : al.getSequences())
424           {
425             while (sq.getDatasetSequence() != null)
426             {
427               sq = sq.getDatasetSequence();
428             }
429             if (sq.getAllPDBEntries() != null)
430             {
431               for (PDBEntry pdbe : sq.getAllPDBEntries())
432               {
433                 // register PDB entries with desktop's structure selection
434                 // manager
435                 StructureSelectionManager
436                         .getStructureSelectionManager(Desktop.getInstance())
437                         .registerPDBEntry(pdbe);
438               }
439             }
440           }
441
442           FeatureSettingsModelI proxyColourScheme = source
443                   .getFeatureColourScheme();
444           if (viewport != null)
445           {
446             // TODO: test if this needs to be done after addAlignment ? (like in 2.11.2)  
447             if (proxyColourScheme != null)
448             {
449               viewport.applyFeaturesStyle(proxyColourScheme);
450             }
451             // append to existing alignment
452             viewport.addAlignment(al, title);
453             if (source instanceof HMMFile)
454             {
455               AlignmentI alignment = viewport.getAlignment();
456               SequenceI seq = alignment
457                       .getSequenceAt(alignment.getHeight() - 1);
458               if (seq.hasHMMProfile())
459               {
460                 /* 
461                  * fudge: move HMM consensus sequence from last to first
462                  */
463                 alignment.deleteSequence(alignment.getAbsoluteHeight() - 1);
464                 alignment.insertSequenceAt(0, seq);
465               }
466               viewport.getAlignPanel().adjustAnnotationHeight();
467               viewport.updateSequenceIdColours();
468             }
469           }
470           else
471           {
472             // otherwise construct the alignFrame
473
474             if (source instanceof ComplexAlignFile)
475             {
476               HiddenColumns colSel = ((ComplexAlignFile) source)
477                       .getHiddenColumns();
478               SequenceI[] hiddenSeqs = ((ComplexAlignFile) source)
479                       .getHiddenSequences();
480               String colourSchemeName = ((ComplexAlignFile) source)
481                       .getGlobalColourScheme();
482               FeaturesDisplayedI fd = ((ComplexAlignFile) source)
483                       .getDisplayedFeatures();
484               alignFrame = new AlignFrame(al, hiddenSeqs, colSel,
485                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
486               alignFrame.getViewport().setFeaturesDisplayed(fd);
487               alignFrame.getViewport().setShowSequenceFeatures(
488                       ((ComplexAlignFile) source).isShowSeqFeatures());
489               ColourSchemeI cs = ColourSchemeMapper
490                       .getJalviewColourScheme(colourSchemeName, al);
491               if (cs != null)
492               {
493                 alignFrame.changeColour(cs);
494               }
495             }
496             else
497             {
498               alignFrame = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
499                       AlignFrame.DEFAULT_HEIGHT);
500               if (source instanceof FeaturesSourceI)
501               {
502                 alignFrame.getViewport().setShowSequenceFeatures(true);
503               }
504             }
505             // add metadata and update ui
506             if (!(protocol == DataSourceType.PASTE))
507             {
508               alignFrame.setFile(file, selectedFile, protocol, format);
509             }
510             if (proxyColourScheme != null)
511             {
512               alignFrame.getViewport()
513                       .applyFeaturesStyle(proxyColourScheme);
514             }
515             alignFrame.setStatus(MessageManager.formatMessage(
516                     "label.successfully_loaded_file", new String[]
517                     { title }));
518
519             if (raiseGUI)
520             {
521               // add the window to the GUI
522               // note - this actually should happen regardless of raiseGUI
523               // status in Jalview 3
524               // TODO: define 'virtual desktop' for benefit of headless scripts
525               // that perform queries to find the 'current working alignment'
526               
527               Dimension dim = Platform.getDimIfEmbedded(alignFrame,
528                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
529               alignFrame.setSize(dim);
530               Desktop.addInternalFrame(alignFrame, title, dim.width,
531                       dim.height);
532             }
533
534             try
535             {
536               alignFrame.setMaximum(jalview.bin.Cache
537                       .getDefault("SHOW_FULLSCREEN", false));
538             } catch (java.beans.PropertyVetoException ex)
539             {
540             }
541           }
542         }
543         else
544         {
545           if (Desktop.getInstance() != null)
546           {
547             Desktop.getInstance().stopLoading();
548           }
549
550           final String errorMessage = MessageManager.getString(
551                   "label.couldnt_load_file") + " " + title + "\n" + error;
552           // TODO: refactor FileLoader to be independent of Desktop / Applet GUI
553           // bits ?
554           if (raiseGUI && Desktop.getDesktopPane() != null)
555           {
556             javax.swing.SwingUtilities.invokeLater(new Runnable()
557             {
558               @Override
559               public void run()
560               {
561                 JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
562                         errorMessage,
563                         MessageManager
564                                 .getString("label.error_loading_file"),
565                         JvOptionPane.WARNING_MESSAGE);
566               }
567             });
568           }
569           else
570           {
571             System.err.println(errorMessage);
572           }
573         }
574       }
575
576       updateRecentlyOpened(file, protocol);
577
578       if (protocol == DataSourceType.FILE && format != null)
579       {
580         Cache.setProperty("DEFAULT_FILE_FORMAT", format.getName());
581       }
582
583     } catch (Exception er)
584     {
585       System.err.println("Exception whilst opening file '" + file);
586       er.printStackTrace();
587       if (raiseGUI)
588       {
589         javax.swing.SwingUtilities.invokeLater(new Runnable()
590         {
591           @Override
592           public void run()
593           {
594             JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
595                     MessageManager.formatMessage(
596                             "label.problems_opening_file", new String[]
597                             { file }),
598                     MessageManager.getString("label.file_open_error"),
599                     JvOptionPane.WARNING_MESSAGE);
600           }
601         });
602       }
603       alignFrame = null;
604     } catch (OutOfMemoryError er)
605     {
606
607       er.printStackTrace();
608       alignFrame = null;
609       if (raiseGUI)
610       {
611         javax.swing.SwingUtilities.invokeLater(new Runnable()
612         {
613           @Override
614           public void run()
615           {
616             JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
617                     MessageManager.formatMessage(
618                             "warn.out_of_memory_loading_file", new String[]
619                             { file }),
620                     MessageManager.getString("label.out_of_memory"),
621                     JvOptionPane.WARNING_MESSAGE);
622           }
623         });
624       }
625       System.err.println("Out of memory loading file " + file + "!!");
626
627     }
628     loadtime += System.currentTimeMillis();
629     // TODO: Estimate percentage of memory used by a newly loaded alignment -
630     // warn if more memory will be needed to work with it
631     // System.gc();
632     memused = memused
633             - (rt.maxMemory() - rt.totalMemory() + rt.freeMemory()); // difference
634     // in free
635     // memory
636     // after
637     // load
638     if (Desktop.getDesktopPane() != null && Desktop.getDesktopPane().isShowMemoryUsage())
639     {
640       if (alignFrame != null)
641       {
642         AlignmentI al = alignFrame.getViewport().getAlignment();
643
644         System.out.println("Loaded '" + title + "' in "
645                 + (loadtime / 1000.0) + "s, took an additional "
646                 + (1.0 * memused / (1024.0 * 1024.0)) + " MB ("
647                 + al.getHeight() + " seqs by " + al.getWidth() + " cols)");
648       }
649       else
650       {
651         // report that we didn't load anything probably due to an out of memory
652         // error
653         System.out.println("Failed to load '" + title + "' in "
654                 + (loadtime / 1000.0) + "s, took an additional "
655                 + (1.0 * memused / (1024.0 * 1024.0))
656                 + " MB (alignment is null)");
657       }
658     }
659     // remove the visual delay indicator
660     if (Desktop.getInstance() != null)
661     {
662       Desktop.getInstance().stopLoading();
663     }
664
665   }
666
667   /**
668    * This method creates the file -
669    * {tmpdir}/jalview/{current_timestamp}/fileName.exetnsion using the supplied
670    * file name and extension
671    * 
672    * @param fileName
673    *          the name of the temp file to be created
674    * @param extension
675    *          the extension of the temp file to be created
676    * @return
677    */
678   private static String createNamedJvTempFile(String fileName,
679           String extension) throws IOException
680   {
681     String seprator = System.getProperty("file.separator");
682     String jvTempDir = System.getProperty("java.io.tmpdir") + "jalview"
683             + seprator + System.currentTimeMillis();
684     File tempStructFile = new File(
685             jvTempDir + seprator + fileName + "." + extension);
686     tempStructFile.mkdirs();
687     return tempStructFile.toString();
688   }
689
690 }