Merge remote-tracking branch 'origin/releases/Release_2_10_2_Branch' into features...
[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.Profile;
33 import jalview.datamodel.Profiles;
34 import jalview.datamodel.SequenceGroup;
35 import jalview.datamodel.SequenceI;
36 import jalview.gui.AlignFrame;
37 import jalview.gui.AlignViewport;
38 import jalview.gui.Desktop;
39 import jalview.gui.Jalview2XML;
40 import jalview.gui.JvOptionPane;
41 import jalview.json.binding.biojson.v1.ColourSchemeMapper;
42 import jalview.schemes.ColourSchemeI;
43 import jalview.structure.StructureSelectionManager;
44 import jalview.util.MessageManager;
45 import jalview.ws.utils.UrlDownloadClient;
46
47 import java.io.File;
48 import java.io.IOException;
49 import java.util.StringTokenizer;
50 import java.util.Vector;
51
52 import javax.swing.SwingUtilities;
53
54 public class FileLoader implements Runnable
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       }
200     }
201
202     return alignFrame;
203   }
204
205   public void LoadFileOntoAlignmentWaitTillLoaded(AlignViewport viewport,
206           String file, DataSourceType sourceType, FileFormatI format)
207   {
208     this.viewport = viewport;
209     this.file = file;
210     this.protocol = sourceType;
211     this.format = format;
212     _LoadAlignmentFileWaitTillLoaded();
213   }
214
215   protected void _LoadAlignmentFileWaitTillLoaded()
216   {
217     Thread loader = new Thread(this);
218     loader.start();
219
220     while (loader.isAlive())
221     {
222       try
223       {
224         Thread.sleep(500);
225       } catch (Exception ex)
226       {
227       }
228     }
229   }
230
231   public void updateRecentlyOpened()
232   {
233     Vector recent = new Vector();
234     if (protocol == DataSourceType.PASTE)
235     {
236       // do nothing if the file was pasted in as text... there is no filename to
237       // refer to it as.
238       return;
239     }
240     if (file != null
241             && file.indexOf(System.getProperty("java.io.tmpdir")) > -1)
242     {
243       // ignore files loaded from the system's temporary directory
244       return;
245     }
246     String type = protocol == DataSourceType.FILE ? "RECENT_FILE"
247             : "RECENT_URL";
248
249     String historyItems = jalview.bin.Cache.getProperty(type);
250
251     StringTokenizer st;
252
253     if (historyItems != null)
254     {
255       st = new StringTokenizer(historyItems, "\t");
256
257       while (st.hasMoreTokens())
258       {
259         recent.addElement(st.nextElement().toString().trim());
260       }
261     }
262
263     if (recent.contains(file))
264     {
265       recent.remove(file);
266     }
267
268     StringBuffer newHistory = new StringBuffer(file);
269     for (int i = 0; i < recent.size() && i < 10; i++)
270     {
271       newHistory.append("\t");
272       newHistory.append(recent.elementAt(i));
273     }
274
275     Cache.setProperty(type, newHistory.toString());
276
277     if (protocol == DataSourceType.FILE)
278     {
279       Cache.setProperty("DEFAULT_FILE_FORMAT", format.getName());
280     }
281   }
282
283   @Override
284   public void run()
285   {
286     String title = protocol == DataSourceType.PASTE
287             ? "Copied From Clipboard"
288             : file;
289     Runtime rt = Runtime.getRuntime();
290     try
291     {
292       if (Desktop.instance != null)
293       {
294         Desktop.instance.startLoading(file);
295       }
296       if (format == null)
297       {
298         // just in case the caller didn't identify the file for us
299         if (source != null)
300         {
301           format = new IdentifyFile().identify(source, false);
302           // identify stream and rewind rather than close
303         }
304         else
305         {
306           format = new IdentifyFile().identify(file, protocol);
307         }
308
309       }
310
311       if (format == null)
312       {
313         Desktop.instance.stopLoading();
314         System.err.println("The input file \"" + file
315                 + "\" has null or unidentifiable data content!");
316         if (!Jalview.isHeadlessMode())
317         {
318           JvOptionPane.showInternalMessageDialog(Desktop.desktop,
319                   MessageManager.getString("label.couldnt_read_data")
320                           + " in " + file + "\n"
321                           + AppletFormatAdapter.getSupportedFormats(),
322                   MessageManager.getString("label.couldnt_read_data"),
323                   JvOptionPane.WARNING_MESSAGE);
324         }
325         return;
326       }
327       // TODO: cache any stream datasources as a temporary file (eg. PDBs
328       // retrieved via URL)
329       if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
330       {
331         System.gc();
332         memused = (rt.maxMemory() - rt.totalMemory() + rt.freeMemory()); // free
333         // memory
334         // before
335         // load
336       }
337       loadtime = -System.currentTimeMillis();
338       AlignmentI al = null;
339
340       if (FileFormat.Jalview.equals(format))
341       {
342         if (source != null)
343         {
344           // Tell the user (developer?) that this is going to cause a problem
345           System.err.println(
346                   "IMPLEMENTATION ERROR: Cannot read consecutive Jalview XML projects from a stream.");
347           // We read the data anyway - it might make sense.
348         }
349         alignFrame = new Jalview2XML(raiseGUI).loadJalviewAlign(file);
350       }
351       else
352       {
353         String error = AppletFormatAdapter.getSupportedFormats();
354         try
355         {
356           if (source != null)
357           {
358             // read from the provided source
359             al = new FormatAdapter().readFromFile(source, format);
360           }
361           else
362           {
363
364             // open a new source and read from it
365             FormatAdapter fa = new FormatAdapter();
366             boolean downloadStructureFile = format.isStructureFile()
367                     && protocol.equals(DataSourceType.URL);
368             if (downloadStructureFile)
369             {
370               String structExt = format.getExtensions().split(",")[0];
371               String urlLeafName = file.substring(
372                       file.lastIndexOf(
373                               System.getProperty("file.separator")),
374                       file.lastIndexOf("."));
375               String tempStructureFileStr = createNamedJvTempFile(
376                       urlLeafName, structExt);
377               UrlDownloadClient.download(file, tempStructureFileStr);
378               al = fa.readFile(tempStructureFileStr, DataSourceType.FILE,
379                       format);
380               source = fa.getAlignFile();
381             }
382             else
383             {
384               al = fa.readFile(file, protocol, format);
385               source = fa.getAlignFile(); // keep reference for later if
386                                           // necessary.
387             }
388           }
389         } catch (java.io.IOException ex)
390         {
391           error = ex.getMessage();
392         }
393
394         if ((al != null) && (al.getHeight() > 0) && al.hasValidSequence())
395         {
396           // construct and register dataset sequences
397           for (SequenceI sq : al.getSequences())
398           {
399             while (sq.getDatasetSequence() != null)
400             {
401               sq = sq.getDatasetSequence();
402             }
403             if (sq.getAllPDBEntries() != null)
404             {
405               for (PDBEntry pdbe : sq.getAllPDBEntries())
406               {
407                 // register PDB entries with desktop's structure selection
408                 // manager
409                 StructureSelectionManager
410                         .getStructureSelectionManager(Desktop.instance)
411                         .registerPDBEntry(pdbe);
412               }
413             }
414           }
415
416           FeatureSettingsModelI proxyColourScheme = source
417                   .getFeatureColourScheme();
418           if (viewport != null)
419           {
420             if (proxyColourScheme != null)
421             {
422               viewport.applyFeaturesStyle(proxyColourScheme);
423             }
424             // append to existing alignment
425             viewport.addAlignment(al, title);
426             if (source instanceof HMMFile)
427             {
428               AlignmentI alignment = viewport.getAlignment();
429               SequenceI seq = alignment
430                       .getSequenceAt(alignment.getAbsoluteHeight() - 1);
431               alignment.deleteSequence(alignment.getAbsoluteHeight() - 1);
432               SequenceGroup sg = viewport.getSelectionGroup();
433               if (sg != null)
434               {
435                 seq.insertCharAt(0, sg.getStartRes(), '-');
436                 seq.insertCharAt(sg.getEndRes() + 1,
437                         alignment.getWidth() - sg.getEndRes(), '-');
438                 SequenceI topSeq = sg.getSequencesInOrder(alignment)[0];
439                 int topIndex = alignment.findIndex(topSeq);
440                 alignment.insertSequenceAt(topIndex, seq);
441                 sg.setSeqrep(seq);
442                 viewport.getSelectionGroup().addSequence(seq, false);
443               }
444               else
445               {
446                 alignment.insertSequenceAt(0, seq);
447               }
448               viewport.initInformation();
449               viewport.setSequenceInformationHash(
450                       new Profiles(new Profile[1]), 0);
451               viewport.updateInformation(viewport.getAlignPanel());
452               viewport.getAlignPanel().adjustAnnotationHeight();
453               viewport.updateSequenceIdColours();
454               if (viewport.getAlignPanel().alignFrame
455                       .getSelectedHMM() == null)
456               {
457                 viewport.getAlignPanel().alignFrame
458                         .setSelectedHMM(seq.getHMM());
459               }
460
461             }
462           }
463           else
464           {
465             // otherwise construct the alignFrame
466
467             if (source instanceof ComplexAlignFile)
468             {
469               HiddenColumns colSel = ((ComplexAlignFile) source)
470                       .getHiddenColumns();
471               SequenceI[] hiddenSeqs = ((ComplexAlignFile) source)
472                       .getHiddenSequences();
473               String colourSchemeName = ((ComplexAlignFile) source)
474                       .getGlobalColourScheme();
475               FeaturesDisplayedI fd = ((ComplexAlignFile) source)
476                       .getDisplayedFeatures();
477               alignFrame = new AlignFrame(al, hiddenSeqs, colSel,
478                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
479               alignFrame.getViewport().setFeaturesDisplayed(fd);
480               alignFrame.getViewport().setShowSequenceFeatures(
481                       ((ComplexAlignFile) source).isShowSeqFeatures());
482               ColourSchemeI cs = ColourSchemeMapper
483                       .getJalviewColourScheme(colourSchemeName, al);
484               if (cs != null)
485               {
486                 alignFrame.changeColour(cs);
487               }
488             }
489             else
490             {
491               alignFrame = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
492                       AlignFrame.DEFAULT_HEIGHT);
493               if (source instanceof FeaturesSourceI)
494               {
495                 alignFrame.getViewport().setShowSequenceFeatures(true);
496               }
497             }
498             // add metadata and update ui
499             if (!(protocol == DataSourceType.PASTE))
500             {
501               alignFrame.setFileName(file, format);
502             }
503             if (proxyColourScheme != null)
504             {
505               alignFrame.getViewport()
506                       .applyFeaturesStyle(proxyColourScheme);
507             }
508             alignFrame.statusBar.setText(MessageManager.formatMessage(
509                     "label.successfully_loaded_file", new String[]
510                     { title }));
511
512             if (raiseGUI)
513             {
514               // add the window to the GUI
515               // note - this actually should happen regardless of raiseGUI
516               // status in Jalview 3
517               // TODO: define 'virtual desktop' for benefit of headless scripts
518               // that perform queries to find the 'current working alignment'
519               Desktop.addInternalFrame(alignFrame, title,
520                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
521             }
522
523             try
524             {
525               alignFrame.setMaximum(jalview.bin.Cache
526                       .getDefault("SHOW_FULLSCREEN", false));
527             } catch (java.beans.PropertyVetoException ex)
528             {
529             }
530           }
531         }
532         else
533         {
534           if (Desktop.instance != null)
535           {
536             Desktop.instance.stopLoading();
537           }
538
539           final String errorMessage = MessageManager.getString(
540                   "label.couldnt_load_file") + " " + title + "\n" + error;
541           // TODO: refactor FileLoader to be independent of Desktop / Applet GUI
542           // bits ?
543           if (raiseGUI && Desktop.desktop != null)
544           {
545             javax.swing.SwingUtilities.invokeLater(new Runnable()
546             {
547               @Override
548               public void run()
549               {
550                 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
551                         errorMessage,
552                         MessageManager
553                                 .getString("label.error_loading_file"),
554                         JvOptionPane.WARNING_MESSAGE);
555               }
556             });
557           }
558           else
559           {
560             System.err.println(errorMessage);
561           }
562         }
563       }
564
565       updateRecentlyOpened();
566
567     } catch (Exception er)
568     {
569       System.err.println("Exception whilst opening file '" + file);
570       er.printStackTrace();
571       if (raiseGUI)
572       {
573         javax.swing.SwingUtilities.invokeLater(new Runnable()
574         {
575           @Override
576           public void run()
577           {
578             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
579                     MessageManager.formatMessage(
580                             "label.problems_opening_file", new String[]
581                             { file }),
582                     MessageManager.getString("label.file_open_error"),
583                     JvOptionPane.WARNING_MESSAGE);
584           }
585         });
586       }
587       alignFrame = null;
588     } catch (OutOfMemoryError er)
589     {
590
591       er.printStackTrace();
592       alignFrame = null;
593       if (raiseGUI)
594       {
595         javax.swing.SwingUtilities.invokeLater(new Runnable()
596         {
597           @Override
598           public void run()
599           {
600             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
601                     MessageManager.formatMessage(
602                             "warn.out_of_memory_loading_file", new String[]
603                             { file }),
604                     MessageManager.getString("label.out_of_memory"),
605                     JvOptionPane.WARNING_MESSAGE);
606           }
607         });
608       }
609       System.err.println("Out of memory loading file " + file + "!!");
610
611     }
612     loadtime += System.currentTimeMillis();
613     // TODO: Estimate percentage of memory used by a newly loaded alignment -
614     // warn if more memory will be needed to work with it
615     // System.gc();
616     memused = memused
617             - (rt.maxMemory() - rt.totalMemory() + rt.freeMemory()); // difference
618     // in free
619     // memory
620     // after
621     // load
622     if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
623     {
624       if (alignFrame != null)
625       {
626         AlignmentI al = alignFrame.getViewport().getAlignment();
627
628         System.out.println("Loaded '" + title + "' in "
629                 + (loadtime / 1000.0) + "s, took an additional "
630                 + (1.0 * memused / (1024.0 * 1024.0)) + " MB ("
631                 + al.getHeight() + " seqs by " + al.getWidth() + " cols)");
632       }
633       else
634       {
635         // report that we didn't load anything probably due to an out of memory
636         // error
637         System.out.println("Failed to load '" + title + "' in "
638                 + (loadtime / 1000.0) + "s, took an additional "
639                 + (1.0 * memused / (1024.0 * 1024.0))
640                 + " MB (alignment is null)");
641       }
642     }
643     // remove the visual delay indicator
644     if (Desktop.instance != null)
645     {
646       Desktop.instance.stopLoading();
647     }
648
649   }
650
651   /**
652    * This method creates the file -
653    * {tmpdir}/jalview/{current_timestamp}/fileName.exetnsion using the supplied
654    * file name and extension
655    * 
656    * @param fileName
657    *          the name of the temp file to be created
658    * @param extension
659    *          the extension of the temp file to be created
660    * @return
661    */
662   private static String createNamedJvTempFile(String fileName,
663           String extension) throws IOException
664   {
665     String seprator = System.getProperty("file.separator");
666     String jvTempDir = System.getProperty("java.io.tmpdir") + "jalview"
667             + seprator + System.currentTimeMillis();
668     File tempStructFile = new File(
669             jvTempDir + seprator + fileName + "." + extension);
670     tempStructFile.mkdirs();
671     return tempStructFile.toString();
672   }
673
674   /*
675    * (non-Javadoc)
676    * 
677    * @see java.lang.Object#finalize()
678    */
679   @Override
680   protected void finalize() throws Throwable
681   {
682     source = null;
683     alignFrame = null;
684     viewport = null;
685     super.finalize();
686   }
687
688 }