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