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