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