JAL-4024 only render partially when new view overlaps with last rendered annotation...
[jalview.git] / unused / AlignmentAnnotation.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ 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 import jalview.util.MapList;
27
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Map.Entry;
37
38 /**
39  * DOCUMENT ME!
40  * 
41  * @author $author$
42  * @version $Revision$
43  */
44 public class AlignmentAnnotation
45 {
46   private static final String ANNOTATION_ID_PREFIX = "ann";
47
48   /*
49    * Identifers for different types of profile data
50    */
51   public static final int SEQUENCE_PROFILE = 0;
52
53   public static final int STRUCTURE_PROFILE = 1;
54
55   public static final int CDNA_PROFILE = 2;
56
57   private static long counter = 0;
58
59   /**
60    * If true, this annotations is calculated every edit, eg consensus, quality
61    * or conservation graphs
62    */
63   public boolean autoCalculated = false;
64
65   /**
66    * unique ID for this annotation, used to match up the same annotation row
67    * shown in multiple views and alignments
68    */
69   public String annotationId;
70
71   /**
72    * the sequence this annotation is associated with (or null)
73    */
74   public SequenceI sequenceRef;
75
76   /** label shown in dropdown menus and in the annotation label area */
77   public String label;
78
79   /** longer description text shown as a tooltip */
80   public String description;
81
82   /** Array of annotations placed in the current coordinate system */
83   public Annotation[] annotations;
84
85   public List<SimpleBP> bps = null;
86
87   /**
88    * RNA secondary structure contact positions
89    */
90   public SequenceFeature[] _rnasecstr = null;
91
92   /**
93    * position of annotation resulting in invalid WUSS parsing or -1. -2 means
94    * there was no RNA structure in this annotation
95    */
96   private long invalidrnastruc = -2;
97
98   /**
99    * Updates the _rnasecstr field Determines the positions that base pair and
100    * the positions of helices based on secondary structure from a Stockholm file
101    * 
102    * @param rnaAnnotation
103    */
104   private void _updateRnaSecStr(CharSequence rnaAnnotation)
105   {
106     try
107     {
108       _rnasecstr = Rna.getHelixMap(rnaAnnotation);
109       invalidrnastruc = -1;
110     } catch (WUSSParseException px)
111     {
112       // DEBUG System.out.println(px);
113       invalidrnastruc = px.getProblemPos();
114     }
115     if (invalidrnastruc > -1)
116     {
117       return;
118     }
119
120     if (_rnasecstr != null && _rnasecstr.length > 0)
121     {
122       // show all the RNA secondary structure annotation symbols.
123       isrna = true;
124       showAllColLabels = true;
125       scaleColLabel = true;
126       _markRnaHelices();
127     }
128     // System.out.println("featuregroup " + _rnasecstr[0].getFeatureGroup());
129
130   }
131
132   private void _markRnaHelices()
133   {
134     int mxval = 0;
135     // Figure out number of helices
136     // Length of rnasecstr is the number of pairs of positions that base pair
137     // with each other in the secondary structure
138     for (int x = 0; x < _rnasecstr.length; x++)
139     {
140
141       /*
142        * System.out.println(this.annotation._rnasecstr[x] + " Begin" +
143        * this.annotation._rnasecstr[x].getBegin());
144        */
145       // System.out.println(this.annotation._rnasecstr[x].getFeatureGroup());
146       int val = 0;
147       try
148       {
149         val = Integer.valueOf(_rnasecstr[x].getFeatureGroup());
150         if (mxval < val)
151         {
152           mxval = val;
153         }
154       } catch (NumberFormatException q)
155       {
156       }
157       ;
158
159       annotations[_rnasecstr[x].getBegin()].value = val;
160       annotations[_rnasecstr[x].getEnd()].value = val;
161
162       // annotations[_rnasecstr[x].getBegin()].displayCharacter = "" + val;
163       // annotations[_rnasecstr[x].getEnd()].displayCharacter = "" + val;
164     }
165     setScore(mxval);
166   }
167
168   /**
169    * Get the RNA Secondary Structure SequenceFeature Array if present
170    */
171   public SequenceFeature[] getRnaSecondaryStructure()
172   {
173     return this._rnasecstr;
174   }
175
176   /**
177    * Check the RNA Secondary Structure is equivalent to one in given
178    * AlignmentAnnotation param
179    */
180   public boolean rnaSecondaryStructureEquivalent(AlignmentAnnotation that)
181   {
182     return rnaSecondaryStructureEquivalent(that, true);
183   }
184
185   public boolean rnaSecondaryStructureEquivalent(AlignmentAnnotation that, boolean compareType)
186   {
187     SequenceFeature[] thisSfArray = this.getRnaSecondaryStructure();
188     SequenceFeature[] thatSfArray = that.getRnaSecondaryStructure();
189     if (thisSfArray == null || thatSfArray == null)
190     {
191       return thisSfArray == null && thatSfArray == null;
192     }
193     if (thisSfArray.length != thatSfArray.length)
194     {
195       return false;
196     }
197     Arrays.sort(thisSfArray, new SFSortByEnd()); // probably already sorted
198                                                    // like this
199     Arrays.sort(thatSfArray, new SFSortByEnd()); // probably already sorted
200                                                    // like this
201     for (int i=0; i < thisSfArray.length; i++) {
202       SequenceFeature thisSf = thisSfArray[i];
203       SequenceFeature thatSf = thatSfArray[i];
204       if (compareType) {
205         if (thisSf.getType() == null || thatSf.getType() == null) {
206           if (thisSf.getType() == null && thatSf.getType() == null) {
207             continue;
208           } else {
209             return false;
210           }
211         }
212         if (! thisSf.getType().equals(thatSf.getType())) {
213           return false;
214         }
215       }
216       if (!(thisSf.getBegin() == thatSf.getBegin()
217               && thisSf.getEnd() == thatSf.getEnd()))
218       {
219         return false;
220       }
221     }
222     return true;
223
224   }
225
226   /**
227    * map of positions in the associated annotation
228    */
229   private Map<Integer, Annotation> sequenceMapping;
230
231   /**
232    * lower range for quantitative data
233    */
234   public float graphMin;
235
236   /**
237    * Upper range for quantitative data
238    */
239   public float graphMax;
240
241   /**
242    * Score associated with label and description.
243    */
244   public double score = Double.NaN;
245
246   /**
247    * flag indicating if annotation has a score.
248    */
249   public boolean hasScore = false;
250
251   public GraphLine threshold;
252
253   // Graphical hints and tips
254
255   /** Can this row be edited by the user ? */
256   public boolean editable = false;
257
258   /** Indicates if annotation has a graphical symbol track */
259   public boolean hasIcons; //
260
261   /** Indicates if annotation has a text character label */
262   public boolean hasText;
263
264   /** is the row visible */
265   public boolean visible = true;
266
267   public int graphGroup = -1;
268
269   /** Displayed height of row in pixels */
270   public int height = 0;
271
272   public int graph = 0;
273
274   public int graphHeight = 40;
275
276   public boolean padGaps = false;
277
278   public static final int NO_GRAPH = 0;
279
280   public static final int BAR_GRAPH = 1;
281
282   public static final int LINE_GRAPH = 2;
283
284   public boolean belowAlignment = true;
285
286   public SequenceGroup groupRef = null;
287
288   /**
289    * display every column label, even if there is a row of identical labels
290    */
291   public boolean showAllColLabels = false;
292
293   /**
294    * scale the column label to fit within the alignment column.
295    */
296   public boolean scaleColLabel = false;
297
298   /**
299    * centre the column labels relative to the alignment column
300    */
301   public boolean centreColLabels = false;
302
303   private boolean isrna;
304
305   public static int getGraphValueFromString(String string)
306   {
307     if (string.equalsIgnoreCase("BAR_GRAPH"))
308     {
309       return BAR_GRAPH;
310     }
311     else if (string.equalsIgnoreCase("LINE_GRAPH"))
312     {
313       return LINE_GRAPH;
314     }
315     else
316     {
317       return NO_GRAPH;
318     }
319   }
320
321   /**
322    * Creates a new AlignmentAnnotation object.
323    * 
324    * @param label
325    *          short label shown under sequence labels
326    * @param description
327    *          text displayed on mouseover
328    * @param annotations
329    *          set of positional annotation elements
330    */
331   public AlignmentAnnotation(String label, String description,
332           Annotation[] annotations)
333   {
334     setAnnotationId();
335     // always editable?
336     editable = true;
337     this.label = label;
338     this.description = description;
339     this.annotations = annotations;
340
341     validateRangeAndDisplay();
342   }
343
344   /**
345    * Checks if annotation labels represent secondary structures
346    * 
347    */
348   void areLabelsSecondaryStructure()
349   {
350     boolean nonSSLabel = false;
351     isrna = false;
352     StringBuffer rnastring = new StringBuffer();
353
354     char firstChar = 0;
355     for (int i = 0; i < annotations.length; i++)
356     {
357       // DEBUG System.out.println(i + ": " + annotations[i]);
358       if (annotations[i] == null)
359       {
360         continue;
361       }
362       if (annotations[i].secondaryStructure == 'H'
363               || annotations[i].secondaryStructure == 'E')
364       {
365         // DEBUG System.out.println( "/H|E/ '" +
366         // annotations[i].secondaryStructure + "'");
367         hasIcons |= true;
368       }
369       else
370       // Check for RNA secondary structure
371       {
372         // DEBUG System.out.println( "/else/ '" +
373         // annotations[i].secondaryStructure + "'");
374         // TODO: 2.8.2 should this ss symbol validation check be a function in
375         // RNA/ResidueProperties ?
376         if (annotations[i].secondaryStructure == '('
377                 || annotations[i].secondaryStructure == '['
378                 || annotations[i].secondaryStructure == '<'
379                 || annotations[i].secondaryStructure == '{'
380                 || annotations[i].secondaryStructure == 'A'
381                 || annotations[i].secondaryStructure == 'B'
382                 || annotations[i].secondaryStructure == 'C'
383                 || annotations[i].secondaryStructure == 'D'
384                 // || annotations[i].secondaryStructure == 'E' // ambiguous on
385                 // its own -- already checked above
386                 || annotations[i].secondaryStructure == 'F'
387                 || annotations[i].secondaryStructure == 'G'
388                 // || annotations[i].secondaryStructure == 'H' // ambiguous on
389                 // its own -- already checked above
390                 || annotations[i].secondaryStructure == 'I'
391                 || annotations[i].secondaryStructure == 'J'
392                 || annotations[i].secondaryStructure == 'K'
393                 || annotations[i].secondaryStructure == 'L'
394                 || annotations[i].secondaryStructure == 'M'
395                 || annotations[i].secondaryStructure == 'N'
396                 || annotations[i].secondaryStructure == 'O'
397                 || annotations[i].secondaryStructure == 'P'
398                 || annotations[i].secondaryStructure == 'Q'
399                 || annotations[i].secondaryStructure == 'R'
400                 || annotations[i].secondaryStructure == 'S'
401                 || annotations[i].secondaryStructure == 'T'
402                 || annotations[i].secondaryStructure == 'U'
403                 || annotations[i].secondaryStructure == 'V'
404                 || annotations[i].secondaryStructure == 'W'
405                 || annotations[i].secondaryStructure == 'X'
406                 || annotations[i].secondaryStructure == 'Y'
407                 || annotations[i].secondaryStructure == 'Z')
408         {
409           hasIcons |= true;
410           isrna |= true;
411         }
412       }
413
414       // System.out.println("displaychar " + annotations[i].displayCharacter);
415
416       if (annotations[i].displayCharacter == null
417               || annotations[i].displayCharacter.length() == 0)
418       {
419         rnastring.append('.');
420         continue;
421       }
422       if (annotations[i].displayCharacter.length() == 1)
423       {
424         firstChar = annotations[i].displayCharacter.charAt(0);
425         // check to see if it looks like a sequence or is secondary structure
426         // labelling.
427         if (annotations[i].secondaryStructure != ' ' && !hasIcons &&
428         // Uncomment to only catch case where
429         // displayCharacter==secondary
430         // Structure
431         // to correctly redisplay SS annotation imported from Stockholm,
432         // exported to JalviewXML and read back in again.
433         // &&
434         // annotations[i].displayCharacter.charAt(0)==annotations[i].secondaryStructure
435                 firstChar != ' ' && firstChar != '$' && firstChar != 0xCE
436                 && firstChar != '(' && firstChar != '[' && firstChar != '<'
437                 && firstChar != '{' && firstChar != 'A' && firstChar != 'B'
438                 && firstChar != 'C' && firstChar != 'D' && firstChar != 'E'
439                 && firstChar != 'F' && firstChar != 'G' && firstChar != 'H'
440                 && firstChar != 'I' && firstChar != 'J' && firstChar != 'K'
441                 && firstChar != 'L' && firstChar != 'M' && firstChar != 'N'
442                 && firstChar != 'O' && firstChar != 'P' && firstChar != 'Q'
443                 && firstChar != 'R' && firstChar != 'S' && firstChar != 'T'
444                 && firstChar != 'U' && firstChar != 'V' && firstChar != 'W'
445                 && firstChar != 'X' && firstChar != 'Y' && firstChar != 'Z'
446                 && firstChar != '-'
447                 && firstChar < jalview.schemes.ResidueProperties.aaIndex.length)
448         {
449           if (jalview.schemes.ResidueProperties.aaIndex[firstChar] < 23) // TODO:
450                                                                          // parameterise
451                                                                          // to
452                                                                          // gap
453                                                                          // symbol
454                                                                          // number
455           {
456             nonSSLabel = true;
457           }
458         }
459       }
460       else
461       {
462         rnastring.append(annotations[i].displayCharacter.charAt(1));
463       }
464
465       if (annotations[i].displayCharacter.length() > 0)
466       {
467         hasText = true;
468       }
469     }
470
471     if (nonSSLabel)
472     {
473       hasIcons = false;
474       for (int j = 0; j < annotations.length; j++)
475       {
476         if (annotations[j] != null
477                 && annotations[j].secondaryStructure != ' ')
478         {
479           annotations[j].displayCharacter = String
480                   .valueOf(annotations[j].secondaryStructure);
481           annotations[j].secondaryStructure = ' ';
482         }
483
484       }
485     }
486     else
487     {
488       if (isrna)
489       {
490         _updateRnaSecStr(new AnnotCharSequence());
491       }
492     }
493   }
494
495   /**
496    * flyweight access to positions in the alignment annotation row for RNA
497    * processing
498    * 
499    * @author jimp
500    * 
501    */
502   private class AnnotCharSequence implements CharSequence
503   {
504     int offset = 0;
505
506     int max = 0;
507
508     public AnnotCharSequence()
509     {
510       this(0, annotations.length);
511     }
512
513     AnnotCharSequence(int start, int end)
514     {
515       offset = start;
516       max = end;
517     }
518
519     @Override
520     public CharSequence subSequence(int start, int end)
521     {
522       return new AnnotCharSequence(offset + start, offset + end);
523     }
524
525     @Override
526     public int length()
527     {
528       return max - offset;
529     }
530
531     @Override
532     public char charAt(int index)
533     {
534       return ((index + offset < 0) || (index + offset) >= max
535               || annotations[index + offset] == null
536               || (annotations[index + offset].secondaryStructure <= ' ')
537                       ? ' '
538                       : annotations[index + offset].displayCharacter == null
539                               || annotations[index
540                                       + offset].displayCharacter
541                                               .length() == 0
542                                                       ? annotations[index
543                                                               + offset].secondaryStructure
544                                                       : annotations[index
545                                                               + offset].displayCharacter
546                                                                       .charAt(0));
547     }
548
549     @Override
550     public String toString()
551     {
552       char[] string = new char[max - offset];
553       int mx = annotations.length;
554
555       for (int i = offset; i < mx; i++)
556       {
557         string[i] = (annotations[i] == null
558                 || (annotations[i].secondaryStructure <= 32))
559                         ? ' '
560                         : (annotations[i].displayCharacter == null
561                                 || annotations[i].displayCharacter
562                                         .length() == 0
563                                                 ? annotations[i].secondaryStructure
564                                                 : annotations[i].displayCharacter
565                                                         .charAt(0));
566       }
567       return new String(string);
568     }
569   };
570
571   private long _lastrnaannot = -1;
572
573   public String getRNAStruc()
574   {
575     if (isrna)
576     {
577       String rnastruc = new AnnotCharSequence().toString();
578       if (_lastrnaannot != rnastruc.hashCode())
579       {
580         // ensure rna structure contacts are up to date
581         _lastrnaannot = rnastruc.hashCode();
582         _updateRnaSecStr(rnastruc);
583       }
584       return rnastruc;
585     }
586     return null;
587   }
588
589   /**
590    * Creates a new AlignmentAnnotation object.
591    * 
592    * @param label
593    *          DOCUMENT ME!
594    * @param description
595    *          DOCUMENT ME!
596    * @param annotations
597    *          DOCUMENT ME!
598    * @param min
599    *          DOCUMENT ME!
600    * @param max
601    *          DOCUMENT ME!
602    * @param winLength
603    *          DOCUMENT ME!
604    */
605   public AlignmentAnnotation(String label, String description,
606           Annotation[] annotations, float min, float max, int graphType)
607   {
608     setAnnotationId();
609     // graphs are not editable
610     editable = graphType == 0;
611
612     this.label = label;
613     this.description = description;
614     this.annotations = annotations;
615     graph = graphType;
616     graphMin = min;
617     graphMax = max;
618     validateRangeAndDisplay();
619   }
620
621   /**
622    * checks graphMin and graphMax, secondary structure symbols, sets graphType
623    * appropriately, sets null labels to the empty string if appropriate.
624    */
625   public void validateRangeAndDisplay()
626   {
627
628     if (annotations == null)
629     {
630       visible = false; // try to prevent renderer from displaying.
631       invalidrnastruc = -1;
632       return; // this is a non-annotation row annotation - ie a sequence score.
633     }
634
635     int graphType = graph;
636     float min = graphMin;
637     float max = graphMax;
638     boolean drawValues = true;
639     _linecolour = null;
640     if (min == max)
641     {
642       min = 999999999;
643       for (int i = 0; i < annotations.length; i++)
644       {
645         if (annotations[i] == null)
646         {
647           continue;
648         }
649
650         if (drawValues && annotations[i].displayCharacter != null
651                 && annotations[i].displayCharacter.length() > 1)
652         {
653           drawValues = false;
654         }
655
656         if (annotations[i].value > max)
657         {
658           max = annotations[i].value;
659         }
660
661         if (annotations[i].value < min)
662         {
663           min = annotations[i].value;
664         }
665         if (_linecolour == null && annotations[i].colour != null)
666         {
667           _linecolour = annotations[i].colour;
668         }
669       }
670       // ensure zero is origin for min/max ranges on only one side of zero
671       if (min > 0)
672       {
673         min = 0;
674       }
675       else
676       {
677         if (max < 0)
678         {
679           max = 0;
680         }
681       }
682     }
683
684     graphMin = min;
685     graphMax = max;
686
687     areLabelsSecondaryStructure();
688
689     if (!drawValues && graphType != NO_GRAPH)
690     {
691       for (int i = 0; i < annotations.length; i++)
692       {
693         if (annotations[i] != null)
694         {
695           annotations[i].displayCharacter = "";
696         }
697       }
698     }
699   }
700
701   /**
702    * Copy constructor creates a new independent annotation row with the same
703    * associated sequenceRef
704    * 
705    * @param annotation
706    */
707   public AlignmentAnnotation(AlignmentAnnotation annotation)
708   {
709     setAnnotationId();
710     this.label = new String(annotation.label);
711     if (annotation.description != null)
712     {
713       this.description = new String(annotation.description);
714     }
715     this.graphMin = annotation.graphMin;
716     this.graphMax = annotation.graphMax;
717     this.graph = annotation.graph;
718     this.graphHeight = annotation.graphHeight;
719     this.graphGroup = annotation.graphGroup;
720     this.groupRef = annotation.groupRef;
721     this.editable = annotation.editable;
722     this.autoCalculated = annotation.autoCalculated;
723     this.hasIcons = annotation.hasIcons;
724     this.hasText = annotation.hasText;
725     this.height = annotation.height;
726     this.label = annotation.label;
727     this.padGaps = annotation.padGaps;
728     this.visible = annotation.visible;
729     this.centreColLabels = annotation.centreColLabels;
730     this.scaleColLabel = annotation.scaleColLabel;
731     this.showAllColLabels = annotation.showAllColLabels;
732     this.calcId = annotation.calcId;
733     if (annotation.properties != null)
734     {
735       properties = new HashMap<>();
736       for (Map.Entry<String, String> val : annotation.properties.entrySet())
737       {
738         properties.put(val.getKey(), val.getValue());
739       }
740     }
741     if (this.hasScore = annotation.hasScore)
742     {
743       this.score = annotation.score;
744     }
745     if (annotation.threshold != null)
746     {
747       threshold = new GraphLine(annotation.threshold);
748     }
749     Annotation[] ann = annotation.annotations;
750     if (annotation.annotations != null)
751     {
752       this.annotations = new Annotation[ann.length];
753       for (int i = 0; i < ann.length; i++)
754       {
755         if (ann[i] != null)
756         {
757           annotations[i] = new Annotation(ann[i]);
758           if (_linecolour != null)
759           {
760             _linecolour = annotations[i].colour;
761           }
762         }
763       }
764     }
765     if (annotation.sequenceRef != null)
766     {
767       this.sequenceRef = annotation.sequenceRef;
768       if (annotation.sequenceMapping != null)
769       {
770         Integer p = null;
771         sequenceMapping = new HashMap<>();
772         Iterator<Integer> pos = annotation.sequenceMapping.keySet()
773                 .iterator();
774         while (pos.hasNext())
775         {
776           // could optimise this!
777           p = pos.next();
778           Annotation a = annotation.sequenceMapping.get(p);
779           if (a == null)
780           {
781             continue;
782           }
783           if (ann != null)
784           {
785             for (int i = 0; i < ann.length; i++)
786             {
787               if (ann[i] == a)
788               {
789                 sequenceMapping.put(p, annotations[i]);
790               }
791             }
792           }
793         }
794       }
795       else
796       {
797         this.sequenceMapping = null;
798       }
799     }
800     // TODO: check if we need to do this: JAL-952
801     // if (this.isrna=annotation.isrna)
802     {
803       // _rnasecstr=new SequenceFeature[annotation._rnasecstr];
804     }
805     validateRangeAndDisplay(); // construct hashcodes, etc.
806   }
807
808   /**
809    * clip the annotation to the columns given by startRes and endRes (inclusive)
810    * and prune any existing sequenceMapping to just those columns.
811    * 
812    * @param startRes
813    * @param endRes
814    */
815   public void restrict(int startRes, int endRes)
816   {
817     if (annotations == null)
818     {
819       // non-positional
820       return;
821     }
822     if (startRes < 0)
823     {
824       startRes = 0;
825     }
826     if (startRes >= annotations.length)
827     {
828       startRes = annotations.length - 1;
829     }
830     if (endRes >= annotations.length)
831     {
832       endRes = annotations.length - 1;
833     }
834     if (annotations == null)
835     {
836       return;
837     }
838     Annotation[] temp = new Annotation[endRes - startRes + 1];
839     if (startRes < annotations.length)
840     {
841       System.arraycopy(annotations, startRes, temp, 0,
842               endRes - startRes + 1);
843     }
844     if (sequenceRef != null)
845     {
846       // Clip the mapping, if it exists.
847       int spos = sequenceRef.findPosition(startRes);
848       int epos = sequenceRef.findPosition(endRes);
849       if (sequenceMapping != null)
850       {
851         Map<Integer, Annotation> newmapping = new HashMap<>();
852         Iterator<Integer> e = sequenceMapping.keySet().iterator();
853         while (e.hasNext())
854         {
855           Integer pos = e.next();
856           if (pos.intValue() >= spos && pos.intValue() <= epos)
857           {
858             newmapping.put(pos, sequenceMapping.get(pos));
859           }
860         }
861         sequenceMapping.clear();
862         sequenceMapping = newmapping;
863       }
864     }
865     annotations = temp;
866   }
867
868   /**
869    * set the annotation row to be at least length Annotations
870    * 
871    * @param length
872    *          minimum number of columns required in the annotation row
873    * @return false if the annotation row is greater than length
874    */
875   public boolean padAnnotation(int length)
876   {
877     if (annotations == null)
878     {
879       return true; // annotation row is correct - null == not visible and
880       // undefined length
881     }
882     if (annotations.length < length)
883     {
884       Annotation[] na = new Annotation[length];
885       System.arraycopy(annotations, 0, na, 0, annotations.length);
886       annotations = na;
887       return true;
888     }
889     return annotations.length > length;
890
891   }
892
893   /**
894    * DOCUMENT ME!
895    * 
896    * @return DOCUMENT ME!
897    */
898   @Override
899   public String toString()
900   {
901     if (annotations == null)
902     {
903       return "";
904     }
905     StringBuilder buffer = new StringBuilder(256);
906
907     for (int i = 0; i < annotations.length; i++)
908     {
909       if (annotations[i] != null)
910       {
911         if (graph != 0)
912         {
913           buffer.append(annotations[i].value);
914         }
915         else if (hasIcons)
916         {
917           buffer.append(annotations[i].secondaryStructure);
918         }
919         else
920         {
921           buffer.append(annotations[i].displayCharacter);
922         }
923       }
924
925       buffer.append(", ");
926     }
927     // TODO: remove disgusting hack for 'special' treatment of consensus line.
928     if (label.indexOf("Consensus") == 0)
929     {
930       buffer.append("\n");
931
932       for (int i = 0; i < annotations.length; i++)
933       {
934         if (annotations[i] != null)
935         {
936           buffer.append(annotations[i].description);
937         }
938
939         buffer.append(", ");
940       }
941     }
942
943     return buffer.toString();
944   }
945
946   public void setThreshold(GraphLine line)
947   {
948     threshold = line;
949   }
950
951   public GraphLine getThreshold()
952   {
953     return threshold;
954   }
955
956   /**
957    * Attach the annotation to seqRef, starting from startRes position. If
958    * alreadyMapped is true then the indices of the annotation[] array are
959    * sequence positions rather than alignment column positions.
960    * 
961    * @param seqRef
962    * @param startRes
963    * @param alreadyMapped
964    */
965   public void createSequenceMapping(SequenceI seqRef, int startRes,
966           boolean alreadyMapped)
967   {
968
969     if (seqRef == null)
970     {
971       return;
972     }
973     sequenceRef = seqRef;
974     if (annotations == null)
975     {
976       return;
977     }
978     sequenceMapping = new HashMap<>();
979
980     int seqPos;
981
982     for (int i = 0; i < annotations.length; i++)
983     {
984       if (annotations[i] != null)
985       {
986         if (alreadyMapped)
987         {
988           seqPos = seqRef.findPosition(i);
989         }
990         else
991         {
992           seqPos = i + startRes;
993         }
994
995         sequenceMapping.put(new Integer(seqPos), annotations[i]);
996       }
997     }
998
999   }
1000
1001   /**
1002    * When positional annotation and a sequence reference is present, clears and
1003    * resizes the annotations array to the current alignment width, and adds
1004    * annotation according to aligned positions of the sequenceRef given by
1005    * sequenceMapping.
1006    */
1007   public void adjustForAlignment()
1008   {
1009     if (sequenceRef == null)
1010     {
1011       return;
1012     }
1013
1014     if (annotations == null)
1015     {
1016       return;
1017     }
1018
1019     int a = 0, aSize = sequenceRef.getLength();
1020
1021     if (aSize == 0)
1022     {
1023       // Its been deleted
1024       return;
1025     }
1026
1027     int position;
1028     Annotation[] temp = new Annotation[aSize];
1029     Integer index;
1030     if (sequenceMapping != null)
1031     {
1032       for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++)
1033       {
1034         index = new Integer(a);
1035         Annotation annot = sequenceMapping.get(index);
1036         if (annot != null)
1037         {
1038           position = sequenceRef.findIndex(a) - 1;
1039
1040           temp[position] = annot;
1041         }
1042       }
1043     }
1044     annotations = temp;
1045   }
1046
1047   /**
1048    * remove any null entries in annotation row and return the number of non-null
1049    * annotation elements.
1050    * 
1051    * @return
1052    */
1053   public int compactAnnotationArray()
1054   {
1055     int i = 0, iSize = annotations.length;
1056     while (i < iSize)
1057     {
1058       if (annotations[i] == null)
1059       {
1060         if (i + 1 < iSize)
1061         {
1062           System.arraycopy(annotations, i + 1, annotations, i,
1063                   iSize - i - 1);
1064         }
1065         iSize--;
1066       }
1067       else
1068       {
1069         i++;
1070       }
1071     }
1072     Annotation[] ann = annotations;
1073     annotations = new Annotation[i];
1074     System.arraycopy(ann, 0, annotations, 0, i);
1075     ann = null;
1076     return iSize;
1077   }
1078
1079   /**
1080    * Associate this annotation with the aligned residues of a particular
1081    * sequence. sequenceMapping will be updated in the following way: null
1082    * sequenceI - existing mapping will be discarded but annotations left in
1083    * mapped positions. valid sequenceI not equal to current sequenceRef: mapping
1084    * is discarded and rebuilt assuming 1:1 correspondence TODO: overload with
1085    * parameter to specify correspondence between current and new sequenceRef
1086    * 
1087    * @param sequenceI
1088    */
1089   public void setSequenceRef(SequenceI sequenceI)
1090   {
1091     if (sequenceI != null)
1092     {
1093       if (sequenceRef != null)
1094       {
1095         boolean rIsDs = sequenceRef.getDatasetSequence() == null,
1096                 tIsDs = sequenceI.getDatasetSequence() == null;
1097         if (sequenceRef != sequenceI
1098                 && (rIsDs && !tIsDs
1099                         && sequenceRef != sequenceI.getDatasetSequence())
1100                 && (!rIsDs && tIsDs
1101                         && sequenceRef.getDatasetSequence() != sequenceI)
1102                 && (!rIsDs && !tIsDs
1103                         && sequenceRef.getDatasetSequence() != sequenceI
1104                                 .getDatasetSequence())
1105                 && !sequenceRef.equals(sequenceI))
1106         {
1107           // if sequenceRef isn't intersecting with sequenceI
1108           // throw away old mapping and reconstruct.
1109           sequenceRef = null;
1110           if (sequenceMapping != null)
1111           {
1112             sequenceMapping = null;
1113             // compactAnnotationArray();
1114           }
1115           createSequenceMapping(sequenceI, 1, true);
1116           adjustForAlignment();
1117         }
1118         else
1119         {
1120           // Mapping carried over
1121           sequenceRef = sequenceI;
1122         }
1123       }
1124       else
1125       {
1126         // No mapping exists
1127         createSequenceMapping(sequenceI, 1, true);
1128         adjustForAlignment();
1129       }
1130     }
1131     else
1132     {
1133       // throw away the mapping without compacting.
1134       sequenceMapping = null;
1135       sequenceRef = null;
1136     }
1137   }
1138
1139   /**
1140    * @return the score
1141    */
1142   public double getScore()
1143   {
1144     return score;
1145   }
1146
1147   /**
1148    * @param score
1149    *          the score to set
1150    */
1151   public void setScore(double score)
1152   {
1153     hasScore = true;
1154     this.score = score;
1155   }
1156
1157   /**
1158    * 
1159    * @return true if annotation has an associated score
1160    */
1161   public boolean hasScore()
1162   {
1163     return hasScore || !Double.isNaN(score);
1164   }
1165
1166   /**
1167    * Score only annotation
1168    * 
1169    * @param label
1170    * @param description
1171    * @param score
1172    */
1173   public AlignmentAnnotation(String label, String description, double score)
1174   {
1175     this(label, description, null);
1176     setScore(score);
1177   }
1178
1179   /**
1180    * copy constructor with edit based on the hidden columns marked in colSel
1181    * 
1182    * @param alignmentAnnotation
1183    * @param colSel
1184    */
1185   public AlignmentAnnotation(AlignmentAnnotation alignmentAnnotation,
1186           HiddenColumns hidden)
1187   {
1188     this(alignmentAnnotation);
1189     if (annotations == null)
1190     {
1191       return;
1192     }
1193     makeVisibleAnnotation(hidden);
1194   }
1195
1196   public void setPadGaps(boolean padgaps, char gapchar)
1197   {
1198     this.padGaps = padgaps;
1199     if (padgaps)
1200     {
1201       hasText = true;
1202       for (int i = 0; i < annotations.length; i++)
1203       {
1204         if (annotations[i] == null)
1205         {
1206           annotations[i] = new Annotation(String.valueOf(gapchar), null,
1207                   ' ', 0f, null);
1208         }
1209         else if (annotations[i].displayCharacter == null
1210                 || annotations[i].displayCharacter.equals(" "))
1211         {
1212           annotations[i].displayCharacter = String.valueOf(gapchar);
1213         }
1214       }
1215     }
1216   }
1217
1218   /**
1219    * format description string for display
1220    * 
1221    * @param seqname
1222    * @return Get the annotation description string optionally prefixed by
1223    *         associated sequence name (if any)
1224    */
1225   public String getDescription(boolean seqname)
1226   {
1227     if (seqname && this.sequenceRef != null)
1228     {
1229       int i = description.toLowerCase().indexOf("<html>");
1230       if (i > -1)
1231       {
1232         // move the html tag to before the sequence reference.
1233         return "<html>" + sequenceRef.getName() + " : "
1234                 + description.substring(i + 6);
1235       }
1236       return sequenceRef.getName() + " : " + description;
1237     }
1238     return description;
1239   }
1240
1241   public boolean isValidStruc()
1242   {
1243     return invalidrnastruc == -1;
1244   }
1245
1246   public long getInvalidStrucPos()
1247   {
1248     return invalidrnastruc;
1249   }
1250
1251   /**
1252    * machine readable ID string indicating what generated this annotation
1253    */
1254   protected String calcId = "";
1255
1256   /**
1257    * properties associated with the calcId
1258    */
1259   protected Map<String, String> properties = new HashMap<>();
1260
1261   /**
1262    * base colour for line graphs. If null, will be set automatically by
1263    * searching the alignment annotation
1264    */
1265   public java.awt.Color _linecolour;
1266
1267   public String getCalcId()
1268   {
1269     return calcId;
1270   }
1271
1272   public void setCalcId(String calcId)
1273   {
1274     this.calcId = calcId;
1275   }
1276
1277   public boolean isRNA()
1278   {
1279     return isrna;
1280   }
1281
1282   /**
1283    * transfer annotation to the given sequence using the given mapping from the
1284    * current positions or an existing sequence mapping
1285    * 
1286    * @param sq
1287    * @param sp2sq
1288    *          map involving sq as To or From
1289    */
1290   public void liftOver(SequenceI sq, Mapping sp2sq)
1291   {
1292     if (sp2sq.getMappedWidth() != sp2sq.getWidth())
1293     {
1294       // TODO: employ getWord/MappedWord to transfer annotation between cDNA and
1295       // Protein reference frames
1296       throw new Error(
1297               "liftOver currently not implemented for transfer of annotation between different types of seqeunce");
1298     }
1299     boolean mapIsTo = (sp2sq != null)
1300             ? (sp2sq.getTo() == sq
1301                     || sp2sq.getTo() == sq.getDatasetSequence())
1302             : false;
1303
1304     // TODO build a better annotation element map and get rid of annotations[]
1305     Map<Integer, Annotation> mapForsq = new HashMap<>();
1306     if (sequenceMapping != null)
1307     {
1308       if (sp2sq != null)
1309       {
1310         int[] reg = new int[MapList.LEN]; 
1311         for (Entry<Integer, Annotation> ie : sequenceMapping.entrySet())
1312         {
1313           reg[MapList.POS] = ie.getKey();
1314           int mpos = sp2sq.getPosition(reg, mapIsTo);
1315           if (mpos >= sq.getStart() && mpos <= sq.getEnd())
1316           {
1317             mapForsq.put(mpos, ie.getValue());
1318           }
1319         }
1320         sequenceMapping = mapForsq;
1321         sequenceRef = sq;
1322         adjustForAlignment();
1323       }
1324       else
1325       {
1326         // trim positions
1327       }
1328     }
1329   }
1330
1331   /**
1332    * like liftOver but more general.
1333    * 
1334    * Takes an array of int pairs that will be used to update the internal
1335    * sequenceMapping and so shuffle the annotated positions
1336    * 
1337    * @param newref
1338    *          - new sequence reference for the annotation row - if null,
1339    *          sequenceRef is left unchanged
1340    * @param mapping
1341    *          array of ints containing corresponding positions
1342    * @param from
1343    *          - column for current coordinate system (-1 for index+1)
1344    * @param to
1345    *          - column for destination coordinate system (-1 for index+1)
1346    * @param idxoffset
1347    *          - offset added to index when referencing either coordinate system
1348    * @note no checks are made as to whether from and/or to are sensible
1349    * @note caller should add the remapped annotation to newref if they have not
1350    *       already
1351    */
1352   public void remap(SequenceI newref, HashMap<Integer, int[]> mapping,
1353           int from, int to, int idxoffset)
1354   {
1355     if (mapping != null)
1356     {
1357       Map<Integer, Annotation> old = sequenceMapping;
1358       Map<Integer, Annotation> remap = new HashMap<>();
1359       int index = -1;
1360       for (int mp[] : mapping.values())
1361       {
1362         if (index++ < 0)
1363         {
1364           continue;
1365         }
1366         Annotation ann = null;
1367         if (from == -1)
1368         {
1369           ann = sequenceMapping.get(Integer.valueOf(idxoffset + index));
1370         }
1371         else
1372         {
1373           if (mp != null && mp.length > from)
1374           {
1375             ann = sequenceMapping.get(Integer.valueOf(mp[from]));
1376           }
1377         }
1378         if (ann != null)
1379         {
1380           if (to == -1)
1381           {
1382             remap.put(Integer.valueOf(idxoffset + index), ann);
1383           }
1384           else
1385           {
1386             if (to > -1 && to < mp.length)
1387             {
1388               remap.put(Integer.valueOf(mp[to]), ann);
1389             }
1390           }
1391         }
1392       }
1393       sequenceMapping = remap;
1394       old.clear();
1395       if (newref != null)
1396       {
1397         sequenceRef = newref;
1398       }
1399       adjustForAlignment();
1400     }
1401   }
1402
1403   public String getProperty(String property)
1404   {
1405     if (properties == null)
1406     {
1407       return null;
1408     }
1409     return properties.get(property);
1410   }
1411
1412   public void setProperty(String property, String value)
1413   {
1414     if (properties == null)
1415     {
1416       properties = new HashMap<>();
1417     }
1418     properties.put(property, value);
1419   }
1420
1421   public boolean hasProperties()
1422   {
1423     return properties != null && properties.size() > 0;
1424   }
1425
1426   public Collection<String> getProperties()
1427   {
1428     if (properties == null)
1429     {
1430       return Collections.emptyList();
1431     }
1432     return properties.keySet();
1433   }
1434
1435   /**
1436    * Returns the Annotation for the given sequence position (base 1) if any,
1437    * else null
1438    * 
1439    * @param position
1440    * @return
1441    */
1442   public Annotation getAnnotationForPosition(int position)
1443   {
1444     return sequenceMapping == null ? null : sequenceMapping.get(position);
1445
1446   }
1447
1448   /**
1449    * Set the id to "ann" followed by a counter that increments so as to be
1450    * unique for the lifetime of the JVM
1451    */
1452   protected final void setAnnotationId()
1453   {
1454     this.annotationId = ANNOTATION_ID_PREFIX + Long.toString(nextId());
1455   }
1456
1457   /**
1458    * Returns the match for the last unmatched opening RNA helix pair symbol
1459    * preceding the given column, or '(' if nothing found to match.
1460    * 
1461    * @param column
1462    * @return
1463    */
1464   public String getDefaultRnaHelixSymbol(int column)
1465   {
1466     String result = "(";
1467     if (annotations == null)
1468     {
1469       return result;
1470     }
1471
1472     /*
1473      * for each preceding column, if it contains an open bracket, 
1474      * count whether it is still unmatched at column, if so return its pair
1475      * (likely faster than the fancy alternative using stacks)
1476      */
1477     for (int col = column - 1; col >= 0; col--)
1478     {
1479       Annotation annotation = annotations[col];
1480       if (annotation == null)
1481       {
1482         continue;
1483       }
1484       String displayed = annotation.displayCharacter;
1485       if (displayed == null || displayed.length() != 1)
1486       {
1487         continue;
1488       }
1489       char symbol = displayed.charAt(0);
1490       if (!Rna.isOpeningParenthesis(symbol))
1491       {
1492         continue;
1493       }
1494
1495       /*
1496        * found an opening bracket symbol
1497        * count (closing-opening) symbols of this type that follow it,
1498        * up to and excluding the target column; if the count is less
1499        * than 1, the opening bracket is unmatched, so return its match
1500        */
1501       String closer = String
1502               .valueOf(Rna.getMatchingClosingParenthesis(symbol));
1503       String opener = String.valueOf(symbol);
1504       int count = 0;
1505       for (int j = col + 1; j < column; j++)
1506       {
1507         if (annotations[j] != null)
1508         {
1509           String s = annotations[j].displayCharacter;
1510           if (closer.equals(s))
1511           {
1512             count++;
1513           }
1514           else if (opener.equals(s))
1515           {
1516             count--;
1517           }
1518         }
1519       }
1520       if (count < 1)
1521       {
1522         return closer;
1523       }
1524     }
1525     return result;
1526   }
1527
1528   protected static synchronized long nextId()
1529   {
1530     return counter++;
1531   }
1532
1533   /**
1534    * 
1535    * @return true for rows that have a range of values in their annotation set
1536    */
1537   public boolean isQuantitative()
1538   {
1539     return graphMin < graphMax;
1540   }
1541
1542   /**
1543    * delete any columns in alignmentAnnotation that are hidden (including
1544    * sequence associated annotation).
1545    * 
1546    * @param hiddenColumns
1547    *          the set of hidden columns
1548    */
1549   public void makeVisibleAnnotation(HiddenColumns hiddenColumns)
1550   {
1551     if (annotations != null)
1552     {
1553       makeVisibleAnnotation(0, annotations.length, hiddenColumns);
1554     }
1555   }
1556
1557   /**
1558    * delete any columns in alignmentAnnotation that are hidden (including
1559    * sequence associated annotation).
1560    * 
1561    * @param start
1562    *          remove any annotation to the right of this column
1563    * @param end
1564    *          remove any annotation to the left of this column
1565    * @param hiddenColumns
1566    *          the set of hidden columns
1567    */
1568   public void makeVisibleAnnotation(int start, int end,
1569           HiddenColumns hiddenColumns)
1570   {
1571     if (annotations != null)
1572     {
1573       if (hiddenColumns.hasHiddenColumns())
1574       {
1575         removeHiddenAnnotation(start, end, hiddenColumns);
1576       }
1577       else
1578       {
1579         restrict(start, end);
1580       }
1581     }
1582   }
1583
1584   /**
1585    * The actual implementation of deleting hidden annotation columns
1586    * 
1587    * @param start
1588    *          remove any annotation to the right of this column
1589    * @param end
1590    *          remove any annotation to the left of this column
1591    * @param hiddenColumns
1592    *          the set of hidden columns
1593    */
1594   private void removeHiddenAnnotation(int start, int end,
1595           HiddenColumns hiddenColumns)
1596   {
1597     // mangle the alignmentAnnotation annotation array
1598     ArrayList<Annotation[]> annels = new ArrayList<>();
1599     Annotation[] els = null;
1600
1601     int w = 0;
1602
1603     Iterator<int[]> blocks = hiddenColumns.getVisContigsIterator(start,
1604             end + 1, false);
1605
1606     int copylength;
1607     int annotationLength;
1608     while (blocks.hasNext())
1609     {
1610       int[] block = blocks.next();
1611       annotationLength = block[1] - block[0] + 1;
1612
1613       if (blocks.hasNext())
1614       {
1615         // copy just the visible segment of the annotation row
1616         copylength = annotationLength;
1617       }
1618       else
1619       {
1620         if (annotationLength + block[0] <= annotations.length)
1621         {
1622           // copy just the visible segment of the annotation row
1623           copylength = annotationLength;
1624         }
1625         else
1626         {
1627           // copy to the end of the annotation row
1628           copylength = annotations.length - block[0];
1629         }
1630       }
1631
1632       els = new Annotation[annotationLength];
1633       annels.add(els);
1634       System.arraycopy(annotations, block[0], els, 0, copylength);
1635       w += annotationLength;
1636     }
1637
1638     if (w != 0)
1639     {
1640       annotations = new Annotation[w];
1641
1642       w = 0;
1643       for (Annotation[] chnk : annels)
1644       {
1645         System.arraycopy(chnk, 0, annotations, w, chnk.length);
1646         w += chnk.length;
1647       }
1648     }
1649   }
1650
1651   public static Iterable<AlignmentAnnotation> findAnnotations(
1652           Iterable<AlignmentAnnotation> list, SequenceI seq, String calcId,
1653           String label)
1654   {
1655
1656     ArrayList<AlignmentAnnotation> aa = new ArrayList<>();
1657     for (AlignmentAnnotation ann : list)
1658     {
1659       if ((calcId == null || (ann.getCalcId() != null
1660               && ann.getCalcId().equals(calcId)))
1661               && (seq == null || (ann.sequenceRef != null
1662                       && ann.sequenceRef == seq))
1663               && (label == null
1664                       || (ann.label != null && ann.label.equals(label))))
1665       {
1666         aa.add(ann);
1667       }
1668     }
1669     return aa;
1670   }
1671
1672   /**
1673    * Answer true if any annotation matches the calcId passed in (if not null).
1674    * 
1675    * @param list
1676    *          annotation to search
1677    * @param calcId
1678    * @return
1679    */
1680   public static boolean hasAnnotation(List<AlignmentAnnotation> list,
1681           String calcId)
1682   {
1683
1684     if (calcId != null && !"".equals(calcId))
1685     {
1686       for (AlignmentAnnotation a : list)
1687       {
1688         if (a.getCalcId() == calcId)
1689         {
1690           return true;
1691         }
1692       }
1693     }
1694     return false;
1695   }
1696
1697   public static Iterable<AlignmentAnnotation> findAnnotation(
1698           List<AlignmentAnnotation> list, String calcId)
1699   {
1700
1701     List<AlignmentAnnotation> aa = new ArrayList<>();
1702     if (calcId == null)
1703     {
1704       return aa;
1705     }
1706     for (AlignmentAnnotation a : list)
1707     {
1708
1709       if (a.getCalcId() == calcId || (a.getCalcId() != null
1710               && calcId != null && a.getCalcId().equals(calcId)))
1711       {
1712         aa.add(a);
1713       }
1714     }
1715     return aa;
1716   }
1717
1718 }