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