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