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