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