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