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