JAL-4047 JAL-4948 show count of each dbref authority and 'first' reference
[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     this.inFile = selectedFile != null ? selectedFile.getPath() : file;
171     try
172     {
173       if (fileFormat.isStructureFile())
174       {
175         String structureParser = StructureImportSettings
176                 .getDefaultPDBFileParser();
177         boolean isParseWithJMOL = structureParser.equalsIgnoreCase(
178                 StructureImportSettings.StructureParser.JMOL_PARSER
179                         .toString());
180         StructureImportSettings.addSettings(annotFromStructure,
181                 localSecondaryStruct, serviceSecondaryStruct);
182         if (tempfacType != null)
183         {
184           StructureImportSettings.setTemperatureFactorType(tempfacType);
185         }
186         if (isParseWithJMOL)
187         {
188           // needs a File option
189           alignFile = new JmolParser(
190                   selectedFile == null ? inFile : selectedFile, sourceType,
191                   StructureImportSettings.getTemperatureFactorType());
192         }
193         else
194         {
195           // todo is mc_view parsing obsolete yet? JAL-2120
196           StructureImportSettings.setShowSeqFeatures(true);
197           alignFile = new mc_view.PDBfile(annotFromStructure,
198                   localSecondaryStruct, serviceSecondaryStruct, inFile,
199                   sourceType);
200         }
201         ((StructureFile) alignFile).setDbRefType(
202                 FileFormat.PDB.equals(fileFormat) ? Type.PDB : Type.MMCIF);
203       }
204       else if (selectedFile != null)
205       {
206         alignFile = fileFormat
207                 .getReader(new FileParse(selectedFile, sourceType));
208       }
209       else
210       {
211         // alignFile = fileFormat.getAlignmentFile(inFile, sourceType);
212         alignFile = fileFormat.getReader(new FileParse(inFile, sourceType));
213       }
214       return buildAlignmentFromFile();
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 buildAlignmentFromFile();
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    * Determines the protocol (i.e DataSourceType.{FILE|PASTE|URL}) for the input
443    * data
444    * 
445    * BH 2018 allows File or String, and can return RELATIVE_URL
446    *
447    * @param dataObject
448    *          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 }