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