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