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