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