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