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