18ab642be987901d19a54679c0f5ba8e98ec774f
[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.FeaturesDisplayedI;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.ColumnSelection;
27 import jalview.datamodel.PDBEntry;
28 import jalview.datamodel.SequenceI;
29 import jalview.gui.AlignFrame;
30 import jalview.gui.AlignViewport;
31 import jalview.gui.Desktop;
32 import jalview.gui.Jalview2XML;
33 import jalview.schemes.ColourSchemeI;
34 import jalview.structure.StructureSelectionManager;
35 import jalview.util.MessageManager;
36
37 import java.util.StringTokenizer;
38 import java.util.Vector;
39
40 import javax.swing.JOptionPane;
41 import javax.swing.SwingUtilities;
42
43 public class FileLoader implements Runnable
44 {
45   String file;
46
47   String protocol;
48
49   String format;
50
51   FileParse source = null; // alternative specification of where data comes
52
53   // from
54
55   AlignViewport viewport;
56
57   AlignFrame alignFrame;
58
59   long loadtime;
60
61   long memused;
62
63   boolean raiseGUI = true;
64
65   /**
66    * default constructor always raised errors in GUI dialog boxes
67    */
68   public FileLoader()
69   {
70     this(true);
71   }
72
73   /**
74    * construct a Fileloader that may raise errors non-interactively
75    * 
76    * @param raiseGUI
77    *          true if errors are to be raised as GUI dialog boxes
78    */
79   public FileLoader(boolean raiseGUI)
80   {
81     this.raiseGUI = raiseGUI;
82   }
83
84   public void LoadFile(AlignViewport viewport, String file,
85           String protocol, String format)
86   {
87     this.viewport = viewport;
88     LoadFile(file, protocol, format);
89   }
90
91   public void LoadFile(String file, String protocol, String format)
92   {
93     this.file = file;
94     this.protocol = protocol;
95     this.format = format;
96
97     final Thread loader = new Thread(this);
98
99     SwingUtilities.invokeLater(new Runnable()
100     {
101       public void run()
102       {
103         loader.start();
104       }
105     });
106   }
107
108   /**
109    * Load a (file, protocol) source of unknown type
110    * 
111    * @param file
112    * @param protocol
113    */
114   public void LoadFile(String file, String protocol)
115   {
116     LoadFile(file, protocol, null);
117   }
118
119   /**
120    * Load alignment from (file, protocol) and wait till loaded
121    * 
122    * @param file
123    * @param protocol
124    * @return alignFrame constructed from file contents
125    */
126   public AlignFrame LoadFileWaitTillLoaded(String file, String protocol)
127   {
128     return LoadFileWaitTillLoaded(file, protocol, null);
129   }
130
131   /**
132    * Load alignment from (file, protocol) of type format and wait till loaded
133    * 
134    * @param file
135    * @param protocol
136    * @param format
137    * @return alignFrame constructed from file contents
138    */
139   public AlignFrame LoadFileWaitTillLoaded(String file, String protocol,
140           String format)
141   {
142     this.file = file;
143     this.protocol = protocol;
144     this.format = format;
145     return _LoadFileWaitTillLoaded();
146   }
147
148   /**
149    * Load alignment from FileParse source of type format and wait till loaded
150    * 
151    * @param source
152    * @param format
153    * @return alignFrame constructed from file contents
154    */
155   public AlignFrame LoadFileWaitTillLoaded(FileParse source, String format)
156   {
157     this.source = source;
158
159     file = source.getInFile();
160     protocol = source.type;
161     this.format = format;
162     return _LoadFileWaitTillLoaded();
163   }
164
165   /**
166    * start thread and wait until finished, then return the alignFrame that's
167    * (hopefully) been read.
168    * 
169    * @return
170    */
171   protected AlignFrame _LoadFileWaitTillLoaded()
172   {
173     Thread loader = new Thread(this);
174     loader.start();
175
176     while (loader.isAlive())
177     {
178       try
179       {
180         Thread.sleep(500);
181       } catch (Exception ex)
182       {
183       }
184     }
185
186     return alignFrame;
187   }
188
189   public void updateRecentlyOpened()
190   {
191     Vector recent = new Vector();
192     if (protocol.equals(FormatAdapter.PASTE))
193     {
194       // do nothing if the file was pasted in as text... there is no filename to
195       // refer to it as.
196       return;
197     }
198     String type = protocol.equals(FormatAdapter.FILE) ? "RECENT_FILE"
199             : "RECENT_URL";
200
201     String historyItems = jalview.bin.Cache.getProperty(type);
202
203     StringTokenizer st;
204
205     if (historyItems != null)
206     {
207       st = new StringTokenizer(historyItems, "\t");
208
209       while (st.hasMoreTokens())
210       {
211         recent.addElement(st.nextElement().toString().trim());
212       }
213     }
214
215     if (recent.contains(file))
216     {
217       recent.remove(file);
218     }
219
220     StringBuffer newHistory = new StringBuffer(file);
221     for (int i = 0; i < recent.size() && i < 10; i++)
222     {
223       newHistory.append("\t");
224       newHistory.append(recent.elementAt(i));
225     }
226
227     jalview.bin.Cache.setProperty(type, newHistory.toString());
228
229     if (protocol.equals(FormatAdapter.FILE))
230     {
231       jalview.bin.Cache.setProperty("DEFAULT_FILE_FORMAT", format);
232     }
233   }
234
235   public void run()
236   {
237     String title = protocol.equals(AppletFormatAdapter.PASTE) ? "Copied From Clipboard"
238             : file;
239     Runtime rt = Runtime.getRuntime();
240     try
241     {
242       if (Desktop.instance != null)
243       {
244         Desktop.instance.startLoading(file);
245       }
246       if (format == null)
247       {
248         // just in case the caller didn't identify the file for us
249         if (source != null)
250         {
251           format = new IdentifyFile().Identify(source, false); // identify
252           // stream and
253           // rewind rather
254           // than close
255         }
256         else
257         {
258           format = new IdentifyFile().Identify(file, protocol);
259         }
260       }
261       // TODO: cache any stream datasources as a temporary file (eg. PDBs
262       // retrieved via URL)
263       if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
264       {
265         System.gc();
266         memused = (rt.maxMemory() - rt.totalMemory() + rt.freeMemory()); // free
267         // memory
268         // before
269         // load
270       }
271       loadtime = -System.currentTimeMillis();
272       AlignmentI al = null;
273
274       if (format.equalsIgnoreCase("Jalview"))
275       {
276         if (source != null)
277         {
278           // Tell the user (developer?) that this is going to cause a problem
279           System.err
280                   .println("IMPLEMENTATION ERROR: Cannot read consecutive Jalview XML projects from a stream.");
281           // We read the data anyway - it might make sense.
282         }
283         alignFrame = new Jalview2XML(raiseGUI).loadJalviewAlign(file);
284       }
285       else
286       {
287         String error = AppletFormatAdapter.SUPPORTED_FORMATS;
288         if (FormatAdapter.isValidFormat(format))
289         {
290           try
291           {
292             if (source != null)
293             {
294               // read from the provided source
295               al = new FormatAdapter().readFromFile(source, format);
296             }
297             else
298             {
299
300               // open a new source and read from it
301               FormatAdapter fa = new FormatAdapter();
302               al = fa.readFile(file, protocol, format);
303               source = fa.getAlignFile(); // keep reference for later if
304                                           // necessary.
305             }
306           } catch (java.io.IOException ex)
307           {
308             error = ex.getMessage();
309           }
310         }
311         else
312         {
313           if (format != null && format.length() > 7)
314           {
315             // ad hoc message in format.
316             error = format + "\n" + error;
317           }
318         }
319
320         if ((al != null) && (al.getHeight() > 0))
321         {
322           // construct and register dataset sequences
323           for (SequenceI sq : al.getSequences())
324           {
325             while (sq.getDatasetSequence() != null)
326             {
327               sq = sq.getDatasetSequence();
328             }
329             if (sq.getPDBId() != null)
330             {
331               for (PDBEntry pdbe : sq.getPDBId())
332               {
333                 // register PDB entries with desktop's structure selection
334                 // manager
335                 StructureSelectionManager.getStructureSelectionManager(
336                         Desktop.instance).registerPDBEntry(pdbe);
337               }
338             }
339           }
340
341           if (viewport != null)
342           {
343             // append to existing alignment
344             viewport.addAlignment(al, title);
345           }
346           else
347           {
348             // otherwise construct the alignFrame
349
350             if (source instanceof ComplexAlignFile)
351             {
352               ColumnSelection colSel = ((ComplexAlignFile) source)
353                       .getColumnSelection();
354               SequenceI[] hiddenSeqs = ((ComplexAlignFile) source)
355                       .getHiddenSequences();
356               boolean showSeqFeatures = ((ComplexAlignFile) source)
357                       .isShowSeqFeatures();
358               ColourSchemeI cs = ((ComplexAlignFile) source)
359                       .getColourScheme();
360               FeaturesDisplayedI fd = ((ComplexAlignFile) source)
361                       .getDisplayedFeatures();
362               alignFrame = new AlignFrame(al, hiddenSeqs, colSel,
363                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
364
365               alignFrame.getViewport().setShowSequenceFeatures(
366                       showSeqFeatures);
367               alignFrame.getViewport().setFeaturesDisplayed(fd);
368               alignFrame.changeColour(cs);
369             }
370             else
371             {
372               alignFrame = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
373                       AlignFrame.DEFAULT_HEIGHT);
374             }
375             // add metadata and update ui
376             if (!protocol.equals(AppletFormatAdapter.PASTE))
377             {
378               alignFrame.setFileName(file, format);
379             }
380
381             alignFrame.statusBar.setText(MessageManager.formatMessage(
382                     "label.successfully_loaded_file", new String[]
383                     { title }));
384
385             if (raiseGUI)
386             {
387               // add the window to the GUI
388               // note - this actually should happen regardless of raiseGUI
389               // status in Jalview 3
390               // TODO: define 'virtual desktop' for benefit of headless scripts
391               // that perform queries to find the 'current working alignment'
392               Desktop.addInternalFrame(alignFrame, title,
393                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
394             }
395
396             try
397             {
398               alignFrame.setMaximum(jalview.bin.Cache.getDefault(
399                       "SHOW_FULLSCREEN", false));
400             } catch (java.beans.PropertyVetoException ex)
401             {
402             }
403           }
404         }
405         else
406         {
407           if (Desktop.instance != null)
408           {
409             Desktop.instance.stopLoading();
410           }
411
412           final String errorMessage = "Couldn't load file " + title + "\n"
413                   + error;
414           // TODO: refactor FileLoader to be independent of Desktop / Applet GUI
415           // bits ?
416           if (raiseGUI && Desktop.desktop != null)
417           {
418             javax.swing.SwingUtilities.invokeLater(new Runnable()
419             {
420               public void run()
421               {
422                 JOptionPane.showInternalMessageDialog(Desktop.desktop,
423                         errorMessage, MessageManager
424                                 .getString("label.error_loading_file"),
425                         JOptionPane.WARNING_MESSAGE);
426               }
427             });
428           }
429           else
430           {
431             System.err.println(errorMessage);
432           }
433         }
434       }
435
436       updateRecentlyOpened();
437
438     } catch (Exception er)
439     {
440       System.err.println("Exception whilst opening file '" + file);
441       er.printStackTrace();
442       if (raiseGUI)
443       {
444         javax.swing.SwingUtilities.invokeLater(new Runnable()
445         {
446           public void run()
447           {
448             javax.swing.JOptionPane.showInternalMessageDialog(
449                     Desktop.desktop, MessageManager.formatMessage(
450                             "label.problems_opening_file", new String[]
451                             { file }), MessageManager
452                             .getString("label.file_open_error"),
453                     javax.swing.JOptionPane.WARNING_MESSAGE);
454           }
455         });
456       }
457       alignFrame = null;
458     } catch (OutOfMemoryError er)
459     {
460
461       er.printStackTrace();
462       alignFrame = null;
463       if (raiseGUI)
464       {
465         javax.swing.SwingUtilities.invokeLater(new Runnable()
466         {
467           public void run()
468           {
469             javax.swing.JOptionPane.showInternalMessageDialog(
470                     Desktop.desktop, MessageManager.formatMessage(
471                             "warn.out_of_memory_loading_file", new String[]
472                             { file }), MessageManager
473                             .getString("label.out_of_memory"),
474                     javax.swing.JOptionPane.WARNING_MESSAGE);
475           }
476         });
477       }
478       System.err.println("Out of memory loading file " + file + "!!");
479
480     }
481     loadtime += System.currentTimeMillis();
482     // TODO: Estimate percentage of memory used by a newly loaded alignment -
483     // warn if more memory will be needed to work with it
484     // System.gc();
485     memused = memused
486             - (rt.maxMemory() - rt.totalMemory() + rt.freeMemory()); // difference
487     // in free
488     // memory
489     // after
490     // load
491     if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
492     {
493       if (alignFrame != null)
494       {
495         AlignmentI al = alignFrame.getViewport().getAlignment();
496
497         System.out.println("Loaded '" + title + "' in "
498                 + (loadtime / 1000.0) + "s, took an additional "
499                 + (1.0 * memused / (1024.0 * 1024.0)) + " MB ("
500                 + al.getHeight() + " seqs by " + al.getWidth() + " cols)");
501       }
502       else
503       {
504         // report that we didn't load anything probably due to an out of memory
505         // error
506         System.out.println("Failed to load '" + title + "' in "
507                 + (loadtime / 1000.0) + "s, took an additional "
508                 + (1.0 * memused / (1024.0 * 1024.0))
509                 + " MB (alignment is null)");
510       }
511     }
512     // remove the visual delay indicator
513     if (Desktop.instance != null)
514     {
515       Desktop.instance.stopLoading();
516     }
517
518   }
519
520   /*
521    * (non-Javadoc)
522    * 
523    * @see java.lang.Object#finalize()
524    */
525   protected void finalize() throws Throwable
526   {
527     source = null;
528     alignFrame = null;
529     viewport = null;
530     super.finalize();
531   }
532
533 }