Merge branch 'features/JAL-2349_matrixvis' into merge/develop_JAL-2340_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
92   public AppletFormatAdapter()
93   {
94   }
95
96   public AppletFormatAdapter(AlignmentViewPanel viewpanel)
97   {
98     this.viewpanel = viewpanel;
99   }
100
101   public AppletFormatAdapter(AlignmentViewPanel alignPanel,
102           AlignExportSettingI settings)
103   {
104     viewpanel = alignPanel;
105     exportSettings = settings;
106   }
107
108   /**
109    * Formats a grammatically correct(ish) list consisting of the given objects
110    * 
111    * @param things
112    * @return
113    */
114   public static String prettyPrint(List<? extends Object> things)
115   {
116     StringBuffer list = new StringBuffer();
117     for (int i = 0, iSize = things.size() - 1; i < iSize; i++)
118     {
119       list.append(things.get(i).toString());
120       list.append(", ");
121     }
122     // could i18n 'and' here
123     list.append(" and " + things.get(things.size() - 1).toString() + ".");
124     return list.toString();
125   }
126
127   public void setNewlineString(String nl)
128   {
129     newline = nl;
130   }
131
132   public String getNewlineString()
133   {
134     return newline;
135   }
136
137   /**
138    * Constructs the correct filetype parser for a characterised datasource
139    *
140    * @param inFile
141    *          data/data location
142    * @param sourceType
143    *          type of datasource
144    * @param fileFormat
145    *
146    * @return
147    */
148   public AlignmentI readFile(String file, DataSourceType sourceType,
149           FileFormatI fileFormat) throws IOException
150   {
151     this.inFile = file;
152     try
153     {
154       if (fileFormat.isStructureFile())
155       {
156         String structureParser = StructureImportSettings
157                 .getDefaultPDBFileParser();
158         boolean isParseWithJMOL = structureParser.equalsIgnoreCase(
159                 StructureImportSettings.StructureParser.JMOL_PARSER
160                         .toString());
161         StructureImportSettings.addSettings(annotFromStructure,
162                 localSecondaryStruct, serviceSecondaryStruct);
163         if (isParseWithJMOL)
164         {
165           alignFile = new JmolParser(inFile, sourceType);
166         }
167         else
168         {
169           // todo is MCview parsing obsolete yet? JAL-2120
170           StructureImportSettings.setShowSeqFeatures(true);
171           alignFile = new MCview.PDBfile(annotFromStructure,
172                   localSecondaryStruct, serviceSecondaryStruct, inFile,
173                   sourceType);
174         }
175         ((StructureFile) alignFile).setDbRefType(
176                 FileFormat.PDB.equals(fileFormat) ? Type.PDB : Type.MMCIF);
177       }
178       else
179       {
180         // alignFile = fileFormat.getAlignmentFile(inFile, sourceType);
181         alignFile = fileFormat.getReader(new FileParse(inFile, sourceType));
182       }
183       return buildAlignmentFromFile();
184     } catch (Exception e)
185     {
186       e.printStackTrace();
187       System.err.println("Failed to read alignment using the '" + fileFormat
188               + "' 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()
340             .getAlignmentView(selectedOnly, false);
341     AlignmentI aselview = selvew
342             .getVisibleAlignment(ap.getAlignViewport().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() + "' file\n");
406       e.printStackTrace();
407     }
408
409     return null;
410   }
411
412   /**
413    * Determines the protocol (i.e DataSourceType.{FILE|PASTE|URL}) for the input
414    * data
415    *
416    * @param data
417    * @return the protocol for the input data
418    */
419   public static DataSourceType checkProtocol(String data)
420   {
421     DataSourceType protocol = DataSourceType.PASTE;
422     String ft = data.toLowerCase().trim();
423     if (ft.indexOf("http:") == 0 || ft.indexOf("https:") == 0
424             || ft.indexOf("file:") == 0)
425     {
426       protocol = DataSourceType.URL;
427     }
428     else if (new File(data).exists())
429     {
430       protocol = DataSourceType.FILE;
431     }
432     return protocol;
433   }
434
435   public static void main(String[] args)
436   {
437     int i = 0;
438     while (i < args.length)
439     {
440       File f = new File(args[i]);
441       if (f.exists())
442       {
443         try
444         {
445           System.out.println("Reading file: " + f);
446           AppletFormatAdapter afa = new AppletFormatAdapter();
447           Runtime r = Runtime.getRuntime();
448           System.gc();
449           long memf = -r.totalMemory() + r.freeMemory();
450           long t1 = -System.currentTimeMillis();
451           AlignmentI al = afa.readFile(args[i], DataSourceType.FILE,
452                   new IdentifyFile().identify(args[i],
453                           DataSourceType.FILE));
454           t1 += System.currentTimeMillis();
455           System.gc();
456           memf += r.totalMemory() - r.freeMemory();
457           if (al != null)
458           {
459             System.out.println("Alignment contains " + al.getHeight()
460                     + " sequences and " + al.getWidth() + " columns.");
461             try
462             {
463               System.out.println(new AppletFormatAdapter()
464                       .formatSequences(FileFormat.Fasta, al, true));
465             } catch (Exception e)
466             {
467               System.err.println(
468                       "Couln't format the alignment for output as a FASTA file.");
469               e.printStackTrace(System.err);
470             }
471           }
472           else
473           {
474             System.out.println("Couldn't read alignment");
475           }
476           System.out.println("Read took " + (t1 / 1000.0) + " seconds.");
477           System.out.println(
478                   "Difference between free memory now and before is "
479                           + (memf / (1024.0 * 1024.0) * 1.0) + " MB");
480         } catch (Exception e)
481         {
482           System.err.println("Exception when dealing with " + i
483                   + "'th argument: " + args[i] + "\n" + e);
484         }
485       }
486       else
487       {
488         System.err.println("Ignoring argument '" + args[i] + "' (" + i
489                 + "'th)- not a readable file.");
490       }
491       i++;
492     }
493   }
494
495   /**
496    * try to discover how to access the given file as a valid datasource that
497    * will be identified as the given type.
498    *
499    * @param file
500    * @param format
501    * @return protocol that yields the data parsable as the given type
502    */
503   public static DataSourceType resolveProtocol(String file,
504           FileFormatI format)
505   {
506     return resolveProtocol(file, format, false);
507   }
508
509   public static DataSourceType resolveProtocol(String file,
510           FileFormatI format, boolean debug)
511   {
512     // TODO: test thoroughly!
513     DataSourceType protocol = null;
514     if (debug)
515     {
516       System.out.println("resolving datasource started with:\n>>file\n"
517               + file + ">>endfile");
518     }
519
520     // This might throw a security exception in certain browsers
521     // Netscape Communicator for instance.
522     try
523     {
524       boolean rtn = false;
525       InputStream is = System.getSecurityManager().getClass()
526               .getResourceAsStream("/" + file);
527       if (is != null)
528       {
529         rtn = true;
530         is.close();
531       }
532       if (debug)
533       {
534         System.err.println("Resource '" + file + "' was "
535                 + (rtn ? "" : "not") + " located by classloader.");
536       }
537       if (rtn)
538       {
539         protocol = DataSourceType.CLASSLOADER;
540       }
541
542     } catch (Exception ex)
543     {
544       System.err
545               .println("Exception checking resources: " + file + " " + ex);
546     }
547
548     if (file.indexOf("://") > -1)
549     {
550       protocol = DataSourceType.URL;
551     }
552     else
553     {
554       // skipping codebase prepend check.
555       protocol = DataSourceType.FILE;
556     }
557     FileParse fp = null;
558     try
559     {
560       if (debug)
561       {
562         System.out.println(
563                 "Trying to get contents of resource as " + protocol + ":");
564       }
565       fp = new FileParse(file, protocol);
566       if (!fp.isValid())
567       {
568         fp = null;
569       }
570       else
571       {
572         if (debug)
573         {
574           System.out.println("Successful.");
575         }
576       }
577     } catch (Exception e)
578     {
579       if (debug)
580       {
581         System.err.println("Exception when accessing content: " + e);
582       }
583       fp = null;
584     }
585     if (fp == null)
586     {
587       if (debug)
588       {
589         System.out.println("Accessing as paste.");
590       }
591       protocol = DataSourceType.PASTE;
592       fp = null;
593       try
594       {
595         fp = new FileParse(file, protocol);
596         if (!fp.isValid())
597         {
598           fp = null;
599         }
600       } catch (Exception e)
601       {
602         System.err.println("Failed to access content as paste!");
603         e.printStackTrace();
604         fp = null;
605       }
606     }
607     if (fp == null)
608     {
609       return null;
610     }
611     if (format == null)
612     {
613       return protocol;
614     }
615     else
616     {
617       try
618       {
619         FileFormatI idformat = new IdentifyFile().identify(file, protocol);
620         if (idformat == null)
621         {
622           if (debug)
623           {
624             System.out.println("Format not identified. Inaccessible file.");
625           }
626           return null;
627         }
628         if (debug)
629         {
630           System.out.println("Format identified as " + idformat
631                   + "and expected as " + format);
632         }
633         if (idformat.equals(format))
634         {
635           if (debug)
636           {
637             System.out.println("Protocol identified as " + protocol);
638           }
639           return protocol;
640         }
641         else
642         {
643           if (debug)
644           {
645             System.out
646                     .println("File deemed not accessible via " + protocol);
647           }
648           fp.close();
649           return null;
650         }
651       } catch (Exception e)
652       {
653         if (debug)
654         {
655           System.err.println("File deemed not accessible via " + protocol);
656           e.printStackTrace();
657         }
658       }
659     }
660     return null;
661   }
662
663   public AlignmentFileReaderI getAlignFile()
664   {
665     return alignFile;
666   }
667 }