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