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