fix null annotation values
[jalview.git] / src / jalview / datamodel / AlignmentAnnotation.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2007 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   /** If true, this annotations is calculated every edit,
33    * eg consensus, quality or conservation graphs */
34   public boolean autoCalculated = false;
35
36   public String annotationId;
37
38   public SequenceI sequenceRef;
39
40   /** DOCUMENT ME!! */
41   public String label;
42
43   /** DOCUMENT ME!! */
44   public String description;
45
46   /** DOCUMENT ME!! */
47   public Annotation[] annotations;
48
49   public java.util.Hashtable sequenceMapping;
50
51   /** DOCUMENT ME!! */
52   public float graphMin;
53
54   /** DOCUMENT ME!! */
55   public float graphMax;
56
57   public GraphLine threshold;
58
59   // Graphical hints and tips
60
61   /** DOCUMENT ME!! */
62   public boolean editable = false;
63
64   /** DOCUMENT ME!! */
65   public boolean hasIcons; //
66
67   /** DOCUMENT ME!! */
68   public boolean hasText;
69
70   /** DOCUMENT ME!! */
71   public boolean visible = true;
72
73   public int graphGroup = -1;
74
75   /** DOCUMENT ME!! */
76   public int height = 0;
77
78   public int graph = 0;
79
80   public int graphHeight = 40;
81
82   public boolean padGaps = true;
83
84   public static final int NO_GRAPH = 0;
85
86   public static final int BAR_GRAPH = 1;
87
88   public static final int LINE_GRAPH = 2;
89
90   public static int getGraphValueFromString(String string)
91   {
92     if (string.equalsIgnoreCase("BAR_GRAPH"))
93     {
94       return BAR_GRAPH;
95     }
96     else if (string.equalsIgnoreCase("LINE_GRAPH"))
97     {
98       return LINE_GRAPH;
99     }
100     else
101     {
102       return NO_GRAPH;
103     }
104   }
105
106   /**
107    * Creates a new AlignmentAnnotation object.
108    *
109    * @param label DOCUMENT ME!
110    * @param description DOCUMENT ME!
111    * @param annotations DOCUMENT ME!
112    */
113   public AlignmentAnnotation(String label, String description,
114                              Annotation[] annotations)
115   {
116     // always editable?
117     editable = true;
118     this.label = label;
119     this.description = description;
120     this.annotations = annotations;
121
122      validateRangeAndDisplay();
123   }
124
125   void areLabelsSecondaryStructure()
126   {
127     boolean nonSSLabel = false;
128     for (int i = 0; i < annotations.length; i++)
129     {
130       if (annotations[i] == null)
131       {
132         padGaps = false;
133         continue;
134       }
135       if (annotations[i].secondaryStructure == 'H' ||
136           annotations[i].secondaryStructure == 'E')
137       {
138           hasIcons = true;
139       }
140
141       if (annotations[i].displayCharacter!=null)
142       {
143         if (annotations[i].displayCharacter.length() == 1
144           && !annotations[i].displayCharacter.equals("H")
145           && !annotations[i].displayCharacter.equals("E")
146           && !annotations[i].displayCharacter.equals("-")
147           && !annotations[i].displayCharacter.equals("."))
148         {
149           if (jalview.schemes.ResidueProperties.aaIndex
150                   [annotations[i].displayCharacter.charAt(0)] < 23)
151           {
152             nonSSLabel = true;
153           }
154         }
155
156         if (annotations[i].displayCharacter.length() > 0)
157         {
158           hasText = true;
159         }
160         else
161           padGaps = false;
162       }
163     }
164
165
166     if (nonSSLabel)
167     {
168       hasIcons = false;
169       for (int j = 0; j < annotations.length; j++)
170       {
171         if (annotations[j] != null && annotations[j].secondaryStructure != ' ')
172         {
173           annotations[j].displayCharacter
174               = String.valueOf(annotations[j].secondaryStructure);
175           annotations[j].secondaryStructure = ' ';
176         }
177
178       }
179     }
180
181     annotationId = this.hashCode() + "";
182   }
183   /**
184    * Creates a new AlignmentAnnotation object.
185    *
186    * @param label DOCUMENT ME!
187    * @param description DOCUMENT ME!
188    * @param annotations DOCUMENT ME!
189    * @param min DOCUMENT ME!
190    * @param max DOCUMENT ME!
191    * @param winLength DOCUMENT ME!
192    */
193   public AlignmentAnnotation(String label, String description,
194                              Annotation[] annotations, float min, float max,
195                              int graphType)
196   {
197     // graphs are not editable
198     editable = graphType==0;
199
200     this.label = label;
201     this.description = description;
202     this.annotations = annotations;
203     graph = graphType;
204     graphMin = min;
205     graphMax = max;
206     validateRangeAndDisplay();
207   }
208   /**
209    * checks graphMin and graphMax,
210    * secondary structure symbols,
211    * sets graphType appropriately,
212    * sets null labels to the empty string
213    * if appropriate.
214    */
215   private void validateRangeAndDisplay() {
216     int graphType = graph;
217     float min = graphMin;
218     float max = graphMax;
219     boolean drawValues = true;
220
221     if (min == max)
222     {
223       min = 999999999;
224       for (int i = 0; i < annotations.length; i++)
225       {
226         if (annotations[i] == null)
227         {
228           continue;
229         }
230
231         if (drawValues && annotations[i].displayCharacter!=null && annotations[i].displayCharacter.length() > 1)
232         {
233           drawValues = false;
234         }
235
236         if (annotations[i].value > max)
237         {
238           max = annotations[i].value;
239         }
240
241         if (annotations[i].value < min)
242         {
243           min = annotations[i].value;
244         }
245       }
246     }
247
248     graphMin = min;
249     graphMax = max;
250
251     areLabelsSecondaryStructure();
252
253     if (!drawValues && graphType != NO_GRAPH)
254     {
255       for (int i = 0; i < annotations.length; i++)
256       {
257         if (annotations[i] != null)
258         {
259           annotations[i].displayCharacter = "";
260         }
261       }
262     }
263   }
264
265   /**
266    * Copy constructor
267    * creates a new independent annotation row with the same associated sequenceRef
268    * @param annotation
269    */
270   public AlignmentAnnotation(AlignmentAnnotation annotation)
271   {
272     this.label = new String(annotation.label);
273     if (annotation.description != null)
274       this.description = new String(annotation.description);
275     this.graphMin = annotation.graphMin;
276     this.graphMax = annotation.graphMax;
277     this.graph = annotation.graph;
278     this.graphHeight = annotation.graphHeight;
279     this.graphGroup = annotation.graphGroup;
280     this.editable = annotation.editable;
281     this.autoCalculated = annotation.autoCalculated;
282     this.hasIcons = annotation.hasIcons;
283     this.hasText = annotation.hasText;
284     this.height = annotation.height;
285     this.label = annotation.label;
286     this.padGaps = annotation.padGaps;
287     if (threshold!=null) {
288       threshold = new GraphLine(annotation.threshold);
289     }
290     if (annotation.annotations!=null) {
291       Annotation[] ann = annotation.annotations;
292       this.annotations = new Annotation[ann.length];
293       for (int i=0; i<ann.length; i++) {
294         annotations[i] = new Annotation(ann[i]);
295       };
296       if (annotation.sequenceRef!=null) {
297         this.sequenceRef = annotation.sequenceRef;
298         if (annotation.sequenceMapping!=null)
299         {
300           Integer p=null;
301           sequenceMapping = new Hashtable();
302           Enumeration pos=annotation.sequenceMapping.keys();
303           while (pos.hasMoreElements()) {
304             // could optimise this!
305             p = (Integer) pos.nextElement();
306             Annotation a = (Annotation) annotation.sequenceMapping.get(p);
307             if (a==null)
308             {
309               continue;
310             }
311             for (int i=0; i<ann.length; i++)
312             {
313               if (ann[i]==a) 
314               { 
315                 sequenceMapping.put(p, annotations[i]);
316               }
317             }
318           }
319         } else {
320           this.sequenceMapping = null;
321         }
322       }
323     }
324     validateRangeAndDisplay(); // construct hashcodes, etc.
325   }
326
327   /**
328    * clip the annotation to the columns given by startRes and endRes (inclusive)
329    * and prune any existing sequenceMapping to just those columns.
330    * @param startRes
331    * @param endRes
332    */
333   public void restrict(int startRes, int endRes)
334   {
335     Annotation[] temp = new Annotation[endRes-startRes+1];
336     if (startRes<annotations.length) 
337     {
338       System.arraycopy(annotations, startRes, temp, 0, Math.min(endRes, annotations.length-1)-startRes+1);
339     }
340     if (sequenceRef!=null) {
341       // Clip the mapping, if it exists.
342       int spos = sequenceRef.findPosition(startRes);
343       int epos = sequenceRef.findPosition(endRes);
344       if (sequenceMapping!=null)
345       {
346         Hashtable newmapping = new Hashtable();
347         Enumeration e = sequenceMapping.keys();
348         while (e.hasMoreElements())
349         {
350           Integer pos = (Integer) e.nextElement();
351           if (pos.intValue()>=spos && pos.intValue()<=epos)
352           {
353             newmapping.put(pos, sequenceMapping.get(pos));
354           }
355         }
356         sequenceMapping.clear();
357         sequenceMapping = newmapping;
358       }
359     }
360     annotations=temp;
361   }
362   /**
363    * set the annotation row to be at least length Annotations
364    * @param length minimum number of columns required in the annotation row
365    * @return false if the annotation row is greater than length
366    */
367   public boolean padAnnotation(int length) {
368     if (annotations==null)
369     {
370       annotations = new Annotation[length];
371       return true;
372     }
373     if (annotations.length<length)
374     {
375       Annotation[] na = new Annotation[length];
376       System.arraycopy(annotations, 0, na, 0, annotations.length);
377       annotations = na;
378       return true;
379     }
380     return annotations.length>length;
381     
382   }
383
384   /**
385    * DOCUMENT ME!
386    *
387    * @return DOCUMENT ME!
388    */
389   public String toString()
390   {
391     StringBuffer buffer = new StringBuffer();
392
393     for (int i = 0; i < annotations.length; i++)
394     {
395       if (annotations[i] != null)
396       {
397         if (graph != 0)
398         {
399           buffer.append(annotations[i].value);
400         }
401         else if (hasIcons)
402         {
403           buffer.append(annotations[i].secondaryStructure);
404         }
405         else
406         {
407           buffer.append(annotations[i].displayCharacter);
408         }
409       }
410
411       buffer.append(", ");
412     }
413
414     if (label.equals("Consensus"))
415     {
416       buffer.append("\n");
417
418       for (int i = 0; i < annotations.length; i++)
419       {
420         if (annotations[i] != null)
421         {
422           buffer.append(annotations[i].description);
423         }
424
425         buffer.append(", ");
426       }
427     }
428
429     return buffer.toString();
430   }
431
432   public void setThreshold(GraphLine line)
433   {
434     threshold = line;
435   }
436
437   public GraphLine getThreshold()
438   {
439     return threshold;
440   }
441
442   /**
443    * Attach the annotation to seqRef, starting from startRes position. If alreadyMapped is true then the indices of the annotation[] array are sequence positions rather than alignment column positions.
444    * @param seqRef
445    * @param startRes
446    * @param alreadyMapped
447    */
448   public void createSequenceMapping(SequenceI seqRef,
449                                     int startRes,
450                                     boolean alreadyMapped)
451   {
452
453     if (seqRef == null)
454     {
455       return;
456     }
457
458     sequenceMapping = new java.util.Hashtable();
459
460     sequenceRef = seqRef;
461     int seqPos;
462
463     for (int i = 0; i < annotations.length; i++)
464     {
465       if (annotations[i] != null)
466       {
467         if (alreadyMapped)
468         {
469           seqPos = seqRef.findPosition(i);
470         }
471         else
472         {
473           seqPos = i + startRes;
474         }
475
476         sequenceMapping.put(new Integer(seqPos), annotations[i]);
477       }
478     }
479
480   }
481
482   public void adjustForAlignment()
483   {
484     if (sequenceRef==null)
485       return;
486
487     int a = 0, aSize = sequenceRef.getLength();
488
489     if (aSize == 0)
490     {
491       //Its been deleted
492       return;
493     }
494
495     int position;
496     Annotation[] temp = new Annotation[aSize];
497     Integer index;
498
499     for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++)
500     {
501       index = new Integer(a);
502       if (sequenceMapping.containsKey(index))
503       {
504         position = sequenceRef.findIndex(a) - 1;
505
506         temp[position] = (Annotation) sequenceMapping.get(index);
507       }
508     }
509
510     annotations = temp;
511   }
512   /**
513    * remove any null entries in annotation row and return the
514    * number of non-null annotation elements.
515    * @return
516    */
517   private int compactAnnotationArray() {
518     int j=0;
519     for (int i=0;i<annotations.length; i++) {
520       if (annotations[i]!=null && j!=i) {
521         annotations[j++] = annotations[i];
522       }
523     }
524     Annotation[] ann = annotations;
525     annotations = new Annotation[j];
526     System.arraycopy(ann, 0, annotations, 0, j);
527     ann = null;
528     return j;
529   }
530
531   /**
532    * Associate this annotion with the aligned residues of a particular sequence.
533    * sequenceMapping will be updated in the following way:
534    *   null sequenceI - existing mapping will be discarded but annotations left in mapped positions.
535    *   valid sequenceI not equal to current sequenceRef: mapping is discarded and rebuilt assuming 1:1 correspondence
536    *   TODO: overload with parameter to specify correspondence between current and new sequenceRef
537    * @param sequenceI
538    */
539   public void setSequenceRef(SequenceI sequenceI)
540   {
541     if (sequenceI != null)
542     {
543       if (sequenceRef != null)
544       {
545         if (sequenceRef != sequenceI && !sequenceRef.equals(sequenceI) && sequenceRef.getDatasetSequence()!=sequenceI.getDatasetSequence())
546         {
547           // if sequenceRef isn't intersecting with sequenceI
548           // throw away old mapping and reconstruct.
549           sequenceRef = null;
550           if (sequenceMapping != null)
551           {
552             sequenceMapping = null;
553             // compactAnnotationArray();
554           }
555           createSequenceMapping(sequenceI, 1, true);
556           adjustForAlignment();
557         }
558         else
559         {
560           // Mapping carried over
561           sequenceRef = sequenceI;
562         }
563       }
564       else
565       {
566         // No mapping exists
567         createSequenceMapping(sequenceI, 1, true);
568         adjustForAlignment();
569       }
570     }
571     else
572     {
573       // throw away the mapping without compacting.
574       sequenceMapping = null;
575       sequenceRef = null;
576     }
577   }
578 }