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