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