JAL-674 JAL-961 properties to stash additional information for an alignment annotatio...
[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 import jalview.analysis.WUSSParseException;
26
27 import java.util.ArrayList;
28 import java.util.Enumeration;
29 import java.util.HashMap;
30 import java.util.Hashtable;
31 import java.util.Map;
32 import java.util.Map.Entry;
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<Integer, Annotation> 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         // TODO: 2.8.2 should this ss symbol validation check be a function in
263         // RNA/ResidueProperties ?
264         if (annotations[i].secondaryStructure == '('
265                 || annotations[i].secondaryStructure == '['
266                 || annotations[i].secondaryStructure == '<'
267                 || annotations[i].secondaryStructure == '{'
268                 || annotations[i].secondaryStructure == 'A'
269                 || annotations[i].secondaryStructure == 'B'
270                 || annotations[i].secondaryStructure == 'C'
271                 || annotations[i].secondaryStructure == 'D'
272                 || annotations[i].secondaryStructure == 'E'
273                 || annotations[i].secondaryStructure == 'F'
274                 || annotations[i].secondaryStructure == 'G'
275                 || annotations[i].secondaryStructure == 'H'
276                 || annotations[i].secondaryStructure == 'I'
277                 || annotations[i].secondaryStructure == 'J'
278                 || annotations[i].secondaryStructure == 'K'
279                 || annotations[i].secondaryStructure == 'L'
280                 || annotations[i].secondaryStructure == 'M'
281                 || annotations[i].secondaryStructure == 'N'
282                 || annotations[i].secondaryStructure == 'O'
283                 || annotations[i].secondaryStructure == 'P'
284                 || annotations[i].secondaryStructure == 'Q'
285                 || annotations[i].secondaryStructure == 'R'
286                 || annotations[i].secondaryStructure == 'S'
287                 || annotations[i].secondaryStructure == 'T'
288                 || annotations[i].secondaryStructure == 'U'
289                 || annotations[i].secondaryStructure == 'V'
290                 || annotations[i].secondaryStructure == 'W'
291                 || annotations[i].secondaryStructure == 'X'
292                 || annotations[i].secondaryStructure == 'Y'
293                 || annotations[i].secondaryStructure == 'Z')
294         {
295           hasIcons |= true;
296           isrna |= true;
297         }
298       }
299
300       // System.out.println("displaychar " + annotations[i].displayCharacter);
301
302       if (annotations[i].displayCharacter == null
303               || annotations[i].displayCharacter.length() == 0)
304       {
305         rnastring.append('.');
306         continue;
307       }
308       if (annotations[i].displayCharacter.length() == 1)
309       {
310         firstChar = annotations[i].displayCharacter.charAt(0);
311         // check to see if it looks like a sequence or is secondary structure
312         // labelling.
313         if (annotations[i].secondaryStructure != ' '
314                 && !hasIcons
315                 &&
316                 // Uncomment to only catch case where
317                 // displayCharacter==secondary
318                 // Structure
319                 // to correctly redisplay SS annotation imported from Stockholm,
320                 // exported to JalviewXML and read back in again.
321                 // &&
322                 // annotations[i].displayCharacter.charAt(0)==annotations[i].secondaryStructure
323                 firstChar != ' '
324                 && firstChar != '$'
325                 && firstChar != 0xCE
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     {
607       this.description = new String(annotation.description);
608     }
609     this.graphMin = annotation.graphMin;
610     this.graphMax = annotation.graphMax;
611     this.graph = annotation.graph;
612     this.graphHeight = annotation.graphHeight;
613     this.graphGroup = annotation.graphGroup;
614     this.groupRef = annotation.groupRef;
615     this.editable = annotation.editable;
616     this.autoCalculated = annotation.autoCalculated;
617     this.hasIcons = annotation.hasIcons;
618     this.hasText = annotation.hasText;
619     this.height = annotation.height;
620     this.label = annotation.label;
621     this.padGaps = annotation.padGaps;
622     this.visible = annotation.visible;
623     this.centreColLabels = annotation.centreColLabels;
624     this.scaleColLabel = annotation.scaleColLabel;
625     this.showAllColLabels = annotation.showAllColLabels;
626     this.calcId = annotation.calcId;
627     if (this.hasScore = annotation.hasScore)
628     {
629       this.score = annotation.score;
630     }
631     if (annotation.threshold != null)
632     {
633       threshold = new GraphLine(annotation.threshold);
634     }
635     if (annotation.annotations != null)
636     {
637       Annotation[] ann = annotation.annotations;
638       this.annotations = new Annotation[ann.length];
639       for (int i = 0; i < ann.length; i++)
640       {
641         if (ann[i] != null)
642         {
643           annotations[i] = new Annotation(ann[i]);
644           if (_linecolour != null)
645           {
646             _linecolour = annotations[i].colour;
647           }
648         }
649       }
650       ;
651       if (annotation.sequenceRef != null)
652       {
653         this.sequenceRef = annotation.sequenceRef;
654         if (annotation.sequenceMapping != null)
655         {
656           Integer p = null;
657           sequenceMapping = new Hashtable();
658           Enumeration pos = annotation.sequenceMapping.keys();
659           while (pos.hasMoreElements())
660           {
661             // could optimise this!
662             p = (Integer) pos.nextElement();
663             Annotation a = annotation.sequenceMapping.get(p);
664             if (a == null)
665             {
666               continue;
667             }
668             for (int i = 0; i < ann.length; i++)
669             {
670               if (ann[i] == a)
671               {
672                 sequenceMapping.put(p, annotations[i]);
673               }
674             }
675           }
676         }
677         else
678         {
679           this.sequenceMapping = null;
680         }
681       }
682     }
683     // TODO: check if we need to do this: JAL-952
684     // if (this.isrna=annotation.isrna)
685     {
686       // _rnasecstr=new SequenceFeature[annotation._rnasecstr];
687     }
688     validateRangeAndDisplay(); // construct hashcodes, etc.
689   }
690
691   /**
692    * clip the annotation to the columns given by startRes and endRes (inclusive)
693    * and prune any existing sequenceMapping to just those columns.
694    * 
695    * @param startRes
696    * @param endRes
697    */
698   public void restrict(int startRes, int endRes)
699   {
700     if (annotations == null)
701     {
702       // non-positional
703       return;
704     }
705     if (startRes < 0)
706     {
707       startRes = 0;
708     }
709     if (startRes >= annotations.length)
710     {
711       startRes = annotations.length - 1;
712     }
713     if (endRes >= annotations.length)
714     {
715       endRes = annotations.length - 1;
716     }
717     if (annotations == null)
718     {
719       return;
720     }
721     Annotation[] temp = new Annotation[endRes - startRes + 1];
722     if (startRes < annotations.length)
723     {
724       System.arraycopy(annotations, startRes, temp, 0, endRes - startRes
725               + 1);
726     }
727     if (sequenceRef != null)
728     {
729       // Clip the mapping, if it exists.
730       int spos = sequenceRef.findPosition(startRes);
731       int epos = sequenceRef.findPosition(endRes);
732       if (sequenceMapping != null)
733       {
734         Hashtable newmapping = new Hashtable();
735         Enumeration e = sequenceMapping.keys();
736         while (e.hasMoreElements())
737         {
738           Integer pos = (Integer) e.nextElement();
739           if (pos.intValue() >= spos && pos.intValue() <= epos)
740           {
741             newmapping.put(pos, sequenceMapping.get(pos));
742           }
743         }
744         sequenceMapping.clear();
745         sequenceMapping = newmapping;
746       }
747     }
748     annotations = temp;
749   }
750
751   /**
752    * set the annotation row to be at least length Annotations
753    * 
754    * @param length
755    *          minimum number of columns required in the annotation row
756    * @return false if the annotation row is greater than length
757    */
758   public boolean padAnnotation(int length)
759   {
760     if (annotations == null)
761     {
762       return true; // annotation row is correct - null == not visible and
763       // undefined length
764     }
765     if (annotations.length < length)
766     {
767       Annotation[] na = new Annotation[length];
768       System.arraycopy(annotations, 0, na, 0, annotations.length);
769       annotations = na;
770       return true;
771     }
772     return annotations.length > length;
773
774   }
775
776   /**
777    * DOCUMENT ME!
778    * 
779    * @return DOCUMENT ME!
780    */
781   public String toString()
782   {
783     StringBuffer buffer = new StringBuffer();
784
785     for (int i = 0; i < annotations.length; i++)
786     {
787       if (annotations[i] != null)
788       {
789         if (graph != 0)
790         {
791           buffer.append(annotations[i].value);
792         }
793         else if (hasIcons)
794         {
795           buffer.append(annotations[i].secondaryStructure);
796         }
797         else
798         {
799           buffer.append(annotations[i].displayCharacter);
800         }
801       }
802
803       buffer.append(", ");
804     }
805     // TODO: remove disgusting hack for 'special' treatment of consensus line.
806     if (label.indexOf("Consensus") == 0)
807     {
808       buffer.append("\n");
809
810       for (int i = 0; i < annotations.length; i++)
811       {
812         if (annotations[i] != null)
813         {
814           buffer.append(annotations[i].description);
815         }
816
817         buffer.append(", ");
818       }
819     }
820
821     return buffer.toString();
822   }
823
824   public void setThreshold(GraphLine line)
825   {
826     threshold = line;
827   }
828
829   public GraphLine getThreshold()
830   {
831     return threshold;
832   }
833
834   /**
835    * Attach the annotation to seqRef, starting from startRes position. If
836    * alreadyMapped is true then the indices of the annotation[] array are
837    * sequence positions rather than alignment column positions.
838    * 
839    * @param seqRef
840    * @param startRes
841    * @param alreadyMapped
842    */
843   public void createSequenceMapping(SequenceI seqRef, int startRes,
844           boolean alreadyMapped)
845   {
846
847     if (seqRef == null)
848     {
849       return;
850     }
851     sequenceRef = seqRef;
852     if (annotations == null)
853     {
854       return;
855     }
856     sequenceMapping = new java.util.Hashtable();
857
858     int seqPos;
859
860     for (int i = 0; i < annotations.length; i++)
861     {
862       if (annotations[i] != null)
863       {
864         if (alreadyMapped)
865         {
866           seqPos = seqRef.findPosition(i);
867         }
868         else
869         {
870           seqPos = i + startRes;
871         }
872
873         sequenceMapping.put(new Integer(seqPos), annotations[i]);
874       }
875     }
876
877   }
878
879   public void adjustForAlignment()
880   {
881     if (sequenceRef == null)
882     {
883       return;
884     }
885
886     if (annotations == null)
887     {
888       return;
889     }
890
891     int a = 0, aSize = sequenceRef.getLength();
892
893     if (aSize == 0)
894     {
895       // Its been deleted
896       return;
897     }
898
899     int position;
900     Annotation[] temp = new Annotation[aSize];
901     Integer index;
902
903     for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++)
904     {
905       index = new Integer(a);
906       if (sequenceMapping.containsKey(index))
907       {
908         position = sequenceRef.findIndex(a) - 1;
909
910         temp[position] = sequenceMapping.get(index);
911       }
912     }
913
914     annotations = temp;
915   }
916
917   /**
918    * remove any null entries in annotation row and return the number of non-null
919    * annotation elements.
920    * 
921    * @return
922    */
923   public int compactAnnotationArray()
924   {
925     int i = 0, iSize = annotations.length;
926     while (i < iSize)
927     {
928       if (annotations[i] == null)
929       {
930         if (i + 1 < iSize)
931         {
932           System.arraycopy(annotations, i + 1, annotations, i, iSize - i
933                   - 1);
934         }
935         iSize--;
936       }
937       else
938       {
939         i++;
940       }
941     }
942     Annotation[] ann = annotations;
943     annotations = new Annotation[i];
944     System.arraycopy(ann, 0, annotations, 0, i);
945     ann = null;
946     return iSize;
947   }
948
949   /**
950    * Associate this annotion with the aligned residues of a particular sequence.
951    * sequenceMapping will be updated in the following way: null sequenceI -
952    * existing mapping will be discarded but annotations left in mapped
953    * positions. valid sequenceI not equal to current sequenceRef: mapping is
954    * discarded and rebuilt assuming 1:1 correspondence TODO: overload with
955    * parameter to specify correspondence between current and new sequenceRef
956    * 
957    * @param sequenceI
958    */
959   public void setSequenceRef(SequenceI sequenceI)
960   {
961     if (sequenceI != null)
962     {
963       if (sequenceRef != null)
964       {
965         boolean rIsDs=sequenceRef.getDatasetSequence()==null,tIsDs=sequenceI.getDatasetSequence()==null;
966         if (sequenceRef != sequenceI
967                 && (rIsDs && !tIsDs && sequenceRef != sequenceI
968                         .getDatasetSequence())
969                 && (!rIsDs && tIsDs && sequenceRef.getDatasetSequence() != sequenceI)
970                 && (!rIsDs && !tIsDs && sequenceRef.getDatasetSequence() != sequenceI
971                         .getDatasetSequence())
972                 && !sequenceRef.equals(sequenceI))
973         {
974           // if sequenceRef isn't intersecting with sequenceI
975           // throw away old mapping and reconstruct.
976           sequenceRef = null;
977           if (sequenceMapping != null)
978           {
979             sequenceMapping = null;
980             // compactAnnotationArray();
981           }
982           createSequenceMapping(sequenceI, 1, true);
983           adjustForAlignment();
984         }
985         else
986         {
987           // Mapping carried over
988           sequenceRef = sequenceI;
989         }
990       }
991       else
992       {
993         // No mapping exists
994         createSequenceMapping(sequenceI, 1, true);
995         adjustForAlignment();
996       }
997     }
998     else
999     {
1000       // throw away the mapping without compacting.
1001       sequenceMapping = null;
1002       sequenceRef = null;
1003     }
1004   }
1005
1006   /**
1007    * @return the score
1008    */
1009   public double getScore()
1010   {
1011     return score;
1012   }
1013
1014   /**
1015    * @param score
1016    *          the score to set
1017    */
1018   public void setScore(double score)
1019   {
1020     hasScore = true;
1021     this.score = score;
1022   }
1023
1024   /**
1025    * 
1026    * @return true if annotation has an associated score
1027    */
1028   public boolean hasScore()
1029   {
1030     return hasScore || !Double.isNaN(score);
1031   }
1032
1033   /**
1034    * Score only annotation
1035    * 
1036    * @param label
1037    * @param description
1038    * @param score
1039    */
1040   public AlignmentAnnotation(String label, String description, double score)
1041   {
1042     this(label, description, null);
1043     setScore(score);
1044   }
1045
1046   /**
1047    * copy constructor with edit based on the hidden columns marked in colSel
1048    * 
1049    * @param alignmentAnnotation
1050    * @param colSel
1051    */
1052   public AlignmentAnnotation(AlignmentAnnotation alignmentAnnotation,
1053           ColumnSelection colSel)
1054   {
1055     this(alignmentAnnotation);
1056     if (annotations == null)
1057     {
1058       return;
1059     }
1060     colSel.makeVisibleAnnotation(this);
1061   }
1062
1063   public void setPadGaps(boolean padgaps, char gapchar)
1064   {
1065     this.padGaps = padgaps;
1066     if (padgaps)
1067     {
1068       hasText = true;
1069       for (int i = 0; i < annotations.length; i++)
1070       {
1071         if (annotations[i] == null)
1072         {
1073           annotations[i] = new Annotation(String.valueOf(gapchar), null,
1074                   ' ', 0f, null);
1075         }
1076         else if (annotations[i].displayCharacter == null
1077                 || annotations[i].displayCharacter.equals(" "))
1078         {
1079           annotations[i].displayCharacter = String.valueOf(gapchar);
1080         }
1081       }
1082     }
1083   }
1084
1085   /**
1086    * format description string for display
1087    * 
1088    * @param seqname
1089    * @return Get the annotation description string optionally prefixed by
1090    *         associated sequence name (if any)
1091    */
1092   public String getDescription(boolean seqname)
1093   {
1094     if (seqname && this.sequenceRef != null)
1095     {
1096       int i = description.toLowerCase().indexOf("<html>");
1097       if (i > -1)
1098       {
1099         // move the html tag to before the sequence reference.
1100         return "<html>" + sequenceRef.getName() + " : "
1101                 + description.substring(i + 6);
1102       }
1103       return sequenceRef.getName() + " : " + description;
1104     }
1105     return description;
1106   }
1107
1108   public boolean isValidStruc()
1109   {
1110     return invalidrnastruc == -1;
1111   }
1112
1113   public long getInvalidStrucPos()
1114   {
1115     return invalidrnastruc;
1116   }
1117
1118   /**
1119    * machine readable ID string indicating what generated this annotation
1120    */
1121   protected String calcId = "";
1122
1123   /**
1124    * properties associated with the calcId
1125    */
1126   protected Map<String, String> properties = new HashMap<String, String>();
1127
1128   /**
1129    * base colour for line graphs. If null, will be set automatically by
1130    * searching the alignment annotation
1131    */
1132   public java.awt.Color _linecolour;
1133
1134   public String getCalcId()
1135   {
1136     return calcId;
1137   }
1138
1139   public void setCalcId(String calcId)
1140   {
1141     this.calcId = calcId;
1142   }
1143
1144   public boolean isRNA()
1145   {
1146     return isrna;
1147   }
1148
1149   /**
1150    * transfer annotation to the given sequence using the given mapping from the
1151    * current positions or an existing sequence mapping
1152    * 
1153    * @param sq
1154    * @param sp2sq
1155    *          map involving sq as To or From
1156    */
1157   public void liftOver(SequenceI sq, Mapping sp2sq)
1158   {
1159     if (sp2sq.getMappedWidth() != sp2sq.getWidth())
1160     {
1161       // TODO: employ getWord/MappedWord to transfer annotation between cDNA and Protein reference frames
1162       throw new Error("liftOver currently not implemented for transfer of annotation between different types of seqeunce");
1163     }
1164     boolean mapIsTo = (sp2sq != null) ? (sp2sq.getTo() == sq || sp2sq
1165             .getTo() == sq.getDatasetSequence()) : false;
1166
1167     // TODO build a better annotation element map and get rid of annotations[]
1168     Hashtable<Integer, Annotation> mapForsq = new Hashtable();
1169     if (sequenceMapping != null)
1170     {
1171       if (sp2sq != null)
1172       {
1173         for (Entry<Integer, Annotation> ie : sequenceMapping.entrySet())
1174         {
1175           Integer mpos = Integer.valueOf(mapIsTo ? sp2sq
1176                   .getMappedPosition(ie.getKey()) : sp2sq.getPosition(ie
1177                   .getKey()));
1178           if (mpos >= sq.getStart() && mpos <= sq.getEnd())
1179           {
1180             mapForsq.put(mpos, ie.getValue());
1181           }
1182         }
1183         sequenceMapping = mapForsq;
1184         sequenceRef = sq;
1185         adjustForAlignment();
1186       }
1187       else
1188       {
1189         // trim positions
1190       }
1191     }
1192   }
1193
1194   /**
1195    * like liftOver but more general.
1196    * 
1197    * Takes an array of int pairs that will be used to update the internal
1198    * sequenceMapping and so shuffle the annotated positions
1199    * 
1200    * @param newref
1201    *          - new sequence reference for the annotation row - if null,
1202    *          sequenceRef is left unchanged
1203    * @param mapping
1204    *          array of ints containing corresponding positions
1205    * @param from
1206    *          - column for current coordinate system (-1 for index+1)
1207    * @param to
1208    *          - column for destination coordinate system (-1 for index+1)
1209    * @param idxoffset
1210    *          - offset added to index when referencing either coordinate system
1211    * @note no checks are made as to whether from and/or to are sensible
1212    * @note caller should add the remapped annotation to newref if they have not
1213    *       already
1214    */
1215   public void remap(SequenceI newref, int[][] mapping, int from, int to,
1216           int idxoffset)
1217   {
1218     if (mapping != null)
1219     {
1220       Hashtable<Integer, Annotation> old = sequenceMapping, remap = new Hashtable<Integer, Annotation>();
1221       int index = -1;
1222       for (int mp[] : mapping)
1223       {
1224         if (index++ < 0)
1225         {
1226           continue;
1227         }
1228         Annotation ann = null;
1229         if (from == -1)
1230         {
1231           ann = sequenceMapping.get(Integer.valueOf(idxoffset + index));
1232         }
1233         else
1234         {
1235           if (mp != null && mp.length > from)
1236           {
1237             ann = sequenceMapping.get(Integer.valueOf(mp[from]));
1238           }
1239         }
1240         if (ann != null)
1241         {
1242           if (to == -1)
1243           {
1244             remap.put(Integer.valueOf(idxoffset + index), ann);
1245           }
1246           else
1247           {
1248             if (to > -1 && to < mp.length)
1249             {
1250               remap.put(Integer.valueOf(mp[to]), ann);
1251             }
1252           }
1253         }
1254       }
1255       sequenceMapping = remap;
1256       old.clear();
1257       if (newref != null)
1258       {
1259         sequenceRef = newref;
1260       }
1261       adjustForAlignment();
1262     }
1263   }
1264
1265   public Object getProperty(String property)
1266   {
1267     if (properties == null)
1268     {
1269       return null;
1270     }
1271     return properties.get(property);
1272   }
1273
1274   public void setProperty(String property, String value)
1275   {
1276     if (properties==null)
1277     {
1278       properties = new HashMap<String,String>();
1279     }
1280     properties.put(property, value);
1281   }
1282 }