9354d55fa447e8dc43a1b6acdd08ae1fa5cb9329
[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   private double bitScore;
98
99   private double eValue;
100
101   /**
102    * Updates the _rnasecstr field Determines the positions that base pair and
103    * the positions of helices based on secondary structure from a Stockholm file
104    * 
105    * @param rnaAnnotation
106    */
107   private void _updateRnaSecStr(CharSequence rnaAnnotation)
108   {
109     try
110     {
111       _rnasecstr = Rna.getHelixMap(rnaAnnotation);
112       invalidrnastruc = -1;
113     } catch (WUSSParseException px)
114     {
115       // DEBUG System.out.println(px);
116       invalidrnastruc = px.getProblemPos();
117     }
118     if (invalidrnastruc > -1)
119     {
120       return;
121     }
122
123     if (_rnasecstr != null && _rnasecstr.length > 0)
124     {
125       // show all the RNA secondary structure annotation symbols.
126       isrna = true;
127       showAllColLabels = true;
128       scaleColLabel = true;
129       _markRnaHelices();
130     }
131     // System.out.println("featuregroup " + _rnasecstr[0].getFeatureGroup());
132
133   }
134
135   private void _markRnaHelices()
136   {
137     int mxval = 0;
138     // Figure out number of helices
139     // Length of rnasecstr is the number of pairs of positions that base pair
140     // with each other in the secondary structure
141     for (int x = 0; x < _rnasecstr.length; x++)
142     {
143
144       /*
145        * System.out.println(this.annotation._rnasecstr[x] + " Begin" +
146        * this.annotation._rnasecstr[x].getBegin());
147        */
148       // System.out.println(this.annotation._rnasecstr[x].getFeatureGroup());
149       int val = 0;
150       try
151       {
152         val = Integer.valueOf(_rnasecstr[x].getFeatureGroup());
153         if (mxval < val)
154         {
155           mxval = val;
156         }
157       } catch (NumberFormatException q)
158       {
159       }
160       ;
161
162       annotations[_rnasecstr[x].getBegin()].value = val;
163       annotations[_rnasecstr[x].getEnd()].value = val;
164
165       // annotations[_rnasecstr[x].getBegin()].displayCharacter = "" + val;
166       // annotations[_rnasecstr[x].getEnd()].displayCharacter = "" + val;
167     }
168     setScore(mxval);
169   }
170
171   /**
172    * Get the RNA Secondary Structure SequenceFeature Array if present
173    */
174   public SequenceFeature[] getRnaSecondaryStructure()
175   {
176     return this._rnasecstr;
177   }
178
179   /**
180    * Check the RNA Secondary Structure is equivalent to one in given
181    * AlignmentAnnotation param
182    */
183   public boolean rnaSecondaryStructureEquivalent(AlignmentAnnotation that)
184   {
185     return rnaSecondaryStructureEquivalent(that, true);
186   }
187
188   public boolean rnaSecondaryStructureEquivalent(AlignmentAnnotation that, boolean compareType)
189   {
190     SequenceFeature[] thisSfArray = this.getRnaSecondaryStructure();
191     SequenceFeature[] thatSfArray = that.getRnaSecondaryStructure();
192     if (thisSfArray == null || thatSfArray == null)
193     {
194       return thisSfArray == null && thatSfArray == null;
195     }
196     if (thisSfArray.length != thatSfArray.length)
197     {
198       return false;
199     }
200     Arrays.sort(thisSfArray, new SFSortByEnd()); // probably already sorted
201                                                    // like this
202     Arrays.sort(thatSfArray, new SFSortByEnd()); // probably already sorted
203                                                    // like this
204     for (int i=0; i < thisSfArray.length; i++) {
205       SequenceFeature thisSf = thisSfArray[i];
206       SequenceFeature thatSf = thatSfArray[i];
207       if (compareType) {
208         if (thisSf.getType() == null || thatSf.getType() == null) {
209           if (thisSf.getType() == null && thatSf.getType() == null) {
210             continue;
211           } else {
212             return false;
213           }
214         }
215         if (! thisSf.getType().equals(thatSf.getType())) {
216           return false;
217         }
218       }
219       if (!(thisSf.getBegin() == thatSf.getBegin()
220               && thisSf.getEnd() == thatSf.getEnd()))
221       {
222         return false;
223       }
224     }
225     return true;
226
227   }
228
229   /**
230    * map of positions in the associated annotation
231    */
232   private Map<Integer, Annotation> sequenceMapping;
233
234   /**
235    * lower range for quantitative data
236    */
237   public float graphMin;
238
239   /**
240    * Upper range for quantitative data
241    */
242   public float graphMax;
243
244   /**
245    * Score associated with label and description.
246    */
247   public double score = Double.NaN;
248
249   /**
250    * flag indicating if annotation has a score.
251    */
252   public boolean hasScore = false;
253
254   public GraphLine threshold;
255
256   // Graphical hints and tips
257
258   /** Can this row be edited by the user ? */
259   public boolean editable = false;
260
261   /** Indicates if annotation has a graphical symbol track */
262   public boolean hasIcons; //
263
264   /** Indicates if annotation has a text character label */
265   public boolean hasText;
266
267   /** is the row visible */
268   public boolean visible = true;
269
270   public int graphGroup = -1;
271
272   /** Displayed height of row in pixels */
273   public int height = 0;
274
275   public int graph = 0;
276
277   public int graphHeight = 40;
278
279   public boolean padGaps = false;
280
281   public static final int NO_GRAPH = 0;
282
283   public static final int BAR_GRAPH = 1;
284
285   public static final int LINE_GRAPH = 2;
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    *          - annotation are at aligned columns
968    */
969   public void createSequenceMapping(SequenceI seqRef, int startRes,
970           boolean alreadyMapped)
971   {
972
973     if (seqRef == null)
974     {
975       return;
976     }
977     sequenceRef = seqRef;
978     if (annotations == null)
979     {
980       return;
981     }
982     sequenceMapping = new HashMap<>();
983
984     int seqPos;
985
986     for (int i = 0; i < annotations.length; i++)
987     {
988       if (annotations[i] != null)
989       {
990         if (alreadyMapped)
991         {
992           seqPos = seqRef.findPosition(i);
993         }
994         else
995         {
996           seqPos = i + startRes;
997         }
998
999         sequenceMapping.put(Integer.valueOf(seqPos), annotations[i]);
1000       }
1001     }
1002
1003   }
1004
1005   /**
1006    * When positional annotation and a sequence reference is present, clears and
1007    * resizes the annotations array to the current alignment width, and adds
1008    * annotation according to aligned positions of the sequenceRef given by
1009    * sequenceMapping.
1010    */
1011   public void adjustForAlignment()
1012   {
1013     if (sequenceRef == null)
1014     {
1015       return;
1016     }
1017
1018     if (annotations == null)
1019     {
1020       return;
1021     }
1022
1023     int a = 0, aSize = sequenceRef.getLength();
1024
1025     if (aSize == 0)
1026     {
1027       // Its been deleted
1028       return;
1029     }
1030
1031     int position;
1032     Annotation[] temp = new Annotation[aSize];
1033     Integer index;
1034     if (sequenceMapping != null)
1035     {
1036       for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++)
1037       {
1038         index = Integer.valueOf(a);
1039         Annotation annot = sequenceMapping.get(index);
1040         if (annot != null)
1041         {
1042           position = sequenceRef.findIndex(a) - 1;
1043
1044           temp[position] = annot;
1045         }
1046       }
1047     }
1048     annotations = temp;
1049   }
1050
1051   /**
1052    * remove any null entries in annotation row and return the number of non-null
1053    * annotation elements.
1054    * 
1055    * @return
1056    */
1057   public int compactAnnotationArray()
1058   {
1059     int i = 0, iSize = annotations.length;
1060     while (i < iSize)
1061     {
1062       if (annotations[i] == null)
1063       {
1064         if (i + 1 < iSize)
1065         {
1066           System.arraycopy(annotations, i + 1, annotations, i,
1067                   iSize - i - 1);
1068         }
1069         iSize--;
1070       }
1071       else
1072       {
1073         i++;
1074       }
1075     }
1076     Annotation[] ann = annotations;
1077     annotations = new Annotation[i];
1078     System.arraycopy(ann, 0, annotations, 0, i);
1079     ann = null;
1080     return iSize;
1081   }
1082
1083   /**
1084    * Associate this annotation with the aligned residues of a particular
1085    * sequence. sequenceMapping will be updated in the following way: null
1086    * sequenceI - existing mapping will be discarded but annotations left in
1087    * mapped positions. valid sequenceI not equal to current sequenceRef: mapping
1088    * is discarded and rebuilt assuming 1:1 correspondence TODO: overload with
1089    * parameter to specify correspondence between current and new sequenceRef
1090    * 
1091    * @param sequenceI
1092    */
1093   public void setSequenceRef(SequenceI sequenceI)
1094   {
1095     if (sequenceI != null)
1096     {
1097       if (sequenceRef != null)
1098       {
1099         boolean rIsDs = sequenceRef.getDatasetSequence() == null,
1100                 tIsDs = sequenceI.getDatasetSequence() == null;
1101         if (sequenceRef != sequenceI
1102                 && (rIsDs && !tIsDs
1103                         && sequenceRef != sequenceI.getDatasetSequence())
1104                 && (!rIsDs && tIsDs
1105                         && sequenceRef.getDatasetSequence() != sequenceI)
1106                 && (!rIsDs && !tIsDs
1107                         && sequenceRef.getDatasetSequence() != sequenceI
1108                                 .getDatasetSequence())
1109                 && !sequenceRef.equals(sequenceI))
1110         {
1111           // if sequenceRef isn't intersecting with sequenceI
1112           // throw away old mapping and reconstruct.
1113           sequenceRef = null;
1114           if (sequenceMapping != null)
1115           {
1116             sequenceMapping = null;
1117             // compactAnnotationArray();
1118           }
1119           createSequenceMapping(sequenceI, 1, true);
1120           adjustForAlignment();
1121         }
1122         else
1123         {
1124           // Mapping carried over
1125           sequenceRef = sequenceI;
1126         }
1127       }
1128       else
1129       {
1130         // No mapping exists
1131         createSequenceMapping(sequenceI, 1, true);
1132         adjustForAlignment();
1133       }
1134     }
1135     else
1136     {
1137       // throw away the mapping without compacting.
1138       sequenceMapping = null;
1139       sequenceRef = null;
1140     }
1141   }
1142
1143   /**
1144    * @return the score
1145    */
1146   public double getScore()
1147   {
1148     return score;
1149   }
1150
1151   /**
1152    * @param score
1153    *          the score to set
1154    */
1155   public void setScore(double score)
1156   {
1157     hasScore = true;
1158     this.score = score;
1159   }
1160
1161   /**
1162    * 
1163    * @return true if annotation has an associated score
1164    */
1165   public boolean hasScore()
1166   {
1167     return hasScore || !Double.isNaN(score);
1168   }
1169   
1170   /**
1171    * Score only annotation
1172    * 
1173    * @param label
1174    * @param description
1175    * @param score
1176    */
1177   public AlignmentAnnotation(String label, String description, double score)
1178   {
1179     this(label, description, null);
1180     setScore(score);
1181   }
1182
1183   /**
1184    * copy constructor with edit based on the hidden columns marked in colSel
1185    * 
1186    * @param alignmentAnnotation
1187    * @param colSel
1188    */
1189   public AlignmentAnnotation(AlignmentAnnotation alignmentAnnotation,
1190           HiddenColumns hidden)
1191   {
1192     this(alignmentAnnotation);
1193     if (annotations == null)
1194     {
1195       return;
1196     }
1197     makeVisibleAnnotation(hidden);
1198   }
1199
1200
1201   public void setPadGaps(boolean padgaps, char gapchar)
1202   {
1203     this.padGaps = padgaps;
1204     if (padgaps)
1205     {
1206       hasText = true;
1207       for (int i = 0; i < annotations.length; i++)
1208       {
1209         if (annotations[i] == null)
1210         {
1211           annotations[i] = new Annotation(String.valueOf(gapchar), null,
1212                   ' ', 0f, null);
1213         }
1214         else if (annotations[i].displayCharacter == null
1215                 || annotations[i].displayCharacter.equals(" "))
1216         {
1217           annotations[i].displayCharacter = String.valueOf(gapchar);
1218         }
1219       }
1220     }
1221   }
1222
1223   /**
1224    * format description string for display
1225    * 
1226    * @param seqname
1227    * @return Get the annotation description string optionally prefixed by
1228    *         associated sequence name (if any)
1229    */
1230   public String getDescription(boolean seqname)
1231   {
1232     if (seqname && this.sequenceRef != null)
1233     {
1234       int i = description.toLowerCase().indexOf("<html>");
1235       if (i > -1)
1236       {
1237         // move the html tag to before the sequence reference.
1238         return "<html>" + sequenceRef.getName() + " : "
1239                 + description.substring(i + 6);
1240       }
1241       return sequenceRef.getName() + " : " + description;
1242     }
1243     return description;
1244   }
1245
1246   public boolean isValidStruc()
1247   {
1248     return invalidrnastruc == -1;
1249   }
1250
1251   public long getInvalidStrucPos()
1252   {
1253     return invalidrnastruc;
1254   }
1255
1256   /**
1257    * machine readable ID string indicating what generated this annotation
1258    */
1259   protected String calcId = "";
1260
1261   /**
1262    * properties associated with the calcId
1263    */
1264   protected Map<String, String> properties = new HashMap<>();
1265
1266   /**
1267    * base colour for line graphs. If null, will be set automatically by
1268    * searching the alignment annotation
1269    */
1270   public java.awt.Color _linecolour;
1271
1272   public String getCalcId()
1273   {
1274     return calcId;
1275   }
1276
1277   public void setCalcId(String calcId)
1278   {
1279     this.calcId = calcId;
1280   }
1281
1282   public boolean isRNA()
1283   {
1284     return isrna;
1285   }
1286
1287   /**
1288    * transfer annotation to the given sequence using the given mapping from the
1289    * current positions or an existing sequence mapping
1290    * 
1291    * @param sq
1292    * @param sp2sq
1293    *          map involving sq as To or From
1294    */
1295   public void liftOver(SequenceI sq, Mapping sp2sq)
1296   {
1297     if (sp2sq.getMappedWidth() != sp2sq.getWidth())
1298     {
1299       // TODO: employ getWord/MappedWord to transfer annotation between cDNA and
1300       // Protein reference frames
1301       throw new Error(
1302               "liftOver currently not implemented for transfer of annotation between different types of seqeunce");
1303     }
1304     boolean mapIsTo = (sp2sq != null)
1305             ? (sp2sq.getTo() == sq
1306                     || sp2sq.getTo() == sq.getDatasetSequence())
1307             : false;
1308
1309     // TODO build a better annotation element map and get rid of annotations[]
1310     Map<Integer, Annotation> mapForsq = new HashMap<>();
1311     if (sequenceMapping != null)
1312     {
1313       if (sp2sq != null)
1314       {
1315         for (Entry<Integer, Annotation> ie : sequenceMapping.entrySet())
1316         {
1317           Integer mpos = Integer
1318                   .valueOf(mapIsTo ? sp2sq.getMappedPosition(ie.getKey())
1319                           : sp2sq.getPosition(ie.getKey()));
1320           if (mpos >= sq.getStart() && mpos <= sq.getEnd())
1321           {
1322             mapForsq.put(mpos, ie.getValue());
1323           }
1324         }
1325         sequenceMapping = mapForsq;
1326         sequenceRef = sq;
1327         adjustForAlignment();
1328       }
1329       else
1330       {
1331         // trim positions
1332       }
1333     }
1334   }
1335
1336   /**
1337    * like liftOver but more general.
1338    * 
1339    * Takes an array of int pairs that will be used to update the internal
1340    * sequenceMapping and so shuffle the annotated positions
1341    * 
1342    * @param newref
1343    *          - new sequence reference for the annotation row - if null,
1344    *          sequenceRef is left unchanged
1345    * @param mapping
1346    *          array of ints containing corresponding positions
1347    * @param from
1348    *          - column for current coordinate system (-1 for index+1)
1349    * @param to
1350    *          - column for destination coordinate system (-1 for index+1)
1351    * @param idxoffset
1352    *          - offset added to index when referencing either coordinate system
1353    * @note no checks are made as to whether from and/or to are sensible
1354    * @note caller should add the remapped annotation to newref if they have not
1355    *       already
1356    */
1357   public void remap(SequenceI newref, HashMap<Integer, int[]> mapping,
1358           int from, int to, int idxoffset)
1359   {
1360     if (mapping != null)
1361     {
1362       Map<Integer, Annotation> old = sequenceMapping;
1363       Map<Integer, Annotation> remap = new HashMap<>();
1364       int index = -1;
1365       for (int mp[] : mapping.values())
1366       {
1367         if (index++ < 0)
1368         {
1369           continue;
1370         }
1371         Annotation ann = null;
1372         if (from == -1)
1373         {
1374           ann = sequenceMapping.get(Integer.valueOf(idxoffset + index));
1375         }
1376         else
1377         {
1378           if (mp != null && mp.length > from)
1379           {
1380             ann = sequenceMapping.get(Integer.valueOf(mp[from]));
1381           }
1382         }
1383         if (ann != null)
1384         {
1385           if (to == -1)
1386           {
1387             remap.put(Integer.valueOf(idxoffset + index), ann);
1388           }
1389           else
1390           {
1391             if (to > -1 && to < mp.length)
1392             {
1393               remap.put(Integer.valueOf(mp[to]), ann);
1394             }
1395           }
1396         }
1397       }
1398       sequenceMapping = remap;
1399       old.clear();
1400       if (newref != null)
1401       {
1402         sequenceRef = newref;
1403       }
1404       adjustForAlignment();
1405     }
1406   }
1407
1408   public String getProperty(String property)
1409   {
1410     if (properties == null)
1411     {
1412       return null;
1413     }
1414     return properties.get(property);
1415   }
1416
1417   public void setProperty(String property, String value)
1418   {
1419     if (properties == null)
1420     {
1421       properties = new HashMap<>();
1422     }
1423     properties.put(property, value);
1424   }
1425
1426   public boolean hasProperties()
1427   {
1428     return properties != null && properties.size() > 0;
1429   }
1430
1431   public Collection<String> getProperties()
1432   {
1433     if (properties == null)
1434     {
1435       return Collections.emptyList();
1436     }
1437     return properties.keySet();
1438   }
1439
1440   /**
1441    * Returns the Annotation for the given sequence position (base 1) if any,
1442    * else null
1443    * 
1444    * @param position
1445    * @return
1446    */
1447   public Annotation getAnnotationForPosition(int position)
1448   {
1449     return sequenceMapping == null ? null : sequenceMapping.get(position);
1450
1451   }
1452
1453   /**
1454    * Set the id to "ann" followed by a counter that increments so as to be
1455    * unique for the lifetime of the JVM
1456    */
1457   protected final void setAnnotationId()
1458   {
1459     this.annotationId = ANNOTATION_ID_PREFIX + Long.toString(nextId());
1460   }
1461
1462   /**
1463    * Returns the match for the last unmatched opening RNA helix pair symbol
1464    * preceding the given column, or '(' if nothing found to match.
1465    * 
1466    * @param column
1467    * @return
1468    */
1469   public String getDefaultRnaHelixSymbol(int column)
1470   {
1471     String result = "(";
1472     if (annotations == null)
1473     {
1474       return result;
1475     }
1476
1477     /*
1478      * for each preceding column, if it contains an open bracket, 
1479      * count whether it is still unmatched at column, if so return its pair
1480      * (likely faster than the fancy alternative using stacks)
1481      */
1482     for (int col = column - 1; col >= 0; col--)
1483     {
1484       Annotation annotation = annotations[col];
1485       if (annotation == null)
1486       {
1487         continue;
1488       }
1489       String displayed = annotation.displayCharacter;
1490       if (displayed == null || displayed.length() != 1)
1491       {
1492         continue;
1493       }
1494       char symbol = displayed.charAt(0);
1495       if (!Rna.isOpeningParenthesis(symbol))
1496       {
1497         continue;
1498       }
1499
1500       /*
1501        * found an opening bracket symbol
1502        * count (closing-opening) symbols of this type that follow it,
1503        * up to and excluding the target column; if the count is less
1504        * than 1, the opening bracket is unmatched, so return its match
1505        */
1506       String closer = String
1507               .valueOf(Rna.getMatchingClosingParenthesis(symbol));
1508       String opener = String.valueOf(symbol);
1509       int count = 0;
1510       for (int j = col + 1; j < column; j++)
1511       {
1512         if (annotations[j] != null)
1513         {
1514           String s = annotations[j].displayCharacter;
1515           if (closer.equals(s))
1516           {
1517             count++;
1518           }
1519           else if (opener.equals(s))
1520           {
1521             count--;
1522           }
1523         }
1524       }
1525       if (count < 1)
1526       {
1527         return closer;
1528       }
1529     }
1530     return result;
1531   }
1532
1533   protected static synchronized long nextId()
1534   {
1535     return counter++;
1536   }
1537
1538   /**
1539    * 
1540    * @return true for rows that have a range of values in their annotation set
1541    */
1542   public boolean isQuantitative()
1543   {
1544     return graphMin < graphMax;
1545   }
1546
1547   /**
1548    * delete any columns in alignmentAnnotation that are hidden (including
1549    * sequence associated annotation).
1550    * 
1551    * @param hiddenColumns
1552    *          the set of hidden columns
1553    */
1554   public void makeVisibleAnnotation(HiddenColumns hiddenColumns)
1555   {
1556     if (annotations != null)
1557     {
1558       makeVisibleAnnotation(0, annotations.length, hiddenColumns);
1559     }
1560   }
1561
1562   /**
1563    * delete any columns in alignmentAnnotation that are hidden (including
1564    * sequence associated annotation).
1565    * 
1566    * @param start
1567    *          remove any annotation to the right of this column
1568    * @param end
1569    *          remove any annotation to the left of this column
1570    * @param hiddenColumns
1571    *          the set of hidden columns
1572    */
1573   public void makeVisibleAnnotation(int start, int end,
1574           HiddenColumns hiddenColumns)
1575   {
1576     if (annotations != null)
1577     {
1578       if (hiddenColumns.hasHiddenColumns())
1579       {
1580         removeHiddenAnnotation(start, end, hiddenColumns);
1581       }
1582       else
1583       {
1584         restrict(start, end);
1585       }
1586     }
1587   }
1588
1589   /**
1590    * The actual implementation of deleting hidden annotation columns
1591    * 
1592    * @param start
1593    *          remove any annotation to the right of this column
1594    * @param end
1595    *          remove any annotation to the left of this column
1596    * @param hiddenColumns
1597    *          the set of hidden columns
1598    */
1599   private void removeHiddenAnnotation(int start, int end,
1600           HiddenColumns hiddenColumns)
1601   {
1602     // mangle the alignmentAnnotation annotation array
1603     ArrayList<Annotation[]> annels = new ArrayList<>();
1604     Annotation[] els = null;
1605
1606     int w = 0;
1607
1608     Iterator<int[]> blocks = hiddenColumns.getVisContigsIterator(start,
1609             end + 1, false);
1610
1611     int copylength;
1612     int annotationLength;
1613     while (blocks.hasNext())
1614     {
1615       int[] block = blocks.next();
1616       annotationLength = block[1] - block[0] + 1;
1617
1618       if (blocks.hasNext())
1619       {
1620         // copy just the visible segment of the annotation row
1621         copylength = annotationLength;
1622       }
1623       else
1624       {
1625         if (annotationLength + block[0] <= annotations.length)
1626         {
1627           // copy just the visible segment of the annotation row
1628           copylength = annotationLength;
1629         }
1630         else
1631         {
1632           // copy to the end of the annotation row
1633           copylength = annotations.length - block[0];
1634         }
1635       }
1636
1637       els = new Annotation[annotationLength];
1638       annels.add(els);
1639       System.arraycopy(annotations, block[0], els, 0, copylength);
1640       w += annotationLength;
1641     }
1642
1643     if (w != 0)
1644     {
1645       annotations = new Annotation[w];
1646
1647       w = 0;
1648       for (Annotation[] chnk : annels)
1649       {
1650         System.arraycopy(chnk, 0, annotations, w, chnk.length);
1651         w += chnk.length;
1652       }
1653     }
1654   }
1655
1656   public static Iterable<AlignmentAnnotation> findAnnotations(
1657           Iterable<AlignmentAnnotation> list, SequenceI seq, String calcId,
1658           String label)
1659   {
1660     ArrayList<AlignmentAnnotation> aa = new ArrayList<>();
1661     for (AlignmentAnnotation ann : list)
1662     {
1663       if ((calcId == null || (ann.getCalcId() != null
1664               && ann.getCalcId().equals(calcId)))
1665               && (seq == null || (ann.sequenceRef != null
1666                       && ann.sequenceRef == seq))
1667               && (label == null
1668                       || (ann.label != null && ann.label.equals(label))))
1669       {
1670         aa.add(ann);
1671       }
1672     }
1673     return aa;
1674   }
1675
1676   /**
1677    * Answer true if any annotation matches the calcId passed in (if not null).
1678    * 
1679    * @param list
1680    *          annotation to search
1681    * @param calcId
1682    * @return
1683    */
1684   public static boolean hasAnnotation(List<AlignmentAnnotation> list,
1685           String calcId)
1686   {
1687
1688     if (calcId != null && !"".equals(calcId))
1689     {
1690       for (AlignmentAnnotation a : list)
1691       {
1692         if (a.getCalcId() == calcId)
1693         {
1694           return true;
1695         }
1696       }
1697     }
1698     return false;
1699   }
1700
1701   public static Iterable<AlignmentAnnotation> findAnnotation(
1702           List<AlignmentAnnotation> list, String calcId)
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   public double getBitScore()
1722   {
1723     return bitScore;
1724   }
1725
1726   public void setBitScore(double bitScore)
1727   {
1728     this.bitScore = bitScore;
1729   }
1730
1731   public double getEValue()
1732   {
1733     return eValue;
1734   }
1735
1736   public void setEValue(double eValue)
1737   {
1738     this.eValue = eValue;
1739   }
1740
1741 }