Jalview.isJS() --> Platform.isJS(), DBRefEntry[] --> List<DBRefEntry>
[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 jalview.api.AlignExportSettingsI;
24 import jalview.api.AlignmentViewPanel;
25 import jalview.datamodel.Alignment;
26 import jalview.datamodel.AlignmentAnnotation;
27 import jalview.datamodel.AlignmentI;
28 import jalview.datamodel.AlignmentView;
29 import jalview.datamodel.PDBEntry.Type;
30 import jalview.datamodel.SequenceI;
31 import jalview.ext.jmol.JmolParser;
32 import jalview.structure.StructureImportSettings;
33 import jalview.util.Platform;
34
35 import java.io.File;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.util.List;
39
40 /**
41  * A low level class for alignment and feature IO with alignment formatting
42  * methods used by both applet and application for generating flat alignment
43  * files. It also holds the lists of magic format names that the applet and
44  * application will allow the user to read or write files with.
45  *
46  * @author $author$
47  * @version $Revision$
48  */
49 public class AppletFormatAdapter
50 {
51   private AlignmentViewPanel viewpanel;
52
53   /**
54    * add jalview-derived non-secondary structure annotation from PDB structure
55    */
56   boolean annotFromStructure = false;
57
58   /**
59    * add secondary structure from PDB data with built-in algorithms
60    */
61   boolean localSecondaryStruct = false;
62
63   /**
64    * process PDB data with web services
65    */
66   boolean serviceSecondaryStruct = false;
67
68   private AlignmentFileReaderI alignFile = null;
69
70   String inFile;
71
72   /**
73    * character used to write newlines
74    */
75   protected String newline = System.getProperty("line.separator");
76
77   private AlignExportSettingsI exportSettings;
78
79   private File selectedFile;
80
81   public static String INVALID_CHARACTERS = "Contains invalid characters";
82
83   /**
84    * Returns an error message with a list of supported readable file formats
85    * 
86    * @return
87    */
88   public static String getSupportedFormats()
89   {
90     return "Formats currently supported are\n"
91             + prettyPrint(FileFormats.getInstance().getReadableFormats());
92   }
93
94   public AppletFormatAdapter()
95   {
96   }
97
98   public AppletFormatAdapter(AlignmentViewPanel viewpanel)
99   {
100     this.viewpanel = viewpanel;
101   }
102
103   public AppletFormatAdapter(AlignmentViewPanel alignPanel,
104           AlignExportSettingsI settings)
105   {
106     viewpanel = alignPanel;
107     exportSettings = settings;
108   }
109
110   /**
111    * Formats a grammatically correct(ish) list consisting of the given objects
112    * 
113    * @param things
114    * @return
115    */
116   public static String prettyPrint(List<? extends Object> things)
117   {
118     StringBuffer list = new StringBuffer();
119     for (int i = 0, iSize = things.size() - 1; i < iSize; i++)
120     {
121       list.append(things.get(i).toString());
122       list.append(", ");
123     }
124     // could i18n 'and' here
125     list.append(" and " + things.get(things.size() - 1).toString() + ".");
126     return list.toString();
127   }
128
129   public void setNewlineString(String nl)
130   {
131     newline = nl;
132   }
133
134   public String getNewlineString()
135   {
136     return newline;
137   }
138
139   /**
140    * Constructs the correct filetype parser for a characterised datasource
141    *
142    * @param inFile
143    *          data/data location
144    * @param sourceType
145    *          type of datasource
146    * @param fileFormat
147    *
148    * @return
149    */
150   public AlignmentI readFile(String file, DataSourceType sourceType,
151           FileFormatI fileFormat) throws IOException
152   {
153     return readFile(null, file, sourceType, fileFormat);
154   }
155   
156   public AlignmentI readFile(File selectedFile, String file, DataSourceType sourceType,
157           FileFormatI fileFormat) throws IOException
158   {
159
160     this.selectedFile = selectedFile;
161     if (selectedFile != null)
162       this.inFile = selectedFile.getPath();
163     this.inFile = file;
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       return DataSourceType.FILE;
439     
440     String data = dataObject.toString();
441     DataSourceType protocol = DataSourceType.PASTE;
442     String ft = data.toLowerCase().trim();
443     if (ft.indexOf("http:") == 0 || ft.indexOf("https:") == 0
444             || ft.indexOf("file:") == 0)
445     {
446       protocol = DataSourceType.URL;
447     }
448     else if (Platform.isJS())
449     {
450       protocol = DataSourceType.RELATIVE_URL;
451     }
452     else if (new File(data).exists())
453     {
454       protocol = DataSourceType.FILE;
455     }
456     return protocol;
457   }
458
459   public static void main(String[] args)
460   {
461     int i = 0;
462     while (i < args.length)
463     {
464       File f = new File(args[i]);
465       if (f.exists())
466       {
467         try
468         {
469           System.out.println("Reading file: " + f);
470           AppletFormatAdapter afa = new AppletFormatAdapter();
471           Runtime r = Runtime.getRuntime();
472           System.gc();
473           long memf = -r.totalMemory() + r.freeMemory();
474           long t1 = -System.currentTimeMillis();
475           AlignmentI al = afa.readFile(args[i], DataSourceType.FILE,
476                   new IdentifyFile().identify(args[i],
477                           DataSourceType.FILE));
478           t1 += System.currentTimeMillis();
479           System.gc();
480           memf += r.totalMemory() - r.freeMemory();
481           if (al != null)
482           {
483             System.out.println("Alignment contains " + al.getHeight()
484                     + " sequences and " + al.getWidth() + " columns.");
485             try
486             {
487               System.out.println(new AppletFormatAdapter()
488                       .formatSequences(FileFormat.Fasta, al, true));
489             } catch (Exception e)
490             {
491               System.err.println(
492                       "Couln't format the alignment for output as a FASTA file.");
493               e.printStackTrace(System.err);
494             }
495           }
496           else
497           {
498             System.out.println("Couldn't read alignment");
499           }
500           System.out.println("Read took " + (t1 / 1000.0) + " seconds.");
501           System.out.println(
502                   "Difference between free memory now and before is "
503                           + (memf / (1024.0 * 1024.0) * 1.0) + " MB");
504         } catch (Exception e)
505         {
506           System.err.println("Exception when dealing with " + i
507                   + "'th argument: " + args[i] + "\n" + e);
508         }
509       }
510       else
511       {
512         System.err.println("Ignoring argument '" + args[i] + "' (" + i
513                 + "'th)- not a readable file.");
514       }
515       i++;
516     }
517   }
518
519   /**
520    * try to discover how to access the given file as a valid datasource that
521    * will be identified as the given type.
522    *
523    * @param file
524    * @param format
525    * @return protocol that yields the data parsable as the given type
526    */
527   public static DataSourceType resolveProtocol(String file,
528           FileFormatI format)
529   {
530     return resolveProtocol(file, format, false);
531   }
532
533   public static DataSourceType resolveProtocol(String file,
534           FileFormatI format, boolean debug)
535   {
536     // TODO: test thoroughly!
537     DataSourceType protocol = null;
538     if (debug)
539     {
540       System.out.println("resolving datasource started with:\n>>file\n"
541               + file + ">>endfile");
542     }
543
544     // This might throw a security exception in certain browsers
545     // Netscape Communicator for instance.
546     try
547     {
548       boolean rtn = false;
549       InputStream is = System.getSecurityManager().getClass()
550               .getResourceAsStream("/" + file);
551       if (is != null)
552       {
553         rtn = true;
554         is.close();
555       }
556       if (debug)
557       {
558         System.err.println("Resource '" + file + "' was "
559                 + (rtn ? "" : "not") + " located by classloader.");
560       }
561       if (rtn)
562       {
563         protocol = DataSourceType.CLASSLOADER;
564       }
565
566     } catch (Exception ex)
567     {
568       System.err
569               .println("Exception checking resources: " + file + " " + ex);
570     }
571
572     if (file.indexOf("://") > -1)
573     {
574       protocol = DataSourceType.URL;
575     }
576     else
577     {
578       // skipping codebase prepend check.
579       protocol = DataSourceType.FILE;
580     }
581     FileParse fp = null;
582     try
583     {
584       if (debug)
585       {
586         System.out.println(
587                 "Trying to get contents of resource as " + protocol + ":");
588       }
589       fp = new FileParse(file, protocol);
590       if (!fp.isValid())
591       {
592         fp = null;
593       }
594       else
595       {
596         if (debug)
597         {
598           System.out.println("Successful.");
599         }
600       }
601     } catch (Exception e)
602     {
603       if (debug)
604       {
605         System.err.println("Exception when accessing content: " + e);
606       }
607       fp = null;
608     }
609     if (fp == null)
610     {
611       if (debug)
612       {
613         System.out.println("Accessing as paste.");
614       }
615       protocol = DataSourceType.PASTE;
616       fp = null;
617       try
618       {
619         fp = new FileParse(file, protocol);
620         if (!fp.isValid())
621         {
622           fp = null;
623         }
624       } catch (Exception e)
625       {
626         System.err.println("Failed to access content as paste!");
627         e.printStackTrace();
628         fp = null;
629       }
630     }
631     if (fp == null)
632     {
633       return null;
634     }
635     if (format == null)
636     {
637       return protocol;
638     }
639     else
640     {
641       try
642       {
643         FileFormatI idformat = new IdentifyFile().identify(file, protocol);
644         if (idformat == null)
645         {
646           if (debug)
647           {
648             System.out.println("Format not identified. Inaccessible file.");
649           }
650           return null;
651         }
652         if (debug)
653         {
654           System.out.println("Format identified as " + idformat
655                   + "and expected as " + format);
656         }
657         if (idformat.equals(format))
658         {
659           if (debug)
660           {
661             System.out.println("Protocol identified as " + protocol);
662           }
663           return protocol;
664         }
665         else
666         {
667           if (debug)
668           {
669             System.out
670                     .println("File deemed not accessible via " + protocol);
671           }
672           fp.close();
673           return null;
674         }
675       } catch (Exception e)
676       {
677         if (debug)
678         {
679           System.err.println("File deemed not accessible via " + protocol);
680           e.printStackTrace();
681         }
682       }
683     }
684     return null;
685   }
686
687   public AlignmentFileReaderI getAlignFile()
688   {
689     return alignFile;
690   }
691 }