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