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