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