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