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