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