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