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