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