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