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