Start merging of Laurens Code
[jalview.git] / src / jalview / datamodel / AlignmentAnnotation.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.datamodel;
19
20 import jalview.analysis.Rna;
21
22 import java.util.Enumeration;
23 import java.util.Hashtable;
24
25 /**
26  * DOCUMENT ME!
27  * 
28  * @author $author$
29  * @version $Revision$
30  */
31 public class AlignmentAnnotation
32 {
33   /**
34    * If true, this annotations is calculated every edit, eg consensus, quality
35    * or conservation graphs
36    */
37   public boolean autoCalculated = false;
38
39   public String annotationId;
40
41   public SequenceI sequenceRef;
42
43   /** DOCUMENT ME!! */
44   public String label;
45
46   /** DOCUMENT ME!! */
47   public String description;
48
49   /** DOCUMENT ME!! */
50   public Annotation[] annotations;
51
52   /**
53    * RNA secondary structure contact positions
54    */
55   public SequenceFeature[] _rnasecstr = null;
56
57   /**
58    * Updates the _rnasecstr field Determines the positions that base pair and
59    * the positions of helices based on secondary structure from a Stockholm file
60    * 
61    * @param RNAannot
62    */
63   private void _updateRnaSecStr(String RNAannot)
64   {
65     _rnasecstr = Rna.GetBasePairs(RNAannot);
66     Rna.HelixMap(_rnasecstr);
67
68     if (_rnasecstr != null && _rnasecstr.length > 0)
69     {
70       // show all the RNA secondary structure annotation symbols.
71       showAllColLabels = true;
72       scaleColLabel = true;
73     }
74     // System.out.println("featuregroup " + _rnasecstr[0].getFeatureGroup());
75   }
76
77   public java.util.Hashtable sequenceMapping;
78
79   /** DOCUMENT ME!! */
80   public float graphMin;
81
82   /** DOCUMENT ME!! */
83   public float graphMax;
84
85   /**
86    * Score associated with label and description.
87    */
88   public double score = Double.NaN;
89
90   /**
91    * flag indicating if annotation has a score.
92    */
93   public boolean hasScore = false;
94
95   public GraphLine threshold;
96
97   // Graphical hints and tips
98
99   /** Can this row be edited by the user ? */
100   public boolean editable = false;
101
102   /** Indicates if annotation has a graphical symbol track */
103   public boolean hasIcons; //
104
105   /** Indicates if annotation has a text character label */
106   public boolean hasText;
107
108   /** is the row visible */
109   public boolean visible = true;
110
111   public int graphGroup = -1;
112
113   /** Displayed height of row in pixels */
114   public int height = 0;
115
116   public int graph = 0;
117
118   public int graphHeight = 40;
119
120   public boolean padGaps = false;
121
122   public static final int NO_GRAPH = 0;
123
124   public static final int BAR_GRAPH = 1;
125
126   public static final int LINE_GRAPH = 2;
127
128   public boolean belowAlignment = true;
129
130   public SequenceGroup groupRef = null;
131
132   /**
133    * display every column label, even if there is a row of identical labels
134    */
135   public boolean showAllColLabels = false;
136
137   /**
138    * scale the column label to fit within the alignment column.
139    */
140   public boolean scaleColLabel = false;
141
142   /**
143    * centre the column labels relative to the alignment column
144    */
145   public boolean centreColLabels = false;
146
147   /*
148    * (non-Javadoc)
149    * 
150    * @see java.lang.Object#finalize()
151    */
152   protected void finalize() throws Throwable
153   {
154     sequenceRef = null;
155     groupRef = null;
156     super.finalize();
157   }
158
159   public static int getGraphValueFromString(String string)
160   {
161     if (string.equalsIgnoreCase("BAR_GRAPH"))
162     {
163       return BAR_GRAPH;
164     }
165     else if (string.equalsIgnoreCase("LINE_GRAPH"))
166     {
167       return LINE_GRAPH;
168     }
169     else
170     {
171       return NO_GRAPH;
172     }
173   }
174
175   /**
176    * Creates a new AlignmentAnnotation object.
177    * 
178    * @param label
179    *          short label shown under sequence labels
180    * @param description
181    *          text displayed on mouseover
182    * @param annotations
183    *          set of positional annotation elements
184    */
185   public AlignmentAnnotation(String label, String description,
186           Annotation[] annotations)
187   {
188     // always editable?
189     editable = true;
190     this.label = label;
191     this.description = description;
192     this.annotations = annotations;
193
194     validateRangeAndDisplay();
195   }
196
197   /**
198    * Checks if annotation labels represent secondary structures
199    * 
200    */
201   void areLabelsSecondaryStructure()
202   {
203     boolean nonSSLabel = false;
204     boolean isrna = false;
205     StringBuffer rnastring = new StringBuffer();
206
207     char firstChar = 0;
208     for (int i = 0; i < annotations.length; i++)
209     {
210       if (annotations[i] == null)
211       {
212         continue;
213       }
214       if (annotations[i].secondaryStructure == 'H'
215               || annotations[i].secondaryStructure == 'E')
216       {
217         hasIcons |= true;
218       }
219       else
220       // Check for RNA secondary structure
221       {
222         if (annotations[i].secondaryStructure == 'S')
223         {
224           hasIcons |= true;
225           isrna |= true;
226         }
227       }
228
229       // System.out.println("displaychar " + annotations[i].displayCharacter);
230
231       if (annotations[i].displayCharacter == null
232               || annotations[i].displayCharacter.length() == 0)
233       {
234         rnastring.append('.');
235         continue;
236       }
237       if (annotations[i].displayCharacter.length() == 1)
238       {
239         firstChar = annotations[i].displayCharacter.charAt(0);
240         // check to see if it looks like a sequence or is secondary structure
241         // labelling.
242         if (annotations[i].secondaryStructure != ' '
243                 && !hasIcons
244                 &&
245                 // Uncomment to only catch case where
246                 // displayCharacter==secondary
247                 // Structure
248                 // to correctly redisplay SS annotation imported from Stockholm,
249                 // exported to JalviewXML and read back in again.
250                 // &&
251                 // annotations[i].displayCharacter.charAt(0)==annotations[i].secondaryStructure
252                 firstChar != ' '
253                 && firstChar != 'H'
254                 && firstChar != 'E'
255                 && firstChar != 'S'
256                 && firstChar != '-'
257                 && firstChar < jalview.schemes.ResidueProperties.aaIndex.length)
258         {
259           if (jalview.schemes.ResidueProperties.aaIndex[firstChar] < 23) // TODO:
260                                                                          // parameterise
261                                                                          // to
262                                                                          // gap
263                                                                          // symbol
264                                                                          // number
265           {
266             nonSSLabel = true;
267           }
268         }
269       }
270       else
271       {
272         rnastring.append(annotations[i].displayCharacter.charAt(1));
273       }
274
275       if (annotations[i].displayCharacter.length() > 0)
276       {
277         hasText = true;
278       }
279     }
280
281     if (nonSSLabel)
282     {
283       hasIcons = false;
284       for (int j = 0; j < annotations.length; j++)
285       {
286         if (annotations[j] != null
287                 && annotations[j].secondaryStructure != ' ')
288         {
289           annotations[j].displayCharacter = String
290                   .valueOf(annotations[j].secondaryStructure);
291           annotations[j].secondaryStructure = ' ';
292         }
293
294       }
295     }
296     else
297     {
298       if (isrna)
299       {
300         _updateRnaSecStr(rnastring.toString());
301       }
302     }
303
304     annotationId = this.hashCode() + "";
305   }
306
307   /**
308    * Creates a new AlignmentAnnotation object.
309    * 
310    * @param label
311    *          DOCUMENT ME!
312    * @param description
313    *          DOCUMENT ME!
314    * @param annotations
315    *          DOCUMENT ME!
316    * @param min
317    *          DOCUMENT ME!
318    * @param max
319    *          DOCUMENT ME!
320    * @param winLength
321    *          DOCUMENT ME!
322    */
323   public AlignmentAnnotation(String label, String description,
324           Annotation[] annotations, float min, float max, int graphType)
325   {
326     // graphs are not editable
327     editable = graphType == 0;
328
329     this.label = label;
330     this.description = description;
331     this.annotations = annotations;
332     graph = graphType;
333     graphMin = min;
334     graphMax = max;
335     validateRangeAndDisplay();
336   }
337
338   /**
339    * checks graphMin and graphMax, secondary structure symbols, sets graphType
340    * appropriately, sets null labels to the empty string if appropriate.
341    */
342   private void validateRangeAndDisplay()
343   {
344
345     if (annotations == null)
346     {
347       visible = false; // try to prevent renderer from displaying.
348       return; // this is a non-annotation row annotation - ie a sequence score.
349     }
350
351     int graphType = graph;
352     float min = graphMin;
353     float max = graphMax;
354     boolean drawValues = true;
355
356     if (min == max)
357     {
358       min = 999999999;
359       for (int i = 0; i < annotations.length; i++)
360       {
361         if (annotations[i] == null)
362         {
363           continue;
364         }
365
366         if (drawValues && annotations[i].displayCharacter != null
367                 && annotations[i].displayCharacter.length() > 1)
368         {
369           drawValues = false;
370         }
371
372         if (annotations[i].value > max)
373         {
374           max = annotations[i].value;
375         }
376
377         if (annotations[i].value < min)
378         {
379           min = annotations[i].value;
380         }
381       }
382       // ensure zero is origin for min/max ranges on only one side of zero
383       if (min > 0)
384       {
385         min = 0;
386       }
387       else
388       {
389         if (max < 0)
390         {
391           max = 0;
392         }
393       }
394     }
395
396     graphMin = min;
397     graphMax = max;
398
399     areLabelsSecondaryStructure();
400
401     if (!drawValues && graphType != NO_GRAPH)
402     {
403       for (int i = 0; i < annotations.length; i++)
404       {
405         if (annotations[i] != null)
406         {
407           annotations[i].displayCharacter = "";
408         }
409       }
410     }
411   }
412
413   /**
414    * Copy constructor creates a new independent annotation row with the same
415    * associated sequenceRef
416    * 
417    * @param annotation
418    */
419   public AlignmentAnnotation(AlignmentAnnotation annotation)
420   {
421     this.label = new String(annotation.label);
422     if (annotation.description != null)
423       this.description = new String(annotation.description);
424     this.graphMin = annotation.graphMin;
425     this.graphMax = annotation.graphMax;
426     this.graph = annotation.graph;
427     this.graphHeight = annotation.graphHeight;
428     this.graphGroup = annotation.graphGroup;
429     this.groupRef = annotation.groupRef;
430     this.editable = annotation.editable;
431     this.autoCalculated = annotation.autoCalculated;
432     this.hasIcons = annotation.hasIcons;
433     this.hasText = annotation.hasText;
434     this.height = annotation.height;
435     this.label = annotation.label;
436     this.padGaps = annotation.padGaps;
437     this.visible = annotation.visible;
438     if (this.hasScore = annotation.hasScore)
439     {
440       this.score = annotation.score;
441     }
442     if (annotation.threshold != null)
443     {
444       threshold = new GraphLine(annotation.threshold);
445     }
446     if (annotation.annotations != null)
447     {
448       Annotation[] ann = annotation.annotations;
449       this.annotations = new Annotation[ann.length];
450       for (int i = 0; i < ann.length; i++)
451       {
452         annotations[i] = new Annotation(ann[i]);
453       }
454       ;
455       if (annotation.sequenceRef != null)
456       {
457         this.sequenceRef = annotation.sequenceRef;
458         if (annotation.sequenceMapping != null)
459         {
460           Integer p = null;
461           sequenceMapping = new Hashtable();
462           Enumeration pos = annotation.sequenceMapping.keys();
463           while (pos.hasMoreElements())
464           {
465             // could optimise this!
466             p = (Integer) pos.nextElement();
467             Annotation a = (Annotation) annotation.sequenceMapping.get(p);
468             if (a == null)
469             {
470               continue;
471             }
472             for (int i = 0; i < ann.length; i++)
473             {
474               if (ann[i] == a)
475               {
476                 sequenceMapping.put(p, annotations[i]);
477               }
478             }
479           }
480         }
481         else
482         {
483           this.sequenceMapping = null;
484         }
485       }
486     }
487     validateRangeAndDisplay(); // construct hashcodes, etc.
488   }
489
490   /**
491    * clip the annotation to the columns given by startRes and endRes (inclusive)
492    * and prune any existing sequenceMapping to just those columns.
493    * 
494    * @param startRes
495    * @param endRes
496    */
497   public void restrict(int startRes, int endRes)
498   {
499     if (annotations == null)
500     {
501       // non-positional
502       return;
503     }
504     if (startRes < 0)
505       startRes = 0;
506     if (startRes >= annotations.length)
507       startRes = annotations.length - 1;
508     if (endRes >= annotations.length)
509       endRes = annotations.length - 1;
510     if (annotations == null)
511       return;
512     Annotation[] temp = new Annotation[endRes - startRes + 1];
513     if (startRes < annotations.length)
514     {
515       System.arraycopy(annotations, startRes, temp, 0, endRes - startRes
516               + 1);
517     }
518     if (sequenceRef != null)
519     {
520       // Clip the mapping, if it exists.
521       int spos = sequenceRef.findPosition(startRes);
522       int epos = sequenceRef.findPosition(endRes);
523       if (sequenceMapping != null)
524       {
525         Hashtable newmapping = new Hashtable();
526         Enumeration e = sequenceMapping.keys();
527         while (e.hasMoreElements())
528         {
529           Integer pos = (Integer) e.nextElement();
530           if (pos.intValue() >= spos && pos.intValue() <= epos)
531           {
532             newmapping.put(pos, sequenceMapping.get(pos));
533           }
534         }
535         sequenceMapping.clear();
536         sequenceMapping = newmapping;
537       }
538     }
539     annotations = temp;
540   }
541
542   /**
543    * set the annotation row to be at least length Annotations
544    * 
545    * @param length
546    *          minimum number of columns required in the annotation row
547    * @return false if the annotation row is greater than length
548    */
549   public boolean padAnnotation(int length)
550   {
551     if (annotations == null)
552     {
553       return true; // annotation row is correct - null == not visible and
554       // undefined length
555     }
556     if (annotations.length < length)
557     {
558       Annotation[] na = new Annotation[length];
559       System.arraycopy(annotations, 0, na, 0, annotations.length);
560       annotations = na;
561       return true;
562     }
563     return annotations.length > length;
564
565   }
566
567   /**
568    * DOCUMENT ME!
569    * 
570    * @return DOCUMENT ME!
571    */
572   public String toString()
573   {
574     StringBuffer buffer = new StringBuffer();
575
576     for (int i = 0; i < annotations.length; i++)
577     {
578       if (annotations[i] != null)
579       {
580         if (graph != 0)
581         {
582           buffer.append(annotations[i].value);
583         }
584         else if (hasIcons)
585         {
586           buffer.append(annotations[i].secondaryStructure);
587         }
588         else
589         {
590           buffer.append(annotations[i].displayCharacter);
591         }
592       }
593
594       buffer.append(", ");
595     }
596     // TODO: remove disgusting hack for 'special' treatment of consensus line.
597     if (label.indexOf("Consensus") == 0)
598     {
599       buffer.append("\n");
600
601       for (int i = 0; i < annotations.length; i++)
602       {
603         if (annotations[i] != null)
604         {
605           buffer.append(annotations[i].description);
606         }
607
608         buffer.append(", ");
609       }
610     }
611
612     return buffer.toString();
613   }
614
615   public void setThreshold(GraphLine line)
616   {
617     threshold = line;
618   }
619
620   public GraphLine getThreshold()
621   {
622     return threshold;
623   }
624
625   /**
626    * Attach the annotation to seqRef, starting from startRes position. If
627    * alreadyMapped is true then the indices of the annotation[] array are
628    * sequence positions rather than alignment column positions.
629    * 
630    * @param seqRef
631    * @param startRes
632    * @param alreadyMapped
633    */
634   public void createSequenceMapping(SequenceI seqRef, int startRes,
635           boolean alreadyMapped)
636   {
637
638     if (seqRef == null)
639     {
640       return;
641     }
642     sequenceRef = seqRef;
643     if (annotations == null)
644     {
645       return;
646     }
647     sequenceMapping = new java.util.Hashtable();
648
649     int seqPos;
650
651     for (int i = 0; i < annotations.length; i++)
652     {
653       if (annotations[i] != null)
654       {
655         if (alreadyMapped)
656         {
657           seqPos = seqRef.findPosition(i);
658         }
659         else
660         {
661           seqPos = i + startRes;
662         }
663
664         sequenceMapping.put(new Integer(seqPos), annotations[i]);
665       }
666     }
667
668   }
669
670   public void adjustForAlignment()
671   {
672     if (sequenceRef == null)
673       return;
674
675     if (annotations == null)
676     {
677       return;
678     }
679
680     int a = 0, aSize = sequenceRef.getLength();
681
682     if (aSize == 0)
683     {
684       // Its been deleted
685       return;
686     }
687
688     int position;
689     Annotation[] temp = new Annotation[aSize];
690     Integer index;
691
692     for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++)
693     {
694       index = new Integer(a);
695       if (sequenceMapping.containsKey(index))
696       {
697         position = sequenceRef.findIndex(a) - 1;
698
699         temp[position] = (Annotation) sequenceMapping.get(index);
700       }
701     }
702
703     annotations = temp;
704   }
705
706   /**
707    * remove any null entries in annotation row and return the number of non-null
708    * annotation elements.
709    * 
710    * @return
711    */
712   public int compactAnnotationArray()
713   {
714     int i = 0, iSize = annotations.length;
715     while (i < iSize)
716     {
717       if (annotations[i] == null)
718       {
719         if (i + 1 < iSize)
720           System.arraycopy(annotations, i + 1, annotations, i, iSize - i
721                   - 1);
722         iSize--;
723       }
724       else
725       {
726         i++;
727       }
728     }
729     Annotation[] ann = annotations;
730     annotations = new Annotation[i];
731     System.arraycopy(ann, 0, annotations, 0, i);
732     ann = null;
733     return iSize;
734   }
735
736   /**
737    * Associate this annotion with the aligned residues of a particular sequence.
738    * sequenceMapping will be updated in the following way: null sequenceI -
739    * existing mapping will be discarded but annotations left in mapped
740    * positions. valid sequenceI not equal to current sequenceRef: mapping is
741    * discarded and rebuilt assuming 1:1 correspondence TODO: overload with
742    * parameter to specify correspondence between current and new sequenceRef
743    * 
744    * @param sequenceI
745    */
746   public void setSequenceRef(SequenceI sequenceI)
747   {
748     if (sequenceI != null)
749     {
750       if (sequenceRef != null)
751       {
752         if (sequenceRef != sequenceI
753                 && !sequenceRef.equals(sequenceI)
754                 && sequenceRef.getDatasetSequence() != sequenceI
755                         .getDatasetSequence())
756         {
757           // if sequenceRef isn't intersecting with sequenceI
758           // throw away old mapping and reconstruct.
759           sequenceRef = null;
760           if (sequenceMapping != null)
761           {
762             sequenceMapping = null;
763             // compactAnnotationArray();
764           }
765           createSequenceMapping(sequenceI, 1, true);
766           adjustForAlignment();
767         }
768         else
769         {
770           // Mapping carried over
771           sequenceRef = sequenceI;
772         }
773       }
774       else
775       {
776         // No mapping exists
777         createSequenceMapping(sequenceI, 1, true);
778         adjustForAlignment();
779       }
780     }
781     else
782     {
783       // throw away the mapping without compacting.
784       sequenceMapping = null;
785       sequenceRef = null;
786     }
787   }
788
789   /**
790    * @return the score
791    */
792   public double getScore()
793   {
794     return score;
795   }
796
797   /**
798    * @param score
799    *          the score to set
800    */
801   public void setScore(double score)
802   {
803     hasScore = true;
804     this.score = score;
805   }
806
807   /**
808    * 
809    * @return true if annotation has an associated score
810    */
811   public boolean hasScore()
812   {
813     return hasScore || !Double.isNaN(score);
814   }
815
816   /**
817    * Score only annotation
818    * 
819    * @param label
820    * @param description
821    * @param score
822    */
823   public AlignmentAnnotation(String label, String description, double score)
824   {
825     this(label, description, null);
826     setScore(score);
827   }
828
829   /**
830    * copy constructor with edit based on the hidden columns marked in colSel
831    * 
832    * @param alignmentAnnotation
833    * @param colSel
834    */
835   public AlignmentAnnotation(AlignmentAnnotation alignmentAnnotation,
836           ColumnSelection colSel)
837   {
838     this(alignmentAnnotation);
839     if (annotations == null)
840     {
841       return;
842     }
843     colSel.makeVisibleAnnotation(this);
844   }
845
846   public void setPadGaps(boolean padgaps, char gapchar)
847   {
848     this.padGaps = padgaps;
849     if (padgaps)
850     {
851       hasText = true;
852       for (int i = 0; i < annotations.length; i++)
853       {
854         if (annotations[i] == null)
855           annotations[i] = new Annotation(String.valueOf(gapchar), null,
856                   ' ', 0f);
857         else if (annotations[i].displayCharacter == null
858                 || annotations[i].displayCharacter.equals(" "))
859           annotations[i].displayCharacter = String.valueOf(gapchar);
860       }
861     }
862   }
863
864   /**
865    * format description string for display
866    * 
867    * @param seqname
868    * @return Get the annotation description string optionally prefixed by
869    *         associated sequence name (if any)
870    */
871   public String getDescription(boolean seqname)
872   {
873     if (seqname && this.sequenceRef != null)
874     {
875       return sequenceRef.getName() + " : " + description;
876     }
877     return description;
878   }
879 }