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