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