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