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