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