JAL-580 ensure WUSS annotation string is up to date, pair list regenerated if necessa...
[jalview.git] / src / jalview / datamodel / AlignmentAnnotation.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, G Barton, M Clamp, S Searle
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  */
18 package jalview.datamodel;
19
20 import jalview.analysis.Rna;
21 import jalview.analysis.WUSSParseException;
22
23 import java.util.Enumeration;
24 import java.util.Hashtable;
25
26 /**
27  * DOCUMENT ME!
28  * 
29  * @author $author$
30  * @version $Revision$
31  */
32 public class AlignmentAnnotation
33 {
34   /**
35    * If true, this annotations is calculated every edit, eg consensus, quality
36    * or conservation graphs
37    */
38   public boolean autoCalculated = false;
39
40   public String annotationId;
41   
42   public SequenceI sequenceRef;
43
44   /** DOCUMENT ME!! */
45   public String label;
46
47   /** DOCUMENT ME!! */
48   public String description;
49
50   /** DOCUMENT ME!! */
51   public Annotation[] annotations;
52
53   /**
54    * RNA secondary structure contact positions
55    */
56   public SequenceFeature[] _rnasecstr = null;
57   /**
58    * position of annotation resulting in invalid WUSS parsing or -1
59    */
60   private long invalidrnastruc=-1;
61   /**
62    * Updates the _rnasecstr field Determines the positions that base pair and
63    * the positions of helices based on secondary structure from a Stockholm file
64    * 
65    * @param RNAannot
66    */
67   private void _updateRnaSecStr(CharSequence RNAannot)
68   {
69     try {
70     _rnasecstr = Rna.GetBasePairs(RNAannot);
71     invalidrnastruc=-1;
72     }
73     catch (WUSSParseException px)
74     {
75       invalidrnastruc=px.getProblemPos();
76     }
77     if (invalidrnastruc>-1)
78     {
79       return;
80     }
81     Rna.HelixMap(_rnasecstr);
82     // setRNAStruc(RNAannot);
83     
84     if (_rnasecstr != null && _rnasecstr.length > 0)
85     {
86       // show all the RNA secondary structure annotation symbols.
87       isrna=true;
88       showAllColLabels = true;
89       scaleColLabel = true;
90     }
91     // System.out.println("featuregroup " + _rnasecstr[0].getFeatureGroup());
92   }
93   public java.util.Hashtable sequenceMapping;
94
95   /** DOCUMENT ME!! */
96   public float graphMin;
97
98   /** DOCUMENT ME!! */
99   public float graphMax;
100
101   /**
102    * Score associated with label and description.
103    */
104   public double score = Double.NaN;
105
106   /**
107    * flag indicating if annotation has a score.
108    */
109   public boolean hasScore = false;
110
111   public GraphLine threshold;
112
113   // Graphical hints and tips
114
115   /** Can this row be edited by the user ? */
116   public boolean editable = false;
117
118   /** Indicates if annotation has a graphical symbol track */
119   public boolean hasIcons; //
120
121   /** Indicates if annotation has a text character label */
122   public boolean hasText;
123
124   /** is the row visible */
125   public boolean visible = true;
126
127   public int graphGroup = -1;
128
129   /** Displayed height of row in pixels */
130   public int height = 0;
131
132   public int graph = 0;
133
134   public int graphHeight = 40;
135
136   public boolean padGaps = false;
137
138   public static final int NO_GRAPH = 0;
139
140   public static final int BAR_GRAPH = 1;
141
142   public static final int LINE_GRAPH = 2;
143
144   public boolean belowAlignment = true;
145
146   public SequenceGroup groupRef = null;
147
148   /**
149    * display every column label, even if there is a row of identical labels
150    */
151   public boolean showAllColLabels = false;
152
153   /**
154    * scale the column label to fit within the alignment column.
155    */
156   public boolean scaleColLabel = false;
157
158   /**
159    * centre the column labels relative to the alignment column
160    */
161   public boolean centreColLabels = false;
162
163   private boolean isrna;
164
165   /*
166    * (non-Javadoc)
167    * 
168    * @see java.lang.Object#finalize()
169    */
170   protected void finalize() throws Throwable
171   {
172     sequenceRef = null;
173     groupRef = null;
174     super.finalize();
175   }
176
177   public static int getGraphValueFromString(String string)
178   {
179     if (string.equalsIgnoreCase("BAR_GRAPH"))
180     {
181       return BAR_GRAPH;
182     }
183     else if (string.equalsIgnoreCase("LINE_GRAPH"))
184     {
185       return LINE_GRAPH;
186     }
187     else
188     {
189       return NO_GRAPH;
190     }
191   }
192
193   /**
194    * Creates a new AlignmentAnnotation object.
195    * 
196    * @param label
197    *          short label shown under sequence labels
198    * @param description
199    *          text displayed on mouseover
200    * @param annotations
201    *          set of positional annotation elements
202    */
203   public AlignmentAnnotation(String label, String description,
204           Annotation[] annotations)
205   {
206     // always editable?
207     editable = true;
208     this.label = label;
209     this.description = description;
210     this.annotations = annotations;
211
212     validateRangeAndDisplay();
213   }
214
215   /**
216    * Checks if annotation labels represent secondary structures
217    * 
218    */
219   void areLabelsSecondaryStructure()
220   {
221     boolean nonSSLabel = false;
222     isrna = false;
223     StringBuffer rnastring = new StringBuffer();
224
225     char firstChar = 0;
226     for (int i = 0; i < annotations.length; i++)
227     {
228       if (annotations[i] == null)
229       {
230         continue;
231       }
232       if (annotations[i].secondaryStructure == 'H'
233               || annotations[i].secondaryStructure == 'E')
234       {
235         hasIcons |= true;
236       }
237       else
238       // Check for RNA secondary structure
239       {
240         if (annotations[i].secondaryStructure == 'S')
241         {
242           hasIcons |= true;
243           isrna |= true;
244         }
245       }
246
247       // System.out.println("displaychar " + annotations[i].displayCharacter);
248
249       if (annotations[i].displayCharacter == null
250               || annotations[i].displayCharacter.length() == 0)
251       {
252         rnastring.append('.');
253         continue;
254       }
255       if (annotations[i].displayCharacter.length() == 1)
256       {
257         firstChar = annotations[i].displayCharacter.charAt(0);
258         // check to see if it looks like a sequence or is secondary structure
259         // labelling.
260         if (annotations[i].secondaryStructure != ' '
261                 && !hasIcons
262                 &&
263                 // Uncomment to only catch case where
264                 // displayCharacter==secondary
265                 // Structure
266                 // to correctly redisplay SS annotation imported from Stockholm,
267                 // exported to JalviewXML and read back in again.
268                 // &&
269                 // annotations[i].displayCharacter.charAt(0)==annotations[i].secondaryStructure
270                 firstChar != ' '
271                 && firstChar != 'H'
272                 && firstChar != 'E'
273                 && firstChar != 'S'
274                 && firstChar != '-'
275                 && firstChar < jalview.schemes.ResidueProperties.aaIndex.length)
276         {
277           if (jalview.schemes.ResidueProperties.aaIndex[firstChar] < 23) // TODO:
278                                                                          // parameterise
279                                                                          // to
280                                                                          // gap
281                                                                          // symbol
282                                                                          // number
283           {
284             nonSSLabel = true;
285           }
286         }
287       }
288       else
289       {
290         rnastring.append(annotations[i].displayCharacter.charAt(1));
291       }
292
293       if (annotations[i].displayCharacter.length() > 0)
294       {
295         hasText = true;
296       }
297     }
298
299     if (nonSSLabel)
300     {
301       hasIcons = false;
302       for (int j = 0; j < annotations.length; j++)
303       {
304         if (annotations[j] != null
305                 && annotations[j].secondaryStructure != ' ')
306         {
307           annotations[j].displayCharacter = String
308                   .valueOf(annotations[j].secondaryStructure);
309           annotations[j].secondaryStructure = ' ';
310         }
311
312       }
313     }
314     else
315     {
316       if (isrna)
317       {
318         _updateRnaSecStr(new AnnotCharSequence());
319       }
320     }
321
322     annotationId = this.hashCode() + "";
323   }
324   /**
325    * flyweight access to positions in the alignment annotation row for RNA processing
326    * @author jimp
327    *
328    */
329   private class AnnotCharSequence  implements CharSequence 
330   {
331     int offset=0;
332     int max=0;
333     
334     public AnnotCharSequence() {
335       this(0,annotations.length);
336     }
337     public AnnotCharSequence(int start, int end) {
338       offset=start;
339       max=end;
340     }
341     @Override
342     public CharSequence subSequence(int start, int end)
343     {
344       return new AnnotCharSequence(offset+start, offset+end);
345     }
346     
347     @Override
348     public int length()
349     {
350       return max-offset;
351     }
352     
353     @Override
354     public char charAt(int index)
355     {
356       String dc;
357       return ((index+offset<0) || (index+offset)>=max || annotations[index+offset]==null || (dc=annotations[index+offset].displayCharacter.trim()).length()<1)
358               ? '.' : dc.charAt(0);
359     }
360     public String toString()
361     {
362       char[] string=new char[max-offset];
363       int mx=annotations.length;
364         
365       for (int i=offset;i<mx;i++)
366       {
367         String dc;
368         string[i]=(annotations[i]==null || (dc=annotations[i].displayCharacter.trim()).length()<1 )? '.' : dc.charAt(0);
369       }
370       return new String(string);
371     }
372   };
373   
374   private long _lastrnaannot=-1;
375   public String getRNAStruc(){
376     if (isrna)
377     {
378       String rnastruc = new AnnotCharSequence().toString();
379       if (_lastrnaannot!=rnastruc.hashCode())
380       {
381         // ensure rna structure contacts are up to date
382         _lastrnaannot=rnastruc.hashCode();
383         _updateRnaSecStr(rnastruc);
384       }
385       return rnastruc;
386     }
387     return null;
388   }
389
390 /**
391    * Creates a new AlignmentAnnotation object.
392    * 
393    * @param label
394    *          DOCUMENT ME!
395    * @param description
396    *          DOCUMENT ME!
397    * @param annotations
398    *          DOCUMENT ME!
399    * @param min
400    *          DOCUMENT ME!
401    * @param max
402    *          DOCUMENT ME!
403    * @param winLength
404    *          DOCUMENT ME!
405    */
406   public AlignmentAnnotation(String label, String description,
407           Annotation[] annotations, float min, float max, int graphType)
408   {
409     // graphs are not editable
410     editable = graphType == 0;
411
412     this.label = label;
413     this.description = description;
414     this.annotations = annotations;
415     graph = graphType;
416     graphMin = min;
417     graphMax = max;
418     validateRangeAndDisplay();
419   }
420
421   /**
422    * checks graphMin and graphMax, secondary structure symbols, sets graphType
423    * appropriately, sets null labels to the empty string if appropriate.
424    */
425   public void validateRangeAndDisplay()
426   {
427
428     if (annotations == null)
429     {
430       visible = false; // try to prevent renderer from displaying.
431       return; // this is a non-annotation row annotation - ie a sequence score.
432     }
433
434     int graphType = graph;
435     float min = graphMin;
436     float max = graphMax;
437     boolean drawValues = true;
438
439     if (min == max)
440     {
441       min = 999999999;
442       for (int i = 0; i < annotations.length; i++)
443       {
444         if (annotations[i] == null)
445         {
446           continue;
447         }
448
449         if (drawValues && annotations[i].displayCharacter != null
450                 && annotations[i].displayCharacter.length() > 1)
451         {
452           drawValues = false;
453         }
454
455         if (annotations[i].value > max)
456         {
457           max = annotations[i].value;
458         }
459
460         if (annotations[i].value < min)
461         {
462           min = annotations[i].value;
463         }
464       }
465       // ensure zero is origin for min/max ranges on only one side of zero
466       if (min > 0)
467       {
468         min = 0;
469       }
470       else
471       {
472         if (max < 0)
473         {
474           max = 0;
475         }
476       }
477     }
478
479     graphMin = min;
480     graphMax = max;
481
482     areLabelsSecondaryStructure();
483
484     if (!drawValues && graphType != NO_GRAPH)
485     {
486       for (int i = 0; i < annotations.length; i++)
487       {
488         if (annotations[i] != null)
489         {
490           annotations[i].displayCharacter = "";
491         }
492       }
493     }
494   }
495
496   /**
497    * Copy constructor creates a new independent annotation row with the same
498    * associated sequenceRef
499    * 
500    * @param annotation
501    */
502   public AlignmentAnnotation(AlignmentAnnotation annotation)
503   {
504     this.label = new String(annotation.label);
505     if (annotation.description != null)
506       this.description = new String(annotation.description);
507     this.graphMin = annotation.graphMin;
508     this.graphMax = annotation.graphMax;
509     this.graph = annotation.graph;
510     this.graphHeight = annotation.graphHeight;
511     this.graphGroup = annotation.graphGroup;
512     this.groupRef = annotation.groupRef;
513     this.editable = annotation.editable;
514     this.autoCalculated = annotation.autoCalculated;
515     this.hasIcons = annotation.hasIcons;
516     this.hasText = annotation.hasText;
517     this.height = annotation.height;
518     this.label = annotation.label;
519     this.padGaps = annotation.padGaps;
520     this.visible = annotation.visible;
521     if (this.hasScore = annotation.hasScore)
522     {
523       this.score = annotation.score;
524     }
525     if (annotation.threshold != null)
526     {
527       threshold = new GraphLine(annotation.threshold);
528     }
529     if (annotation.annotations != null)
530     {
531       Annotation[] ann = annotation.annotations;
532       this.annotations = new Annotation[ann.length];
533       for (int i = 0; i < ann.length; i++)
534       {
535         annotations[i] = new Annotation(ann[i]);
536       }
537       ;
538       if (annotation.sequenceRef != null)
539       {
540         this.sequenceRef = annotation.sequenceRef;
541         if (annotation.sequenceMapping != null)
542         {
543           Integer p = null;
544           sequenceMapping = new Hashtable();
545           Enumeration pos = annotation.sequenceMapping.keys();
546           while (pos.hasMoreElements())
547           {
548             // could optimise this!
549             p = (Integer) pos.nextElement();
550             Annotation a = (Annotation) annotation.sequenceMapping.get(p);
551             if (a == null)
552             {
553               continue;
554             }
555             for (int i = 0; i < ann.length; i++)
556             {
557               if (ann[i] == a)
558               {
559                 sequenceMapping.put(p, annotations[i]);
560               }
561             }
562           }
563         }
564         else
565         {
566           this.sequenceMapping = null;
567         }
568       }
569     }
570     validateRangeAndDisplay(); // construct hashcodes, etc.
571   }
572
573   /**
574    * clip the annotation to the columns given by startRes and endRes (inclusive)
575    * and prune any existing sequenceMapping to just those columns.
576    * 
577    * @param startRes
578    * @param endRes
579    */
580   public void restrict(int startRes, int endRes)
581   {
582     if (annotations == null)
583     {
584       // non-positional
585       return;
586     }
587     if (startRes < 0)
588       startRes = 0;
589     if (startRes >= annotations.length)
590       startRes = annotations.length - 1;
591     if (endRes >= annotations.length)
592       endRes = annotations.length - 1;
593     if (annotations == null)
594       return;
595     Annotation[] temp = new Annotation[endRes - startRes + 1];
596     if (startRes < annotations.length)
597     {
598       System.arraycopy(annotations, startRes, temp, 0, endRes - startRes
599               + 1);
600     }
601     if (sequenceRef != null)
602     {
603       // Clip the mapping, if it exists.
604       int spos = sequenceRef.findPosition(startRes);
605       int epos = sequenceRef.findPosition(endRes);
606       if (sequenceMapping != null)
607       {
608         Hashtable newmapping = new Hashtable();
609         Enumeration e = sequenceMapping.keys();
610         while (e.hasMoreElements())
611         {
612           Integer pos = (Integer) e.nextElement();
613           if (pos.intValue() >= spos && pos.intValue() <= epos)
614           {
615             newmapping.put(pos, sequenceMapping.get(pos));
616           }
617         }
618         sequenceMapping.clear();
619         sequenceMapping = newmapping;
620       }
621     }
622     annotations = temp;
623   }
624
625   /**
626    * set the annotation row to be at least length Annotations
627    * 
628    * @param length
629    *          minimum number of columns required in the annotation row
630    * @return false if the annotation row is greater than length
631    */
632   public boolean padAnnotation(int length)
633   {
634     if (annotations == null)
635     {
636       return true; // annotation row is correct - null == not visible and
637       // undefined length
638     }
639     if (annotations.length < length)
640     {
641       Annotation[] na = new Annotation[length];
642       System.arraycopy(annotations, 0, na, 0, annotations.length);
643       annotations = na;
644       return true;
645     }
646     return annotations.length > length;
647
648   }
649
650   /**
651    * DOCUMENT ME!
652    * 
653    * @return DOCUMENT ME!
654    */
655   public String toString()
656   {
657     StringBuffer buffer = new StringBuffer();
658
659     for (int i = 0; i < annotations.length; i++)
660     {
661       if (annotations[i] != null)
662       {
663         if (graph != 0)
664         {
665           buffer.append(annotations[i].value);
666         }
667         else if (hasIcons)
668         {
669           buffer.append(annotations[i].secondaryStructure);
670         }
671         else
672         {
673           buffer.append(annotations[i].displayCharacter);
674         }
675       }
676
677       buffer.append(", ");
678     }
679     // TODO: remove disgusting hack for 'special' treatment of consensus line.
680     if (label.indexOf("Consensus") == 0)
681     {
682       buffer.append("\n");
683
684       for (int i = 0; i < annotations.length; i++)
685       {
686         if (annotations[i] != null)
687         {
688           buffer.append(annotations[i].description);
689         }
690
691         buffer.append(", ");
692       }
693     }
694
695     return buffer.toString();
696   }
697
698   public void setThreshold(GraphLine line)
699   {
700     threshold = line;
701   }
702
703   public GraphLine getThreshold()
704   {
705     return threshold;
706   }
707
708   /**
709    * Attach the annotation to seqRef, starting from startRes position. If
710    * alreadyMapped is true then the indices of the annotation[] array are
711    * sequence positions rather than alignment column positions.
712    * 
713    * @param seqRef
714    * @param startRes
715    * @param alreadyMapped
716    */
717   public void createSequenceMapping(SequenceI seqRef, int startRes,
718           boolean alreadyMapped)
719   {
720
721     if (seqRef == null)
722     {
723       return;
724     }
725     sequenceRef = seqRef;
726     if (annotations == null)
727     {
728       return;
729     }
730     sequenceMapping = new java.util.Hashtable();
731
732     int seqPos;
733
734     for (int i = 0; i < annotations.length; i++)
735     {
736       if (annotations[i] != null)
737       {
738         if (alreadyMapped)
739         {
740           seqPos = seqRef.findPosition(i);
741         }
742         else
743         {
744           seqPos = i + startRes;
745         }
746
747         sequenceMapping.put(new Integer(seqPos), annotations[i]);
748       }
749     }
750
751   }
752
753   public void adjustForAlignment()
754   {
755     if (sequenceRef == null)
756       return;
757
758     if (annotations == null)
759     {
760       return;
761     }
762
763     int a = 0, aSize = sequenceRef.getLength();
764
765     if (aSize == 0)
766     {
767       // Its been deleted
768       return;
769     }
770
771     int position;
772     Annotation[] temp = new Annotation[aSize];
773     Integer index;
774
775     for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++)
776     {
777       index = new Integer(a);
778       if (sequenceMapping.containsKey(index))
779       {
780         position = sequenceRef.findIndex(a) - 1;
781
782         temp[position] = (Annotation) sequenceMapping.get(index);
783       }
784     }
785
786     annotations = temp;
787   }
788
789   /**
790    * remove any null entries in annotation row and return the number of non-null
791    * annotation elements.
792    * 
793    * @return
794    */
795   public int compactAnnotationArray()
796   {
797     int i = 0, iSize = annotations.length;
798     while (i < iSize)
799     {
800       if (annotations[i] == null)
801       {
802         if (i + 1 < iSize)
803           System.arraycopy(annotations, i + 1, annotations, i, iSize - i
804                   - 1);
805         iSize--;
806       }
807       else
808       {
809         i++;
810       }
811     }
812     Annotation[] ann = annotations;
813     annotations = new Annotation[i];
814     System.arraycopy(ann, 0, annotations, 0, i);
815     ann = null;
816     return iSize;
817   }
818
819   /**
820    * Associate this annotion with the aligned residues of a particular sequence.
821    * sequenceMapping will be updated in the following way: null sequenceI -
822    * existing mapping will be discarded but annotations left in mapped
823    * positions. valid sequenceI not equal to current sequenceRef: mapping is
824    * discarded and rebuilt assuming 1:1 correspondence TODO: overload with
825    * parameter to specify correspondence between current and new sequenceRef
826    * 
827    * @param sequenceI
828    */
829   public void setSequenceRef(SequenceI sequenceI)
830   {
831     if (sequenceI != null)
832     {
833       if (sequenceRef != null)
834       {
835         if (sequenceRef != sequenceI
836                 && !sequenceRef.equals(sequenceI)
837                 && sequenceRef.getDatasetSequence() != sequenceI
838                         .getDatasetSequence())
839         {
840           // if sequenceRef isn't intersecting with sequenceI
841           // throw away old mapping and reconstruct.
842           sequenceRef = null;
843           if (sequenceMapping != null)
844           {
845             sequenceMapping = null;
846             // compactAnnotationArray();
847           }
848           createSequenceMapping(sequenceI, 1, true);
849           adjustForAlignment();
850         }
851         else
852         {
853           // Mapping carried over
854           sequenceRef = sequenceI;
855         }
856       }
857       else
858       {
859         // No mapping exists
860         createSequenceMapping(sequenceI, 1, true);
861         adjustForAlignment();
862       }
863     }
864     else
865     {
866       // throw away the mapping without compacting.
867       sequenceMapping = null;
868       sequenceRef = null;
869     }
870   }
871
872   /**
873    * @return the score
874    */
875   public double getScore()
876   {
877     return score;
878   }
879
880   /**
881    * @param score
882    *          the score to set
883    */
884   public void setScore(double score)
885   {
886     hasScore = true;
887     this.score = score;
888   }
889
890   /**
891    * 
892    * @return true if annotation has an associated score
893    */
894   public boolean hasScore()
895   {
896     return hasScore || !Double.isNaN(score);
897   }
898
899   /**
900    * Score only annotation
901    * 
902    * @param label
903    * @param description
904    * @param score
905    */
906   public AlignmentAnnotation(String label, String description, double score)
907   {
908     this(label, description, null);
909     setScore(score);
910   }
911
912   /**
913    * copy constructor with edit based on the hidden columns marked in colSel
914    * 
915    * @param alignmentAnnotation
916    * @param colSel
917    */
918   public AlignmentAnnotation(AlignmentAnnotation alignmentAnnotation,
919           ColumnSelection colSel)
920   {
921     this(alignmentAnnotation);
922     if (annotations == null)
923     {
924       return;
925     }
926     colSel.makeVisibleAnnotation(this);
927   }
928
929   public void setPadGaps(boolean padgaps, char gapchar)
930   {
931     this.padGaps = padgaps;
932     if (padgaps)
933     {
934       hasText = true;
935       for (int i = 0; i < annotations.length; i++)
936       {
937         if (annotations[i] == null)
938           annotations[i] = new Annotation(String.valueOf(gapchar), null,
939                   ' ', 0f);
940         else if (annotations[i].displayCharacter == null
941                 || annotations[i].displayCharacter.equals(" "))
942           annotations[i].displayCharacter = String.valueOf(gapchar);
943       }
944     }
945   }
946
947   /**
948    * format description string for display
949    * 
950    * @param seqname
951    * @return Get the annotation description string optionally prefixed by
952    *         associated sequence name (if any)
953    */
954   public String getDescription(boolean seqname)
955   {
956     if (seqname && this.sequenceRef != null)
957     {
958       int i=description.toLowerCase().indexOf("<html>");
959       if (i>-1)
960       {
961         // move the html tag to before the sequence reference.
962         return "<html>"+sequenceRef.getName()+" : "+description.substring(i+6);
963       }
964       return sequenceRef.getName() + " : " + description;
965     }
966     return description;
967   }
968
969   public boolean isValidStruc()
970   {
971     return invalidrnastruc==-1;
972   }
973   public long getInvalidStrucPos()
974   {
975     return invalidrnastruc;
976   }
977   
978 }