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