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