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