copy constructors and AlignmentAnnotation sequenceRef mechanisms for pasting sequence...
[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 static final int NO_GRAPH = 0;
84
85   public static final int BAR_GRAPH = 1;
86
87   public static final int LINE_GRAPH = 2;
88
89   public static int getGraphValueFromString(String string)
90   {
91     if (string.equalsIgnoreCase("BAR_GRAPH"))
92     {
93       return BAR_GRAPH;
94     }
95     else if (string.equalsIgnoreCase("LINE_GRAPH"))
96     {
97       return LINE_GRAPH;
98     }
99     else
100     {
101       return NO_GRAPH;
102     }
103   }
104
105   /**
106    * Creates a new AlignmentAnnotation object.
107    *
108    * @param label DOCUMENT ME!
109    * @param description DOCUMENT ME!
110    * @param annotations DOCUMENT ME!
111    */
112   public AlignmentAnnotation(String label, String description,
113                              Annotation[] annotations)
114   {
115     // always editable?
116     editable = true;
117     this.label = label;
118     this.description = description;
119     this.annotations = annotations;
120
121     areLabelsSecondaryStructure();
122   }
123
124   void areLabelsSecondaryStructure()
125   {
126     boolean nonSSLabel = false;
127     for (int i = 0; i < annotations.length; i++)
128     {
129       if (annotations[i] == null)
130       {
131         continue;
132       }
133
134       if (annotations[i].secondaryStructure == 'H' ||
135           annotations[i].secondaryStructure == 'E')
136       {
137         hasIcons = true;
138       }
139
140       if (annotations[i].displayCharacter.length() == 1
141           && !annotations[i].displayCharacter.equals("H")
142           && !annotations[i].displayCharacter.equals("E")
143           && !annotations[i].displayCharacter.equals("-")
144           && !annotations[i].displayCharacter.equals("."))
145       {
146         if (jalview.schemes.ResidueProperties.aaIndex
147             [annotations[i].displayCharacter.charAt(0)] < 23)
148         {
149           nonSSLabel = true;
150         }
151       }
152
153       if (annotations[i].displayCharacter.length() > 0)
154       {
155         hasText = true;
156       }
157     }
158
159     if (nonSSLabel)
160     {
161       hasIcons = false;
162       for (int j = 0; j < annotations.length; j++)
163       {
164         if (annotations[j] != null && annotations[j].secondaryStructure != ' ')
165         {
166           annotations[j].displayCharacter
167               = String.valueOf(annotations[j].secondaryStructure);
168           annotations[j].secondaryStructure = ' ';
169         }
170
171       }
172
173     }
174
175     annotationId = this.hashCode() + "";
176   }
177   /**
178    * Creates a new AlignmentAnnotation object.
179    *
180    * @param label DOCUMENT ME!
181    * @param description DOCUMENT ME!
182    * @param annotations DOCUMENT ME!
183    * @param min DOCUMENT ME!
184    * @param max DOCUMENT ME!
185    * @param winLength DOCUMENT ME!
186    */
187   public AlignmentAnnotation(String label, String description,
188                              Annotation[] annotations, float min, float max,
189                              int graphType)
190   {
191     // graphs are not editable
192     this.label = label;
193     this.description = description;
194     this.annotations = annotations;
195     graph = graphType;
196     graphMin = min;
197     graphMax = max;
198     validateRangeAndDisplay();
199   }
200   /**
201    * checks graphMin and graphMax,
202    * secondary structure symbols,
203    * sets graphType appropriately,
204    * sets null labels to the empty string
205    * if appropriate.
206    */
207   private void validateRangeAndDisplay() {
208     int graphType = graph;
209     float min = graphMin;
210     float max = graphMax;
211     boolean drawValues = true;
212     
213     if (min == max)
214     {
215       min = 999999999;
216       for (int i = 0; i < annotations.length; i++)
217       {
218         if (annotations[i] == null)
219         {
220           continue;
221         }
222
223         if (drawValues && annotations[i].displayCharacter.length() > 1)
224         {
225           drawValues = false;
226         }
227
228         if (annotations[i].value > max)
229         {
230           max = annotations[i].value;
231         }
232
233         if (annotations[i].value < min)
234         {
235           min = annotations[i].value;
236         }
237       }
238     }
239
240     graphMin = min;
241     graphMax = max;
242
243     areLabelsSecondaryStructure();
244
245     if (!drawValues && graphType != NO_GRAPH)
246     {
247       for (int i = 0; i < annotations.length; i++)
248       {
249         if (annotations[i] != null)
250         {
251           annotations[i].displayCharacter = "";
252         }
253       }
254     }
255   }
256   
257   /**
258    * Copy constructor
259    * creates a new independent annotation row with the same associated sequenceRef 
260    * @param annotation
261    */
262   public AlignmentAnnotation(AlignmentAnnotation annotation)
263   {
264     this.label = new String(annotation.label);
265     this.description = new String(annotation.description);
266     this.graphMin = annotation.graphMin;
267     this.graphMax = annotation.graphMax;
268     this.graph = annotation.graph;
269     this.graphHeight = annotation.graphHeight;
270     this.graphGroup = annotation.graphGroup;
271     this.editable = annotation.editable;
272     this.autoCalculated = annotation.autoCalculated;
273     this.hasIcons = annotation.hasIcons;
274     this.hasText = annotation.hasText;
275     this.height = annotation.height;
276     this.label = annotation.label;
277     if (threshold!=null) {
278       threshold = new GraphLine(annotation.threshold);
279     }
280     if (annotation.annotations!=null) {
281       Vector anvec = new Vector();
282       Annotation[] ann = annotation.annotations;
283       this.annotations = new Annotation[ann.length];
284       for (int i=0; i<ann.length; i++) {
285         annotations[i] = new Annotation(ann[i]);
286         anvec.add(ann[i]); // for lookup if sequenceMapping exists.
287       };
288       if (annotation.sequenceRef!=null) {
289         this.sequenceRef = annotation.sequenceRef;
290         if (annotation.sequenceMapping!=null)
291         {
292           sequenceMapping = new Hashtable();
293           Enumeration pos=annotation.sequenceMapping.keys();
294           while (pos.hasMoreElements()) {
295             Integer p = (Integer) pos.nextElement();
296             Annotation a = (Annotation) sequenceMapping.get(p);
297             sequenceMapping.put(p, annotations[anvec.indexOf(a)]); 
298           }
299           anvec.clear();
300         } else {
301           this.sequenceMapping = null;
302         }
303       }
304     }
305     validateRangeAndDisplay(); // construct hashcodes, etc.
306   }
307
308   /**
309    * DOCUMENT ME!
310    *
311    * @return DOCUMENT ME!
312    */
313   public String toString()
314   {
315     StringBuffer buffer = new StringBuffer();
316
317     for (int i = 0; i < annotations.length; i++)
318     {
319       if (annotations[i] != null)
320       {
321         if (graph != 0)
322         {
323           buffer.append(annotations[i].value);
324         }
325         else if (hasIcons)
326         {
327           buffer.append(annotations[i].secondaryStructure);
328         }
329         else
330         {
331           buffer.append(annotations[i].displayCharacter);
332         }
333       }
334
335       buffer.append(", ");
336     }
337
338     if (label.equals("Consensus"))
339     {
340       buffer.append("\n");
341
342       for (int i = 0; i < annotations.length; i++)
343       {
344         if (annotations[i] != null)
345         {
346           buffer.append(annotations[i].description);
347         }
348
349         buffer.append(", ");
350       }
351     }
352
353     return buffer.toString();
354   }
355
356   public void setThreshold(GraphLine line)
357   {
358     threshold = line;
359   }
360
361   public GraphLine getThreshold()
362   {
363     return threshold;
364   }
365
366   /**
367    * 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.
368    * @param seqRef
369    * @param startRes
370    * @param alreadyMapped
371    */
372   public void createSequenceMapping(SequenceI seqRef,
373                                     int startRes,
374                                     boolean alreadyMapped)
375   {
376
377     if (seqRef == null)
378     {
379       return;
380     }
381
382     sequenceMapping = new java.util.Hashtable();
383
384     sequenceRef = seqRef;
385     int seqPos;
386
387     for (int i = 0; i < annotations.length; i++)
388     {
389       if (annotations[i] != null)
390       {
391         if (alreadyMapped)
392         {
393           seqPos = seqRef.findPosition(i);
394         }
395         else
396         {
397           seqPos = i + startRes;
398         }
399
400         sequenceMapping.put(new Integer(seqPos), annotations[i]);
401       }
402     }
403
404   }
405
406   public void adjustForAlignment()
407   {
408     if (sequenceRef==null)
409       return;
410     
411     int a = 0, aSize = sequenceRef.getLength();
412
413     if (aSize == 0)
414     {
415       //Its been deleted
416       return;
417     }
418
419     int position;
420     Annotation[] temp = new Annotation[aSize];
421     Integer index;
422
423     for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++)
424     {
425       index = new Integer(a);
426       if (sequenceMapping.containsKey(index))
427       {
428         position = sequenceRef.findIndex(a) - 1;
429
430         temp[position] = (Annotation) sequenceMapping.get(index);
431       }
432     }
433
434     annotations = temp;
435   }
436   /**
437    * remove any null entries in annotation row and return the
438    * number of non-null annotation elements.
439    * @return
440    */
441   private int compactAnnotationArray() {
442     int j=0;
443     for (int i=0;i<annotations.length; i++) {
444       if (annotations[i]!=null && j!=i) {
445         annotations[j++] = annotations[i];
446       }
447     }
448     Annotation[] ann = annotations;
449     annotations = new Annotation[j];
450     System.arraycopy(ann, 0, annotations, 0, j);
451     ann = null;
452     return j;
453   }
454   
455   /**
456    * Associate this annotion with the aligned residues of a particular sequence.
457    * sequenceMapping will be updated in the following way:
458    *   null sequenceI - existing mapping will be discarded but annotations left in mapped positions.
459    *   valid sequenceI not equal to current sequenceRef: mapping is discarded and rebuilt assuming 1:1 correspondence
460    *   TODO: overload with parameter to specify correspondence between current and new sequenceRef
461    * @param sequenceI
462    */
463   public void setSequenceRef(SequenceI sequenceI)
464   {
465     if (sequenceI!=null) {
466       if (sequenceRef!=null) {
467         if (sequenceRef!=sequenceI && !sequenceRef.equals(sequenceI)) {
468           // throw away old mapping and reconstruct.
469           sequenceRef=null;
470           if (sequenceMapping!=null) 
471           {
472             sequenceMapping=null;
473             // compactAnnotationArray();
474           }
475           createSequenceMapping(sequenceI, 1,true);
476           adjustForAlignment();
477         } else {
478           // Mapping carried over
479           sequenceRef = sequenceI;
480         }
481       } else {
482         // No mapping exists
483         createSequenceMapping(sequenceI, 1, true);
484         adjustForAlignment();
485       }
486     } else {
487       // throw away the mapping without compacting.
488       sequenceMapping=null;
489       sequenceRef = null;
490     }
491   }
492 }