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