more efficient parsing of large files, do not create sequence features from GR annota...
[jalview.git] / src / jalview / io / StockholmFile.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer\r
3  * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle\r
4  *\r
5  * This program is free software; you can redistribute it and/or\r
6  * modify it under the terms of the GNU General Public License\r
7  * as published by the Free Software Foundation; either version 2\r
8  * of the License, or (at your option) any later version.\r
9  *\r
10  * This program is distributed in the hope that it will be useful,\r
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13  * GNU General Public License for more details.\r
14  *\r
15  * You should have received a copy of the GNU General Public License\r
16  * along with this program; if not, write to the Free Software\r
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\r
18  */\r
19 /*\r
20  * This extension was written by Benjamin Schuster-Boeckler at sanger.ac.uk\r
21  */\r
22 package jalview.io;\r
23 \r
24 import java.io.*;\r
25 import java.util.*;\r
26 \r
27 import com.stevesoft.pat.*;\r
28 import jalview.datamodel.*;\r
29 \r
30 // import org.apache.log4j.*;\r
31 \r
32 /**\r
33  * This class is supposed to parse a Stockholm format file into Jalview\r
34  * There are TODOs in this class: we do not know what the database source and\r
35  * version is for the file when parsing the #GS= AC tag which associates accessions\r
36  * with sequences. \r
37  * Database references are also not parsed correctly: a separate reference string\r
38  * parser must be added to parse the database reference form into Jalview's local\r
39  * representation.\r
40  * @author bsb at sanger.ac.uk\r
41  * @version 0.3 + jalview mods\r
42  * \r
43  */\r
44 public class StockholmFile extends AlignFile\r
45 {\r
46   // static Logger logger = Logger.getLogger("jalview.io.StockholmFile");\r
47 \r
48   public StockholmFile()\r
49   {\r
50   }\r
51 \r
52   public StockholmFile(String inFile, String type) throws IOException\r
53   {\r
54     super(inFile, type);\r
55   }\r
56 \r
57   public void initData()\r
58   {\r
59     super.initData();\r
60   }\r
61 \r
62   /**\r
63    * Parse a file in Stockholm format into Jalview's data model. The file has to\r
64    * be passed at construction time\r
65    * \r
66    * @throws IOException\r
67    *           If there is an error with the input file\r
68    */\r
69   public void parse() throws IOException\r
70   {\r
71     StringBuffer treeString = new StringBuffer();\r
72     String treeName = null;\r
73     // --------------- Variable Definitions -------------------\r
74     String line;\r
75     String version;\r
76     // String id;\r
77     Hashtable seqAnn = new Hashtable(); // Sequence related annotations\r
78     Hashtable seqs = new Hashtable();\r
79     Regex p, r, rend, s, x;\r
80 \r
81     // ------------------ Parsing File ----------------------\r
82     // First, we have to check that this file has STOCKHOLM format, i.e. the\r
83     // first line must match\r
84     r = new Regex("# STOCKHOLM ([\\d\\.]+)");\r
85     if (!r.search(nextLine()))\r
86     {\r
87       throw new IOException(\r
88               "This file is not in valid STOCKHOLM format: First line does not contain '# STOCKHOLM'");\r
89     }\r
90     else\r
91     {\r
92       version = r.stringMatched(1);\r
93       // logger.debug("Stockholm version: " + version);\r
94     }\r
95 \r
96     // We define some Regexes here that will be used regularily later\r
97     rend = new Regex("\\/\\/"); // Find the end of an alignment\r
98     p = new Regex("(\\S+)\\/(\\d+)\\-(\\d+)"); // split sequence id in\r
99                                                 // id/from/to\r
100     s = new Regex("(\\S+)\\s+(\\S*)\\s+(.*)"); // Parses annotation subtype\r
101     r = new Regex("#=(G[FSRC]?)\\s+(.*)"); // Finds any annotation line\r
102     x = new Regex("(\\S+)\\s+(\\S+)"); // split id from sequence\r
103 \r
104     rend.optimize();\r
105     p.optimize();\r
106     s.optimize();\r
107     r.optimize();\r
108     x.optimize();\r
109 \r
110     while ((line = nextLine()) != null)\r
111     {\r
112       if (line.length() == 0)\r
113       {\r
114         continue;\r
115       }\r
116       if (rend.search(line))\r
117       {\r
118         // End of the alignment, pass stuff back\r
119 \r
120         this.noSeqs = seqs.size();\r
121         // logger.debug("Number of sequences: " + this.noSeqs);\r
122         Enumeration accs = seqs.keys();\r
123         while (accs.hasMoreElements())\r
124         {\r
125           String acc = (String) accs.nextElement();\r
126           // logger.debug("Processing sequence " + acc);\r
127           String seq = (String) seqs.remove(acc);\r
128           if (maxLength < seq.length())\r
129           {\r
130             maxLength = seq.length();\r
131           }\r
132           int start = 1;\r
133           int end = -1;\r
134           String sid = acc;\r
135           // Retrieve hash of annotations for this accession\r
136           Hashtable accAnnotations = null;\r
137 \r
138           if (seqAnn != null && seqAnn.containsKey(acc))\r
139           {\r
140             accAnnotations = (Hashtable) seqAnn.remove(acc);\r
141           }\r
142 \r
143           // Split accession in id and from/to\r
144           if (p.search(acc))\r
145           {\r
146             sid = p.stringMatched(1);\r
147             start = Integer.parseInt(p.stringMatched(2));\r
148             end = Integer.parseInt(p.stringMatched(3));\r
149           }\r
150           // logger.debug(sid + ", " + start + ", " + end);\r
151 \r
152           Sequence seqO = new Sequence(sid, seq, start, end);\r
153           // Add Description (if any)\r
154           if (accAnnotations != null && accAnnotations.containsKey("DE"))\r
155           {\r
156             String desc = (String) accAnnotations.get("DE");\r
157             seqO.setDescription((desc == null) ? "" : desc);\r
158           }\r
159           // Add DB References (if any)\r
160           if (accAnnotations != null && accAnnotations.containsKey("DR"))\r
161           {\r
162             String dbr = (String) accAnnotations.get("DR");\r
163             if (dbr != null && dbr.indexOf(";") > -1)\r
164             {\r
165               String src = dbr.substring(0, dbr.indexOf(";"));\r
166               String acn = dbr.substring(dbr.indexOf(";") + 1);\r
167               jalview.util.DBRefUtils.parseToDbRef(seqO, src, "0", acn);\r
168               //seqO.addDBRef(dbref);\r
169             }\r
170           }\r
171           Hashtable features = null;\r
172           // We need to adjust the positions of all features to account for gaps\r
173           try\r
174           {\r
175             features = (Hashtable) accAnnotations.remove("features");\r
176           } catch (java.lang.NullPointerException e)\r
177           {\r
178             // loggerwarn("Getting Features for " + acc + ": " +\r
179             // e.getMessage());\r
180             // continue;\r
181           }\r
182           // if we have features\r
183           if (features != null)\r
184           {\r
185             int posmap[] = seqO.findPositionMap();\r
186             Enumeration i = features.keys();\r
187             while (i.hasMoreElements())\r
188             {\r
189               // TODO: parse out secondary structure annotation as annotation\r
190               // row\r
191               // TODO: parse out scores as annotation row\r
192               // TODO: map coding region to core jalview feature types\r
193               String type = i.nextElement().toString();\r
194               Hashtable content = (Hashtable) features.remove(type);\r
195               Enumeration j = content.keys();\r
196               while (j.hasMoreElements())\r
197               {\r
198                 String desc = j.nextElement().toString();\r
199                 String ns = content.get(desc).toString();\r
200                 char[] byChar = ns.toCharArray();\r
201                 for (int k = 0; k < byChar.length; k++)\r
202                 {\r
203                   char c = byChar[k];\r
204                   if (!(c == ' ' || c == '_' || c == '-' || c == '.')) // PFAM uses '.' for feature background\r
205                   {\r
206                     int new_pos = posmap[k]; // look up nearest seqeunce position to this column\r
207                     SequenceFeature feat = new SequenceFeature(type, desc,\r
208                             new_pos, new_pos, 0f, null);\r
209 \r
210                     seqO.addSequenceFeature(feat);\r
211                   }\r
212                 }\r
213               }\r
214 \r
215             }\r
216             \r
217           }\r
218           // garbage collect\r
219           \r
220           // logger.debug("Adding seq " + acc + " from " + start + " to " + end\r
221           // + ": " + seq);\r
222           this.seqs.addElement(seqO);\r
223         }\r
224       }\r
225       else if (!r.search(line))\r
226       {\r
227         // System.err.println("Found sequence line: " + line);\r
228 \r
229         // Split sequence in sequence and accession parts\r
230         if (!x.search(line))\r
231         {\r
232           // logger.error("Could not parse sequence line: " + line);\r
233           throw new IOException("Could not parse sequence line: " + line);\r
234         }\r
235         String ns = (String) seqs.get(x.stringMatched(1));\r
236         if (ns == null)\r
237         {\r
238           ns = "";\r
239         }\r
240         ns += x.stringMatched(2);\r
241 \r
242         seqs.put(x.stringMatched(1), ns);\r
243       }\r
244       else\r
245       {\r
246         String annType = r.stringMatched(1);\r
247         String annContent = r.stringMatched(2);\r
248 \r
249         // System.err.println("type:" + annType + " content: " + annContent);\r
250 \r
251         if (annType.equals("GF"))\r
252         {\r
253           /*\r
254            * Generic per-File annotation, free text Magic features: #=GF NH\r
255            * <tree in New Hampshire eXtended format> #=GF TN <Unique identifier\r
256            * for the next tree> Pfam descriptions: 7. DESCRIPTION OF FIELDS\r
257            * \r
258            * Compulsory fields: ------------------\r
259            * \r
260            * AC Accession number: Accession number in form PFxxxxx.version or\r
261            * PBxxxxxx. ID Identification: One word name for family. DE\r
262            * Definition: Short description of family. AU Author: Authors of the\r
263            * entry. SE Source of seed: The source suggesting the seed members\r
264            * belong to one family. GA Gathering method: Search threshold to\r
265            * build the full alignment. TC Trusted Cutoff: Lowest sequence score\r
266            * and domain score of match in the full alignment. NC Noise Cutoff:\r
267            * Highest sequence score and domain score of match not in full\r
268            * alignment. TP Type: Type of family -- presently Family, Domain,\r
269            * Motif or Repeat. SQ Sequence: Number of sequences in alignment. AM\r
270            * Alignment Method The order ls and fs hits are aligned to the model\r
271            * to build the full align. // End of alignment.\r
272            * \r
273            * Optional fields: ----------------\r
274            * \r
275            * DC Database Comment: Comment about database reference. DR Database\r
276            * Reference: Reference to external database. RC Reference Comment:\r
277            * Comment about literature reference. RN Reference Number: Reference\r
278            * Number. RM Reference Medline: Eight digit medline UI number. RT\r
279            * Reference Title: Reference Title. RA Reference Author: Reference\r
280            * Author RL Reference Location: Journal location. PI Previous\r
281            * identifier: Record of all previous ID lines. KW Keywords: Keywords.\r
282            * CC Comment: Comments. NE Pfam accession: Indicates a nested domain.\r
283            * NL Location: Location of nested domains - sequence ID, start and\r
284            * end of insert.\r
285            * \r
286            * Obsolete fields: ----------- AL Alignment method of seed: The\r
287            * method used to align the seed members.\r
288            */\r
289           // Let's save the annotations, maybe we'll be able to do something\r
290           // with them later...\r
291           Regex an = new Regex("(\\w+)\\s*(.*)");\r
292           if (an.search(annContent))\r
293           {\r
294             if (an.stringMatched(1).equals("NH"))\r
295             {\r
296               treeString.append(an.stringMatched(2));\r
297             }\r
298             else if (an.stringMatched(1).equals("TN"))\r
299             {\r
300               if (treeString.length() > 0)\r
301               {\r
302                 if (treeName == null)\r
303                 {\r
304                   treeName = "Tree " + (getTreeCount() + 1);\r
305                 }\r
306                 addNewickTree(treeName, treeString.toString());\r
307               }\r
308               treeName = an.stringMatched(2);\r
309               treeString = new StringBuffer();\r
310             }\r
311             setAlignmentProperty(an.stringMatched(1), an.stringMatched(2));\r
312           }\r
313         }\r
314         else if (annType.equals("GS"))\r
315         {\r
316           // Generic per-Sequence annotation, free text\r
317           /*\r
318            * Pfam uses these features: Feature Description ---------------------\r
319            * ----------- AC <accession> ACcession number DE <freetext>\r
320            * DEscription DR <db>; <accession>; Database Reference OS <organism>\r
321            * OrganiSm (species) OC <clade> Organism Classification (clade, etc.)\r
322            * LO <look> Look (Color, etc.)\r
323            */\r
324           if (s.search(annContent))\r
325           {\r
326             String acc = s.stringMatched(1);\r
327             String type = s.stringMatched(2);\r
328             String content = s.stringMatched(3);\r
329             // TODO: store DR in a vector.\r
330             // TODO: store AC according to generic file db annotation.\r
331             Hashtable ann;\r
332             if (seqAnn.containsKey(acc))\r
333             {\r
334               ann = (Hashtable) seqAnn.get(acc);\r
335             }\r
336             else\r
337             {\r
338               ann = new Hashtable();\r
339             }\r
340             ann.put(type, content);\r
341             seqAnn.put(acc, ann);\r
342           }\r
343           else\r
344           {\r
345             throw new IOException("Error parsing " + line);\r
346           }\r
347         }\r
348         else if (annType.equals("GC"))\r
349         {\r
350           // Generic per-Column annotation, exactly 1 char per column\r
351           // always need a label.\r
352           if (x.search(annContent))\r
353           {\r
354             // parse out and create alignment annotation directly.\r
355             parseAnnotationRow(annotations, x.stringMatched(1), x\r
356                     .stringMatched(2));\r
357           }\r
358         }\r
359         else if (annType.equals("GR"))\r
360         {\r
361           // Generic per-Sequence AND per-Column markup, exactly 1 char per\r
362           // column\r
363           /*\r
364            * Feature Description Markup letters ------- -----------\r
365            * -------------- SS Secondary Structure [HGIEBTSCX] SA Surface\r
366            * Accessibility [0-9X] (0=0%-10%; ...; 9=90%-100%) TM TransMembrane\r
367            * [Mio] PP Posterior Probability [0-9*] (0=0.00-0.05; 1=0.05-0.15;\r
368            * *=0.95-1.00) LI LIgand binding [*] AS Active Site [*] IN INtron (in\r
369            * or after) [0-2]\r
370            */\r
371           if (s.search(annContent))\r
372           {\r
373             String acc = s.stringMatched(1);\r
374             String type = s.stringMatched(2);\r
375             String seq = new String(s.stringMatched(3));\r
376             String description = null;\r
377             // Check for additional information about the current annotation\r
378             // We use a simple  string tokenizer here for speed\r
379             StringTokenizer sep = new StringTokenizer(seq," \t");\r
380             description = sep.nextToken();\r
381             if (sep.hasMoreTokens())\r
382             {\r
383               seq = sep.nextToken();\r
384             } else {\r
385               seq = description;\r
386               description = new String();\r
387             }\r
388             // sequence id with from-to fields\r
389 \r
390             Hashtable ann;\r
391             // Get an object with all the annotations for this sequence\r
392             if (seqAnn.containsKey(acc))\r
393             {\r
394               // logger.debug("Found annotations for " + acc);\r
395               ann = (Hashtable) seqAnn.get(acc);\r
396             }\r
397             else\r
398             {\r
399               // logger.debug("Creating new annotations holder for " + acc);\r
400               ann = new Hashtable();\r
401               seqAnn.put(acc, ann);\r
402             }\r
403 \r
404             Hashtable features;\r
405             // Get an object with all the content for an annotation\r
406             if (ann.containsKey("features"))\r
407             {\r
408               // logger.debug("Found features for " + acc);\r
409               features = (Hashtable) ann.get("features");\r
410             }\r
411             else\r
412             {\r
413               // logger.debug("Creating new features holder for " + acc);\r
414               features = new Hashtable();\r
415               ann.put("features", features);\r
416             }\r
417 \r
418             Hashtable content;\r
419             if (features.containsKey(this.id2type(type)))\r
420             {\r
421               // logger.debug("Found content for " + this.id2type(type));\r
422               content = (Hashtable) features.get(this.id2type(type));\r
423             }\r
424             else\r
425             {\r
426               // logger.debug("Creating new content holder for " +\r
427               // this.id2type(type));\r
428               content = new Hashtable();\r
429               features.put(this.id2type(type), content);\r
430             }\r
431             String ns = (String) content.get(description);\r
432             if (ns == null)\r
433             {\r
434               ns = "";\r
435             }\r
436             ns += seq;\r
437             content.put(description, seq);\r
438           }\r
439           else\r
440           {\r
441             System.err.println("Warning - couldn't parse sequence annotation row line:\n"+line);\r
442             // throw new IOException("Error parsing " + line);\r
443           }\r
444         }\r
445         else\r
446         {\r
447           throw new IOException("Unknown annotation detected: " + annType\r
448                   + " " + annContent);\r
449         }\r
450       }\r
451     }\r
452     if (treeString.length() > 0)\r
453     {\r
454       if (treeName == null)\r
455       {\r
456         treeName = "Tree " + (1 + getTreeCount());\r
457       }\r
458       addNewickTree(treeName, treeString.toString());\r
459     }\r
460   }\r
461 \r
462   private AlignmentAnnotation parseAnnotationRow(Vector annotation,\r
463           String label, String annots)\r
464   {\r
465     String type = (label.indexOf("_cons") == label.length() - 5) ? label\r
466             .substring(0, label.length() - 5) : label;\r
467     boolean ss = false;\r
468     type = id2type(type);\r
469     if (type.equals("secondary structure"))\r
470     {\r
471       ss = true;\r
472     }\r
473     // decide on secondary structure or not.\r
474     Annotation[] els = new Annotation[annots.length()];\r
475     for (int i = 0; i < annots.length(); i++)\r
476     {\r
477       String pos = annots.substring(i, i + 1);\r
478       Annotation ann;\r
479       ann = new Annotation(pos, "", ' ', Float.NaN);\r
480       if (ss)\r
481       {\r
482         ann.secondaryStructure = jalview.schemes.ResidueProperties\r
483                 .getDssp3state(pos).charAt(0);\r
484         if (ann.secondaryStructure == pos.charAt(0) || pos.charAt(0) == 'C')\r
485         {\r
486           ann.displayCharacter = "";\r
487         }\r
488         else\r
489         {\r
490           ann.displayCharacter += " ";\r
491         }\r
492       }\r
493 \r
494       els[i] = ann;\r
495     }\r
496     AlignmentAnnotation annot = null;\r
497     Enumeration e = annotation.elements();\r
498     while (e.hasMoreElements())\r
499     {\r
500       annot = (AlignmentAnnotation) e.nextElement();\r
501       if (annot.label.equals(type))\r
502         break;\r
503       annot = null;\r
504     }\r
505     if (annot == null)\r
506     {\r
507       annot = new AlignmentAnnotation(type, type, els);\r
508       annotation.addElement(annot);\r
509     }\r
510     else\r
511     {\r
512       Annotation[] anns = new Annotation[annot.annotations.length\r
513               + els.length];\r
514       System.arraycopy(annot.annotations, 0, anns, 0,\r
515               annot.annotations.length);\r
516       System.arraycopy(els, 0, anns, annot.annotations.length, els.length);\r
517       annot.annotations = anns;\r
518     }\r
519     return annot;\r
520   }\r
521 \r
522   public static String print(SequenceI[] s)\r
523   {\r
524     return "not yet implemented";\r
525   }\r
526 \r
527   public String print()\r
528   {\r
529     return print(getSeqsAsArray());\r
530   }\r
531 \r
532   private static Hashtable typeIds = null;\r
533   static\r
534   {\r
535     if (typeIds == null)\r
536     {\r
537       typeIds = new Hashtable();\r
538       typeIds.put("SS", "secondary structure");\r
539       typeIds.put("SA", "surface accessibility");\r
540       typeIds.put("TM", "transmembrane");\r
541       typeIds.put("PP", "posterior probability");\r
542       typeIds.put("LI", "ligand binding");\r
543       typeIds.put("AS", "active site");\r
544       typeIds.put("IN", "intron");\r
545       typeIds.put("IR", "interacting residue");\r
546       typeIds.put("AC", "accession");\r
547       typeIds.put("OS", "organism");\r
548       typeIds.put("CL", "class");\r
549       typeIds.put("DE", "description");\r
550       typeIds.put("DR", "reference");\r
551       typeIds.put("LO", "look");\r
552       typeIds.put("RF", "reference positions");\r
553 \r
554     }\r
555   }\r
556 \r
557   private String id2type(String id)\r
558   {\r
559     if (typeIds.containsKey(id))\r
560     {\r
561       return (String) typeIds.get(id);\r
562     }\r
563     System.err.println("Warning : Unknown Stockholm annotation type code "\r
564             + id);\r
565     return id;\r
566   }\r
567 }\r