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