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