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