Merge branch 'releases/Release_2_11_3_Branch'
[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       jalview.bin.Console.errPrintln("Failed to read alignment using the '"
219               + fileFormat + "' 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       jalview.bin.Console.errPrintln("Failed to read alignment using the '"
301               + format + "' 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         jalview.bin.Console.errPrintln("Warning raised when writing as "
428                 + format + " : " + afile.getWarningMessage());
429       }
430       return afileresp;
431     } catch (Exception e)
432     {
433       jalview.bin.Console.errPrintln("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           jalview.bin.Console.outPrintln("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             jalview.bin.Console.outPrintln("Alignment contains "
506                     + al.getHeight() + " sequences and " + al.getWidth()
507                     + " columns.");
508             try
509             {
510               jalview.bin.Console.outPrintln(new AppletFormatAdapter()
511                       .formatSequences(FileFormat.Fasta, al, true));
512             } catch (Exception e)
513             {
514               jalview.bin.Console.errPrintln(
515                       "Couln't format the alignment for output as a FASTA file.");
516               e.printStackTrace(System.err);
517             }
518           }
519           else
520           {
521             jalview.bin.Console.outPrintln("Couldn't read alignment");
522           }
523           jalview.bin.Console
524                   .outPrintln("Read took " + (t1 / 1000.0) + " seconds.");
525           jalview.bin.Console.outPrintln(
526                   "Difference between free memory now and before is "
527                           + (memf / (1024.0 * 1024.0) * 1.0) + " MB");
528         } catch (Exception e)
529         {
530           jalview.bin.Console.errPrintln("Exception when dealing with " + i
531                   + "'th argument: " + args[i] + "\n" + e);
532         }
533       }
534       else
535       {
536         jalview.bin.Console.errPrintln("Ignoring argument '" + args[i]
537                 + "' (" + i + "'th)- not a readable file.");
538       }
539       i++;
540     }
541   }
542
543   /**
544    * try to discover how to access the given file as a valid datasource that
545    * will be identified as the given type.
546    *
547    * @param file
548    * @param format
549    * @return protocol that yields the data parsable as the given type
550    */
551   public static DataSourceType resolveProtocol(String file,
552           FileFormatI format)
553   {
554     return resolveProtocol(file, format, false);
555   }
556
557   public static DataSourceType resolveProtocol(String file,
558           FileFormatI format, boolean debug)
559   {
560     // TODO: test thoroughly!
561     DataSourceType protocol = null;
562     if (debug)
563     {
564       jalview.bin.Console
565               .outPrintln("resolving datasource started with:\n>>file\n"
566                       + file + ">>endfile");
567     }
568
569     // This might throw a security exception in certain browsers
570     // Netscape Communicator for instance.
571     try
572     {
573       boolean rtn = false;
574       InputStream is = System.getSecurityManager().getClass()
575               .getResourceAsStream("/" + file);
576       if (is != null)
577       {
578         rtn = true;
579         is.close();
580       }
581       if (debug)
582       {
583         jalview.bin.Console.errPrintln("Resource '" + file + "' was "
584                 + (rtn ? "" : "not") + " located by classloader.");
585       }
586       if (rtn)
587       {
588         protocol = DataSourceType.CLASSLOADER;
589       }
590
591     } catch (Exception ex)
592     {
593       System.err
594               .println("Exception checking resources: " + file + " " + ex);
595     }
596
597     if (file.indexOf("://") > -1)
598     {
599       protocol = DataSourceType.URL;
600     }
601     else
602     {
603       // skipping codebase prepend check.
604       protocol = DataSourceType.FILE;
605     }
606     FileParse fp = null;
607     try
608     {
609       if (debug)
610       {
611         jalview.bin.Console.outPrintln(
612                 "Trying to get contents of resource as " + protocol + ":");
613       }
614       fp = new FileParse(file, protocol);
615       if (!fp.isValid())
616       {
617         fp = null;
618       }
619       else
620       {
621         if (debug)
622         {
623           jalview.bin.Console.outPrintln("Successful.");
624         }
625       }
626     } catch (Exception e)
627     {
628       if (debug)
629       {
630         jalview.bin.Console
631                 .errPrintln("Exception when accessing content: " + e);
632       }
633       fp = null;
634     }
635     if (fp == null)
636     {
637       if (debug)
638       {
639         jalview.bin.Console.outPrintln("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         jalview.bin.Console
653                 .errPrintln("Failed to access content as paste!");
654         e.printStackTrace();
655         fp = null;
656       }
657     }
658     if (fp == null)
659     {
660       return null;
661     }
662     if (format == null)
663     {
664       return protocol;
665     }
666     else
667     {
668       try
669       {
670         FileFormatI idformat = new IdentifyFile().identify(file, protocol);
671         if (idformat == null)
672         {
673           if (debug)
674           {
675             jalview.bin.Console.outPrintln(
676                     "Format not identified. Inaccessible file.");
677           }
678           return null;
679         }
680         if (debug)
681         {
682           jalview.bin.Console.outPrintln("Format identified as " + idformat
683                   + "and expected as " + format);
684         }
685         if (idformat.equals(format))
686         {
687           if (debug)
688           {
689             jalview.bin.Console
690                     .outPrintln("Protocol identified as " + protocol);
691           }
692           return protocol;
693         }
694         else
695         {
696           if (debug)
697           {
698             System.out
699                     .println("File deemed not accessible via " + protocol);
700           }
701           fp.close();
702           return null;
703         }
704       } catch (Exception e)
705       {
706         if (debug)
707         {
708           jalview.bin.Console
709                   .errPrintln("File deemed not accessible via " + protocol);
710           e.printStackTrace();
711         }
712       }
713     }
714     return null;
715   }
716
717   public AlignmentFileReaderI getAlignFile()
718   {
719     return alignFile;
720   }
721 }