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