Merge branch 'alpha/JAL-3362_Jalview_212_alpha' into alpha/merge_212_JalviewJS_2112
[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.json.binding.biojson.v1.ColourSchemeMapper;
45 import jalview.project.Jalview2XML;
46 import jalview.schemes.ColourSchemeI;
47 import jalview.structure.StructureSelectionManager;
48 import jalview.util.MessageManager;
49 import jalview.ws.utils.UrlDownloadClient;
50
51 import java.util.ArrayList;
52 import java.util.List;
53
54 public class FileLoader implements Runnable
55 {
56   private static final String TAB = "\t";
57
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
251     String propertyName = sourceType == DataSourceType.FILE ? "RECENT_FILE"
252             : "RECENT_URL";
253     String historyItems = Cache.getProperty(propertyName);
254     if (filePath != null
255             && filePath.indexOf(System.getProperty("java.io.tmpdir")) > -1)
256     {
257       // ignore files loaded from the system's temporary directory
258       return null;
259     }
260
261     List<String> recent = new ArrayList<>();
262
263     if (historyItems != null)
264     {
265       StringTokenizer st = new StringTokenizer(historyItems, TAB);
266
267       while (st.hasMoreTokens())
268       {
269         String trimmed = st.nextToken().trim();
270         recent.add(trimmed);
271       }
272     }
273
274     /*
275      * if file was already in the list, it moves to the top
276      */
277     if (recent.contains(filePath))
278     {
279       recent.remove(filePath);
280     }
281
282     StringBuilder newHistory = new StringBuilder(filePath);
283     for (int i = 0; i < recent.size() && i < 10; i++)
284     {
285       newHistory.append(TAB);
286       newHistory.append(recent.get(i));
287     }
288
289     String newProperty = newHistory.toString();
290     Cache.setProperty(propertyName, newProperty);
291
292     return newProperty;
293   }
294
295   @Override
296   public void run()
297   {
298     String title = protocol == DataSourceType.PASTE
299             ? "Copied From Clipboard"
300             : file;
301     Runtime rt = Runtime.getRuntime();
302     try
303     {
304       if (Desktop.instance != null)
305       {
306         Desktop.instance.startLoading(file);
307       }
308       if (format == null)
309       {
310         // just in case the caller didn't identify the file for us
311         if (source != null)
312         {
313           format = new IdentifyFile().identify(source, false);
314           // identify stream and rewind rather than close
315         }
316         else if (selectedFile != null) {
317           format = new IdentifyFile().identify(selectedFile, protocol);
318         }
319         else
320         {
321           format = new IdentifyFile().identify(file, protocol);
322         }
323
324       }
325
326       if (format == null)
327       {
328         Desktop.instance.stopLoading();
329         System.err.println("The input file \"" + file
330                 + "\" has null or unidentifiable data content!");
331         if (!Jalview.isHeadlessMode())
332         {
333           JvOptionPane.showInternalMessageDialog(Desktop.desktop,
334                   MessageManager.getString("label.couldnt_read_data")
335                           + " in " + file + "\n"
336                           + AppletFormatAdapter.getSupportedFormats(),
337                   MessageManager.getString("label.couldnt_read_data"),
338                   JvOptionPane.WARNING_MESSAGE);
339         }
340         return;
341       }
342       // TODO: cache any stream datasources as a temporary file (eg. PDBs
343       // retrieved via URL)
344       if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
345       {
346         System.gc();
347         memused = (rt.maxMemory() - rt.totalMemory() + rt.freeMemory()); // free
348         // memory
349         // before
350         // load
351       }
352       loadtime = -System.currentTimeMillis();
353       AlignmentI al = null;
354
355       if (FileFormat.Jalview.equals(format))
356       {
357         if (source != null)
358         {
359           // Tell the user (developer?) that this is going to cause a problem
360           System.err.println(
361                   "IMPLEMENTATION ERROR: Cannot read consecutive Jalview XML projects from a stream.");
362           // We read the data anyway - it might make sense.
363         }
364         // BH 2018 switch to File object here instead of filename
365         alignFrame = new Jalview2XML(raiseGUI).loadJalviewAlign(selectedFile == null ? file : selectedFile);
366       }
367       else
368       {
369         String error = AppletFormatAdapter.getSupportedFormats();
370         try
371         {
372           if (source != null)
373           {
374             // read from the provided source
375             al = new FormatAdapter().readFromFile(source, format);
376           }
377           else
378           {
379
380             // open a new source and read from it
381             FormatAdapter fa = new FormatAdapter();
382             boolean downloadStructureFile = format.isStructureFile()
383                     && protocol.equals(DataSourceType.URL);
384             if (downloadStructureFile)
385             {
386               String structExt = format.getExtensions().split(",")[0];
387               String urlLeafName = file.substring(
388                       file.lastIndexOf(
389                               System.getProperty("file.separator")),
390                       file.lastIndexOf("."));
391               String tempStructureFileStr = createNamedJvTempFile(
392                       urlLeafName, structExt);
393               
394               // BH - switching to File object here so as to hold
395               // ._bytes array directly
396               File tempFile = new File(tempStructureFileStr);
397               UrlDownloadClient.download(file, tempFile);
398               
399               al = fa.readFile(tempFile, DataSourceType.FILE,
400                       format);
401               source = fa.getAlignFile();
402             }
403             else
404             {
405               if (selectedFile == null) {
406                 al = fa.readFile(file, protocol, format);
407                 
408               } else {
409                 al = fa.readFile(selectedFile, 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             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.setFileName(file, format);
509               alignFrame.setFileObject(selectedFile); // BH 2018 SwingJS
510             }
511             if (proxyColourScheme != null)
512             {
513               alignFrame.getViewport()
514                       .applyFeaturesStyle(proxyColourScheme);
515             }
516             alignFrame.setStatus(MessageManager.formatMessage(
517                     "label.successfully_loaded_file", new String[]
518                     { title }));
519
520             if (raiseGUI)
521             {
522               // add the window to the GUI
523               // note - this actually should happen regardless of raiseGUI
524               // status in Jalview 3
525               // TODO: define 'virtual desktop' for benefit of headless scripts
526               // that perform queries to find the 'current working alignment'
527               Desktop.addInternalFrame(alignFrame, title,
528                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
529             }
530
531             try
532             {
533               alignFrame.setMaximum(jalview.bin.Cache
534                       .getDefault("SHOW_FULLSCREEN", false));
535             } catch (java.beans.PropertyVetoException ex)
536             {
537             }
538           }
539         }
540         else
541         {
542           if (Desktop.instance != null)
543           {
544             Desktop.instance.stopLoading();
545           }
546
547           final String errorMessage = MessageManager.getString(
548                   "label.couldnt_load_file") + " " + title + "\n" + error;
549           // TODO: refactor FileLoader to be independent of Desktop / Applet GUI
550           // bits ?
551           if (raiseGUI && Desktop.desktop != null)
552           {
553             javax.swing.SwingUtilities.invokeLater(new Runnable()
554             {
555               @Override
556               public void run()
557               {
558                 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
559                         errorMessage,
560                         MessageManager
561                                 .getString("label.error_loading_file"),
562                         JvOptionPane.WARNING_MESSAGE);
563               }
564             });
565           }
566           else
567           {
568             System.err.println(errorMessage);
569           }
570         }
571       }
572
573       updateRecentlyOpened(file, protocol);
574
575       if (protocol == DataSourceType.FILE && format != null)
576       {
577         Cache.setProperty("DEFAULT_FILE_FORMAT", format.getName());
578       }
579
580     } catch (Exception er)
581     {
582       System.err.println("Exception whilst opening file '" + file);
583       er.printStackTrace();
584       if (raiseGUI)
585       {
586         javax.swing.SwingUtilities.invokeLater(new Runnable()
587         {
588           @Override
589           public void run()
590           {
591             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
592                     MessageManager.formatMessage(
593                             "label.problems_opening_file", new String[]
594                             { file }),
595                     MessageManager.getString("label.file_open_error"),
596                     JvOptionPane.WARNING_MESSAGE);
597           }
598         });
599       }
600       alignFrame = null;
601     } catch (OutOfMemoryError er)
602     {
603
604       er.printStackTrace();
605       alignFrame = null;
606       if (raiseGUI)
607       {
608         javax.swing.SwingUtilities.invokeLater(new Runnable()
609         {
610           @Override
611           public void run()
612           {
613             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
614                     MessageManager.formatMessage(
615                             "warn.out_of_memory_loading_file", new String[]
616                             { file }),
617                     MessageManager.getString("label.out_of_memory"),
618                     JvOptionPane.WARNING_MESSAGE);
619           }
620         });
621       }
622       System.err.println("Out of memory loading file " + file + "!!");
623
624     }
625     loadtime += System.currentTimeMillis();
626     // TODO: Estimate percentage of memory used by a newly loaded alignment -
627     // warn if more memory will be needed to work with it
628     // System.gc();
629     memused = memused
630             - (rt.maxMemory() - rt.totalMemory() + rt.freeMemory()); // difference
631     // in free
632     // memory
633     // after
634     // load
635     if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
636     {
637       if (alignFrame != null)
638       {
639         AlignmentI al = alignFrame.getViewport().getAlignment();
640
641         System.out.println("Loaded '" + title + "' in "
642                 + (loadtime / 1000.0) + "s, took an additional "
643                 + (1.0 * memused / (1024.0 * 1024.0)) + " MB ("
644                 + al.getHeight() + " seqs by " + al.getWidth() + " cols)");
645       }
646       else
647       {
648         // report that we didn't load anything probably due to an out of memory
649         // error
650         System.out.println("Failed to load '" + title + "' in "
651                 + (loadtime / 1000.0) + "s, took an additional "
652                 + (1.0 * memused / (1024.0 * 1024.0))
653                 + " MB (alignment is null)");
654       }
655     }
656     // remove the visual delay indicator
657     if (Desktop.instance != null)
658     {
659       Desktop.instance.stopLoading();
660     }
661
662   }
663
664   /**
665    * This method creates the file -
666    * {tmpdir}/jalview/{current_timestamp}/fileName.exetnsion using the supplied
667    * file name and extension
668    * 
669    * @param fileName
670    *          the name of the temp file to be created
671    * @param extension
672    *          the extension of the temp file to be created
673    * @return
674    */
675   private static String createNamedJvTempFile(String fileName,
676           String extension) throws IOException
677   {
678     String seprator = System.getProperty("file.separator");
679     String jvTempDir = System.getProperty("java.io.tmpdir") + "jalview"
680             + seprator + System.currentTimeMillis();
681     File tempStructFile = new File(
682             jvTempDir + seprator + fileName + "." + extension);
683     tempStructFile.mkdirs();
684     return tempStructFile.toString();
685   }
686
687 }