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