95af29ddc826611aa5ae455f838e8dc0874411d6
[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 import java.util.Vector;
24
25 /**
26  * DOCUMENT ME!
27  *
28  * @author $author$
29  * @version $Revision$
30  */
31 public class AlignmentAnnotation
32 {
33   /** If true, this annotations is calculated every edit,
34    * eg consensus, quality or conservation graphs */
35   public boolean autoCalculated = false;
36
37   public String annotationId;
38
39   public SequenceI sequenceRef;
40
41   /** DOCUMENT ME!! */
42   public String label;
43
44   /** DOCUMENT ME!! */
45   public String description;
46
47   /** DOCUMENT ME!! */
48   public Annotation[] annotations;
49
50   public java.util.Hashtable sequenceMapping;
51
52   /** DOCUMENT ME!! */
53   public float graphMin;
54
55   /** DOCUMENT ME!! */
56   public float graphMax;
57
58   public GraphLine threshold;
59
60   // Graphical hints and tips
61
62   /** DOCUMENT ME!! */
63   public boolean editable = false;
64
65   /** DOCUMENT ME!! */
66   public boolean hasIcons; //
67
68   /** DOCUMENT ME!! */
69   public boolean hasText;
70
71   /** DOCUMENT ME!! */
72   public boolean visible = true;
73
74   public int graphGroup = -1;
75
76   /** DOCUMENT ME!! */
77   public int height = 0;
78
79   public int graph = 0;
80
81   public int graphHeight = 40;
82
83   public boolean padGaps = true;
84
85   public static final int NO_GRAPH = 0;
86
87   public static final int BAR_GRAPH = 1;
88
89   public static final int LINE_GRAPH = 2;
90
91   public static int getGraphValueFromString(String string)
92   {
93     if (string.equalsIgnoreCase("BAR_GRAPH"))
94     {
95       return BAR_GRAPH;
96     }
97     else if (string.equalsIgnoreCase("LINE_GRAPH"))
98     {
99       return LINE_GRAPH;
100     }
101     else
102     {
103       return NO_GRAPH;
104     }
105   }
106
107   /**
108    * Creates a new AlignmentAnnotation object.
109    *
110    * @param label DOCUMENT ME!
111    * @param description DOCUMENT ME!
112    * @param annotations DOCUMENT ME!
113    */
114   public AlignmentAnnotation(String label, String description,
115                              Annotation[] annotations)
116   {
117     // always editable?
118     editable = true;
119     this.label = label;
120     this.description = description;
121     this.annotations = annotations;
122
123     areLabelsSecondaryStructure();
124   }
125
126   void areLabelsSecondaryStructure()
127   {
128     boolean nonSSLabel = false;
129     for (int i = 0; i < annotations.length; i++)
130     {
131       if (annotations[i] == null)
132       {
133         padGaps = false;
134         continue;
135       }
136
137       if (annotations[i].secondaryStructure == 'H' ||
138           annotations[i].secondaryStructure == 'E')
139       {
140         hasIcons = true;
141       }
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     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     if (threshold!=null) {
285       threshold = new GraphLine(annotation.threshold);
286     }
287     if (annotation.annotations!=null) {
288       Vector anvec = new Vector();
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         anvec.addElement(ann[i]); // for lookup if sequenceMapping exists.
294       };
295       if (annotation.sequenceRef!=null) {
296         this.sequenceRef = annotation.sequenceRef;
297         if (annotation.sequenceMapping!=null)
298         {
299           sequenceMapping = new Hashtable();
300           Enumeration pos=annotation.sequenceMapping.keys();
301           while (pos.hasMoreElements()) {
302             Integer p = (Integer) pos.nextElement();
303             Annotation a = (Annotation) sequenceMapping.get(p);
304             sequenceMapping.put(p, annotations[anvec.indexOf(a)]);
305           }
306           anvec.removeAllElements();
307         } else {
308           this.sequenceMapping = null;
309         }
310       }
311     }
312     validateRangeAndDisplay(); // construct hashcodes, etc.
313   }
314
315   /**
316    * DOCUMENT ME!
317    *
318    * @return DOCUMENT ME!
319    */
320   public String toString()
321   {
322     StringBuffer buffer = new StringBuffer();
323
324     for (int i = 0; i < annotations.length; i++)
325     {
326       if (annotations[i] != null)
327       {
328         if (graph != 0)
329         {
330           buffer.append(annotations[i].value);
331         }
332         else if (hasIcons)
333         {
334           buffer.append(annotations[i].secondaryStructure);
335         }
336         else
337         {
338           buffer.append(annotations[i].displayCharacter);
339         }
340       }
341
342       buffer.append(", ");
343     }
344
345     if (label.equals("Consensus"))
346     {
347       buffer.append("\n");
348
349       for (int i = 0; i < annotations.length; i++)
350       {
351         if (annotations[i] != null)
352         {
353           buffer.append(annotations[i].description);
354         }
355
356         buffer.append(", ");
357       }
358     }
359
360     return buffer.toString();
361   }
362
363   public void setThreshold(GraphLine line)
364   {
365     threshold = line;
366   }
367
368   public GraphLine getThreshold()
369   {
370     return threshold;
371   }
372
373   /**
374    * 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.
375    * @param seqRef
376    * @param startRes
377    * @param alreadyMapped
378    */
379   public void createSequenceMapping(SequenceI seqRef,
380                                     int startRes,
381                                     boolean alreadyMapped)
382   {
383
384     if (seqRef == null)
385     {
386       return;
387     }
388
389     sequenceMapping = new java.util.Hashtable();
390
391     sequenceRef = seqRef;
392     int seqPos;
393
394     for (int i = 0; i < annotations.length; i++)
395     {
396       if (annotations[i] != null)
397       {
398         if (alreadyMapped)
399         {
400           seqPos = seqRef.findPosition(i);
401         }
402         else
403         {
404           seqPos = i + startRes;
405         }
406
407         sequenceMapping.put(new Integer(seqPos), annotations[i]);
408       }
409     }
410
411   }
412
413   public void adjustForAlignment()
414   {
415     if (sequenceRef==null)
416       return;
417
418     int a = 0, aSize = sequenceRef.getLength();
419
420     if (aSize == 0)
421     {
422       //Its been deleted
423       return;
424     }
425
426     int position;
427     Annotation[] temp = new Annotation[aSize];
428     Integer index;
429
430     for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++)
431     {
432       index = new Integer(a);
433       if (sequenceMapping.containsKey(index))
434       {
435         position = sequenceRef.findIndex(a) - 1;
436
437         temp[position] = (Annotation) sequenceMapping.get(index);
438       }
439     }
440
441     annotations = temp;
442   }
443   /**
444    * remove any null entries in annotation row and return the
445    * number of non-null annotation elements.
446    * @return
447    */
448   private int compactAnnotationArray() {
449     int j=0;
450     for (int i=0;i<annotations.length; i++) {
451       if (annotations[i]!=null && j!=i) {
452         annotations[j++] = annotations[i];
453       }
454     }
455     Annotation[] ann = annotations;
456     annotations = new Annotation[j];
457     System.arraycopy(ann, 0, annotations, 0, j);
458     ann = null;
459     return j;
460   }
461
462   /**
463    * Associate this annotion with the aligned residues of a particular sequence.
464    * sequenceMapping will be updated in the following way:
465    *   null sequenceI - existing mapping will be discarded but annotations left in mapped positions.
466    *   valid sequenceI not equal to current sequenceRef: mapping is discarded and rebuilt assuming 1:1 correspondence
467    *   TODO: overload with parameter to specify correspondence between current and new sequenceRef
468    * @param sequenceI
469    */
470   public void setSequenceRef(SequenceI sequenceI)
471   {
472     if (sequenceI!=null) {
473       if (sequenceRef!=null) {
474         if (sequenceRef!=sequenceI && !sequenceRef.equals(sequenceI)) {
475           // throw away old mapping and reconstruct.
476           sequenceRef=null;
477           if (sequenceMapping!=null)
478           {
479             sequenceMapping=null;
480             // compactAnnotationArray();
481           }
482           createSequenceMapping(sequenceI, 1,true);
483           adjustForAlignment();
484         } else {
485           // Mapping carried over
486           sequenceRef = sequenceI;
487         }
488       } else {
489         // No mapping exists
490         createSequenceMapping(sequenceI, 1, true);
491         adjustForAlignment();
492       }
493     } else {
494       // throw away the mapping without compacting.
495       sequenceMapping=null;
496       sequenceRef = null;
497     }
498   }
499 }