update the copy constructor
[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     this.centreColLabels=annotation.centreColLabels;
522     this.scaleColLabel=annotation.scaleColLabel;
523     this.showAllColLabels=annotation.showAllColLabels;
524     this.calcId = annotation.calcId;
525     if (this.hasScore = annotation.hasScore)
526     {
527       this.score = annotation.score;
528     }
529     if (annotation.threshold != null)
530     {
531       threshold = new GraphLine(annotation.threshold);
532     }
533     if (annotation.annotations != null)
534     {
535       Annotation[] ann = annotation.annotations;
536       this.annotations = new Annotation[ann.length];
537       for (int i = 0; i < ann.length; i++)
538       {
539         annotations[i] = new Annotation(ann[i]);
540       }
541       ;
542       if (annotation.sequenceRef != null)
543       {
544         this.sequenceRef = annotation.sequenceRef;
545         if (annotation.sequenceMapping != null)
546         {
547           Integer p = null;
548           sequenceMapping = new Hashtable();
549           Enumeration pos = annotation.sequenceMapping.keys();
550           while (pos.hasMoreElements())
551           {
552             // could optimise this!
553             p = (Integer) pos.nextElement();
554             Annotation a = (Annotation) annotation.sequenceMapping.get(p);
555             if (a == null)
556             {
557               continue;
558             }
559             for (int i = 0; i < ann.length; i++)
560             {
561               if (ann[i] == a)
562               {
563                 sequenceMapping.put(p, annotations[i]);
564               }
565             }
566           }
567         }
568         else
569         {
570           this.sequenceMapping = null;
571         }
572       }
573     }
574     // TODO: check if we need to do this: JAL-952
575     //if (this.isrna=annotation.isrna)
576     {
577       // _rnasecstr=new SequenceFeature[annotation._rnasecstr];
578     }
579     validateRangeAndDisplay(); // construct hashcodes, etc.
580   }
581
582   /**
583    * clip the annotation to the columns given by startRes and endRes (inclusive)
584    * and prune any existing sequenceMapping to just those columns.
585    * 
586    * @param startRes
587    * @param endRes
588    */
589   public void restrict(int startRes, int endRes)
590   {
591     if (annotations == null)
592     {
593       // non-positional
594       return;
595     }
596     if (startRes < 0)
597       startRes = 0;
598     if (startRes >= annotations.length)
599       startRes = annotations.length - 1;
600     if (endRes >= annotations.length)
601       endRes = annotations.length - 1;
602     if (annotations == null)
603       return;
604     Annotation[] temp = new Annotation[endRes - startRes + 1];
605     if (startRes < annotations.length)
606     {
607       System.arraycopy(annotations, startRes, temp, 0, endRes - startRes
608               + 1);
609     }
610     if (sequenceRef != null)
611     {
612       // Clip the mapping, if it exists.
613       int spos = sequenceRef.findPosition(startRes);
614       int epos = sequenceRef.findPosition(endRes);
615       if (sequenceMapping != null)
616       {
617         Hashtable newmapping = new Hashtable();
618         Enumeration e = sequenceMapping.keys();
619         while (e.hasMoreElements())
620         {
621           Integer pos = (Integer) e.nextElement();
622           if (pos.intValue() >= spos && pos.intValue() <= epos)
623           {
624             newmapping.put(pos, sequenceMapping.get(pos));
625           }
626         }
627         sequenceMapping.clear();
628         sequenceMapping = newmapping;
629       }
630     }
631     annotations = temp;
632   }
633
634   /**
635    * set the annotation row to be at least length Annotations
636    * 
637    * @param length
638    *          minimum number of columns required in the annotation row
639    * @return false if the annotation row is greater than length
640    */
641   public boolean padAnnotation(int length)
642   {
643     if (annotations == null)
644     {
645       return true; // annotation row is correct - null == not visible and
646       // undefined length
647     }
648     if (annotations.length < length)
649     {
650       Annotation[] na = new Annotation[length];
651       System.arraycopy(annotations, 0, na, 0, annotations.length);
652       annotations = na;
653       return true;
654     }
655     return annotations.length > length;
656
657   }
658
659   /**
660    * DOCUMENT ME!
661    * 
662    * @return DOCUMENT ME!
663    */
664   public String toString()
665   {
666     StringBuffer buffer = new StringBuffer();
667
668     for (int i = 0; i < annotations.length; i++)
669     {
670       if (annotations[i] != null)
671       {
672         if (graph != 0)
673         {
674           buffer.append(annotations[i].value);
675         }
676         else if (hasIcons)
677         {
678           buffer.append(annotations[i].secondaryStructure);
679         }
680         else
681         {
682           buffer.append(annotations[i].displayCharacter);
683         }
684       }
685
686       buffer.append(", ");
687     }
688     // TODO: remove disgusting hack for 'special' treatment of consensus line.
689     if (label.indexOf("Consensus") == 0)
690     {
691       buffer.append("\n");
692
693       for (int i = 0; i < annotations.length; i++)
694       {
695         if (annotations[i] != null)
696         {
697           buffer.append(annotations[i].description);
698         }
699
700         buffer.append(", ");
701       }
702     }
703
704     return buffer.toString();
705   }
706
707   public void setThreshold(GraphLine line)
708   {
709     threshold = line;
710   }
711
712   public GraphLine getThreshold()
713   {
714     return threshold;
715   }
716
717   /**
718    * Attach the annotation to seqRef, starting from startRes position. If
719    * alreadyMapped is true then the indices of the annotation[] array are
720    * sequence positions rather than alignment column positions.
721    * 
722    * @param seqRef
723    * @param startRes
724    * @param alreadyMapped
725    */
726   public void createSequenceMapping(SequenceI seqRef, int startRes,
727           boolean alreadyMapped)
728   {
729
730     if (seqRef == null)
731     {
732       return;
733     }
734     sequenceRef = seqRef;
735     if (annotations == null)
736     {
737       return;
738     }
739     sequenceMapping = new java.util.Hashtable();
740
741     int seqPos;
742
743     for (int i = 0; i < annotations.length; i++)
744     {
745       if (annotations[i] != null)
746       {
747         if (alreadyMapped)
748         {
749           seqPos = seqRef.findPosition(i);
750         }
751         else
752         {
753           seqPos = i + startRes;
754         }
755
756         sequenceMapping.put(new Integer(seqPos), annotations[i]);
757       }
758     }
759
760   }
761
762   public void adjustForAlignment()
763   {
764     if (sequenceRef == null)
765       return;
766
767     if (annotations == null)
768     {
769       return;
770     }
771
772     int a = 0, aSize = sequenceRef.getLength();
773
774     if (aSize == 0)
775     {
776       // Its been deleted
777       return;
778     }
779
780     int position;
781     Annotation[] temp = new Annotation[aSize];
782     Integer index;
783
784     for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++)
785     {
786       index = new Integer(a);
787       if (sequenceMapping.containsKey(index))
788       {
789         position = sequenceRef.findIndex(a) - 1;
790
791         temp[position] = (Annotation) sequenceMapping.get(index);
792       }
793     }
794
795     annotations = temp;
796   }
797
798   /**
799    * remove any null entries in annotation row and return the number of non-null
800    * annotation elements.
801    * 
802    * @return
803    */
804   public int compactAnnotationArray()
805   {
806     int i = 0, iSize = annotations.length;
807     while (i < iSize)
808     {
809       if (annotations[i] == null)
810       {
811         if (i + 1 < iSize)
812           System.arraycopy(annotations, i + 1, annotations, i, iSize - i
813                   - 1);
814         iSize--;
815       }
816       else
817       {
818         i++;
819       }
820     }
821     Annotation[] ann = annotations;
822     annotations = new Annotation[i];
823     System.arraycopy(ann, 0, annotations, 0, i);
824     ann = null;
825     return iSize;
826   }
827
828   /**
829    * Associate this annotion with the aligned residues of a particular sequence.
830    * sequenceMapping will be updated in the following way: null sequenceI -
831    * existing mapping will be discarded but annotations left in mapped
832    * positions. valid sequenceI not equal to current sequenceRef: mapping is
833    * discarded and rebuilt assuming 1:1 correspondence TODO: overload with
834    * parameter to specify correspondence between current and new sequenceRef
835    * 
836    * @param sequenceI
837    */
838   public void setSequenceRef(SequenceI sequenceI)
839   {
840     if (sequenceI != null)
841     {
842       if (sequenceRef != null)
843       {
844         if (sequenceRef != sequenceI
845                 && !sequenceRef.equals(sequenceI)
846                 && sequenceRef.getDatasetSequence() != sequenceI
847                         .getDatasetSequence())
848         {
849           // if sequenceRef isn't intersecting with sequenceI
850           // throw away old mapping and reconstruct.
851           sequenceRef = null;
852           if (sequenceMapping != null)
853           {
854             sequenceMapping = null;
855             // compactAnnotationArray();
856           }
857           createSequenceMapping(sequenceI, 1, true);
858           adjustForAlignment();
859         }
860         else
861         {
862           // Mapping carried over
863           sequenceRef = sequenceI;
864         }
865       }
866       else
867       {
868         // No mapping exists
869         createSequenceMapping(sequenceI, 1, true);
870         adjustForAlignment();
871       }
872     }
873     else
874     {
875       // throw away the mapping without compacting.
876       sequenceMapping = null;
877       sequenceRef = null;
878     }
879   }
880
881   /**
882    * @return the score
883    */
884   public double getScore()
885   {
886     return score;
887   }
888
889   /**
890    * @param score
891    *          the score to set
892    */
893   public void setScore(double score)
894   {
895     hasScore = true;
896     this.score = score;
897   }
898
899   /**
900    * 
901    * @return true if annotation has an associated score
902    */
903   public boolean hasScore()
904   {
905     return hasScore || !Double.isNaN(score);
906   }
907
908   /**
909    * Score only annotation
910    * 
911    * @param label
912    * @param description
913    * @param score
914    */
915   public AlignmentAnnotation(String label, String description, double score)
916   {
917     this(label, description, null);
918     setScore(score);
919   }
920
921   /**
922    * copy constructor with edit based on the hidden columns marked in colSel
923    * 
924    * @param alignmentAnnotation
925    * @param colSel
926    */
927   public AlignmentAnnotation(AlignmentAnnotation alignmentAnnotation,
928           ColumnSelection colSel)
929   {
930     this(alignmentAnnotation);
931     if (annotations == null)
932     {
933       return;
934     }
935     colSel.makeVisibleAnnotation(this);
936   }
937
938   public void setPadGaps(boolean padgaps, char gapchar)
939   {
940     this.padGaps = padgaps;
941     if (padgaps)
942     {
943       hasText = true;
944       for (int i = 0; i < annotations.length; i++)
945       {
946         if (annotations[i] == null)
947           annotations[i] = new Annotation(String.valueOf(gapchar), null,
948                   ' ', 0f);
949         else if (annotations[i].displayCharacter == null
950                 || annotations[i].displayCharacter.equals(" "))
951           annotations[i].displayCharacter = String.valueOf(gapchar);
952       }
953     }
954   }
955
956   /**
957    * format description string for display
958    * 
959    * @param seqname
960    * @return Get the annotation description string optionally prefixed by
961    *         associated sequence name (if any)
962    */
963   public String getDescription(boolean seqname)
964   {
965     if (seqname && this.sequenceRef != null)
966     {
967       int i=description.toLowerCase().indexOf("<html>");
968       if (i>-1)
969       {
970         // move the html tag to before the sequence reference.
971         return "<html>"+sequenceRef.getName()+" : "+description.substring(i+6);
972       }
973       return sequenceRef.getName() + " : " + description;
974     }
975     return description;
976   }
977
978   public boolean isValidStruc()
979   {
980     return invalidrnastruc==-1;
981   }
982   public long getInvalidStrucPos()
983   {
984     return invalidrnastruc;
985   }
986
987   /**
988    * machine readable ID string indicating what generated this annotation
989    */
990   protected String calcId="";
991   public String getCalcId()
992   {
993     return calcId;
994   }
995
996   public void setCalcId(String calcId)
997   {
998     this.calcId = calcId;
999   }
1000   
1001 }