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