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