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