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