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