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