JAL-1489 change initial value to distinguish non-rna from valid and invalid RNA struc...
[jalview.git] / src / jalview / datamodel / AlignmentAnnotation.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 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.Collection;
29 import java.util.Collections;
30 import java.util.Enumeration;
31 import java.util.HashMap;
32 import java.util.Hashtable;
33 import java.util.Map;
34 import java.util.Map.Entry;
35
36 /**
37  * DOCUMENT ME!
38  * 
39  * @author $author$
40  * @version $Revision$
41  */
42 public class AlignmentAnnotation
43 {
44   /**
45    * If true, this annotations is calculated every edit, eg consensus, quality
46    * or conservation graphs
47    */
48   public boolean autoCalculated = false;
49
50   /**
51    * unique ID for this annotation, used to match up the same annotation row
52    * shown in multiple views and alignments
53    */
54   public String annotationId;
55
56   /**
57    * the sequence this annotation is associated with (or null)
58    */
59   public SequenceI sequenceRef;
60
61   /** label shown in dropdown menus and in the annotation label area */
62   public String label;
63
64   /** longer description text shown as a tooltip */
65   public String description;
66
67   /** Array of annotations placed in the current coordinate system */
68   public Annotation[] annotations;
69
70   public ArrayList<SimpleBP> bps = null;
71
72   /**
73    * RNA secondary structure contact positions
74    */
75   public SequenceFeature[] _rnasecstr = null;
76
77   /**
78    * position of annotation resulting in invalid WUSS parsing or -1. -2 means
79    * there was no RNA structure in this annotation
80    */
81   private long invalidrnastruc = -2;
82
83   /**
84    * Updates the _rnasecstr field Determines the positions that base pair and
85    * the positions of helices based on secondary structure from a Stockholm file
86    * 
87    * @param RNAannot
88    */
89   private void _updateRnaSecStr(CharSequence RNAannot)
90   {
91     try
92     {
93       _rnasecstr = Rna.GetBasePairs(RNAannot);
94       bps = Rna.GetModeleBP(RNAannot);
95       invalidrnastruc = -1;
96     } catch (WUSSParseException px)
97     {
98       // DEBUG System.out.println(px);
99       invalidrnastruc = px.getProblemPos();
100     }
101     if (invalidrnastruc > -1)
102     {
103       return;
104     }
105     Rna.HelixMap(_rnasecstr);
106     // setRNAStruc(RNAannot);
107
108     if (_rnasecstr != null && _rnasecstr.length > 0)
109     {
110       // show all the RNA secondary structure annotation symbols.
111       isrna = true;
112       showAllColLabels = true;
113       scaleColLabel = true;
114       _markRnaHelices();
115     }
116     // System.out.println("featuregroup " + _rnasecstr[0].getFeatureGroup());
117
118   }
119
120   private void _markRnaHelices()
121   {
122     // Figure out number of helices
123     // Length of rnasecstr is the number of pairs of positions that base pair
124     // with each other in the secondary structure
125     for (int x = 0; x < _rnasecstr.length; x++)
126     {
127
128       /*
129        * System.out.println(this.annotation._rnasecstr[x] + " Begin" +
130        * this.annotation._rnasecstr[x].getBegin());
131        */
132       // System.out.println(this.annotation._rnasecstr[x].getFeatureGroup());
133       int val = 0;
134       try
135       {
136         val = Integer.valueOf(_rnasecstr[x].getFeatureGroup());
137       } catch (NumberFormatException q)
138       {
139       }
140       ;
141
142       annotations[_rnasecstr[x].getBegin()].value = val;
143       annotations[_rnasecstr[x].getEnd()].value = val;
144
145       // annotations[_rnasecstr[x].getBegin()].displayCharacter = "" + val;
146       // annotations[_rnasecstr[x].getEnd()].displayCharacter = "" + val;
147     }
148   }
149   /**
150    * map of positions in the associated annotation
151    */
152   public java.util.Hashtable<Integer, Annotation> sequenceMapping;
153
154   /** DOCUMENT ME!! */
155   public float graphMin;
156
157   /** DOCUMENT ME!! */
158   public float graphMax;
159
160   /**
161    * Score associated with label and description.
162    */
163   public double score = Double.NaN;
164
165   /**
166    * flag indicating if annotation has a score.
167    */
168   public boolean hasScore = false;
169
170   public GraphLine threshold;
171
172   // Graphical hints and tips
173
174   /** Can this row be edited by the user ? */
175   public boolean editable = false;
176
177   /** Indicates if annotation has a graphical symbol track */
178   public boolean hasIcons; //
179
180   /** Indicates if annotation has a text character label */
181   public boolean hasText;
182
183   /** is the row visible */
184   public boolean visible = true;
185
186   public int graphGroup = -1;
187
188   /** Displayed height of row in pixels */
189   public int height = 0;
190
191   public int graph = 0;
192
193   public int graphHeight = 40;
194
195   public boolean padGaps = false;
196
197   public static final int NO_GRAPH = 0;
198
199   public static final int BAR_GRAPH = 1;
200
201   public static final int LINE_GRAPH = 2;
202
203   public boolean belowAlignment = true;
204
205   public SequenceGroup groupRef = null;
206
207   /**
208    * display every column label, even if there is a row of identical labels
209    */
210   public boolean showAllColLabels = false;
211
212   /**
213    * scale the column label to fit within the alignment column.
214    */
215   public boolean scaleColLabel = false;
216
217   /**
218    * centre the column labels relative to the alignment column
219    */
220   public boolean centreColLabels = false;
221
222   private boolean isrna;
223
224   /*
225    * (non-Javadoc)
226    * 
227    * @see java.lang.Object#finalize()
228    */
229   protected void finalize() throws Throwable
230   {
231     sequenceRef = null;
232     groupRef = null;
233     super.finalize();
234   }
235
236   public static int getGraphValueFromString(String string)
237   {
238     if (string.equalsIgnoreCase("BAR_GRAPH"))
239     {
240       return BAR_GRAPH;
241     }
242     else if (string.equalsIgnoreCase("LINE_GRAPH"))
243     {
244       return LINE_GRAPH;
245     }
246     else
247     {
248       return NO_GRAPH;
249     }
250   }
251
252   // JBPNote: what does this do ?
253   public void ConcenStru(CharSequence RNAannot) throws WUSSParseException
254   {
255     bps = Rna.GetModeleBP(RNAannot);
256   }
257
258   /**
259    * Creates a new AlignmentAnnotation object.
260    * 
261    * @param label
262    *          short label shown under sequence labels
263    * @param description
264    *          text displayed on mouseover
265    * @param annotations
266    *          set of positional annotation elements
267    */
268   public AlignmentAnnotation(String label, String description,
269           Annotation[] annotations)
270   {
271     // always editable?
272     editable = true;
273     this.label = label;
274     this.description = description;
275     this.annotations = annotations;
276
277     validateRangeAndDisplay();
278   }
279
280   /**
281    * Checks if annotation labels represent secondary structures
282    * 
283    */
284   void areLabelsSecondaryStructure()
285   {
286     boolean nonSSLabel = false;
287     isrna = false;
288     StringBuffer rnastring = new StringBuffer();
289
290     char firstChar = 0;
291     for (int i = 0; i < annotations.length; i++)
292     {
293       if (annotations[i] == null)
294       {
295         continue;
296       }
297       if (annotations[i].secondaryStructure == 'H'
298               || annotations[i].secondaryStructure == 'E')
299       {
300         hasIcons |= true;
301       }
302       else
303       // Check for RNA secondary structure
304       {
305         // System.out.println(annotations[i].secondaryStructure);
306         // TODO: 2.8.2 should this ss symbol validation check be a function in
307         // RNA/ResidueProperties ?
308         if (annotations[i].secondaryStructure == '('
309                 || annotations[i].secondaryStructure == '['
310                 || annotations[i].secondaryStructure == '<'
311                 || annotations[i].secondaryStructure == '{'
312                 || annotations[i].secondaryStructure == 'A'
313                 || annotations[i].secondaryStructure == 'B'
314                 || annotations[i].secondaryStructure == 'C'
315                 || annotations[i].secondaryStructure == 'D'
316                 || annotations[i].secondaryStructure == 'E'
317                 || annotations[i].secondaryStructure == 'F'
318                 || annotations[i].secondaryStructure == 'G'
319                 || annotations[i].secondaryStructure == 'H'
320                 || annotations[i].secondaryStructure == 'I'
321                 || annotations[i].secondaryStructure == 'J'
322                 || annotations[i].secondaryStructure == 'K'
323                 || annotations[i].secondaryStructure == 'L'
324                 || annotations[i].secondaryStructure == 'M'
325                 || annotations[i].secondaryStructure == 'N'
326                 || annotations[i].secondaryStructure == 'O'
327                 || annotations[i].secondaryStructure == 'P'
328                 || annotations[i].secondaryStructure == 'Q'
329                 || annotations[i].secondaryStructure == 'R'
330                 || annotations[i].secondaryStructure == 'S'
331                 || annotations[i].secondaryStructure == 'T'
332                 || annotations[i].secondaryStructure == 'U'
333                 || annotations[i].secondaryStructure == 'V'
334                 || annotations[i].secondaryStructure == 'W'
335                 || annotations[i].secondaryStructure == 'X'
336                 || annotations[i].secondaryStructure == 'Y'
337                 || annotations[i].secondaryStructure == 'Z')
338         {
339           hasIcons |= true;
340           isrna |= true;
341         }
342       }
343
344       // System.out.println("displaychar " + annotations[i].displayCharacter);
345
346       if (annotations[i].displayCharacter == null
347               || annotations[i].displayCharacter.length() == 0)
348       {
349         rnastring.append('.');
350         continue;
351       }
352       if (annotations[i].displayCharacter.length() == 1)
353       {
354         firstChar = annotations[i].displayCharacter.charAt(0);
355         // check to see if it looks like a sequence or is secondary structure
356         // labelling.
357         if (annotations[i].secondaryStructure != ' '
358                 && !hasIcons
359                 &&
360                 // Uncomment to only catch case where
361                 // displayCharacter==secondary
362                 // Structure
363                 // to correctly redisplay SS annotation imported from Stockholm,
364                 // exported to JalviewXML and read back in again.
365                 // &&
366                 // annotations[i].displayCharacter.charAt(0)==annotations[i].secondaryStructure
367                 firstChar != ' '
368                 && firstChar != '$'
369                 && firstChar != 0xCE
370                 && firstChar != '('
371                 && firstChar != '['
372                 && firstChar != '>'
373                 && firstChar != '{'
374                 && firstChar != 'A'
375                 && firstChar != 'B'
376                 && firstChar != 'C'
377                 && firstChar != 'D'
378                 && firstChar != 'E'
379                 && firstChar != 'F'
380                 && firstChar != 'G'
381                 && firstChar != 'H'
382                 && firstChar != 'I'
383                 && firstChar != 'J'
384                 && firstChar != 'K'
385                 && firstChar != 'L'
386                 && firstChar != 'M'
387                 && firstChar != 'N'
388                 && firstChar != 'O'
389                 && firstChar != 'P'
390                 && firstChar != 'Q'
391                 && firstChar != 'R'
392                 && firstChar != 'S'
393                 && firstChar != 'T'
394                 && firstChar != 'U'
395                 && firstChar != 'V'
396                 && firstChar != 'W'
397                 && firstChar != 'X'
398                 && firstChar != 'Y'
399                 && firstChar != 'Z'
400                 && firstChar != '-'
401                 && firstChar < jalview.schemes.ResidueProperties.aaIndex.length)
402         {
403           if (jalview.schemes.ResidueProperties.aaIndex[firstChar] < 23) // TODO:
404                                                                          // parameterise
405                                                                          // to
406                                                                          // gap
407                                                                          // symbol
408                                                                          // number
409           {
410             nonSSLabel = true;
411           }
412         }
413       }
414       else
415       {
416         rnastring.append(annotations[i].displayCharacter.charAt(1));
417       }
418
419       if (annotations[i].displayCharacter.length() > 0)
420       {
421         hasText = true;
422       }
423     }
424
425     if (nonSSLabel)
426     {
427       hasIcons = false;
428       for (int j = 0; j < annotations.length; j++)
429       {
430         if (annotations[j] != null
431                 && annotations[j].secondaryStructure != ' ')
432         {
433           annotations[j].displayCharacter = String
434                   .valueOf(annotations[j].secondaryStructure);
435           annotations[j].secondaryStructure = ' ';
436         }
437
438       }
439     }
440     else
441     {
442       if (isrna)
443       {
444         _updateRnaSecStr(new AnnotCharSequence());
445       }
446     }
447
448     annotationId = this.hashCode() + "";
449   }
450
451   /**
452    * flyweight access to positions in the alignment annotation row for RNA
453    * processing
454    * 
455    * @author jimp
456    * 
457    */
458   private class AnnotCharSequence implements CharSequence
459   {
460     int offset = 0;
461
462     int max = 0;
463
464     public AnnotCharSequence()
465     {
466       this(0, annotations.length);
467     }
468
469     public AnnotCharSequence(int start, int end)
470     {
471       offset = start;
472       max = end;
473     }
474
475     @Override
476     public CharSequence subSequence(int start, int end)
477     {
478       return new AnnotCharSequence(offset + start, offset + end);
479     }
480
481     @Override
482     public int length()
483     {
484       return max - offset;
485     }
486
487     @Override
488     public char charAt(int index)
489     {
490       return ((index + offset < 0) || (index + offset) >= max
491               || annotations[index + offset] == null || (annotations[index
492  + offset].secondaryStructure < ' ') ? ' '
493               : annotations[index + offset].secondaryStructure);
494     }
495
496     public String toString()
497     {
498       char[] string = new char[max - offset];
499       int mx = annotations.length;
500
501       for (int i = offset; i < mx; i++)
502       {
503         string[i] = (annotations[i] == null || (annotations[i].secondaryStructure < 32)) ? ' '
504                 : annotations[i].secondaryStructure;
505       }
506       return new String(string);
507     }
508   };
509
510   private long _lastrnaannot = -1;
511
512   public String getRNAStruc()
513   {
514     if (isrna)
515     {
516       String rnastruc = new AnnotCharSequence().toString();
517       if (_lastrnaannot != rnastruc.hashCode())
518       {
519         // ensure rna structure contacts are up to date
520         _lastrnaannot = rnastruc.hashCode();
521         _updateRnaSecStr(rnastruc);
522       }
523       return rnastruc;
524     }
525     return null;
526   }
527
528   /**
529    * Creates a new AlignmentAnnotation object.
530    * 
531    * @param label
532    *          DOCUMENT ME!
533    * @param description
534    *          DOCUMENT ME!
535    * @param annotations
536    *          DOCUMENT ME!
537    * @param min
538    *          DOCUMENT ME!
539    * @param max
540    *          DOCUMENT ME!
541    * @param winLength
542    *          DOCUMENT ME!
543    */
544   public AlignmentAnnotation(String label, String description,
545           Annotation[] annotations, float min, float max, int graphType)
546   {
547     // graphs are not editable
548     editable = graphType == 0;
549
550     this.label = label;
551     this.description = description;
552     this.annotations = annotations;
553     graph = graphType;
554     graphMin = min;
555     graphMax = max;
556     validateRangeAndDisplay();
557   }
558
559   /**
560    * checks graphMin and graphMax, secondary structure symbols, sets graphType
561    * appropriately, sets null labels to the empty string if appropriate.
562    */
563   public void validateRangeAndDisplay()
564   {
565
566     if (annotations == null)
567     {
568       visible = false; // try to prevent renderer from displaying.
569       return; // this is a non-annotation row annotation - ie a sequence score.
570     }
571
572     int graphType = graph;
573     float min = graphMin;
574     float max = graphMax;
575     boolean drawValues = true;
576     _linecolour = null;
577     if (min == max)
578     {
579       min = 999999999;
580       for (int i = 0; i < annotations.length; i++)
581       {
582         if (annotations[i] == null)
583         {
584           continue;
585         }
586
587         if (drawValues && annotations[i].displayCharacter != null
588                 && annotations[i].displayCharacter.length() > 1)
589         {
590           drawValues = false;
591         }
592
593         if (annotations[i].value > max)
594         {
595           max = annotations[i].value;
596         }
597
598         if (annotations[i].value < min)
599         {
600           min = annotations[i].value;
601         }
602         if (_linecolour == null && annotations[i].colour != null)
603         {
604           _linecolour = annotations[i].colour;
605         }
606       }
607       // ensure zero is origin for min/max ranges on only one side of zero
608       if (min > 0)
609       {
610         min = 0;
611       }
612       else
613       {
614         if (max < 0)
615         {
616           max = 0;
617         }
618       }
619     }
620
621     graphMin = min;
622     graphMax = max;
623
624     areLabelsSecondaryStructure();
625
626     if (!drawValues && graphType != NO_GRAPH)
627     {
628       for (int i = 0; i < annotations.length; i++)
629       {
630         if (annotations[i] != null)
631         {
632           annotations[i].displayCharacter = "X";
633         }
634       }
635     }
636   }
637
638   /**
639    * Copy constructor creates a new independent annotation row with the same
640    * associated sequenceRef
641    * 
642    * @param annotation
643    */
644   public AlignmentAnnotation(AlignmentAnnotation annotation)
645   {
646     this.label = new String(annotation.label);
647     if (annotation.description != null)
648     {
649       this.description = new String(annotation.description);
650     }
651     this.graphMin = annotation.graphMin;
652     this.graphMax = annotation.graphMax;
653     this.graph = annotation.graph;
654     this.graphHeight = annotation.graphHeight;
655     this.graphGroup = annotation.graphGroup;
656     this.groupRef = annotation.groupRef;
657     this.editable = annotation.editable;
658     this.autoCalculated = annotation.autoCalculated;
659     this.hasIcons = annotation.hasIcons;
660     this.hasText = annotation.hasText;
661     this.height = annotation.height;
662     this.label = annotation.label;
663     this.padGaps = annotation.padGaps;
664     this.visible = annotation.visible;
665     this.centreColLabels = annotation.centreColLabels;
666     this.scaleColLabel = annotation.scaleColLabel;
667     this.showAllColLabels = annotation.showAllColLabels;
668     this.calcId = annotation.calcId;
669     if (annotation.properties!=null)
670     {
671       properties = new HashMap<String,String>();
672       for (Map.Entry<String, String> val:annotation.properties.entrySet())
673       {
674         properties.put(val.getKey(), val.getValue());
675       }
676     }
677     if (this.hasScore = annotation.hasScore)
678     {
679       this.score = annotation.score;
680     }
681     if (annotation.threshold != null)
682     {
683       threshold = new GraphLine(annotation.threshold);
684     }
685     Annotation[] ann = annotation.annotations;
686     if (annotation.annotations != null)
687     {
688       this.annotations = new Annotation[ann.length];
689       for (int i = 0; i < ann.length; i++)
690       {
691         if (ann[i] != null)
692         {
693           annotations[i] = new Annotation(ann[i]);
694           if (_linecolour != null)
695           {
696             _linecolour = annotations[i].colour;
697           }
698         }
699       }
700     }
701     if (annotation.sequenceRef != null)
702     {
703       this.sequenceRef = annotation.sequenceRef;
704       if (annotation.sequenceMapping != null)
705       {
706         Integer p = null;
707         sequenceMapping = new Hashtable();
708         Enumeration pos = annotation.sequenceMapping.keys();
709         while (pos.hasMoreElements())
710         {
711           // could optimise this!
712           p = (Integer) pos.nextElement();
713           Annotation a = annotation.sequenceMapping.get(p);
714           if (a == null)
715           {
716             continue;
717           }
718           if (ann != null)
719           {
720             for (int i = 0; i < ann.length; i++)
721             {
722               if (ann[i] == a)
723               {
724                 sequenceMapping.put(p, annotations[i]);
725               }
726             }
727           }
728         }
729       }
730       else
731       {
732         this.sequenceMapping = null;
733       }
734     }
735     // TODO: check if we need to do this: JAL-952
736     // if (this.isrna=annotation.isrna)
737     {
738       // _rnasecstr=new SequenceFeature[annotation._rnasecstr];
739     }
740     validateRangeAndDisplay(); // construct hashcodes, etc.
741   }
742
743   /**
744    * clip the annotation to the columns given by startRes and endRes (inclusive)
745    * and prune any existing sequenceMapping to just those columns.
746    * 
747    * @param startRes
748    * @param endRes
749    */
750   public void restrict(int startRes, int endRes)
751   {
752     if (annotations == null)
753     {
754       // non-positional
755       return;
756     }
757     if (startRes < 0)
758     {
759       startRes = 0;
760     }
761     if (startRes >= annotations.length)
762     {
763       startRes = annotations.length - 1;
764     }
765     if (endRes >= annotations.length)
766     {
767       endRes = annotations.length - 1;
768     }
769     if (annotations == null)
770     {
771       return;
772     }
773     Annotation[] temp = new Annotation[endRes - startRes + 1];
774     if (startRes < annotations.length)
775     {
776       System.arraycopy(annotations, startRes, temp, 0, endRes - startRes
777               + 1);
778     }
779     if (sequenceRef != null)
780     {
781       // Clip the mapping, if it exists.
782       int spos = sequenceRef.findPosition(startRes);
783       int epos = sequenceRef.findPosition(endRes);
784       if (sequenceMapping != null)
785       {
786         Hashtable newmapping = new Hashtable();
787         Enumeration e = sequenceMapping.keys();
788         while (e.hasMoreElements())
789         {
790           Integer pos = (Integer) e.nextElement();
791           if (pos.intValue() >= spos && pos.intValue() <= epos)
792           {
793             newmapping.put(pos, sequenceMapping.get(pos));
794           }
795         }
796         sequenceMapping.clear();
797         sequenceMapping = newmapping;
798       }
799     }
800     annotations = temp;
801   }
802
803   /**
804    * set the annotation row to be at least length Annotations
805    * 
806    * @param length
807    *          minimum number of columns required in the annotation row
808    * @return false if the annotation row is greater than length
809    */
810   public boolean padAnnotation(int length)
811   {
812     if (annotations == null)
813     {
814       return true; // annotation row is correct - null == not visible and
815       // undefined length
816     }
817     if (annotations.length < length)
818     {
819       Annotation[] na = new Annotation[length];
820       System.arraycopy(annotations, 0, na, 0, annotations.length);
821       annotations = na;
822       return true;
823     }
824     return annotations.length > length;
825
826   }
827
828   /**
829    * DOCUMENT ME!
830    * 
831    * @return DOCUMENT ME!
832    */
833   public String toString()
834   {
835     StringBuffer buffer = new StringBuffer();
836
837     for (int i = 0; i < annotations.length; i++)
838     {
839       if (annotations[i] != null)
840       {
841         if (graph != 0)
842         {
843           buffer.append(annotations[i].value);
844         }
845         else if (hasIcons)
846         {
847           buffer.append(annotations[i].secondaryStructure);
848         }
849         else
850         {
851           buffer.append(annotations[i].displayCharacter);
852         }
853       }
854
855       buffer.append(", ");
856     }
857     // TODO: remove disgusting hack for 'special' treatment of consensus line.
858     if (label.indexOf("Consensus") == 0)
859     {
860       buffer.append("\n");
861
862       for (int i = 0; i < annotations.length; i++)
863       {
864         if (annotations[i] != null)
865         {
866           buffer.append(annotations[i].description);
867         }
868
869         buffer.append(", ");
870       }
871     }
872
873     return buffer.toString();
874   }
875
876   public void setThreshold(GraphLine line)
877   {
878     threshold = line;
879   }
880
881   public GraphLine getThreshold()
882   {
883     return threshold;
884   }
885
886   /**
887    * Attach the annotation to seqRef, starting from startRes position. If
888    * alreadyMapped is true then the indices of the annotation[] array are
889    * sequence positions rather than alignment column positions.
890    * 
891    * @param seqRef
892    * @param startRes
893    * @param alreadyMapped
894    */
895   public void createSequenceMapping(SequenceI seqRef, int startRes,
896           boolean alreadyMapped)
897   {
898
899     if (seqRef == null)
900     {
901       return;
902     }
903     sequenceRef = seqRef;
904     if (annotations == null)
905     {
906       return;
907     }
908     sequenceMapping = new java.util.Hashtable();
909
910     int seqPos;
911
912     for (int i = 0; i < annotations.length; i++)
913     {
914       if (annotations[i] != null)
915       {
916         if (alreadyMapped)
917         {
918           seqPos = seqRef.findPosition(i);
919         }
920         else
921         {
922           seqPos = i + startRes;
923         }
924
925         sequenceMapping.put(new Integer(seqPos), annotations[i]);
926       }
927     }
928
929   }
930
931   public void adjustForAlignment()
932   {
933     if (sequenceRef == null)
934     {
935       return;
936     }
937
938     if (annotations == null)
939     {
940       return;
941     }
942
943     int a = 0, aSize = sequenceRef.getLength();
944
945     if (aSize == 0)
946     {
947       // Its been deleted
948       return;
949     }
950
951     int position;
952     Annotation[] temp = new Annotation[aSize];
953     Integer index;
954
955     for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++)
956     {
957       index = new Integer(a);
958       if (sequenceMapping.containsKey(index))
959       {
960         position = sequenceRef.findIndex(a) - 1;
961
962         temp[position] = sequenceMapping.get(index);
963       }
964     }
965
966     annotations = temp;
967   }
968
969   /**
970    * remove any null entries in annotation row and return the number of non-null
971    * annotation elements.
972    * 
973    * @return
974    */
975   public int compactAnnotationArray()
976   {
977     int i = 0, iSize = annotations.length;
978     while (i < iSize)
979     {
980       if (annotations[i] == null)
981       {
982         if (i + 1 < iSize)
983         {
984           System.arraycopy(annotations, i + 1, annotations, i, iSize - i
985                   - 1);
986         }
987         iSize--;
988       }
989       else
990       {
991         i++;
992       }
993     }
994     Annotation[] ann = annotations;
995     annotations = new Annotation[i];
996     System.arraycopy(ann, 0, annotations, 0, i);
997     ann = null;
998     return iSize;
999   }
1000
1001   /**
1002    * Associate this annotion with the aligned residues of a particular sequence.
1003    * sequenceMapping will be updated in the following way: null sequenceI -
1004    * existing mapping will be discarded but annotations left in mapped
1005    * positions. valid sequenceI not equal to current sequenceRef: mapping is
1006    * discarded and rebuilt assuming 1:1 correspondence TODO: overload with
1007    * parameter to specify correspondence between current and new sequenceRef
1008    * 
1009    * @param sequenceI
1010    */
1011   public void setSequenceRef(SequenceI sequenceI)
1012   {
1013     if (sequenceI != null)
1014     {
1015       if (sequenceRef != null)
1016       {
1017         boolean rIsDs=sequenceRef.getDatasetSequence()==null,tIsDs=sequenceI.getDatasetSequence()==null;
1018         if (sequenceRef != sequenceI
1019                 && (rIsDs && !tIsDs && sequenceRef != sequenceI
1020                         .getDatasetSequence())
1021                 && (!rIsDs && tIsDs && sequenceRef.getDatasetSequence() != sequenceI)
1022                 && (!rIsDs && !tIsDs && sequenceRef.getDatasetSequence() != sequenceI
1023                         .getDatasetSequence())
1024                 && !sequenceRef.equals(sequenceI))
1025         {
1026           // if sequenceRef isn't intersecting with sequenceI
1027           // throw away old mapping and reconstruct.
1028           sequenceRef = null;
1029           if (sequenceMapping != null)
1030           {
1031             sequenceMapping = null;
1032             // compactAnnotationArray();
1033           }
1034           createSequenceMapping(sequenceI, 1, true);
1035           adjustForAlignment();
1036         }
1037         else
1038         {
1039           // Mapping carried over
1040           sequenceRef = sequenceI;
1041         }
1042       }
1043       else
1044       {
1045         // No mapping exists
1046         createSequenceMapping(sequenceI, 1, true);
1047         adjustForAlignment();
1048       }
1049     }
1050     else
1051     {
1052       // throw away the mapping without compacting.
1053       sequenceMapping = null;
1054       sequenceRef = null;
1055     }
1056   }
1057
1058   /**
1059    * @return the score
1060    */
1061   public double getScore()
1062   {
1063     return score;
1064   }
1065
1066   /**
1067    * @param score
1068    *          the score to set
1069    */
1070   public void setScore(double score)
1071   {
1072     hasScore = true;
1073     this.score = score;
1074   }
1075
1076   /**
1077    * 
1078    * @return true if annotation has an associated score
1079    */
1080   public boolean hasScore()
1081   {
1082     return hasScore || !Double.isNaN(score);
1083   }
1084
1085   /**
1086    * Score only annotation
1087    * 
1088    * @param label
1089    * @param description
1090    * @param score
1091    */
1092   public AlignmentAnnotation(String label, String description, double score)
1093   {
1094     this(label, description, null);
1095     setScore(score);
1096   }
1097
1098   /**
1099    * copy constructor with edit based on the hidden columns marked in colSel
1100    * 
1101    * @param alignmentAnnotation
1102    * @param colSel
1103    */
1104   public AlignmentAnnotation(AlignmentAnnotation alignmentAnnotation,
1105           ColumnSelection colSel)
1106   {
1107     this(alignmentAnnotation);
1108     if (annotations == null)
1109     {
1110       return;
1111     }
1112     colSel.makeVisibleAnnotation(this);
1113   }
1114
1115   public void setPadGaps(boolean padgaps, char gapchar)
1116   {
1117     this.padGaps = padgaps;
1118     if (padgaps)
1119     {
1120       hasText = true;
1121       for (int i = 0; i < annotations.length; i++)
1122       {
1123         if (annotations[i] == null)
1124         {
1125           annotations[i] = new Annotation(String.valueOf(gapchar), null,
1126                   ' ', 0f, null);
1127         }
1128         else if (annotations[i].displayCharacter == null
1129                 || annotations[i].displayCharacter.equals(" "))
1130         {
1131           annotations[i].displayCharacter = String.valueOf(gapchar);
1132         }
1133       }
1134     }
1135   }
1136
1137   /**
1138    * format description string for display
1139    * 
1140    * @param seqname
1141    * @return Get the annotation description string optionally prefixed by
1142    *         associated sequence name (if any)
1143    */
1144   public String getDescription(boolean seqname)
1145   {
1146     if (seqname && this.sequenceRef != null)
1147     {
1148       int i = description.toLowerCase().indexOf("<html>");
1149       if (i > -1)
1150       {
1151         // move the html tag to before the sequence reference.
1152         return "<html>" + sequenceRef.getName() + " : "
1153                 + description.substring(i + 6);
1154       }
1155       return sequenceRef.getName() + " : " + description;
1156     }
1157     return description;
1158   }
1159
1160   public boolean isValidStruc()
1161   {
1162     return invalidrnastruc == -1;
1163   }
1164
1165   public long getInvalidStrucPos()
1166   {
1167     return invalidrnastruc;
1168   }
1169
1170   /**
1171    * machine readable ID string indicating what generated this annotation
1172    */
1173   protected String calcId = "";
1174
1175   /**
1176    * properties associated with the calcId
1177    */
1178   protected Map<String, String> properties = new HashMap<String, String>();
1179
1180   /**
1181    * base colour for line graphs. If null, will be set automatically by
1182    * searching the alignment annotation
1183    */
1184   public java.awt.Color _linecolour;
1185
1186   public String getCalcId()
1187   {
1188     return calcId;
1189   }
1190
1191   public void setCalcId(String calcId)
1192   {
1193     this.calcId = calcId;
1194   }
1195
1196   public boolean isRNA()
1197   {
1198     return isrna;
1199   }
1200
1201   /**
1202    * transfer annotation to the given sequence using the given mapping from the
1203    * current positions or an existing sequence mapping
1204    * 
1205    * @param sq
1206    * @param sp2sq
1207    *          map involving sq as To or From
1208    */
1209   public void liftOver(SequenceI sq, Mapping sp2sq)
1210   {
1211     if (sp2sq.getMappedWidth() != sp2sq.getWidth())
1212     {
1213       // TODO: employ getWord/MappedWord to transfer annotation between cDNA and Protein reference frames
1214       throw new Error("liftOver currently not implemented for transfer of annotation between different types of seqeunce");
1215     }
1216     boolean mapIsTo = (sp2sq != null) ? (sp2sq.getTo() == sq || sp2sq
1217             .getTo() == sq.getDatasetSequence()) : false;
1218
1219     // TODO build a better annotation element map and get rid of annotations[]
1220     Hashtable<Integer, Annotation> mapForsq = new Hashtable();
1221     if (sequenceMapping != null)
1222     {
1223       if (sp2sq != null)
1224       {
1225         for (Entry<Integer, Annotation> ie : sequenceMapping.entrySet())
1226         {
1227           Integer mpos = Integer.valueOf(mapIsTo ? sp2sq
1228                   .getMappedPosition(ie.getKey()) : sp2sq.getPosition(ie
1229                   .getKey()));
1230           if (mpos >= sq.getStart() && mpos <= sq.getEnd())
1231           {
1232             mapForsq.put(mpos, ie.getValue());
1233           }
1234         }
1235         sequenceMapping = mapForsq;
1236         sequenceRef = sq;
1237         adjustForAlignment();
1238       }
1239       else
1240       {
1241         // trim positions
1242       }
1243     }
1244   }
1245
1246   /**
1247    * like liftOver but more general.
1248    * 
1249    * Takes an array of int pairs that will be used to update the internal
1250    * sequenceMapping and so shuffle the annotated positions
1251    * 
1252    * @param newref
1253    *          - new sequence reference for the annotation row - if null,
1254    *          sequenceRef is left unchanged
1255    * @param mapping
1256    *          array of ints containing corresponding positions
1257    * @param from
1258    *          - column for current coordinate system (-1 for index+1)
1259    * @param to
1260    *          - column for destination coordinate system (-1 for index+1)
1261    * @param idxoffset
1262    *          - offset added to index when referencing either coordinate system
1263    * @note no checks are made as to whether from and/or to are sensible
1264    * @note caller should add the remapped annotation to newref if they have not
1265    *       already
1266    */
1267   public void remap(SequenceI newref, int[][] mapping, int from, int to,
1268           int idxoffset)
1269   {
1270     if (mapping != null)
1271     {
1272       Hashtable<Integer, Annotation> old = sequenceMapping, remap = new Hashtable<Integer, Annotation>();
1273       int index = -1;
1274       for (int mp[] : mapping)
1275       {
1276         if (index++ < 0)
1277         {
1278           continue;
1279         }
1280         Annotation ann = null;
1281         if (from == -1)
1282         {
1283           ann = sequenceMapping.get(Integer.valueOf(idxoffset + index));
1284         }
1285         else
1286         {
1287           if (mp != null && mp.length > from)
1288           {
1289             ann = sequenceMapping.get(Integer.valueOf(mp[from]));
1290           }
1291         }
1292         if (ann != null)
1293         {
1294           if (to == -1)
1295           {
1296             remap.put(Integer.valueOf(idxoffset + index), ann);
1297           }
1298           else
1299           {
1300             if (to > -1 && to < mp.length)
1301             {
1302               remap.put(Integer.valueOf(mp[to]), ann);
1303             }
1304           }
1305         }
1306       }
1307       sequenceMapping = remap;
1308       old.clear();
1309       if (newref != null)
1310       {
1311         sequenceRef = newref;
1312       }
1313       adjustForAlignment();
1314     }
1315   }
1316
1317   public String getProperty(String property)
1318   {
1319     if (properties == null)
1320     {
1321       return null;
1322     }
1323     return properties.get(property);
1324   }
1325
1326   public void setProperty(String property, String value)
1327   {
1328     if (properties==null)
1329     {
1330       properties = new HashMap<String,String>();
1331     }
1332     properties.put(property, value);
1333   }
1334
1335   public boolean hasProperties()
1336   {
1337     return properties != null && properties.size() > 0;
1338   }
1339
1340   public Collection<String> getProperties()
1341   {
1342     if (properties == null)
1343     {
1344       return Collections.EMPTY_LIST;
1345     }
1346     return properties.keySet();
1347   }
1348 }