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