JAL-3070 JAL-3066 AlignmentI.findOrAddAnnotation(AlignmentAnnotation ala) - calls...
[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     updateAlignmentAnnotationFrom(annotation);
714   }
715
716   /**
717    * copy attributes and annotation from an existing annotation (used by copy
718    * constructor). This method does not update the unique annotationId
719    * 
720    * @param annotation
721    */
722   public void updateAlignmentAnnotationFrom(AlignmentAnnotation annotation)
723   {
724     this.label = new String(annotation.label);
725     if (annotation.description != null)
726     {
727       this.description = new String(annotation.description);
728     }
729     this.graphMin = annotation.graphMin;
730     this.graphMax = annotation.graphMax;
731     this.graph = annotation.graph;
732     this.graphHeight = annotation.graphHeight;
733     this.graphGroup = annotation.graphGroup;
734     this.groupRef = annotation.groupRef;
735     this.editable = annotation.editable;
736     this.autoCalculated = annotation.autoCalculated;
737     this.hasIcons = annotation.hasIcons;
738     this.hasText = annotation.hasText;
739     this.height = annotation.height;
740     this.label = annotation.label;
741     this.padGaps = annotation.padGaps;
742     this.visible = annotation.visible;
743     this.centreColLabels = annotation.centreColLabels;
744     this.scaleColLabel = annotation.scaleColLabel;
745     this.showAllColLabels = annotation.showAllColLabels;
746     this.calcId = annotation.calcId;
747     this.bitScore = annotation.bitScore;
748     this.eValue = annotation.eValue;
749
750     if (annotation.properties != null)
751     {
752       properties = new HashMap<>();
753       for (Map.Entry<String, String> val : annotation.properties.entrySet())
754       {
755         properties.put(val.getKey(), val.getValue());
756       }
757     }
758     if (this.hasScore = annotation.hasScore)
759     {
760       this.score = annotation.score;
761     }
762     if (annotation.threshold != null)
763     {
764       threshold = new GraphLine(annotation.threshold);
765     }
766     Annotation[] ann = annotation.annotations;
767     if (annotation.annotations != null)
768     {
769       this.annotations = new Annotation[ann.length];
770       for (int i = 0; i < ann.length; i++)
771       {
772         if (ann[i] != null)
773         {
774           annotations[i] = new Annotation(ann[i]);
775           if (_linecolour != null)
776           {
777             _linecolour = annotations[i].colour;
778           }
779         }
780       }
781     }
782     if (annotation.sequenceRef != null)
783     {
784       this.sequenceRef = annotation.sequenceRef;
785       if (annotation.sequenceMapping != null)
786       {
787         Integer p = null;
788         sequenceMapping = new HashMap<>();
789         Iterator<Integer> pos = annotation.sequenceMapping.keySet()
790                 .iterator();
791         while (pos.hasNext())
792         {
793           // could optimise this!
794           p = pos.next();
795           Annotation a = annotation.sequenceMapping.get(p);
796           if (a == null)
797           {
798             continue;
799           }
800           if (ann != null)
801           {
802             for (int i = 0; i < ann.length; i++)
803             {
804               if (ann[i] == a)
805               {
806                 sequenceMapping.put(p, annotations[i]);
807               }
808             }
809           }
810         }
811       }
812       else
813       {
814         this.sequenceMapping = null;
815       }
816     }
817     // TODO: check if we need to do this: JAL-952
818     // if (this.isrna=annotation.isrna)
819     {
820       // _rnasecstr=new SequenceFeature[annotation._rnasecstr];
821     }
822     validateRangeAndDisplay(); // construct hashcodes, etc.
823   }
824
825   /**
826    * clip the annotation to the columns given by startRes and endRes (inclusive)
827    * and prune any existing sequenceMapping to just those columns.
828    * 
829    * @param startRes
830    * @param endRes
831    */
832   public void restrict(int startRes, int endRes)
833   {
834     if (annotations == null)
835     {
836       // non-positional
837       return;
838     }
839     if (startRes < 0)
840     {
841       startRes = 0;
842     }
843     if (startRes >= annotations.length)
844     {
845       startRes = annotations.length - 1;
846     }
847     if (endRes >= annotations.length)
848     {
849       endRes = annotations.length - 1;
850     }
851     if (annotations == null)
852     {
853       return;
854     }
855     Annotation[] temp = new Annotation[endRes - startRes + 1];
856     if (startRes < annotations.length)
857     {
858       System.arraycopy(annotations, startRes, temp, 0,
859               endRes - startRes + 1);
860     }
861     if (sequenceRef != null)
862     {
863       // Clip the mapping, if it exists.
864       int spos = sequenceRef.findPosition(startRes);
865       int epos = sequenceRef.findPosition(endRes);
866       if (sequenceMapping != null)
867       {
868         Map<Integer, Annotation> newmapping = new HashMap<>();
869         Iterator<Integer> e = sequenceMapping.keySet().iterator();
870         while (e.hasNext())
871         {
872           Integer pos = e.next();
873           if (pos.intValue() >= spos && pos.intValue() <= epos)
874           {
875             newmapping.put(pos, sequenceMapping.get(pos));
876           }
877         }
878         sequenceMapping.clear();
879         sequenceMapping = newmapping;
880       }
881     }
882     annotations = temp;
883   }
884
885   /**
886    * set the annotation row to be at least length Annotations
887    * 
888    * @param length
889    *          minimum number of columns required in the annotation row
890    * @return false if the annotation row is greater than length
891    */
892   public boolean padAnnotation(int length)
893   {
894     if (annotations == null)
895     {
896       return true; // annotation row is correct - null == not visible and
897       // undefined length
898     }
899     if (annotations.length < length)
900     {
901       Annotation[] na = new Annotation[length];
902       System.arraycopy(annotations, 0, na, 0, annotations.length);
903       annotations = na;
904       return true;
905     }
906     return annotations.length > length;
907
908   }
909
910   /**
911    * DOCUMENT ME!
912    * 
913    * @return DOCUMENT ME!
914    */
915   @Override
916   public String toString()
917   {
918     if (annotations == null)
919     {
920       return "";
921     }
922     StringBuilder buffer = new StringBuilder(256);
923
924     for (int i = 0; i < annotations.length; i++)
925     {
926       if (annotations[i] != null)
927       {
928         if (graph != 0)
929         {
930           buffer.append(annotations[i].value);
931         }
932         else if (hasIcons)
933         {
934           buffer.append(annotations[i].secondaryStructure);
935         }
936         else
937         {
938           buffer.append(annotations[i].displayCharacter);
939         }
940       }
941
942       buffer.append(", ");
943     }
944     // TODO: remove disgusting hack for 'special' treatment of consensus line.
945     if (label.indexOf("Consensus") == 0)
946     {
947       buffer.append("\n");
948
949       for (int i = 0; i < annotations.length; i++)
950       {
951         if (annotations[i] != null)
952         {
953           buffer.append(annotations[i].description);
954         }
955
956         buffer.append(", ");
957       }
958     }
959
960     return buffer.toString();
961   }
962
963   public void setThreshold(GraphLine line)
964   {
965     threshold = line;
966   }
967
968   public GraphLine getThreshold()
969   {
970     return threshold;
971   }
972
973   /**
974    * Attach the annotation to seqRef, starting from startRes position. If
975    * alreadyMapped is true then the indices of the annotation[] array are
976    * sequence positions rather than alignment column positions.
977    * 
978    * @param seqRef
979    * @param startRes
980    * @param alreadyMapped
981    *          - annotation are at aligned columns
982    */
983   public void createSequenceMapping(SequenceI seqRef, int startRes,
984           boolean alreadyMapped)
985   {
986
987     if (seqRef == null)
988     {
989       return;
990     }
991     sequenceRef = seqRef;
992     if (annotations == null)
993     {
994       return;
995     }
996     sequenceMapping = new HashMap<>();
997
998     int seqPos;
999
1000     for (int i = 0; i < annotations.length; i++)
1001     {
1002       if (annotations[i] != null)
1003       {
1004         if (alreadyMapped)
1005         {
1006           seqPos = seqRef.findPosition(i);
1007         }
1008         else
1009         {
1010           seqPos = i + startRes;
1011         }
1012
1013         sequenceMapping.put(Integer.valueOf(seqPos), annotations[i]);
1014       }
1015     }
1016
1017   }
1018
1019   /**
1020    * When positional annotation and a sequence reference is present, clears and
1021    * resizes the annotations array to the current alignment width, and adds
1022    * annotation according to aligned positions of the sequenceRef given by
1023    * sequenceMapping.
1024    */
1025   public void adjustForAlignment()
1026   {
1027     if (sequenceRef == null)
1028     {
1029       return;
1030     }
1031
1032     if (annotations == null)
1033     {
1034       return;
1035     }
1036
1037     int a = 0, aSize = sequenceRef.getLength();
1038
1039     if (aSize == 0)
1040     {
1041       // Its been deleted
1042       return;
1043     }
1044
1045     int position;
1046     Annotation[] temp = new Annotation[aSize];
1047     Integer index;
1048     if (sequenceMapping != null)
1049     {
1050       for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++)
1051       {
1052         index = Integer.valueOf(a);
1053         Annotation annot = sequenceMapping.get(index);
1054         if (annot != null)
1055         {
1056           position = sequenceRef.findIndex(a) - 1;
1057
1058           temp[position] = annot;
1059         }
1060       }
1061     }
1062     annotations = temp;
1063   }
1064
1065   /**
1066    * remove any null entries in annotation row and return the number of non-null
1067    * annotation elements.
1068    * 
1069    * @return
1070    */
1071   public int compactAnnotationArray()
1072   {
1073     int i = 0, iSize = annotations.length;
1074     while (i < iSize)
1075     {
1076       if (annotations[i] == null)
1077       {
1078         if (i + 1 < iSize)
1079         {
1080           System.arraycopy(annotations, i + 1, annotations, i,
1081                   iSize - i - 1);
1082         }
1083         iSize--;
1084       }
1085       else
1086       {
1087         i++;
1088       }
1089     }
1090     Annotation[] ann = annotations;
1091     annotations = new Annotation[i];
1092     System.arraycopy(ann, 0, annotations, 0, i);
1093     ann = null;
1094     return iSize;
1095   }
1096
1097   /**
1098    * Associate this annotation with the aligned residues of a particular
1099    * sequence. sequenceMapping will be updated in the following way: null
1100    * sequenceI - existing mapping will be discarded but annotations left in
1101    * mapped positions. valid sequenceI not equal to current sequenceRef: mapping
1102    * is discarded and rebuilt assuming 1:1 correspondence TODO: overload with
1103    * parameter to specify correspondence between current and new sequenceRef
1104    * 
1105    * @param sequenceI
1106    */
1107   public void setSequenceRef(SequenceI sequenceI)
1108   {
1109     if (sequenceI != null)
1110     {
1111       if (sequenceRef != null)
1112       {
1113         boolean rIsDs = sequenceRef.getDatasetSequence() == null,
1114                 tIsDs = sequenceI.getDatasetSequence() == null;
1115         if (sequenceRef != sequenceI
1116                 && (rIsDs && !tIsDs
1117                         && sequenceRef != sequenceI.getDatasetSequence())
1118                 && (!rIsDs && tIsDs
1119                         && sequenceRef.getDatasetSequence() != sequenceI)
1120                 && (!rIsDs && !tIsDs
1121                         && sequenceRef.getDatasetSequence() != sequenceI
1122                                 .getDatasetSequence())
1123                 && !sequenceRef.equals(sequenceI))
1124         {
1125           // if sequenceRef isn't intersecting with sequenceI
1126           // throw away old mapping and reconstruct.
1127           sequenceRef = null;
1128           if (sequenceMapping != null)
1129           {
1130             sequenceMapping = null;
1131             // compactAnnotationArray();
1132           }
1133           createSequenceMapping(sequenceI, 1, true);
1134           adjustForAlignment();
1135         }
1136         else
1137         {
1138           // Mapping carried over
1139           sequenceRef = sequenceI;
1140         }
1141       }
1142       else
1143       {
1144         // No mapping exists
1145         createSequenceMapping(sequenceI, 1, true);
1146         adjustForAlignment();
1147       }
1148     }
1149     else
1150     {
1151       // throw away the mapping without compacting.
1152       sequenceMapping = null;
1153       sequenceRef = null;
1154     }
1155   }
1156
1157   /**
1158    * @return the score
1159    */
1160   public double getScore()
1161   {
1162     return score;
1163   }
1164
1165   /**
1166    * @param score
1167    *          the score to set
1168    */
1169   public void setScore(double score)
1170   {
1171     hasScore = true;
1172     this.score = score;
1173   }
1174
1175   /**
1176    * 
1177    * @return true if annotation has an associated score
1178    */
1179   public boolean hasScore()
1180   {
1181     return hasScore || !Double.isNaN(score);
1182   }
1183   
1184   /**
1185    * Score only annotation
1186    * 
1187    * @param label
1188    * @param description
1189    * @param score
1190    */
1191   public AlignmentAnnotation(String label, String description, double score)
1192   {
1193     this(label, description, null);
1194     setScore(score);
1195   }
1196
1197   /**
1198    * copy constructor with edit based on the hidden columns marked in colSel
1199    * 
1200    * @param alignmentAnnotation
1201    * @param colSel
1202    */
1203   public AlignmentAnnotation(AlignmentAnnotation alignmentAnnotation,
1204           HiddenColumns hidden)
1205   {
1206     this(alignmentAnnotation);
1207     if (annotations == null)
1208     {
1209       return;
1210     }
1211     makeVisibleAnnotation(hidden);
1212   }
1213
1214
1215   public void setPadGaps(boolean padgaps, char gapchar)
1216   {
1217     this.padGaps = padgaps;
1218     if (padgaps)
1219     {
1220       hasText = true;
1221       for (int i = 0; i < annotations.length; i++)
1222       {
1223         if (annotations[i] == null)
1224         {
1225           annotations[i] = new Annotation(String.valueOf(gapchar), null,
1226                   ' ', 0f, null);
1227         }
1228         else if (annotations[i].displayCharacter == null
1229                 || annotations[i].displayCharacter.equals(" "))
1230         {
1231           annotations[i].displayCharacter = String.valueOf(gapchar);
1232         }
1233       }
1234     }
1235   }
1236
1237   /**
1238    * format description string for display
1239    * 
1240    * @param seqname
1241    * @return Get the annotation description string optionally prefixed by
1242    *         associated sequence name (if any)
1243    */
1244   public String getDescription(boolean seqname)
1245   {
1246     if (seqname && this.sequenceRef != null)
1247     {
1248       int i = description.toLowerCase().indexOf("<html>");
1249       if (i > -1)
1250       {
1251         // move the html tag to before the sequence reference.
1252         return "<html>" + sequenceRef.getName() + " : "
1253                 + description.substring(i + 6);
1254       }
1255       return sequenceRef.getName() + " : " + description;
1256     }
1257     return description;
1258   }
1259
1260   public boolean isValidStruc()
1261   {
1262     return invalidrnastruc == -1;
1263   }
1264
1265   public long getInvalidStrucPos()
1266   {
1267     return invalidrnastruc;
1268   }
1269
1270   /**
1271    * machine readable ID string indicating what generated this annotation
1272    */
1273   protected String calcId = "";
1274
1275   /**
1276    * properties associated with the calcId
1277    */
1278   protected Map<String, String> properties = new HashMap<>();
1279
1280   /**
1281    * base colour for line graphs. If null, will be set automatically by
1282    * searching the alignment annotation
1283    */
1284   public java.awt.Color _linecolour;
1285
1286   public String getCalcId()
1287   {
1288     return calcId;
1289   }
1290
1291   public void setCalcId(String calcId)
1292   {
1293     this.calcId = calcId;
1294   }
1295
1296   public boolean isRNA()
1297   {
1298     return isrna;
1299   }
1300
1301   /**
1302    * transfer annotation to the given sequence using the given mapping from the
1303    * current positions or an existing sequence mapping
1304    * 
1305    * @param sq
1306    * @param sp2sq
1307    *          map involving sq as To or From
1308    */
1309   public void liftOver(SequenceI sq, Mapping sp2sq)
1310   {
1311     if (sp2sq.getMappedWidth() != sp2sq.getWidth())
1312     {
1313       // TODO: employ getWord/MappedWord to transfer annotation between cDNA and
1314       // Protein reference frames
1315       throw new Error(
1316               "liftOver currently not implemented for transfer of annotation between different types of seqeunce");
1317     }
1318     boolean mapIsTo = (sp2sq != null)
1319             ? (sp2sq.getTo() == sq
1320                     || sp2sq.getTo() == sq.getDatasetSequence())
1321             : false;
1322
1323     // TODO build a better annotation element map and get rid of annotations[]
1324     Map<Integer, Annotation> mapForsq = new HashMap<>();
1325     if (sequenceMapping != null)
1326     {
1327       if (sp2sq != null)
1328       {
1329         for (Entry<Integer, Annotation> ie : sequenceMapping.entrySet())
1330         {
1331           Integer mpos = Integer
1332                   .valueOf(mapIsTo ? sp2sq.getMappedPosition(ie.getKey())
1333                           : sp2sq.getPosition(ie.getKey()));
1334           if (mpos >= sq.getStart() && mpos <= sq.getEnd())
1335           {
1336             mapForsq.put(mpos, ie.getValue());
1337           }
1338         }
1339         sequenceMapping = mapForsq;
1340         sequenceRef = sq;
1341         adjustForAlignment();
1342       }
1343       else
1344       {
1345         // trim positions
1346       }
1347     }
1348   }
1349
1350   /**
1351    * like liftOver but more general.
1352    * 
1353    * Takes an array of int pairs that will be used to update the internal
1354    * sequenceMapping and so shuffle the annotated positions
1355    * 
1356    * @param newref
1357    *          - new sequence reference for the annotation row - if null,
1358    *          sequenceRef is left unchanged
1359    * @param mapping
1360    *          array of ints containing corresponding positions
1361    * @param from
1362    *          - column for current coordinate system (-1 for index+1)
1363    * @param to
1364    *          - column for destination coordinate system (-1 for index+1)
1365    * @param idxoffset
1366    *          - offset added to index when referencing either coordinate system
1367    * @note no checks are made as to whether from and/or to are sensible
1368    * @note caller should add the remapped annotation to newref if they have not
1369    *       already
1370    */
1371   public void remap(SequenceI newref, HashMap<Integer, int[]> mapping,
1372           int from, int to, int idxoffset)
1373   {
1374     if (mapping != null)
1375     {
1376       Map<Integer, Annotation> old = sequenceMapping;
1377       Map<Integer, Annotation> remap = new HashMap<>();
1378       int index = -1;
1379       for (int mp[] : mapping.values())
1380       {
1381         if (index++ < 0)
1382         {
1383           continue;
1384         }
1385         Annotation ann = null;
1386         if (from == -1)
1387         {
1388           ann = sequenceMapping.get(Integer.valueOf(idxoffset + index));
1389         }
1390         else
1391         {
1392           if (mp != null && mp.length > from)
1393           {
1394             ann = sequenceMapping.get(Integer.valueOf(mp[from]));
1395           }
1396         }
1397         if (ann != null)
1398         {
1399           if (to == -1)
1400           {
1401             remap.put(Integer.valueOf(idxoffset + index), ann);
1402           }
1403           else
1404           {
1405             if (to > -1 && to < mp.length)
1406             {
1407               remap.put(Integer.valueOf(mp[to]), ann);
1408             }
1409           }
1410         }
1411       }
1412       sequenceMapping = remap;
1413       old.clear();
1414       if (newref != null)
1415       {
1416         sequenceRef = newref;
1417       }
1418       adjustForAlignment();
1419     }
1420   }
1421
1422   public String getProperty(String property)
1423   {
1424     if (properties == null)
1425     {
1426       return null;
1427     }
1428     return properties.get(property);
1429   }
1430
1431   public void setProperty(String property, String value)
1432   {
1433     if (properties == null)
1434     {
1435       properties = new HashMap<>();
1436     }
1437     properties.put(property, value);
1438   }
1439
1440   public boolean hasProperties()
1441   {
1442     return properties != null && properties.size() > 0;
1443   }
1444
1445   public Collection<String> getProperties()
1446   {
1447     if (properties == null)
1448     {
1449       return Collections.emptyList();
1450     }
1451     return properties.keySet();
1452   }
1453
1454   /**
1455    * Returns the Annotation for the given sequence position (base 1) if any,
1456    * else null
1457    * 
1458    * @param position
1459    * @return
1460    */
1461   public Annotation getAnnotationForPosition(int position)
1462   {
1463     return sequenceMapping == null ? null : sequenceMapping.get(position);
1464
1465   }
1466
1467   /**
1468    * Set the id to "ann" followed by a counter that increments so as to be
1469    * unique for the lifetime of the JVM
1470    */
1471   protected final void setAnnotationId()
1472   {
1473     this.annotationId = ANNOTATION_ID_PREFIX + Long.toString(nextId());
1474   }
1475
1476   /**
1477    * Returns the match for the last unmatched opening RNA helix pair symbol
1478    * preceding the given column, or '(' if nothing found to match.
1479    * 
1480    * @param column
1481    * @return
1482    */
1483   public String getDefaultRnaHelixSymbol(int column)
1484   {
1485     String result = "(";
1486     if (annotations == null)
1487     {
1488       return result;
1489     }
1490
1491     /*
1492      * for each preceding column, if it contains an open bracket, 
1493      * count whether it is still unmatched at column, if so return its pair
1494      * (likely faster than the fancy alternative using stacks)
1495      */
1496     for (int col = column - 1; col >= 0; col--)
1497     {
1498       Annotation annotation = annotations[col];
1499       if (annotation == null)
1500       {
1501         continue;
1502       }
1503       String displayed = annotation.displayCharacter;
1504       if (displayed == null || displayed.length() != 1)
1505       {
1506         continue;
1507       }
1508       char symbol = displayed.charAt(0);
1509       if (!Rna.isOpeningParenthesis(symbol))
1510       {
1511         continue;
1512       }
1513
1514       /*
1515        * found an opening bracket symbol
1516        * count (closing-opening) symbols of this type that follow it,
1517        * up to and excluding the target column; if the count is less
1518        * than 1, the opening bracket is unmatched, so return its match
1519        */
1520       String closer = String
1521               .valueOf(Rna.getMatchingClosingParenthesis(symbol));
1522       String opener = String.valueOf(symbol);
1523       int count = 0;
1524       for (int j = col + 1; j < column; j++)
1525       {
1526         if (annotations[j] != null)
1527         {
1528           String s = annotations[j].displayCharacter;
1529           if (closer.equals(s))
1530           {
1531             count++;
1532           }
1533           else if (opener.equals(s))
1534           {
1535             count--;
1536           }
1537         }
1538       }
1539       if (count < 1)
1540       {
1541         return closer;
1542       }
1543     }
1544     return result;
1545   }
1546
1547   protected static synchronized long nextId()
1548   {
1549     return counter++;
1550   }
1551
1552   /**
1553    * 
1554    * @return true for rows that have a range of values in their annotation set
1555    */
1556   public boolean isQuantitative()
1557   {
1558     return graphMin < graphMax;
1559   }
1560
1561   /**
1562    * delete any columns in alignmentAnnotation that are hidden (including
1563    * sequence associated annotation).
1564    * 
1565    * @param hiddenColumns
1566    *          the set of hidden columns
1567    */
1568   public void makeVisibleAnnotation(HiddenColumns hiddenColumns)
1569   {
1570     if (annotations != null)
1571     {
1572       makeVisibleAnnotation(0, annotations.length, hiddenColumns);
1573     }
1574   }
1575
1576   /**
1577    * delete any columns in alignmentAnnotation that are hidden (including
1578    * sequence associated annotation).
1579    * 
1580    * @param start
1581    *          remove any annotation to the right of this column
1582    * @param end
1583    *          remove any annotation to the left of this column
1584    * @param hiddenColumns
1585    *          the set of hidden columns
1586    */
1587   public void makeVisibleAnnotation(int start, int end,
1588           HiddenColumns hiddenColumns)
1589   {
1590     if (annotations != null)
1591     {
1592       if (hiddenColumns.hasHiddenColumns())
1593       {
1594         removeHiddenAnnotation(start, end, hiddenColumns);
1595       }
1596       else
1597       {
1598         restrict(start, end);
1599       }
1600     }
1601   }
1602
1603   /**
1604    * The actual implementation of deleting hidden annotation columns
1605    * 
1606    * @param start
1607    *          remove any annotation to the right of this column
1608    * @param end
1609    *          remove any annotation to the left of this column
1610    * @param hiddenColumns
1611    *          the set of hidden columns
1612    */
1613   private void removeHiddenAnnotation(int start, int end,
1614           HiddenColumns hiddenColumns)
1615   {
1616     // mangle the alignmentAnnotation annotation array
1617     ArrayList<Annotation[]> annels = new ArrayList<>();
1618     Annotation[] els = null;
1619
1620     int w = 0;
1621
1622     Iterator<int[]> blocks = hiddenColumns.getVisContigsIterator(start,
1623             end + 1, false);
1624
1625     int copylength;
1626     int annotationLength;
1627     while (blocks.hasNext())
1628     {
1629       int[] block = blocks.next();
1630       annotationLength = block[1] - block[0] + 1;
1631
1632       if (blocks.hasNext())
1633       {
1634         // copy just the visible segment of the annotation row
1635         copylength = annotationLength;
1636       }
1637       else
1638       {
1639         if (annotationLength + block[0] <= annotations.length)
1640         {
1641           // copy just the visible segment of the annotation row
1642           copylength = annotationLength;
1643         }
1644         else
1645         {
1646           // copy to the end of the annotation row
1647           copylength = annotations.length - block[0];
1648         }
1649       }
1650
1651       els = new Annotation[annotationLength];
1652       annels.add(els);
1653       System.arraycopy(annotations, block[0], els, 0, copylength);
1654       w += annotationLength;
1655     }
1656
1657     if (w != 0)
1658     {
1659       annotations = new Annotation[w];
1660
1661       w = 0;
1662       for (Annotation[] chnk : annels)
1663       {
1664         System.arraycopy(chnk, 0, annotations, w, chnk.length);
1665         w += chnk.length;
1666       }
1667     }
1668   }
1669
1670   public static Iterable<AlignmentAnnotation> findAnnotations(
1671           Iterable<AlignmentAnnotation> list, SequenceI seq, String calcId,
1672           String label)
1673   {
1674     ArrayList<AlignmentAnnotation> aa = new ArrayList<>();
1675     for (AlignmentAnnotation ann : list)
1676     {
1677       if ((calcId == null || (ann.getCalcId() != null
1678               && ann.getCalcId().equals(calcId)))
1679               && (seq == null || (ann.sequenceRef != null
1680                       && ann.sequenceRef == seq))
1681               && (label == null
1682                       || (ann.label != null && ann.label.equals(label))))
1683       {
1684         aa.add(ann);
1685       }
1686     }
1687     return aa;
1688   }
1689
1690   /**
1691    * Answer true if any annotation matches the calcId passed in (if not null).
1692    * 
1693    * @param list
1694    *          annotation to search
1695    * @param calcId
1696    * @return
1697    */
1698   public static boolean hasAnnotation(List<AlignmentAnnotation> list,
1699           String calcId)
1700   {
1701
1702     if (calcId != null && !"".equals(calcId))
1703     {
1704       for (AlignmentAnnotation a : list)
1705       {
1706         if (a.getCalcId() == calcId)
1707         {
1708           return true;
1709         }
1710       }
1711     }
1712     return false;
1713   }
1714
1715   public static Iterable<AlignmentAnnotation> findAnnotation(
1716           List<AlignmentAnnotation> list, String calcId)
1717   {
1718     List<AlignmentAnnotation> aa = new ArrayList<>();
1719     if (calcId == null)
1720     {
1721       return aa;
1722     }
1723     for (AlignmentAnnotation a : list)
1724     {
1725
1726       if (a.getCalcId() == calcId || (a.getCalcId() != null
1727               && calcId != null && a.getCalcId().equals(calcId)))
1728       {
1729         aa.add(a);
1730       }
1731     }
1732     return aa;
1733   }
1734
1735   public double getBitScore()
1736   {
1737     return bitScore;
1738   }
1739
1740   public void setBitScore(double bitScore)
1741   {
1742     this.bitScore = bitScore;
1743   }
1744
1745   public double getEValue()
1746   {
1747     return eValue;
1748   }
1749
1750   public void setEValue(double eValue)
1751   {
1752     this.eValue = eValue;
1753   }
1754
1755   public static AlignmentAnnotation findFirstAnnotation(
1756           Iterable<AlignmentAnnotation> alignmentAnnotation, String name,
1757           String calcId, boolean autoCalc, SequenceI seqRef,
1758           SequenceGroup groupRef)
1759   {
1760
1761     for (AlignmentAnnotation annot : alignmentAnnotation)
1762     {
1763       if (annot.autoCalculated == autoCalc && (name.equals(annot.label))
1764               && (calcId == null || annot.getCalcId().equals(calcId))
1765               && annot.sequenceRef == seqRef && annot.groupRef == groupRef)
1766       {
1767         return annot;
1768       }
1769     }
1770     return null;
1771   }
1772
1773 }