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