Merge branch 'Jalview-JS/develop.JAL-3446.ctrlDown' into
[jalview.git] / src / jalview / io / AppletFormatAdapter.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.AlignExportSettingsI;
24 import jalview.api.AlignmentViewPanel;
25 import jalview.datamodel.Alignment;
26 import jalview.datamodel.AlignmentAnnotation;
27 import jalview.datamodel.AlignmentI;
28 import jalview.datamodel.AlignmentView;
29 import jalview.datamodel.PDBEntry.Type;
30 import jalview.datamodel.SequenceI;
31 import jalview.ext.jmol.JmolParser;
32 import jalview.structure.StructureImportSettings;
33 import jalview.util.Platform;
34
35 import java.io.File;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.util.List;
39
40 /**
41  * A low level class for alignment and feature IO with alignment formatting
42  * methods used by both applet and application for generating flat alignment
43  * files. It also holds the lists of magic format names that the applet and
44  * application will allow the user to read or write files with.
45  *
46  * @author $author$
47  * @version $Revision$
48  */
49 public class AppletFormatAdapter
50 {
51   private AlignmentViewPanel viewpanel;
52
53   /**
54    * add jalview-derived non-secondary structure annotation from PDB structure
55    */
56   boolean annotFromStructure = false;
57
58   /**
59    * add secondary structure from PDB data with built-in algorithms
60    */
61   boolean localSecondaryStruct = false;
62
63   /**
64    * process PDB data with web services
65    */
66   boolean serviceSecondaryStruct = false;
67
68   private AlignmentFileReaderI alignFile = null;
69
70   String inFile;
71
72   /**
73    * character used to write newlines
74    */
75   protected String newline = System.getProperty("line.separator");
76
77   private AlignExportSettingsI exportSettings;
78
79   private File selectedFile;
80
81   public static String INVALID_CHARACTERS = "Contains invalid characters";
82
83   /**
84    * Returns an error message with a list of supported readable file formats
85    * 
86    * @return
87    */
88   public static String getSupportedFormats()
89   {
90     return "Formats currently supported are\n"
91             + prettyPrint(FileFormats.getInstance().getReadableFormats());
92   }
93
94   public AppletFormatAdapter()
95   {
96   }
97
98   public AppletFormatAdapter(AlignmentViewPanel viewpanel)
99   {
100     this.viewpanel = viewpanel;
101   }
102
103   public AppletFormatAdapter(AlignmentViewPanel alignPanel,
104           AlignExportSettingsI settings)
105   {
106     viewpanel = alignPanel;
107     exportSettings = settings;
108   }
109
110   /**
111    * Formats a grammatically correct(ish) list consisting of the given objects
112    * 
113    * @param things
114    * @return
115    */
116   public static String prettyPrint(List<? extends Object> things)
117   {
118     StringBuffer list = new StringBuffer();
119     for (int i = 0, iSize = things.size() - 1; i < iSize; i++)
120     {
121       list.append(things.get(i).toString());
122       list.append(", ");
123     }
124     // could i18n 'and' here
125     list.append(" and " + things.get(things.size() - 1).toString() + ".");
126     return list.toString();
127   }
128
129   public void setNewlineString(String nl)
130   {
131     newline = nl;
132   }
133
134   public String getNewlineString()
135   {
136     return newline;
137   }
138
139   /**
140    * Constructs the correct filetype parser for a characterised datasource
141    *
142    * @param inFile
143    *          data/data location
144    * @param sourceType
145    *          type of datasource
146    * @param fileFormat
147    *
148    * @return
149    */
150   public AlignmentI readFile(String file, DataSourceType sourceType,
151           FileFormatI fileFormat) throws IOException
152   {
153     return readFile(null, file, sourceType, fileFormat);
154   }
155   
156   public AlignmentI readFile(File selectedFile, String file, DataSourceType sourceType,
157           FileFormatI fileFormat) throws IOException
158   {
159
160     this.selectedFile = selectedFile;
161     inFile = (selectedFile == null ? file : selectedFile.getPath());
162     try
163     {
164       if (fileFormat.isStructureFile())
165       {
166         String structureParser = StructureImportSettings
167                 .getDefaultPDBFileParser();
168         boolean isParseWithJMOL = structureParser.equalsIgnoreCase(
169                 StructureImportSettings.StructureParser.JMOL_PARSER
170                         .toString());
171         StructureImportSettings.addSettings(annotFromStructure,
172                 localSecondaryStruct, serviceSecondaryStruct);
173         if (isParseWithJMOL)
174         {
175           // needs a File option
176           alignFile = new JmolParser(selectedFile == null ? inFile : selectedFile, sourceType);
177         }
178         else
179         {
180           // todo is mc_view parsing obsolete yet? JAL-2120
181           StructureImportSettings.setShowSeqFeatures(true);
182           alignFile = new mc_view.PDBfile(annotFromStructure,
183                   localSecondaryStruct, serviceSecondaryStruct, inFile,
184                   sourceType);
185         }
186         ((StructureFile) alignFile).setDbRefType(
187                 FileFormat.PDB.equals(fileFormat) ? Type.PDB : Type.MMCIF);
188       }
189       else if (selectedFile != null) {
190         alignFile = fileFormat.getReader(new FileParse(selectedFile, sourceType));
191       } else 
192       {
193         // alignFile = fileFormat.getAlignmentFile(inFile, sourceType);
194         alignFile = fileFormat.getReader(new FileParse(inFile, sourceType));
195       }
196       return buildAlignmentFromFile();
197     } catch (Exception e)
198     {
199       e.printStackTrace();
200       System.err.println("Failed to read alignment using the '" + fileFormat
201               + "' reader.\n" + e);
202
203       if (e.getMessage() != null
204               && e.getMessage().startsWith(INVALID_CHARACTERS))
205       {
206         throw new IOException(e.getMessage());
207       }
208
209       // Finally test if the user has pasted just the sequence, no id
210       if (sourceType == DataSourceType.PASTE)
211       {
212         try
213         {
214           // Possible sequence is just residues with no label
215           alignFile = new FastaFile(">UNKNOWN\n" + inFile,
216                   DataSourceType.PASTE);
217           return buildAlignmentFromFile();
218
219         } catch (Exception ex)
220         {
221           if (ex.toString().startsWith(INVALID_CHARACTERS))
222           {
223             throw new IOException(e.getMessage());
224           }
225
226           ex.printStackTrace();
227         }
228       }
229       if (FileFormat.Html.equals(fileFormat))
230       {
231         throw new IOException(e.getMessage());
232       }
233     }
234     throw new FileFormatException(getSupportedFormats());
235   }
236
237   /**
238    * Constructs the correct filetype parser for an already open datasource
239    *
240    * @param source
241    *          an existing datasource
242    * @param format
243    *          File format of data that will be provided by datasource
244    *
245    * @return
246    */
247   public AlignmentI readFromFile(FileParse source, FileFormatI format)
248           throws IOException
249   {
250     this.inFile = source.getInFile();
251     DataSourceType type = source.dataSourceType;
252     try
253     {
254       if (FileFormat.PDB.equals(format) || FileFormat.MMCif.equals(format))
255       {
256         // TODO obtain config value from preference settings
257         boolean isParseWithJMOL = false;
258         if (isParseWithJMOL)
259         {
260           StructureImportSettings.addSettings(annotFromStructure,
261                   localSecondaryStruct, serviceSecondaryStruct);
262           alignFile = new JmolParser(source);
263         }
264         else
265         {
266           StructureImportSettings.setShowSeqFeatures(true);
267           alignFile = new mc_view.PDBfile(annotFromStructure,
268                   localSecondaryStruct, serviceSecondaryStruct, source);
269         }
270         ((StructureFile) alignFile).setDbRefType(Type.PDB);
271       }
272       else
273       {
274         alignFile = format.getReader(source);
275       }
276
277       return buildAlignmentFromFile();
278
279     } catch (Exception e)
280     {
281       e.printStackTrace();
282       System.err.println("Failed to read alignment using the '" + format
283               + "' reader.\n" + e);
284
285       if (e.getMessage() != null
286               && e.getMessage().startsWith(INVALID_CHARACTERS))
287       {
288         throw new FileFormatException(e.getMessage());
289       }
290
291       // Finally test if the user has pasted just the sequence, no id
292       if (type == DataSourceType.PASTE)
293       {
294         try
295         {
296           // Possible sequence is just residues with no label
297           alignFile = new FastaFile(">UNKNOWN\n" + inFile,
298                   DataSourceType.PASTE);
299           return buildAlignmentFromFile();
300
301         } catch (Exception ex)
302         {
303           if (ex.toString().startsWith(INVALID_CHARACTERS))
304           {
305             throw new IOException(e.getMessage());
306           }
307
308           ex.printStackTrace();
309         }
310       }
311
312       // If we get to this stage, the format was not supported
313       throw new FileFormatException(getSupportedFormats());
314     }
315   }
316
317   /**
318    * boilerplate method to handle data from an AlignFile and construct a new
319    * alignment or import to an existing alignment
320    * 
321    * @return AlignmentI instance ready to pass to a UI constructor
322    */
323   private AlignmentI buildAlignmentFromFile()
324   {
325     // Standard boilerplate for creating alignment from parser
326     // alignFile.configureForView(viewpanel);
327
328     AlignmentI al = new Alignment(alignFile.getSeqsAsArray());
329
330     alignFile.addAnnotations(al);
331
332     alignFile.addGroups(al);
333
334     return al;
335   }
336
337   /**
338    * create an alignment flatfile from a Jalview alignment view
339    * 
340    * @param format
341    * @param jvsuffix
342    * @param av
343    * @param selectedOnly
344    * @return flatfile in a string
345    */
346   public String formatSequences(FileFormatI format, boolean jvsuffix,
347           AlignmentViewPanel ap, boolean selectedOnly)
348   {
349
350     AlignmentView selvew = ap.getAlignViewport()
351             .getAlignmentView(selectedOnly, false);
352     AlignmentI aselview = selvew
353             .getVisibleAlignment(ap.getAlignViewport().getGapCharacter());
354     List<AlignmentAnnotation> ala = (ap.getAlignViewport()
355             .getVisibleAlignmentAnnotation(selectedOnly));
356     if (ala != null)
357     {
358       for (AlignmentAnnotation aa : ala)
359       {
360         aselview.addAnnotation(aa);
361       }
362     }
363     viewpanel = ap;
364     return formatSequences(format, aselview, jvsuffix);
365   }
366
367   /**
368    * Construct an output class for an alignment in a particular filetype TODO:
369    * allow caller to detect errors and warnings encountered when generating
370    * output
371    *
372    * @param format
373    *          string name of alignment format
374    * @param alignment
375    *          the alignment to be written out
376    * @param jvsuffix
377    *          passed to AlnFile class controls whether /START-END is added to
378    *          sequence names
379    *
380    * @return alignment flat file contents
381    */
382   public String formatSequences(FileFormatI format, AlignmentI alignment,
383           boolean jvsuffix)
384   {
385     try
386     {
387       AlignmentFileWriterI afile = format.getWriter(alignment);
388
389       afile.setNewlineString(newline);
390       afile.setExportSettings(exportSettings);
391       afile.configureForView(viewpanel);
392
393       // check whether we were given a specific alignment to export, rather than
394       // the one in the viewpanel
395       SequenceI[] seqs = null;
396       if (viewpanel == null || viewpanel.getAlignment() == null
397               || viewpanel.getAlignment() != alignment)
398       {
399         seqs = alignment.getSequencesArray();
400       }
401       else
402       {
403         seqs = viewpanel.getAlignment().getSequencesArray();
404       }
405
406       String afileresp = afile.print(seqs, jvsuffix);
407       if (afile.hasWarningMessage())
408       {
409         System.err.println("Warning raised when writing as " + format
410                 + " : " + afile.getWarningMessage());
411       }
412       return afileresp;
413     } catch (Exception e)
414     {
415       System.err.println("Failed to write alignment as a '"
416               + format.getName() + "' file\n");
417       e.printStackTrace();
418     }
419
420     return null;
421   }
422
423
424   /**
425    * Determines the protocol (i.e DataSourceType.{FILE|PASTE|URL}) for the input
426    * data
427    * 
428    * BH 2018 allows File or String, and can return RELATIVE_URL
429    *
430    * @param dataObject File or String
431    * @return the protocol for the input data
432    */
433   public static DataSourceType checkProtocol(Object dataObject)
434   {
435     if(dataObject instanceof File)
436     {
437       return DataSourceType.FILE;
438     }
439     
440     String data = dataObject.toString();
441     DataSourceType protocol = DataSourceType.PASTE;
442     String ft = data.toLowerCase().trim();
443     if (ft.indexOf("http:") == 0 || ft.indexOf("https:") == 0
444             || ft.indexOf("file:") == 0)
445     {
446       protocol = DataSourceType.URL;
447     }
448     else if (Platform.isJS())
449     {
450       protocol = DataSourceType.RELATIVE_URL;
451     }
452     else if (new File(data).exists())
453     {
454       protocol = DataSourceType.FILE;
455     }
456     return protocol;
457   }
458
459   /**
460    * @param args
461    * @j2sIgnore
462    */
463   public static void main(String[] args)
464   {
465     int i = 0;
466     while (i < args.length)
467     {
468       File f = new File(args[i]);
469       if (f.exists())
470       {
471         try
472         {
473           System.out.println("Reading file: " + f);
474           AppletFormatAdapter afa = new AppletFormatAdapter();
475           Runtime r = Runtime.getRuntime();
476           System.gc();
477           long memf = -r.totalMemory() + r.freeMemory();
478           long t1 = -System.currentTimeMillis();
479           AlignmentI al = afa.readFile(args[i], DataSourceType.FILE,
480                   new IdentifyFile().identify(args[i],
481                           DataSourceType.FILE));
482           t1 += System.currentTimeMillis();
483           System.gc();
484           memf += r.totalMemory() - r.freeMemory();
485           if (al != null)
486           {
487             System.out.println("Alignment contains " + al.getHeight()
488                     + " sequences and " + al.getWidth() + " columns.");
489             try
490             {
491               System.out.println(new AppletFormatAdapter()
492                       .formatSequences(FileFormat.Fasta, al, true));
493             } catch (Exception e)
494             {
495               System.err.println(
496                       "Couln't format the alignment for output as a FASTA file.");
497               e.printStackTrace(System.err);
498             }
499           }
500           else
501           {
502             System.out.println("Couldn't read alignment");
503           }
504           System.out.println("Read took " + (t1 / 1000.0) + " seconds.");
505           System.out.println(
506                   "Difference between free memory now and before is "
507                           + (memf / (1024.0 * 1024.0) * 1.0) + " MB");
508         } catch (Exception e)
509         {
510           System.err.println("Exception when dealing with " + i
511                   + "'th argument: " + args[i] + "\n" + e);
512         }
513       }
514       else
515       {
516         System.err.println("Ignoring argument '" + args[i] + "' (" + i
517                 + "'th)- not a readable file.");
518       }
519       i++;
520     }
521   }
522
523   /**
524    * try to discover how to access the given file as a valid datasource that
525    * will be identified as the given type.
526    *
527    * @param file
528    * @param format
529    * @return protocol that yields the data parsable as the given type
530    */
531   public static DataSourceType resolveProtocol(String file,
532           FileFormatI format)
533   {
534     return resolveProtocol(file, format, false);
535   }
536
537   public static DataSourceType resolveProtocol(String file,
538           FileFormatI format, boolean debug)
539   {
540     // TODO: test thoroughly!
541     DataSourceType protocol = null;
542     if (debug)
543     {
544       System.out.println("resolving datasource started with:\n>>file\n"
545               + file + ">>endfile");
546     }
547
548     // This might throw a security exception in certain browsers
549     // Netscape Communicator for instance.
550     try
551     {
552       boolean rtn = false;
553       InputStream is = System.getSecurityManager().getClass()
554               .getResourceAsStream("/" + file);
555       if (is != null)
556       {
557         rtn = true;
558         is.close();
559       }
560       if (debug)
561       {
562         System.err.println("Resource '" + file + "' was "
563                 + (rtn ? "" : "not") + " located by classloader.");
564       }
565       if (rtn)
566       {
567         protocol = DataSourceType.CLASSLOADER;
568       }
569
570     } catch (Exception ex)
571     {
572       System.err
573               .println("Exception checking resources: " + file + " " + ex);
574     }
575
576     if (file.indexOf("://") > -1)
577     {
578       protocol = DataSourceType.URL;
579     }
580     else
581     {
582       // skipping codebase prepend check.
583       protocol = DataSourceType.FILE;
584     }
585     FileParse fp = null;
586     try
587     {
588       if (debug)
589       {
590         System.out.println(
591                 "Trying to get contents of resource as " + protocol + ":");
592       }
593       fp = new FileParse(file, protocol);
594       if (!fp.isValid())
595       {
596         fp = null;
597       }
598       else
599       {
600         if (debug)
601         {
602           System.out.println("Successful.");
603         }
604       }
605     } catch (Exception e)
606     {
607       if (debug)
608       {
609         System.err.println("Exception when accessing content: " + e);
610       }
611       fp = null;
612     }
613     if (fp == null)
614     {
615       if (debug)
616       {
617         System.out.println("Accessing as paste.");
618       }
619       protocol = DataSourceType.PASTE;
620       fp = null;
621       try
622       {
623         fp = new FileParse(file, protocol);
624         if (!fp.isValid())
625         {
626           fp = null;
627         }
628       } catch (Exception e)
629       {
630         System.err.println("Failed to access content as paste!");
631         e.printStackTrace();
632         fp = null;
633       }
634     }
635     if (fp == null)
636     {
637       return null;
638     }
639     if (format == null)
640     {
641       return protocol;
642     }
643     else
644     {
645       try
646       {
647         FileFormatI idformat = new IdentifyFile().identify(file, protocol);
648         if (idformat == null)
649         {
650           if (debug)
651           {
652             System.out.println("Format not identified. Inaccessible file.");
653           }
654           return null;
655         }
656         if (debug)
657         {
658           System.out.println("Format identified as " + idformat
659                   + "and expected as " + format);
660         }
661         if (idformat.equals(format))
662         {
663           if (debug)
664           {
665             System.out.println("Protocol identified as " + protocol);
666           }
667           return protocol;
668         }
669         else
670         {
671           if (debug)
672           {
673             System.out
674                     .println("File deemed not accessible via " + protocol);
675           }
676           fp.close();
677           return null;
678         }
679       } catch (Exception e)
680       {
681         if (debug)
682         {
683           System.err.println("File deemed not accessible via " + protocol);
684           e.printStackTrace();
685         }
686       }
687     }
688     return null;
689   }
690
691   public AlignmentFileReaderI getAlignFile()
692   {
693     return alignFile;
694   }
695 }