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