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