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