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