file format enum wip changes
[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.json.binding.biojson.v1.ColourSchemeMapper;
38 import jalview.schemes.ColourSchemeI;
39 import jalview.structure.StructureSelectionManager;
40 import jalview.util.MessageManager;
41
42 import java.util.StringTokenizer;
43 import java.util.Vector;
44
45 import javax.swing.JOptionPane;
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); // identify
262           // stream and
263           // rewind rather
264           // than close
265         }
266         else
267         {
268           format = new IdentifyFile().identify(file, protocol);
269         }
270
271       }
272
273       if (format == null)
274       {
275         Desktop.instance.stopLoading();
276         System.err.println("The input file \"" + file
277                 + "\" has null or unidentifiable data content!");
278         if (!Jalview.isHeadlessMode())
279         {
280           javax.swing.JOptionPane.showInternalMessageDialog(
281                   Desktop.desktop,
282                   MessageManager.getString("label.couldnt_read_data")
283                           + " in " + file + "\n"
284                           + AppletFormatAdapter.SUPPORTED_FORMATS,
285                   MessageManager.getString("label.couldnt_read_data"),
286                   JOptionPane.WARNING_MESSAGE);
287         }
288         return;
289       }
290       // TODO: cache any stream datasources as a temporary file (eg. PDBs
291       // retrieved via URL)
292       if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
293       {
294         System.gc();
295         memused = (rt.maxMemory() - rt.totalMemory() + rt.freeMemory()); // free
296         // memory
297         // before
298         // load
299       }
300       loadtime = -System.currentTimeMillis();
301       AlignmentI al = null;
302
303       if (format == FileFormat.Jalview)
304       {
305         if (source != null)
306         {
307           // Tell the user (developer?) that this is going to cause a problem
308           System.err
309                   .println("IMPLEMENTATION ERROR: Cannot read consecutive Jalview XML projects from a stream.");
310           // We read the data anyway - it might make sense.
311         }
312         alignFrame = new Jalview2XML(raiseGUI).loadJalviewAlign(file);
313       }
314       else
315       {
316         String error = AppletFormatAdapter.SUPPORTED_FORMATS;
317           try
318           {
319             if (source != null)
320             {
321               // read from the provided source
322               al = new FormatAdapter().readFromFile(source, format);
323             }
324             else
325             {
326
327               // open a new source and read from it
328               FormatAdapter fa = new FormatAdapter();
329               al = fa.readFile(file, protocol, format);
330               source = fa.getAlignFile(); // keep reference for later if
331                                           // necessary.
332             }
333           } catch (java.io.IOException ex)
334           {
335             error = ex.getMessage();
336           }
337
338         if ((al != null) && (al.getHeight() > 0) && al.hasValidSequence())
339         {
340           // construct and register dataset sequences
341           for (SequenceI sq : al.getSequences())
342           {
343             while (sq.getDatasetSequence() != null)
344             {
345               sq = sq.getDatasetSequence();
346             }
347             if (sq.getAllPDBEntries() != null)
348             {
349               for (PDBEntry pdbe : sq.getAllPDBEntries())
350               {
351                 // register PDB entries with desktop's structure selection
352                 // manager
353                 StructureSelectionManager.getStructureSelectionManager(
354                         Desktop.instance).registerPDBEntry(pdbe);
355               }
356             }
357           }
358
359           FeatureSettingsModelI proxyColourScheme = source
360                   .getFeatureColourScheme();
361           if (viewport != null)
362           {
363             if (proxyColourScheme != null)
364             {
365               viewport.applyFeaturesStyle(proxyColourScheme);
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 == DataSourceType.PASTE))
407             {
408               alignFrame.setFileName(file, format);
409             }
410             if (proxyColourScheme != null)
411             {
412               alignFrame.getViewport()
413                       .applyFeaturesStyle(proxyColourScheme);
414             }
415             alignFrame.statusBar.setText(MessageManager.formatMessage(
416                     "label.successfully_loaded_file",
417                     new String[] { title }));
418
419             if (raiseGUI)
420             {
421               // add the window to the GUI
422               // note - this actually should happen regardless of raiseGUI
423               // status in Jalview 3
424               // TODO: define 'virtual desktop' for benefit of headless scripts
425               // that perform queries to find the 'current working alignment'
426               Desktop.addInternalFrame(alignFrame, title,
427                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
428             }
429
430             try
431             {
432               alignFrame.setMaximum(jalview.bin.Cache.getDefault(
433                       "SHOW_FULLSCREEN", false));
434             } catch (java.beans.PropertyVetoException ex)
435             {
436             }
437           }
438         }
439         else
440         {
441           if (Desktop.instance != null)
442           {
443             Desktop.instance.stopLoading();
444           }
445
446           final String errorMessage = MessageManager
447                   .getString("label.couldnt_load_file")
448                   + " "
449                   + title
450                   + "\n" + error;
451           // TODO: refactor FileLoader to be independent of Desktop / Applet GUI
452           // bits ?
453           if (raiseGUI && Desktop.desktop != null)
454           {
455             javax.swing.SwingUtilities.invokeLater(new Runnable()
456             {
457               @Override
458               public void run()
459               {
460                 JOptionPane.showInternalMessageDialog(Desktop.desktop,
461                         errorMessage, MessageManager
462                                 .getString("label.error_loading_file"),
463                         JOptionPane.WARNING_MESSAGE);
464               }
465             });
466           }
467           else
468           {
469             System.err.println(errorMessage);
470           }
471         }
472       }
473
474       updateRecentlyOpened();
475
476     } catch (Exception er)
477     {
478       System.err.println("Exception whilst opening file '" + file);
479       er.printStackTrace();
480       if (raiseGUI)
481       {
482         javax.swing.SwingUtilities.invokeLater(new Runnable()
483         {
484           @Override
485           public void run()
486           {
487             javax.swing.JOptionPane.showInternalMessageDialog(
488                     Desktop.desktop, MessageManager.formatMessage(
489                             "label.problems_opening_file",
490                             new String[] { file }), MessageManager
491                             .getString("label.file_open_error"),
492                     javax.swing.JOptionPane.WARNING_MESSAGE);
493           }
494         });
495       }
496       alignFrame = null;
497     } catch (OutOfMemoryError er)
498     {
499
500       er.printStackTrace();
501       alignFrame = null;
502       if (raiseGUI)
503       {
504         javax.swing.SwingUtilities.invokeLater(new Runnable()
505         {
506           @Override
507           public void run()
508           {
509             javax.swing.JOptionPane.showInternalMessageDialog(
510                     Desktop.desktop, MessageManager.formatMessage(
511                             "warn.out_of_memory_loading_file", new String[]
512                             { file }), MessageManager
513                             .getString("label.out_of_memory"),
514                     javax.swing.JOptionPane.WARNING_MESSAGE);
515           }
516         });
517       }
518       System.err.println("Out of memory loading file " + file + "!!");
519
520     }
521     loadtime += System.currentTimeMillis();
522     // TODO: Estimate percentage of memory used by a newly loaded alignment -
523     // warn if more memory will be needed to work with it
524     // System.gc();
525     memused = memused
526             - (rt.maxMemory() - rt.totalMemory() + rt.freeMemory()); // difference
527     // in free
528     // memory
529     // after
530     // load
531     if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
532     {
533       if (alignFrame != null)
534       {
535         AlignmentI al = alignFrame.getViewport().getAlignment();
536
537         System.out.println("Loaded '" + title + "' in "
538                 + (loadtime / 1000.0) + "s, took an additional "
539                 + (1.0 * memused / (1024.0 * 1024.0)) + " MB ("
540                 + al.getHeight() + " seqs by " + al.getWidth() + " cols)");
541       }
542       else
543       {
544         // report that we didn't load anything probably due to an out of memory
545         // error
546         System.out.println("Failed to load '" + title + "' in "
547                 + (loadtime / 1000.0) + "s, took an additional "
548                 + (1.0 * memused / (1024.0 * 1024.0))
549                 + " MB (alignment is null)");
550       }
551     }
552     // remove the visual delay indicator
553     if (Desktop.instance != null)
554     {
555       Desktop.instance.stopLoading();
556     }
557
558   }
559
560   /*
561    * (non-Javadoc)
562    * 
563    * @see java.lang.Object#finalize()
564    */
565   @Override
566   protected void finalize() throws Throwable
567   {
568     source = null;
569     alignFrame = null;
570     viewport = null;
571     super.finalize();
572   }
573
574 }