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