JAL-629 Test tidyup
[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.AlignmentI;
37 import jalview.datamodel.HiddenColumns;
38 import jalview.datamodel.PDBEntry;
39 import jalview.datamodel.SequenceI;
40 import jalview.gui.AlignFrame;
41 import jalview.gui.AlignViewport;
42 import jalview.gui.Desktop;
43 import jalview.gui.JvOptionPane;
44 import jalview.gui.QuitHandler;
45 import jalview.json.binding.biojson.v1.ColourSchemeMapper;
46 import jalview.project.Jalview2XML;
47 import jalview.schemes.ColourSchemeI;
48 import jalview.structure.StructureSelectionManager;
49 import jalview.util.MessageManager;
50 import jalview.ws.utils.UrlDownloadClient;
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   private File selectedFile;
76
77   /**
78    * default constructor always raised errors in GUI dialog boxes
79    */
80   public FileLoader()
81   {
82     this(true);
83   }
84
85   /**
86    * construct a Fileloader that may raise errors non-interactively
87    * 
88    * @param raiseGUI
89    *          true if errors are to be raised as GUI dialog boxes
90    */
91   public FileLoader(boolean raiseGUI)
92   {
93     this.raiseGUI = raiseGUI;
94   }
95
96   public void LoadFile(AlignViewport viewport, Object file,
97           DataSourceType protocol, FileFormatI format)
98   {
99     LoadFile(viewport, file, protocol, format, true);
100   }
101
102   public void LoadFile(AlignViewport viewport, Object file,
103           DataSourceType protocol, FileFormatI format, boolean async)
104   {
105     this.viewport = viewport;
106     if (file instanceof File)
107     {
108       this.selectedFile = (File) file;
109       file = selectedFile.getPath();
110     }
111     LoadFile(file.toString(), protocol, format, async);
112   }
113
114   public void LoadFile(String file, DataSourceType protocol,
115           FileFormatI format)
116   {
117     LoadFile(file, protocol, format, true);
118   }
119
120   public void LoadFile(String file, DataSourceType protocol,
121           FileFormatI format, boolean async)
122   {
123     this.file = file;
124     this.protocol = protocol;
125     this.format = format;
126
127     if (async)
128     {
129       final Thread loader = new Thread(this);
130
131       SwingUtilities.invokeLater(new Runnable()
132       {
133         @Override
134         public void run()
135         {
136           loader.start();
137         }
138       });
139     }
140     else
141     {
142       this.run();
143     }
144   }
145
146   /**
147    * Load a (file, protocol) source of unknown type
148    * 
149    * @param file
150    * @param protocol
151    */
152   public void LoadFile(String file, DataSourceType protocol)
153   {
154     LoadFile(file, protocol, null);
155   }
156
157   /**
158    * Load alignment from (file, protocol) and wait till loaded
159    * 
160    * @param file
161    * @param sourceType
162    * @return alignFrame constructed from file contents
163    */
164   public AlignFrame LoadFileWaitTillLoaded(String file,
165           DataSourceType sourceType)
166   {
167     return LoadFileWaitTillLoaded(file, sourceType, null);
168   }
169
170   /**
171    * Load alignment from (file, protocol) of type format and wait till loaded
172    * 
173    * @param file
174    * @param sourceType
175    * @param format
176    * @return alignFrame constructed from file contents
177    */
178   public AlignFrame LoadFileWaitTillLoaded(String file,
179           DataSourceType sourceType, FileFormatI format)
180   {
181     this.file = file;
182     this.protocol = sourceType;
183     this.format = format;
184     return _LoadFileWaitTillLoaded();
185   }
186
187   /**
188    * Load alignment from (file, protocol) of type format and wait till loaded
189    * 
190    * @param file
191    * @param sourceType
192    * @param format
193    * @return alignFrame constructed from file contents
194    */
195   public AlignFrame LoadFileWaitTillLoaded(File file,
196           DataSourceType sourceType, FileFormatI format)
197   {
198     this.selectedFile = file;
199     this.file = file.getPath();
200     this.protocol = sourceType;
201     this.format = format;
202     return _LoadFileWaitTillLoaded();
203   }
204
205   /**
206    * Load alignment from FileParse source of type format and wait till loaded
207    * 
208    * @param source
209    * @param format
210    * @return alignFrame constructed from file contents
211    */
212   public AlignFrame LoadFileWaitTillLoaded(AlignmentFileReaderI source,
213           FileFormatI format)
214   {
215     this.source = source;
216
217     file = source.getInFile();
218     protocol = source.getDataSourceType();
219     this.format = format;
220     return _LoadFileWaitTillLoaded();
221   }
222
223   /**
224    * runs the 'run' method (in this thread), then return the alignFrame that's
225    * (hopefully) been read
226    * 
227    * @return
228    */
229   protected AlignFrame _LoadFileWaitTillLoaded()
230   {
231     this.run();
232
233     return alignFrame;
234   }
235
236   public void updateRecentlyOpened()
237   {
238     Vector<String> recent = new Vector<>();
239     if (protocol == DataSourceType.PASTE)
240     {
241       // do nothing if the file was pasted in as text... there is no filename to
242       // refer to it as.
243       return;
244     }
245     if (file != null
246             && file.indexOf(System.getProperty("java.io.tmpdir")) > -1)
247     {
248       // ignore files loaded from the system's temporary directory
249       return;
250     }
251     String type = protocol == DataSourceType.FILE ? "RECENT_FILE"
252             : "RECENT_URL";
253
254     String historyItems = Cache.getProperty(type);
255
256     StringTokenizer st;
257
258     if (historyItems != null)
259     {
260       st = new StringTokenizer(historyItems, "\t");
261
262       while (st.hasMoreTokens())
263       {
264         recent.addElement(st.nextToken().trim());
265       }
266     }
267
268     if (recent.contains(file))
269     {
270       recent.remove(file);
271     }
272
273     StringBuffer newHistory = new StringBuffer(file);
274     for (int i = 0; i < recent.size() && i < 10; i++)
275     {
276       newHistory.append("\t");
277       newHistory.append(recent.elementAt(i));
278     }
279
280     Cache.setProperty(type, newHistory.toString());
281
282     if (protocol == DataSourceType.FILE)
283     {
284       Cache.setProperty("DEFAULT_FILE_FORMAT", format.getName());
285     }
286   }
287
288   @Override
289   public void run()
290   {
291     String title = protocol == DataSourceType.PASTE
292             ? "Copied From Clipboard"
293             : file;
294     Runtime rt = Runtime.getRuntime();
295
296     try
297     {
298       if (Desktop.instance != null)
299       {
300         Desktop.instance.startLoading(file);
301       }
302       if (format == null)
303       {
304         // just in case the caller didn't identify the file for us
305         if (source != null)
306         {
307           format = new IdentifyFile().identify(source, false);
308           // identify stream and rewind rather than close
309         }
310         else if (selectedFile != null)
311         {
312           format = new IdentifyFile().identify(selectedFile, protocol);
313         }
314         else
315         {
316           format = new IdentifyFile().identify(file, protocol);
317         }
318
319       }
320
321       if (format == null)
322       {
323         Desktop.instance.stopLoading();
324         System.err.println("The input file \"" + file
325                 + "\" has null or unidentifiable data content!");
326         if (!Jalview.isHeadlessMode())
327         {
328           JvOptionPane.showInternalMessageDialog(Desktop.desktop,
329                   MessageManager.getString("label.couldnt_read_data")
330                           + " in " + file + "\n"
331                           + AppletFormatAdapter.getSupportedFormats(),
332                   MessageManager.getString("label.couldnt_read_data"),
333                   JvOptionPane.WARNING_MESSAGE);
334         }
335         this.setShouldBeSaved();
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
405               }
406               else
407               {
408                 al = fa.readFile(selectedFile, null, protocol, format);
409               }
410               source = fa.getAlignFile(); // keep reference for later if
411
412               // necessary.
413             }
414           }
415         } catch (java.io.IOException ex)
416         {
417           error = ex.getMessage();
418         }
419
420         if ((al != null) && (al.getHeight() > 0) && al.hasValidSequence())
421         {
422           // construct and register dataset sequences
423           for (SequenceI sq : al.getSequences())
424           {
425             while (sq.getDatasetSequence() != null)
426             {
427               sq = sq.getDatasetSequence();
428             }
429             if (sq.getAllPDBEntries() != null)
430             {
431               for (PDBEntry pdbe : sq.getAllPDBEntries())
432               {
433                 // register PDB entries with desktop's structure selection
434                 // manager
435                 StructureSelectionManager
436                         .getStructureSelectionManager(Desktop.instance)
437                         .registerPDBEntry(pdbe);
438               }
439             }
440           }
441
442           FeatureSettingsModelI proxyColourScheme = source
443                   .getFeatureColourScheme();
444           if (viewport != null)
445           {
446             // append to existing alignment
447             viewport.addAlignment(al, title);
448             viewport.applyFeaturesStyle(proxyColourScheme);
449           }
450           else
451           {
452             // otherwise construct the alignFrame
453
454             if (source instanceof ComplexAlignFile)
455             {
456               HiddenColumns colSel = ((ComplexAlignFile) source)
457                       .getHiddenColumns();
458               SequenceI[] hiddenSeqs = ((ComplexAlignFile) source)
459                       .getHiddenSequences();
460               String colourSchemeName = ((ComplexAlignFile) source)
461                       .getGlobalColourScheme();
462               FeaturesDisplayedI fd = ((ComplexAlignFile) source)
463                       .getDisplayedFeatures();
464               alignFrame = new AlignFrame(al, hiddenSeqs, colSel,
465                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
466               alignFrame.getViewport().setFeaturesDisplayed(fd);
467               alignFrame.getViewport().setShowSequenceFeatures(
468                       ((ComplexAlignFile) source).isShowSeqFeatures());
469               ColourSchemeI cs = ColourSchemeMapper
470                       .getJalviewColourScheme(colourSchemeName, al);
471               if (cs != null)
472               {
473                 alignFrame.changeColour(cs);
474               }
475             }
476             else
477             {
478               alignFrame = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
479                       AlignFrame.DEFAULT_HEIGHT);
480               if (source instanceof FeaturesSourceI)
481               {
482                 alignFrame.getViewport().setShowSequenceFeatures(true);
483               }
484             }
485             // add metadata and update ui
486             if (!(protocol == DataSourceType.PASTE))
487             {
488               alignFrame.setFileName(file, format);
489               alignFrame.setFileObject(selectedFile); // BH 2018 SwingJS
490             }
491             if (proxyColourScheme != null)
492             {
493               alignFrame.getViewport()
494                       .applyFeaturesStyle(proxyColourScheme);
495             }
496             alignFrame.setStatus(MessageManager.formatMessage(
497                     "label.successfully_loaded_file", new String[]
498                     { title }));
499
500             if (raiseGUI)
501             {
502               // add the window to the GUI
503               // note - this actually should happen regardless of raiseGUI
504               // status in Jalview 3
505               // TODO: define 'virtual desktop' for benefit of headless scripts
506               // that perform queries to find the 'current working alignment'
507               Desktop.addInternalFrame(alignFrame, title,
508                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
509
510               /*
511                * for an Overview automatically opened with alignment,
512                * set its title now alignFrame title has been set
513                */
514               alignFrame.alignPanel.setOverviewTitle(alignFrame);
515             }
516
517             try
518             {
519               alignFrame.setMaximum(
520                       Cache.getDefault("SHOW_FULLSCREEN", false));
521             } catch (java.beans.PropertyVetoException ex)
522             {
523             }
524           }
525         }
526         else
527         {
528           if (Desktop.instance != null)
529           {
530             Desktop.instance.stopLoading();
531           }
532
533           final String errorMessage = MessageManager.getString(
534                   "label.couldnt_load_file") + " " + title + "\n" + error;
535           // TODO: refactor FileLoader to be independent of Desktop / Applet GUI
536           // bits ?
537           if (raiseGUI && Desktop.desktop != null)
538           {
539             javax.swing.SwingUtilities.invokeLater(new Runnable()
540             {
541               @Override
542               public void run()
543               {
544                 JvOptionPane.showInternalMessageDialog(Desktop.desktop,
545                         errorMessage,
546                         MessageManager
547                                 .getString("label.error_loading_file"),
548                         JvOptionPane.WARNING_MESSAGE);
549               }
550             });
551           }
552           else
553           {
554             System.err.println(errorMessage);
555           }
556         }
557       }
558
559       updateRecentlyOpened();
560
561     } catch (Exception er)
562     {
563       System.err.println("Exception whilst opening file '" + file);
564       er.printStackTrace();
565       if (raiseGUI)
566       {
567         javax.swing.SwingUtilities.invokeLater(new Runnable()
568         {
569           @Override
570           public void run()
571           {
572             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
573                     MessageManager.formatMessage(
574                             "label.problems_opening_file", new String[]
575                             { file }),
576                     MessageManager.getString("label.file_open_error"),
577                     JvOptionPane.WARNING_MESSAGE);
578           }
579         });
580       }
581       alignFrame = null;
582     } catch (OutOfMemoryError er)
583     {
584
585       er.printStackTrace();
586       alignFrame = null;
587       if (raiseGUI)
588       {
589         javax.swing.SwingUtilities.invokeLater(new Runnable()
590         {
591           @Override
592           public void run()
593           {
594             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
595                     MessageManager.formatMessage(
596                             "warn.out_of_memory_loading_file", new String[]
597                             { file }),
598                     MessageManager.getString("label.out_of_memory"),
599                     JvOptionPane.WARNING_MESSAGE);
600           }
601         });
602       }
603       System.err.println("Out of memory loading file " + file + "!!");
604
605     }
606     loadtime += System.currentTimeMillis();
607     // TODO: Estimate percentage of memory used by a newly loaded alignment -
608     // warn if more memory will be needed to work with it
609     // System.gc();
610     memused = memused
611             - (rt.maxMemory() - rt.totalMemory() + rt.freeMemory()); // difference
612     // in free
613     // memory
614     // after
615     // load
616     if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
617     {
618       if (alignFrame != null)
619       {
620         AlignmentI al = alignFrame.getViewport().getAlignment();
621
622         System.out.println("Loaded '" + title + "' in "
623                 + (loadtime / 1000.0) + "s, took an additional "
624                 + (1.0 * memused / (1024.0 * 1024.0)) + " MB ("
625                 + al.getHeight() + " seqs by " + al.getWidth() + " cols)");
626       }
627       else
628       {
629         // report that we didn't load anything probably due to an out of memory
630         // error
631         System.out.println("Failed to load '" + title + "' in "
632                 + (loadtime / 1000.0) + "s, took an additional "
633                 + (1.0 * memused / (1024.0 * 1024.0))
634                 + " MB (alignment is null)");
635       }
636     }
637     // remove the visual delay indicator
638     if (Desktop.instance != null)
639     {
640       Desktop.instance.stopLoading();
641     }
642
643     this.setShouldBeSaved();
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    * set whether quit should ask to save when just loaded this source
671    */
672   private void setShouldBeSaved()
673   {
674     if (protocol == null)
675       return;
676     AlignFrame af = this.alignFrame;
677     if (af == null)
678       return;
679     AlignViewport avp = af.getViewport();
680     if (avp == null)
681       return;
682     avp.setSavedUpToDate(!protocol.isDynamic(),
683             QuitHandler.Message.UNSAVED_ALIGNMENTS);
684   }
685
686 }