JAL-2344 separated AlignmentFileI into AlignmentFileReaderI,
[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 AlignmentFileReaderI 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   public static String INVALID_CHARACTERS = "Contains invalid characters";
79
80   /**
81    * Returns an error message with a list of supported readable file formats
82    * 
83    * @return
84    */
85   public static String getSupportedFormats()
86   {
87     return "Formats currently supported are\n"
88           + prettyPrint(FileFormats.getInstance().getReadableFormats());
89   }
90   public AppletFormatAdapter()
91   {
92   }
93
94   public AppletFormatAdapter(AlignmentViewPanel viewpanel)
95   {
96     this.viewpanel = viewpanel;
97   }
98
99   public AppletFormatAdapter(AlignmentViewPanel alignPanel,
100           AlignExportSettingI settings)
101   {
102     viewpanel = alignPanel;
103     exportSettings = settings;
104   }
105
106   /**
107    * Formats a grammatically correct(ish) list consisting of the given objects
108    * 
109    * @param things
110    * @return
111    */
112   public static String prettyPrint(List<? extends Object> things)
113   {
114     StringBuffer list = new StringBuffer();
115     for (int i = 0, iSize = things.size() - 1; i < iSize; i++)
116     {
117       list.append(things.get(i).toString());
118       list.append(", ");
119     }
120     // could i18n 'and' here
121     list.append(" and " + things.get(things.size() - 1).toString() + ".");
122     return list.toString();
123   }
124
125   public void setNewlineString(String nl)
126   {
127     newline = nl;
128   }
129
130   public String getNewlineString()
131   {
132     return newline;
133   }
134
135   /**
136    * Constructs the correct filetype parser for a characterised datasource
137    *
138    * @param inFile
139    *          data/data location
140    * @param sourceType
141    *          type of datasource
142    * @param fileFormat
143    *
144    * @return
145    */
146   public AlignmentI readFile(String file, DataSourceType sourceType,
147           FileFormatI fileFormat) throws IOException
148   {
149     this.inFile = file;
150     try
151     {
152       if (fileFormat.isStructureFile())
153       {
154         String structureParser = StructureImportSettings
155                 .getDefaultPDBFileParser();
156         boolean isParseWithJMOL = structureParser.equalsIgnoreCase(
157                         StructureImportSettings.StructureParser.JMOL_PARSER
158                                 .toString());
159         StructureImportSettings.addSettings(annotFromStructure,
160                 localSecondaryStruct, serviceSecondaryStruct);
161         if (isParseWithJMOL)
162         {
163           alignFile = new JmolParser(inFile, sourceType);
164         }
165         else
166         {
167           // todo is MCview parsing obsolete yet? JAL-2120
168           StructureImportSettings.setShowSeqFeatures(true);
169           alignFile = new MCview.PDBfile(annotFromStructure,
170                   localSecondaryStruct, serviceSecondaryStruct, inFile,
171                   sourceType);
172         }
173         ((StructureFile) alignFile).setDbRefType(FileFormat.PDB
174                 .equals(fileFormat) ? Type.PDB : Type.MMCIF);
175       }
176       else
177       {
178         // alignFile = fileFormat.getAlignmentFile(inFile, sourceType);
179         alignFile = fileFormat.getReader(new FileParse(inFile,
180                 sourceType));
181       }
182       return buildAlignmentFromFile();
183     } catch (Exception e)
184     {
185       e.printStackTrace();
186       System.err.println("Failed to read alignment using the '"
187               + fileFormat + "' reader.\n" + e);
188
189       if (e.getMessage() != null
190               && e.getMessage().startsWith(INVALID_CHARACTERS))
191       {
192         throw new IOException(e.getMessage());
193       }
194
195       // Finally test if the user has pasted just the sequence, no id
196       if (sourceType == DataSourceType.PASTE)
197       {
198         try
199         {
200           // Possible sequence is just residues with no label
201           alignFile = new FastaFile(">UNKNOWN\n" + inFile,
202                   DataSourceType.PASTE);
203           return buildAlignmentFromFile();
204
205         } catch (Exception ex)
206         {
207           if (ex.toString().startsWith(INVALID_CHARACTERS))
208           {
209             throw new IOException(e.getMessage());
210           }
211
212           ex.printStackTrace();
213         }
214       }
215       if (FileFormat.Html.equals(fileFormat))
216       {
217         throw new IOException(e.getMessage());
218       }
219     }
220     throw new FileFormatException(getSupportedFormats());
221   }
222
223   /**
224    * Constructs the correct filetype parser for an already open datasource
225    *
226    * @param source
227    *          an existing datasource
228    * @param format
229    *          File format of data that will be provided by datasource
230    *
231    * @return
232    */
233   public AlignmentI readFromFile(FileParse source, FileFormatI format)
234           throws IOException
235   {
236     this.inFile = source.getInFile();
237     DataSourceType type = source.dataSourceType;
238     try
239     {
240       if (FileFormat.PDB.equals(format) || FileFormat.MMCif.equals(format))
241       {
242         // TODO obtain config value from preference settings
243         boolean isParseWithJMOL = false;
244         if (isParseWithJMOL)
245         {
246           StructureImportSettings.addSettings(annotFromStructure,
247                   localSecondaryStruct, serviceSecondaryStruct);
248           alignFile = new JmolParser(source);
249         }
250         else
251         {
252           StructureImportSettings.setShowSeqFeatures(true);
253           alignFile = new MCview.PDBfile(annotFromStructure,
254                   localSecondaryStruct, serviceSecondaryStruct, source);
255         }
256         ((StructureFile) alignFile).setDbRefType(Type.PDB);
257       }
258       else
259       {
260         alignFile = format.getReader(source);
261       }
262
263       return buildAlignmentFromFile();
264
265     } catch (Exception e)
266     {
267       e.printStackTrace();
268       System.err.println("Failed to read alignment using the '" + format
269               + "' reader.\n" + e);
270
271       if (e.getMessage() != null
272               && e.getMessage().startsWith(INVALID_CHARACTERS))
273       {
274         throw new FileFormatException(e.getMessage());
275       }
276
277       // Finally test if the user has pasted just the sequence, no id
278       if (type == DataSourceType.PASTE)
279       {
280         try
281         {
282           // Possible sequence is just residues with no label
283           alignFile = new FastaFile(">UNKNOWN\n" + inFile,
284                   DataSourceType.PASTE);
285           return buildAlignmentFromFile();
286
287         } catch (Exception ex)
288         {
289           if (ex.toString().startsWith(INVALID_CHARACTERS))
290           {
291             throw new IOException(e.getMessage());
292           }
293
294           ex.printStackTrace();
295         }
296       }
297
298       // If we get to this stage, the format was not supported
299       throw new FileFormatException(getSupportedFormats());
300     }
301   }
302
303   /**
304    * boilerplate method to handle data from an AlignFile and construct a new
305    * alignment or import to an existing alignment
306    * 
307    * @return AlignmentI instance ready to pass to a UI constructor
308    */
309   private AlignmentI buildAlignmentFromFile()
310   {
311     // Standard boilerplate for creating alignment from parser
312     // alignFile.configureForView(viewpanel);
313
314     AlignmentI al = new Alignment(alignFile.getSeqsAsArray());
315
316     alignFile.addAnnotations(al);
317
318     alignFile.addGroups(al);
319
320     return al;
321   }
322
323   /**
324    * create an alignment flatfile from a Jalview alignment view
325    * 
326    * @param format
327    * @param jvsuffix
328    * @param av
329    * @param selectedOnly
330    * @return flatfile in a string
331    */
332   public String formatSequences(FileFormatI format, boolean jvsuffix,
333           AlignmentViewPanel ap, boolean selectedOnly)
334   {
335
336     AlignmentView selvew = ap.getAlignViewport().getAlignmentView(
337             selectedOnly, false);
338     AlignmentI aselview = selvew.getVisibleAlignment(ap.getAlignViewport()
339             .getGapCharacter());
340     List<AlignmentAnnotation> ala = (ap.getAlignViewport()
341             .getVisibleAlignmentAnnotation(selectedOnly));
342     if (ala != null)
343     {
344       for (AlignmentAnnotation aa : ala)
345       {
346         aselview.addAnnotation(aa);
347       }
348     }
349     viewpanel = ap;
350     return formatSequences(format, aselview, jvsuffix);
351   }
352
353   /**
354    * Construct an output class for an alignment in a particular filetype TODO:
355    * allow caller to detect errors and warnings encountered when generating
356    * output
357    *
358    * @param format
359    *          string name of alignment format
360    * @param alignment
361    *          the alignment to be written out
362    * @param jvsuffix
363    *          passed to AlnFile class controls whether /START-END is added to
364    *          sequence names
365    *
366    * @return alignment flat file contents
367    */
368   public String formatSequences(FileFormatI format, AlignmentI alignment,
369           boolean jvsuffix)
370   {
371     try
372     {
373       AlignmentFileWriterI afile = format.getWriter(alignment);
374
375       afile.setNewlineString(newline);
376       afile.setExportSettings(exportSettings);
377       afile.configureForView(viewpanel);
378
379       // check whether we were given a specific alignment to export, rather than
380       // the one in the viewpanel
381       SequenceI[] seqs = null;
382       if (viewpanel == null || viewpanel.getAlignment() == null
383               || viewpanel.getAlignment() != alignment)
384       {
385         seqs = alignment.getSequencesArray();
386       }
387       else
388       {
389         seqs = viewpanel.getAlignment().getSequencesArray();
390       }
391
392       String afileresp = afile.print(seqs, jvsuffix);
393       if (afile.hasWarningMessage())
394       {
395         System.err.println("Warning raised when writing as " + format
396                 + " : " + afile.getWarningMessage());
397       }
398       return afileresp;
399     } catch (Exception e)
400     {
401       System.err.println("Failed to write alignment as a '"
402               + format.getName()
403               + "' file\n");
404       e.printStackTrace();
405     }
406
407     return null;
408   }
409
410   public static DataSourceType checkProtocol(String file)
411   {
412     DataSourceType protocol = DataSourceType.FILE;
413     String ft = file.toLowerCase().trim();
414     if (ft.indexOf("http:") == 0 || ft.indexOf("https:") == 0
415             || ft.indexOf("file:") == 0)
416     {
417       protocol = DataSourceType.URL;
418     }
419     return protocol;
420   }
421
422   public static void main(String[] args)
423   {
424     int i = 0;
425     while (i < args.length)
426     {
427       File f = new File(args[i]);
428       if (f.exists())
429       {
430         try
431         {
432           System.out.println("Reading file: " + f);
433           AppletFormatAdapter afa = new AppletFormatAdapter();
434           Runtime r = Runtime.getRuntime();
435           System.gc();
436           long memf = -r.totalMemory() + r.freeMemory();
437           long t1 = -System.currentTimeMillis();
438           AlignmentI al = afa
439                   .readFile(args[i], DataSourceType.FILE,
440                           new IdentifyFile().identify(args[i],
441                                   DataSourceType.FILE));
442           t1 += System.currentTimeMillis();
443           System.gc();
444           memf += r.totalMemory() - r.freeMemory();
445           if (al != null)
446           {
447             System.out.println("Alignment contains " + al.getHeight()
448                     + " sequences and " + al.getWidth() + " columns.");
449             try
450             {
451               System.out.println(new AppletFormatAdapter().formatSequences(
452                       FileFormat.Fasta, al, true));
453             } catch (Exception e)
454             {
455               System.err
456                       .println("Couln't format the alignment for output as a FASTA file.");
457               e.printStackTrace(System.err);
458             }
459           }
460           else
461           {
462             System.out.println("Couldn't read alignment");
463           }
464           System.out.println("Read took " + (t1 / 1000.0) + " seconds.");
465           System.out
466                   .println("Difference between free memory now and before is "
467                           + (memf / (1024.0 * 1024.0) * 1.0) + " MB");
468         } catch (Exception e)
469         {
470           System.err.println("Exception when dealing with " + i
471                   + "'th argument: " + args[i] + "\n" + e);
472         }
473       }
474       else
475       {
476         System.err.println("Ignoring argument '" + args[i] + "' (" + i
477                 + "'th)- not a readable file.");
478       }
479       i++;
480     }
481   }
482
483   /**
484    * try to discover how to access the given file as a valid datasource that
485    * will be identified as the given type.
486    *
487    * @param file
488    * @param format
489    * @return protocol that yields the data parsable as the given type
490    */
491   public static DataSourceType resolveProtocol(String file,
492           FileFormatI format)
493   {
494     return resolveProtocol(file, format, false);
495   }
496
497   public static DataSourceType resolveProtocol(String file,
498           FileFormatI format, boolean debug)
499   {
500     // TODO: test thoroughly!
501     DataSourceType protocol = null;
502     if (debug)
503     {
504       System.out.println("resolving datasource started with:\n>>file\n"
505               + file + ">>endfile");
506     }
507
508     // This might throw a security exception in certain browsers
509     // Netscape Communicator for instance.
510     try
511     {
512       boolean rtn = false;
513       InputStream is = System.getSecurityManager().getClass()
514               .getResourceAsStream("/" + file);
515       if (is != null)
516       {
517         rtn = true;
518         is.close();
519       }
520       if (debug)
521       {
522         System.err.println("Resource '" + file + "' was "
523                 + (rtn ? "" : "not") + " located by classloader.");
524       }
525       if (rtn)
526       {
527         protocol = DataSourceType.CLASSLOADER;
528       }
529
530     } catch (Exception ex)
531     {
532       System.err
533               .println("Exception checking resources: " + file + " " + ex);
534     }
535
536     if (file.indexOf("://") > -1)
537     {
538       protocol = DataSourceType.URL;
539     }
540     else
541     {
542       // skipping codebase prepend check.
543       protocol = DataSourceType.FILE;
544     }
545     FileParse fp = null;
546     try
547     {
548       if (debug)
549       {
550         System.out.println("Trying to get contents of resource as "
551                 + protocol + ":");
552       }
553       fp = new FileParse(file, protocol);
554       if (!fp.isValid())
555       {
556         fp = null;
557       }
558       else
559       {
560         if (debug)
561         {
562           System.out.println("Successful.");
563         }
564       }
565     } catch (Exception e)
566     {
567       if (debug)
568       {
569         System.err.println("Exception when accessing content: " + e);
570       }
571       fp = null;
572     }
573     if (fp == null)
574     {
575       if (debug)
576       {
577         System.out.println("Accessing as paste.");
578       }
579       protocol = DataSourceType.PASTE;
580       fp = null;
581       try
582       {
583         fp = new FileParse(file, protocol);
584         if (!fp.isValid())
585         {
586           fp = null;
587         }
588       } catch (Exception e)
589       {
590         System.err.println("Failed to access content as paste!");
591         e.printStackTrace();
592         fp = null;
593       }
594     }
595     if (fp == null)
596     {
597       return null;
598     }
599     if (format == null)
600     {
601       return protocol;
602     }
603     else
604     {
605       try
606       {
607         FileFormatI idformat = new IdentifyFile().identify(file, protocol);
608         if (idformat == null)
609         {
610           if (debug)
611           {
612             System.out.println("Format not identified. Inaccessible file.");
613           }
614           return null;
615         }
616         if (debug)
617         {
618           System.out.println("Format identified as " + idformat
619                   + "and expected as " + format);
620         }
621         if (idformat.equals(format))
622         {
623           if (debug)
624           {
625             System.out.println("Protocol identified as " + protocol);
626           }
627           return protocol;
628         }
629         else
630         {
631           if (debug)
632           {
633             System.out
634                     .println("File deemed not accessible via " + protocol);
635           }
636           fp.close();
637           return null;
638         }
639       } catch (Exception e)
640       {
641         if (debug)
642         {
643           System.err.println("File deemed not accessible via " + protocol);
644           e.printStackTrace();
645         }
646       }
647     }
648     return null;
649   }
650
651   public AlignmentFileReaderI getAlignFile()
652   {
653     return alignFile;
654   }
655 }