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