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