Merge branch 'JAL-1161_optimisingAnnotationPanel' into develop
[jalview.git] / src / jalview / io / StockholmFile.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)\r
3  * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle\r
4  * \r
5  * This file is part of Jalview.\r
6  * \r
7  * Jalview is free software: you can redistribute it and/or\r
8  * modify it under the terms of the GNU General Public License \r
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\r
10  *  \r
11  * Jalview is distributed in the hope that it will be useful, but \r
12  * WITHOUT ANY WARRANTY; without even the implied warranty \r
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR \r
14  * PURPOSE.  See the GNU General Public License for more details.\r
15  * \r
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.\r
17  */\r
18 /*\r
19  * This extension was written by Benjamin Schuster-Boeckler at sanger.ac.uk\r
20  */\r
21 package jalview.io;\r
22 \r
23 import jalview.datamodel.AlignmentAnnotation;\r
24 import jalview.datamodel.AlignmentI;\r
25 import jalview.datamodel.Annotation;\r
26 import jalview.datamodel.DBRefEntry;\r
27 import jalview.datamodel.Sequence;\r
28 import jalview.datamodel.SequenceFeature;\r
29 import jalview.datamodel.SequenceI;\r
30 import jalview.util.Format;\r
31 \r
32 import java.io.BufferedReader;\r
33 import java.io.FileReader;\r
34 import java.io.IOException;\r
35 import java.util.ArrayList;\r
36 import java.util.Enumeration;\r
37 import java.util.Hashtable;\r
38 import java.util.List;\r
39 import java.util.StringTokenizer;\r
40 import java.util.Vector;\r
41 \r
42 import com.stevesoft.pat.Regex;\r
43 \r
44 import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;\r
45 import fr.orsay.lri.varna.factories.RNAFactory;\r
46 import fr.orsay.lri.varna.models.rna.RNA;\r
47 \r
48 // import org.apache.log4j.*;\r
49 \r
50 /**\r
51  * This class is supposed to parse a Stockholm format file into Jalview There\r
52  * are TODOs in this class: we do not know what the database source and version\r
53  * is for the file when parsing the #GS= AC tag which associates accessions with\r
54  * sequences. Database references are also not parsed correctly: a separate\r
55  * reference string parser must be added to parse the database reference form\r
56  * into Jalview's local representation.\r
57  * \r
58  * @author bsb at sanger.ac.uk\r
59  * @author Natasha Shersnev (Dundee, UK) (Stockholm file writer)\r
60  * @author Lauren Lui (UCSC, USA) (RNA secondary structure annotation import as stockholm)\r
61  * @author Anne Menard (Paris, FR) (VARNA parsing of Stockholm file data)\r
62  * @version 0.3 + jalview mods\r
63  * \r
64  */\r
65 public class StockholmFile extends AlignFile\r
66 {\r
67   // static Logger logger = Logger.getLogger("jalview.io.StockholmFile");\r
68   protected ArrayList<RNA> result;\r
69   StringBuffer out; // output buffer
70
71   AlignmentI al;
72 \r
73   public String id;\r
74 \r
75   public StockholmFile()\r
76   {\r
77   }\r
78 \r
79   /**
80    * Creates a new StockholmFile object for output.
81    */
82   public StockholmFile(AlignmentI al)
83   {
84     this.al = al;
85   }
86
87   public StockholmFile(String inFile, String type) throws IOException\r
88   {\r
89     super(inFile, type);\r
90   }\r
91 \r
92   public StockholmFile(FileParse source) throws IOException\r
93   {\r
94     super(source);\r
95   }\r
96 \r
97   public void initData()\r
98   {\r
99     super.initData();\r
100   }\r
101 \r
102   /**\r
103    * Parse a file in Stockholm format using VARNA\r
104    */\r
105   public void _parse_withVARNA(java.io.File inFile) throws IOException\r
106   {\r
107     FileReader fr = null;\r
108     fr = new FileReader(inFile);\r
109 \r
110     BufferedReader r = new BufferedReader(fr);\r
111     result = null;\r
112     try\r
113     {\r
114       result = RNAFactory.loadSecStrStockholm(r);\r
115     } catch (ExceptionUnmatchedClosingParentheses umcp)\r
116     {\r
117       errormessage = "Unmatched parentheses in annotation. Aborting ("\r
118               + umcp.getMessage() + ")";\r
119       throw new IOException(umcp);\r
120     }\r
121     // DEBUG System.out.println("this is the secondary scructure:"\r
122     // +result.size());\r
123     SequenceI[] seqs = new SequenceI[result.size()];\r
124     for (int i = 0; i < result.size(); i++)\r
125     {\r
126       // DEBUG System.err.println("Processing i'th sequence in Stockholm file")\r
127       RNA current = result.get(i);\r
128 \r
129       String seq = current.getSeq();\r
130       String rna = current.getStructDBN(true);\r
131       // DEBUG System.out.println(seq);\r
132       // DEBUG System.err.println(rna);\r
133       int begin = 0;\r
134       int end = seq.length() - 1;\r
135       id = safeName(getDataName());\r
136       seqs[i] = new Sequence(id, seq, begin, end);\r
137       String[] annot = new String[rna.length()];\r
138       Annotation[] ann = new Annotation[rna.length()];\r
139       for (int j = 0; j < rna.length(); j++)\r
140       {\r
141         annot[j] = rna.substring(j, j + 1);\r
142 \r
143       }\r
144 \r
145       for (int k = 0; k < rna.length(); k++)\r
146       {\r
147         ann[k] = new Annotation(annot[k], "",\r
148                 jalview.schemes.ResidueProperties.getRNASecStrucState(\r
149                         annot[k]).charAt(0), 0f);\r
150 \r
151       }\r
152       AlignmentAnnotation align = new AlignmentAnnotation("Sec. str.",\r
153               current.getID(), ann);\r
154 \r
155       seqs[i].addAlignmentAnnotation(align);\r
156       seqs[i].setRNA(result.get(i));\r
157       this.annotations.addElement(align);\r
158     }\r
159     this.setSeqs(seqs);\r
160 \r
161   }\r
162 \r
163   \r
164   /**\r
165    * Parse a file in Stockholm format into Jalview's data model. The file has to\r
166    * be passed at construction time\r
167    * \r
168    * @throws IOException\r
169    *           If there is an error with the input file\r
170    */\r
171   public void parse() throws IOException\r
172   {\r
173     StringBuffer treeString = new StringBuffer();\r
174     String treeName = null;\r
175     // --------------- Variable Definitions -------------------\r
176     String line;\r
177     String version;\r
178     // String id;\r
179     Hashtable seqAnn = new Hashtable(); // Sequence related annotations\r
180     Hashtable seqs = new Hashtable();\r
181     Regex p, r, rend, s, x;\r
182 \r
183     // Temporary line for processing RNA annotation\r
184     // String RNAannot = "";\r
185 \r
186     // ------------------ Parsing File ----------------------\r
187     // First, we have to check that this file has STOCKHOLM format, i.e. the\r
188     // first line must match\r
189     r = new Regex("# STOCKHOLM ([\\d\\.]+)");\r
190     if (!r.search(nextLine()))\r
191     {\r
192       throw new IOException(\r
193               "This file is not in valid STOCKHOLM format: First line does not contain '# STOCKHOLM'");\r
194     }\r
195     else\r
196     {\r
197       version = r.stringMatched(1);\r
198       // logger.debug("Stockholm version: " + version);\r
199     }\r
200 \r
201     // We define some Regexes here that will be used regularily later\r
202     rend = new Regex("^\\s*\\/\\/"); // Find the end of an alignment\r
203     p = new Regex("(\\S+)\\/(\\d+)\\-(\\d+)"); // split sequence id in\r
204     // id/from/to\r
205     s = new Regex("(\\S+)\\s+(\\S*)\\s+(.*)"); // Parses annotation subtype\r
206     r = new Regex("#=(G[FSRC]?)\\s+(.*)"); // Finds any annotation line\r
207     x = new Regex("(\\S+)\\s+(\\S+)"); // split id from sequence\r
208 \r
209     // Convert all bracket types to parentheses (necessary for passing to VARNA)\r
210     Regex openparen = new Regex("(<|\\[)", "(");\r
211     Regex closeparen = new Regex("(>|\\])", ")");\r
212 \r
213     // Detect if file is RNA by looking for bracket types\r
214     Regex detectbrackets = new Regex("(<|>|\\[|\\]|\\(|\\))");\r
215 \r
216     rend.optimize();\r
217     p.optimize();\r
218     s.optimize();\r
219     r.optimize();\r
220     x.optimize();\r
221     openparen.optimize();\r
222     closeparen.optimize();\r
223 \r
224     while ((line = nextLine()) != null)\r
225     {\r
226       if (line.length() == 0)\r
227       {\r
228         continue;\r
229       }\r
230       if (rend.search(line))\r
231       {\r
232         // End of the alignment, pass stuff back\r
233         this.noSeqs = seqs.size();\r
234 \r
235         String seqdb,dbsource = null;\r
236         Regex pf = new Regex("PF[0-9]{5}(.*)"); // Finds AC for Pfam\r
237         Regex rf = new Regex("RF[0-9]{5}(.*)"); // Finds AC for Rfam\r
238         if (getAlignmentProperty("AC") != null)\r
239         {\r
240           String dbType = getAlignmentProperty("AC").toString();\r
241           if (pf.search(dbType))\r
242           {\r
243             // PFAM Alignment - so references are typically from Uniprot\r
244             dbsource = "PFAM";\r
245           }\r
246           else if (rf.search(dbType))\r
247           {\r
248             dbsource = "RFAM";\r
249           }\r
250         }\r
251         // logger.debug("Number of sequences: " + this.noSeqs);\r
252         Enumeration accs = seqs.keys();\r
253         while (accs.hasMoreElements())\r
254         {\r
255           String acc = (String) accs.nextElement();\r
256           // logger.debug("Processing sequence " + acc);\r
257           String seq = (String) seqs.remove(acc);\r
258           if (maxLength < seq.length())\r
259           {\r
260             maxLength = seq.length();\r
261           }\r
262           int start = 1;\r
263           int end = -1;\r
264           String sid = acc;\r
265           /*\r
266            * Retrieve hash of annotations for this accession Associate\r
267            * Annotation with accession\r
268            */\r
269           Hashtable accAnnotations = null;\r
270 \r
271           if (seqAnn != null && seqAnn.containsKey(acc))\r
272           {\r
273             accAnnotations = (Hashtable) seqAnn.remove(acc);\r
274             // TODO: add structures to sequence\r
275           }\r
276 \r
277           // Split accession in id and from/to\r
278           if (p.search(acc))\r
279           {\r
280             sid = p.stringMatched(1);\r
281             start = Integer.parseInt(p.stringMatched(2));\r
282             end = Integer.parseInt(p.stringMatched(3));\r
283           }\r
284           // logger.debug(sid + ", " + start + ", " + end);\r
285 \r
286           Sequence seqO = new Sequence(sid, seq, start, end);\r
287           // Add Description (if any)\r
288           if (accAnnotations != null && accAnnotations.containsKey("DE"))\r
289           {\r
290             String desc = (String) accAnnotations.get("DE");\r
291             seqO.setDescription((desc == null) ? "" : desc);\r
292           }\r
293 \r
294           // Add DB References (if any)\r
295           if (accAnnotations != null && accAnnotations.containsKey("DR"))\r
296           {\r
297             String dbr = (String) accAnnotations.get("DR");\r
298             if (dbr != null && dbr.indexOf(";") > -1)\r
299             {\r
300               String src = dbr.substring(0, dbr.indexOf(";"));\r
301               String acn = dbr.substring(dbr.indexOf(";") + 1);\r
302               jalview.util.DBRefUtils.parseToDbRef(seqO, src, "0", acn);\r
303             }\r
304           }\r
305 \r
306           if (accAnnotations != null && accAnnotations.containsKey("AC"))\r
307           {\r
308             if (dbsource != null)\r
309             {\r
310               String dbr = (String) accAnnotations.get("AC");\r
311               if (dbr != null)\r
312               {\r
313                 // we could get very clever here - but for now - just try to guess accession type from source of alignment plus structure of accession\r
314                 guessDatabaseFor(seqO, dbr, dbsource);\r
315                 \r
316               }\r
317             } \r
318             // else - do what ?  add the data anyway and prompt the user to specify what references these are ?\r
319           }\r
320 \r
321           Hashtable features = null;\r
322           // We need to adjust the positions of all features to account for gaps\r
323           try\r
324           {\r
325             features = (Hashtable) accAnnotations.remove("features");\r
326           } catch (java.lang.NullPointerException e)\r
327           {\r
328             // loggerwarn("Getting Features for " + acc + ": " +\r
329             // e.getMessage());\r
330             // continue;\r
331           }\r
332           // if we have features\r
333           if (features != null)\r
334           {\r
335             int posmap[] = seqO.findPositionMap();\r
336             Enumeration i = features.keys();\r
337             while (i.hasMoreElements())\r
338             {\r
339               // TODO: parse out secondary structure annotation as annotation\r
340               // row\r
341               // TODO: parse out scores as annotation row\r
342               // TODO: map coding region to core jalview feature types\r
343               String type = i.nextElement().toString();\r
344               Hashtable content = (Hashtable) features.remove(type);\r
345 \r
346               // add alignment annotation for this feature\r
347               String key = type2id(type);\r
348               if (key != null)\r
349               {\r
350                 if (accAnnotations != null\r
351                         && accAnnotations.containsKey(key))\r
352                 {\r
353                   Vector vv = (Vector) accAnnotations.get(key);\r
354                   for (int ii = 0; ii < vv.size(); ii++)\r
355                   {\r
356                     AlignmentAnnotation an = (AlignmentAnnotation) vv\r
357                             .elementAt(ii);\r
358                     seqO.addAlignmentAnnotation(an);\r
359                   }\r
360                 }\r
361               }\r
362 \r
363               Enumeration j = content.keys();\r
364               while (j.hasMoreElements())\r
365               {\r
366                 String desc = j.nextElement().toString();\r
367                 String ns = content.get(desc).toString();\r
368                 char[] byChar = ns.toCharArray();\r
369                 for (int k = 0; k < byChar.length; k++)\r
370                 {\r
371                   char c = byChar[k];\r
372                   if (!(c == ' ' || c == '_' || c == '-' || c == '.')) // PFAM\r
373                   // uses\r
374                   // '.'\r
375                   // for\r
376                   // feature\r
377                   // background\r
378                   {\r
379                     int new_pos = posmap[k]; // look up nearest seqeunce\r
380                     // position to this column\r
381                     SequenceFeature feat = new SequenceFeature(type, desc,\r
382                             new_pos, new_pos, 0f, null);\r
383 \r
384                     seqO.addSequenceFeature(feat);\r
385                   }\r
386                 }\r
387               }\r
388 \r
389             }\r
390 \r
391           }\r
392           // garbage collect\r
393 \r
394           // logger.debug("Adding seq " + acc + " from " + start + " to " + end\r
395           // + ": " + seq);\r
396           this.seqs.addElement(seqO);\r
397         }\r
398         return; // finished parsing this segment of source\r
399       }\r
400       else if (!r.search(line))\r
401       {\r
402         // System.err.println("Found sequence line: " + line);\r
403 \r
404         // Split sequence in sequence and accession parts\r
405         if (!x.search(line))\r
406         {\r
407           // logger.error("Could not parse sequence line: " + line);\r
408           throw new IOException("Could not parse sequence line: " + line);\r
409         }\r
410         String ns = (String) seqs.get(x.stringMatched(1));\r
411         if (ns == null)\r
412         {\r
413           ns = "";\r
414         }\r
415         ns += x.stringMatched(2);\r
416 \r
417         seqs.put(x.stringMatched(1), ns);\r
418       }\r
419       else\r
420       {\r
421         String annType = r.stringMatched(1);\r
422         String annContent = r.stringMatched(2);\r
423 \r
424         // System.err.println("type:" + annType + " content: " + annContent);\r
425 \r
426         if (annType.equals("GF"))\r
427         {\r
428           /*\r
429            * Generic per-File annotation, free text Magic features: #=GF NH\r
430            * <tree in New Hampshire eXtended format> #=GF TN <Unique identifier\r
431            * for the next tree> Pfam descriptions: 7. DESCRIPTION OF FIELDS\r
432            * \r
433            * Compulsory fields: ------------------\r
434            * \r
435            * AC Accession number: Accession number in form PFxxxxx.version or\r
436            * PBxxxxxx. ID Identification: One word name for family. DE\r
437            * Definition: Short description of family. AU Author: Authors of the\r
438            * entry. SE Source of seed: The source suggesting the seed members\r
439            * belong to one family. GA Gathering method: Search threshold to\r
440            * build the full alignment. TC Trusted Cutoff: Lowest sequence score\r
441            * and domain score of match in the full alignment. NC Noise Cutoff:\r
442            * Highest sequence score and domain score of match not in full\r
443            * alignment. TP Type: Type of family -- presently Family, Domain,\r
444            * Motif or Repeat. SQ Sequence: Number of sequences in alignment. AM\r
445            * Alignment Method The order ls and fs hits are aligned to the model\r
446            * to build the full align. // End of alignment.\r
447            * \r
448            * Optional fields: ----------------\r
449            * \r
450            * DC Database Comment: Comment about database reference. DR Database\r
451            * Reference: Reference to external database. RC Reference Comment:\r
452            * Comment about literature reference. RN Reference Number: Reference\r
453            * Number. RM Reference Medline: Eight digit medline UI number. RT\r
454            * Reference Title: Reference Title. RA Reference Author: Reference\r
455            * Author RL Reference Location: Journal location. PI Previous\r
456            * identifier: Record of all previous ID lines. KW Keywords: Keywords.\r
457            * CC Comment: Comments. NE Pfam accession: Indicates a nested domain.\r
458            * NL Location: Location of nested domains - sequence ID, start and\r
459            * end of insert.\r
460            * \r
461            * Obsolete fields: ----------- AL Alignment method of seed: The\r
462            * method used to align the seed members.\r
463            */\r
464           // Let's save the annotations, maybe we'll be able to do something\r
465           // with them later...\r
466           Regex an = new Regex("(\\w+)\\s*(.*)");\r
467           if (an.search(annContent))\r
468           {\r
469             if (an.stringMatched(1).equals("NH"))\r
470             {\r
471               treeString.append(an.stringMatched(2));\r
472             }\r
473             else if (an.stringMatched(1).equals("TN"))\r
474             {\r
475               if (treeString.length() > 0)\r
476               {\r
477                 if (treeName == null)\r
478                 {\r
479                   treeName = "Tree " + (getTreeCount() + 1);\r
480                 }\r
481                 addNewickTree(treeName, treeString.toString());\r
482               }\r
483               treeName = an.stringMatched(2);\r
484               treeString = new StringBuffer();\r
485             }\r
486             setAlignmentProperty(an.stringMatched(1), an.stringMatched(2));\r
487           }\r
488         }\r
489         else if (annType.equals("GS"))\r
490         {\r
491           // Generic per-Sequence annotation, free text\r
492           /*\r
493            * Pfam uses these features: Feature Description ---------------------\r
494            * ----------- AC <accession> ACcession number DE <freetext>\r
495            * DEscription DR <db>; <accession>; Database Reference OS <organism>\r
496            * OrganiSm (species) OC <clade> Organism Classification (clade, etc.)\r
497            * LO <look> Look (Color, etc.)\r
498            */\r
499           if (s.search(annContent))\r
500           {\r
501             String acc = s.stringMatched(1);\r
502             String type = s.stringMatched(2);\r
503             String content = s.stringMatched(3);\r
504             // TODO: store DR in a vector.\r
505             // TODO: store AC according to generic file db annotation.\r
506             Hashtable ann;\r
507             if (seqAnn.containsKey(acc))\r
508             {\r
509               ann = (Hashtable) seqAnn.get(acc);\r
510             }\r
511             else\r
512             {\r
513               ann = new Hashtable();\r
514             }\r
515             ann.put(type, content);\r
516             seqAnn.put(acc, ann);\r
517           }\r
518           else\r
519           {\r
520             throw new IOException("Error parsing " + line);\r
521           }\r
522         }\r
523         else if (annType.equals("GC"))\r
524         {\r
525           // Generic per-Column annotation, exactly 1 char per column\r
526           // always need a label.\r
527           if (x.search(annContent))\r
528           {\r
529             // parse out and create alignment annotation directly.\r
530             parseAnnotationRow(annotations, x.stringMatched(1),\r
531                     x.stringMatched(2));\r
532           }\r
533         }\r
534         else if (annType.equals("GR"))\r
535         {\r
536           // Generic per-Sequence AND per-Column markup, exactly 1 char per\r
537           // column\r
538           /*\r
539            * Feature Description Markup letters ------- -----------\r
540            * -------------- SS Secondary Structure [HGIEBTSCX] SA Surface\r
541            * Accessibility [0-9X] (0=0%-10%; ...; 9=90%-100%) TM TransMembrane\r
542            * [Mio] PP Posterior Probability [0-9*] (0=0.00-0.05; 1=0.05-0.15;\r
543            * *=0.95-1.00) LI LIgand binding [*] AS Active Site [*] IN INtron (in\r
544            * or after) [0-2]\r
545            */\r
546           if (s.search(annContent))\r
547           {\r
548             String acc = s.stringMatched(1);\r
549             String type = s.stringMatched(2);\r
550             String seq = new String(s.stringMatched(3));\r
551             String description = null;\r
552             // Check for additional information about the current annotation\r
553             // We use a simple string tokenizer here for speed\r
554             StringTokenizer sep = new StringTokenizer(seq, " \t");\r
555             description = sep.nextToken();\r
556             if (sep.hasMoreTokens())\r
557             {\r
558               seq = sep.nextToken();\r
559             }\r
560             else\r
561             {\r
562               seq = description;\r
563               description = new String();\r
564             }\r
565             // sequence id with from-to fields\r
566 \r
567             Hashtable ann;\r
568             // Get an object with all the annotations for this sequence\r
569             if (seqAnn.containsKey(acc))\r
570             {\r
571               // logger.debug("Found annotations for " + acc);\r
572               ann = (Hashtable) seqAnn.get(acc);\r
573             }\r
574             else\r
575             {\r
576               // logger.debug("Creating new annotations holder for " + acc);\r
577               ann = new Hashtable();\r
578               seqAnn.put(acc, ann);\r
579             }\r
580             // TODO test structure, call parseAnnotationRow with vector from\r
581             // hashtable for specific sequence\r
582             Hashtable features;\r
583             // Get an object with all the content for an annotation\r
584             if (ann.containsKey("features"))\r
585             {\r
586               // logger.debug("Found features for " + acc);\r
587               features = (Hashtable) ann.get("features");\r
588             }\r
589             else\r
590             {\r
591               // logger.debug("Creating new features holder for " + acc);\r
592               features = new Hashtable();\r
593               ann.put("features", features);\r
594             }\r
595 \r
596             Hashtable content;\r
597             if (features.containsKey(this.id2type(type)))\r
598             {\r
599               // logger.debug("Found content for " + this.id2type(type));\r
600               content = (Hashtable) features.get(this.id2type(type));\r
601             }\r
602             else\r
603             {\r
604               // logger.debug("Creating new content holder for " +\r
605               // this.id2type(type));\r
606               content = new Hashtable();\r
607               features.put(this.id2type(type), content);\r
608             }\r
609             String ns = (String) content.get(description);\r
610             if (ns == null)\r
611             {\r
612               ns = "";\r
613             }\r
614             ns += seq;\r
615             content.put(description, ns);\r
616             Hashtable strucAnn;\r
617             if (seqAnn.containsKey(acc))\r
618             {\r
619               strucAnn = (Hashtable) seqAnn.get(acc);\r
620             }\r
621             else\r
622             {\r
623               strucAnn = new Hashtable();\r
624             }\r
625 \r
626             Vector newStruc = new Vector();\r
627             parseAnnotationRow(newStruc, type, ns);\r
628             strucAnn.put(type, newStruc);\r
629             seqAnn.put(acc, strucAnn);\r
630           }\r
631           else\r
632           {\r
633             System.err\r
634                     .println("Warning - couldn't parse sequence annotation row line:\n"\r
635                             + line);\r
636             // throw new IOException("Error parsing " + line);\r
637           }\r
638         }\r
639         else\r
640         {\r
641           throw new IOException("Unknown annotation detected: " + annType\r
642                   + " " + annContent);\r
643         }\r
644       }\r
645     }\r
646     if (treeString.length() > 0)\r
647     {\r
648       if (treeName == null)\r
649       {\r
650         treeName = "Tree " + (1 + getTreeCount());\r
651       }\r
652       addNewickTree(treeName, treeString.toString());\r
653     }\r
654   }\r
655 \r
656   /**\r
657    * Demangle an accession string and guess the originating sequence database for a given sequence\r
658    * @param seqO sequence to be annotated\r
659    * @param dbr Accession string for sequence\r
660    * @param dbsource source database for alignment (PFAM or RFAM)\r
661    */\r
662   private void guessDatabaseFor(Sequence seqO, String dbr, String dbsource)\r
663   {\r
664     DBRefEntry dbrf=null;\r
665     List<DBRefEntry> dbrs=new ArrayList<DBRefEntry>();\r
666     String seqdb="Unknown",sdbac=""+dbr;\r
667     int st=-1,en=-1,p;\r
668     if ((st=sdbac.indexOf("/"))>-1)\r
669     {\r
670       String num,range=sdbac.substring(st+1);\r
671       sdbac = sdbac.substring(0,st);\r
672       if ((p=range.indexOf("-"))>-1)\r
673       {\r
674         p++;\r
675         if (p<range.length())\r
676         {\r
677         num = range.substring(p).trim();\r
678         try {\r
679           en = Integer.parseInt(num);\r
680         } catch (NumberFormatException x)\r
681         {\r
682           // could warn here that index is invalid\r
683           en = -1;\r
684         }\r
685         }\r
686       } else {\r
687         p=range.length();\r
688       }\r
689       num=range.substring(0,p).trim();\r
690       try {\r
691         st = Integer.parseInt(num);\r
692       } catch (NumberFormatException x)\r
693       {\r
694         // could warn here that index is invalid\r
695         st = -1;\r
696       }\r
697     }\r
698     if (dbsource.equals("PFAM")) {\r
699       seqdb = "UNIPROT";\r
700       if (sdbac.indexOf(".")>-1)\r
701       {\r
702         // strip of last subdomain\r
703         sdbac = sdbac.substring(0,sdbac.indexOf("."));\r
704         dbrf = jalview.util.DBRefUtils.parseToDbRef(seqO, seqdb, dbsource, sdbac);\r
705         if (dbrf!=null)\r
706         {\r
707           dbrs.add(dbrf);\r
708         }\r
709       }\r
710       dbrf = jalview.util.DBRefUtils.parseToDbRef(seqO, dbsource, dbsource, dbr);\r
711       if (dbr!=null)\r
712       {\r
713         dbrs.add(dbrf);\r
714       }\r
715     } else {\r
716       seqdb = "EMBL"; // total guess - could be ENA, or something else these days\r
717       if (sdbac.indexOf(".")>-1)\r
718       {\r
719         // strip off last subdomain\r
720         sdbac = sdbac.substring(0,sdbac.indexOf("."));\r
721         dbrf = jalview.util.DBRefUtils.parseToDbRef(seqO, seqdb, dbsource, sdbac);\r
722         if (dbrf!=null)\r
723         {\r
724           dbrs.add(dbrf);\r
725         }\r
726       }\r
727       \r
728       dbrf = jalview.util.DBRefUtils.parseToDbRef(seqO, dbsource, dbsource, dbr);\r
729       if (dbrf!=null)\r
730       {\r
731         dbrs.add(dbrf);\r
732       }\r
733     }\r
734     if (st!=-1 && en!=-1)\r
735     {\r
736       for (DBRefEntry d:dbrs)\r
737       {\r
738         jalview.util.MapList mp = new jalview.util.MapList(new int[] { seqO.getStart(),seqO.getEnd()},new int[] { st,en},1,1);\r
739         jalview.datamodel.Mapping mping = new jalview.datamodel.Mapping(mp);\r
740         d.setMap(mping);\r
741       }\r
742     }\r
743   }\r
744   \r
745   protected static AlignmentAnnotation parseAnnotationRow(\r
746           Vector annotation, String label, String annots)\r
747   {\r
748     String convert1, convert2 = null;\r
749 \r
750     // Convert all bracket types to parentheses\r
751     Regex openparen = new Regex("(<|\\[)", "(");\r
752     Regex closeparen = new Regex("(>|\\])", ")");\r
753 \r
754     // Detect if file is RNA by looking for bracket types\r
755     Regex detectbrackets = new Regex("(<|>|\\[|\\]|\\(|\\))");\r
756 \r
757     convert1 = openparen.replaceAll(annots);\r
758     convert2 = closeparen.replaceAll(convert1);\r
759     annots = convert2;\r
760 \r
761     String type = label;
762     if (label.contains("_cons"))
763     {
764       type = (label.indexOf("_cons") == label.length() - 5) ? label
765             .substring(0, label.length() - 5) : label;\r
766     }
767     boolean ss = false;\r
768     type = id2type(type);\r
769     if (type.equals("secondary structure"))\r
770     {\r
771       ss = true;\r
772     }\r
773     // decide on secondary structure or not.\r
774     Annotation[] els = new Annotation[annots.length()];\r
775     for (int i = 0; i < annots.length(); i++)\r
776     {\r
777       String pos = annots.substring(i, i + 1);\r
778       Annotation ann;\r
779       ann = new Annotation(pos, "", ' ', 0f); // 0f is 'valid' null - will not\r
780       // be written out\r
781       if (ss)\r
782       {\r
783         if (detectbrackets.search(pos))\r
784         {\r
785           ann.secondaryStructure = jalview.schemes.ResidueProperties\r
786                   .getRNASecStrucState(pos).charAt(0);\r
787         }\r
788         else\r
789         {\r
790           ann.secondaryStructure = jalview.schemes.ResidueProperties\r
791                   .getDssp3state(pos).charAt(0);\r
792         }\r
793 \r
794         if (ann.secondaryStructure == pos.charAt(0) || pos.charAt(0) == 'C')\r
795         {\r
796           ann.displayCharacter = ""; // null; // " ";\r
797         }\r
798         else\r
799         {\r
800           ann.displayCharacter = " " + ann.displayCharacter;\r
801         }\r
802       }\r
803 \r
804       els[i] = ann;\r
805     }\r
806     AlignmentAnnotation annot = null;\r
807     Enumeration e = annotation.elements();\r
808     while (e.hasMoreElements())\r
809     {\r
810       annot = (AlignmentAnnotation) e.nextElement();\r
811       if (annot.label.equals(type))\r
812         break;\r
813       annot = null;\r
814     }\r
815     if (annot == null)\r
816     {\r
817       annot = new AlignmentAnnotation(type, type, els);\r
818       annotation.addElement(annot);\r
819     }\r
820     else\r
821     {\r
822       Annotation[] anns = new Annotation[annot.annotations.length\r
823               + els.length];\r
824       System.arraycopy(annot.annotations, 0, anns, 0,\r
825               annot.annotations.length);\r
826       System.arraycopy(els, 0, anns, annot.annotations.length, els.length);\r
827       annot.annotations = anns;\r
828       // System.out.println("else: ");\r
829     }\r
830     return annot;\r
831   }\r
832 \r
833   public String print(SequenceI[] s)
834   {
835     // find max length of id
836     int max = 0;
837     int maxid = 0;
838     int in = 0;
839     Hashtable dataRef = null;
840     while ((in < s.length) && (s[in] != null))
841     {
842       String tmp = printId(s[in]);
843       if (s[in].getSequence().length > max)
844       {
845         max = s[in].getSequence().length;
846       }
847
848       if (tmp.length() > maxid)
849       {
850         maxid = tmp.length();
851       }
852       if (s[in].getDBRef() != null)
853       {
854         for (int idb = 0; idb < s[in].getDBRef().length; idb++)
855         {
856           if (dataRef == null)
857             dataRef = new Hashtable();
858
859           String datAs1 = s[in].getDBRef()[idb].getSource().toString()
860                   + " ; "
861                   + s[in].getDBRef()[idb].getAccessionId().toString();
862           dataRef.put(tmp, datAs1);
863         }
864       }
865       in++;
866     }
867     maxid += 9;
868     int i = 0;
869
870     // output database type
871     if (al.getProperties() != null)
872     {
873       if (!al.getProperties().isEmpty())
874       {
875         Enumeration key = al.getProperties().keys();
876         Enumeration val = al.getProperties().elements();
877         while (key.hasMoreElements())
878         {
879           out.append("#=GF " + key.nextElement() + " " + val.nextElement());
880           out.append(newline);
881         }
882       }
883     }
884
885     // output database accessions
886     if (dataRef != null)
887     {
888       Enumeration en = dataRef.keys();
889       while (en.hasMoreElements())
890       {
891         Object idd = en.nextElement();
892         String type = (String) dataRef.remove(idd);
893         out.append(new Format("%-" + (maxid - 2) + "s").form("#=GS "
894                 + idd.toString() + " "));
895         if (type.contains("PFAM") || type.contains("RFAM"))
896         {
897
898           out.append(" AC " + type.substring(type.indexOf(";") + 1));
899         }
900         else
901         {
902           out.append(" DR " + type + " ");
903         }
904         out.append(newline);
905       }
906     }
907
908     // output annotations
909     while (i < s.length && s[i] != null)
910     {
911       if (s[i].getDatasetSequence() != null)
912       {
913         SequenceI ds = s[i].getDatasetSequence();
914         AlignmentAnnotation[] alAnot;
915         Annotation[] ann;
916         Annotation annot;
917         alAnot = s[i].getAnnotation();
918         String feature = "";
919         if (alAnot != null)
920         {
921           for (int j = 0; j < alAnot.length; j++)
922           {
923             if (ds.getSequenceFeatures() != null)
924             {
925               feature = ds.getSequenceFeatures()[0].type;
926             }
927             String key = type2id(feature);
928
929             if (key == null)
930               continue;
931
932             // out.append("#=GR ");
933             out.append(new Format("%-" + maxid + "s").form("#=GR "
934                     + printId(s[i]) + " " + key + " "));
935             ann = alAnot[j].annotations;
936             String seq = "";
937             for (int k = 0; k < ann.length; k++)
938             {
939               annot = ann[k];
940               String ch = (annot == null) ? Character.toString(s[i]
941                       .getCharAt(k)) : annot.displayCharacter;
942               if (ch.length() == 0)
943               {
944                 if (key.equals("SS"))
945                 {
946                   char ll = annot.secondaryStructure;
947                   seq = (Character.toString(ll).equals(" ")) ? seq + "C"
948                           : seq + ll;
949                 }
950                 else
951                 {
952                   seq += ".";
953                 }
954               }
955               else if (ch.length() == 1)
956               {
957                 seq += ch;
958               }
959               else if (ch.length() > 1)
960               {
961                 seq += ch.charAt(1);
962               }
963             }
964             out.append(seq);
965             out.append(newline);
966           }
967         }
968       }
969
970       out.append(new Format("%-" + maxid + "s").form(printId(s[i]) + " "));
971       out.append(s[i].getSequenceAsString());
972       out.append(newline);
973       i++;
974     }
975
976     // alignment annotation
977     AlignmentAnnotation aa;
978     if (al.getAlignmentAnnotation() != null)
979     {
980       for (int ia = 0; ia < al.getAlignmentAnnotation().length; ia++)
981       {
982         aa = al.getAlignmentAnnotation()[ia];
983         if (aa.autoCalculated || !aa.visible)
984         {
985           continue;
986         }
987         String seq = "";
988         String label;
989
990         if (aa.label.equals("seq"))
991           label = "seq_cons";
992         else
993           label = type2id(aa.label.toLowerCase()) + "_cons";
994
995         if (label == null)
996           label = aa.label;
997
998         out.append(new Format("%-" + maxid + "s").form("#=GC " + label
999                 + " "));
1000         for (int j = 0; j < aa.annotations.length; j++)
1001         {
1002           String ch = (aa.annotations[j] == null) ? "-"
1003                   : aa.annotations[j].displayCharacter;
1004           if (ch.length() == 0)
1005           {
1006             char ll = aa.annotations[j].secondaryStructure;
1007             if (Character.toString(ll).equals(" "))
1008               seq += "C";
1009             else
1010               seq += ll;
1011           }
1012           else if (ch.length() == 1)
1013           {
1014             seq += ch;
1015           }
1016           else if (ch.length() > 1)
1017   {\r
1018             seq += ch.charAt(1);
1019           }
1020         }
1021         out.append(seq);
1022         out.append(newline);
1023       }
1024     }
1025     return out.toString();
1026   }\r
1027 \r
1028   public String print()\r
1029   {\r
1030     out = new StringBuffer();
1031     out.append("# STOCKHOLM 1.0");
1032     out.append(newline);
1033     print(getSeqsAsArray());
1034
1035     out.append("//");
1036     out.append(newline);
1037     return out.toString();
1038   }\r
1039 \r
1040   private static Hashtable typeIds = null;\r
1041   static\r
1042   {\r
1043     if (typeIds == null)\r
1044     {\r
1045       typeIds = new Hashtable();\r
1046       typeIds.put("SS", "secondary structure");\r
1047       typeIds.put("SA", "surface accessibility");\r
1048       typeIds.put("TM", "transmembrane");\r
1049       typeIds.put("PP", "posterior probability");\r
1050       typeIds.put("LI", "ligand binding");\r
1051       typeIds.put("AS", "active site");\r
1052       typeIds.put("IN", "intron");\r
1053       typeIds.put("IR", "interacting residue");\r
1054       typeIds.put("AC", "accession");\r
1055       typeIds.put("OS", "organism");\r
1056       typeIds.put("CL", "class");\r
1057       typeIds.put("DE", "description");\r
1058       typeIds.put("DR", "reference");\r
1059       typeIds.put("LO", "look");\r
1060       typeIds.put("RF", "reference positions");\r
1061 \r
1062     }\r
1063   }\r
1064 \r
1065   protected static String id2type(String id)\r
1066   {\r
1067     if (typeIds.containsKey(id))\r
1068     {\r
1069       return (String) typeIds.get(id);\r
1070     }\r
1071     System.err.println("Warning : Unknown Stockholm annotation type code "\r
1072             + id);\r
1073     return id;\r
1074   }\r
1075   \r
1076   protected static String type2id(String type)\r
1077   {\r
1078     String key = null;\r
1079     Enumeration e = typeIds.keys();\r
1080     while (e.hasMoreElements())\r
1081     {\r
1082       Object ll = e.nextElement();\r
1083       if (typeIds.get(ll).toString().equals(type))\r
1084       {\r
1085         key = (String) ll;\r
1086         break;\r
1087       }\r
1088     }\r
1089     if (key != null)\r
1090     {\r
1091       return (String) key;\r
1092     }\r
1093     System.err.println("Warning : Unknown Stockholm annotation type: "\r
1094             + type);\r
1095     return key;\r
1096   }\r
1097   /**\r
1098    * make a friendly ID string.\r
1099    * \r
1100    * @param dataName\r
1101    * @return truncated dataName to after last '/'\r
1102    */\r
1103   private String safeName(String dataName)\r
1104   {\r
1105     int b = 0;\r
1106     while ((b = dataName.indexOf("/")) > -1 && b < dataName.length())\r
1107     {\r
1108       dataName = dataName.substring(b + 1).trim();\r
1109 \r
1110     }\r
1111     int e = (dataName.length() - dataName.indexOf(".")) + 1;\r
1112     dataName = dataName.substring(1, e).trim();\r
1113     return dataName;\r
1114   }\r
1115 }