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