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