JAL-2446 merged to spike branch
[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   /**
411    * Determines the protocol (i.e DataSourceType.{FILE|PASTE|URL}) for the input
412    * data
413    *
414    * @param data
415    * @return the protocol for the input data
416    */
417   public static DataSourceType checkProtocol(String data)
418   {
419     DataSourceType protocol = DataSourceType.PASTE;
420     String ft = data.toLowerCase().trim();
421     if (ft.indexOf("http:") == 0 || ft.indexOf("https:") == 0
422             || ft.indexOf("file:") == 0)
423     {
424       protocol = DataSourceType.URL;
425     }
426     else if (new File(data).exists())
427     {
428       protocol = DataSourceType.FILE;
429     }
430     return protocol;
431   }
432
433   public static void main(String[] args)
434   {
435     int i = 0;
436     while (i < args.length)
437     {
438       File f = new File(args[i]);
439       if (f.exists())
440       {
441         try
442         {
443           System.out.println("Reading file: " + f);
444           AppletFormatAdapter afa = new AppletFormatAdapter();
445           Runtime r = Runtime.getRuntime();
446           System.gc();
447           long memf = -r.totalMemory() + r.freeMemory();
448           long t1 = -System.currentTimeMillis();
449           AlignmentI al = afa
450                   .readFile(args[i], DataSourceType.FILE,
451                           new IdentifyFile().identify(args[i],
452                                   DataSourceType.FILE));
453           t1 += System.currentTimeMillis();
454           System.gc();
455           memf += r.totalMemory() - r.freeMemory();
456           if (al != null)
457           {
458             System.out.println("Alignment contains " + al.getHeight()
459                     + " sequences and " + al.getWidth() + " columns.");
460             try
461             {
462               System.out.println(new AppletFormatAdapter().formatSequences(
463                       FileFormat.Fasta, al, true));
464             } catch (Exception e)
465             {
466               System.err
467                       .println("Couln't format the alignment for output as a FASTA file.");
468               e.printStackTrace(System.err);
469             }
470           }
471           else
472           {
473             System.out.println("Couldn't read alignment");
474           }
475           System.out.println("Read took " + (t1 / 1000.0) + " seconds.");
476           System.out
477                   .println("Difference between free memory now and before is "
478                           + (memf / (1024.0 * 1024.0) * 1.0) + " MB");
479         } catch (Exception e)
480         {
481           System.err.println("Exception when dealing with " + i
482                   + "'th argument: " + args[i] + "\n" + e);
483         }
484       }
485       else
486       {
487         System.err.println("Ignoring argument '" + args[i] + "' (" + i
488                 + "'th)- not a readable file.");
489       }
490       i++;
491     }
492   }
493
494   /**
495    * try to discover how to access the given file as a valid datasource that
496    * will be identified as the given type.
497    *
498    * @param file
499    * @param format
500    * @return protocol that yields the data parsable as the given type
501    */
502   public static DataSourceType resolveProtocol(String file,
503           FileFormatI format)
504   {
505     return resolveProtocol(file, format, false);
506   }
507
508   public static DataSourceType resolveProtocol(String file,
509           FileFormatI format, boolean debug)
510   {
511     // TODO: test thoroughly!
512     DataSourceType protocol = null;
513     if (debug)
514     {
515       System.out.println("resolving datasource started with:\n>>file\n"
516               + file + ">>endfile");
517     }
518
519     // This might throw a security exception in certain browsers
520     // Netscape Communicator for instance.
521     try
522     {
523       boolean rtn = false;
524       InputStream is = System.getSecurityManager().getClass()
525               .getResourceAsStream("/" + file);
526       if (is != null)
527       {
528         rtn = true;
529         is.close();
530       }
531       if (debug)
532       {
533         System.err.println("Resource '" + file + "' was "
534                 + (rtn ? "" : "not") + " located by classloader.");
535       }
536       if (rtn)
537       {
538         protocol = DataSourceType.CLASSLOADER;
539       }
540
541     } catch (Exception ex)
542     {
543       System.err
544               .println("Exception checking resources: " + file + " " + ex);
545     }
546
547     if (file.indexOf("://") > -1)
548     {
549       protocol = DataSourceType.URL;
550     }
551     else
552     {
553       // skipping codebase prepend check.
554       protocol = DataSourceType.FILE;
555     }
556     FileParse fp = null;
557     try
558     {
559       if (debug)
560       {
561         System.out.println("Trying to get contents of resource as "
562                 + protocol + ":");
563       }
564       fp = new FileParse(file, protocol);
565       if (!fp.isValid())
566       {
567         fp = null;
568       }
569       else
570       {
571         if (debug)
572         {
573           System.out.println("Successful.");
574         }
575       }
576     } catch (Exception e)
577     {
578       if (debug)
579       {
580         System.err.println("Exception when accessing content: " + e);
581       }
582       fp = null;
583     }
584     if (fp == null)
585     {
586       if (debug)
587       {
588         System.out.println("Accessing as paste.");
589       }
590       protocol = DataSourceType.PASTE;
591       fp = null;
592       try
593       {
594         fp = new FileParse(file, protocol);
595         if (!fp.isValid())
596         {
597           fp = null;
598         }
599       } catch (Exception e)
600       {
601         System.err.println("Failed to access content as paste!");
602         e.printStackTrace();
603         fp = null;
604       }
605     }
606     if (fp == null)
607     {
608       return null;
609     }
610     if (format == null)
611     {
612       return protocol;
613     }
614     else
615     {
616       try
617       {
618         FileFormatI idformat = new IdentifyFile().identify(file, protocol);
619         if (idformat == null)
620         {
621           if (debug)
622           {
623             System.out.println("Format not identified. Inaccessible file.");
624           }
625           return null;
626         }
627         if (debug)
628         {
629           System.out.println("Format identified as " + idformat
630                   + "and expected as " + format);
631         }
632         if (idformat.equals(format))
633         {
634           if (debug)
635           {
636             System.out.println("Protocol identified as " + protocol);
637           }
638           return protocol;
639         }
640         else
641         {
642           if (debug)
643           {
644             System.out
645                     .println("File deemed not accessible via " + protocol);
646           }
647           fp.close();
648           return null;
649         }
650       } catch (Exception e)
651       {
652         if (debug)
653         {
654           System.err.println("File deemed not accessible via " + protocol);
655           e.printStackTrace();
656         }
657       }
658     }
659     return null;
660   }
661
662   public AlignmentFileReaderI getAlignFile()
663   {
664     return alignFile;
665   }
666 }