JAL-2055 prototype alternate feature colouring based on
[jalview.git] / src / jalview / datamodel / SequenceFeature.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel;
22
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.Vector;
26
27 /**
28  * DOCUMENT ME!
29  * 
30  * @author $author$
31  * @version $Revision$
32  */
33 public class SequenceFeature
34 {
35   private static final String STATUS = "status";
36
37   private static final String STRAND = "STRAND";
38
39   // private key for Phase designed not to conflict with real GFF data
40   private static final String PHASE = "!Phase";
41
42   // private key for feature number designed not to conflict with real GFF data
43   private static final String NUMBER = "!Num";
44
45   /*
46    * ATTRIBUTES is reserved for the GFF 'column 9' data, formatted as
47    * name1=value1;name2=value2,value3;...etc
48    */
49   private static final String ATTRIBUTES = "ATTRIBUTES";
50
51   public int begin;
52
53   public int end;
54
55   public float score;
56
57   public String type;
58
59   public String description;
60
61   public Map<String, Object> otherDetails;
62
63   public Vector<String> links;
64
65   // Feature group can be set from a features file
66   // as a group of features between STARTGROUP and ENDGROUP markers
67   public String featureGroup;
68
69   public SequenceFeature()
70   {
71   }
72
73   /**
74    * Constructs a duplicate feature. Note: Uses makes a shallow copy of the
75    * otherDetails map, so the new and original SequenceFeature may reference the
76    * same objects in the map.
77    * 
78    * @param cpy
79    */
80   public SequenceFeature(SequenceFeature cpy)
81   {
82     if (cpy != null)
83     {
84       begin = cpy.begin;
85       end = cpy.end;
86       score = cpy.score;
87       if (cpy.type != null)
88       {
89         type = new String(cpy.type);
90       }
91       if (cpy.description != null)
92       {
93         description = new String(cpy.description);
94       }
95       if (cpy.featureGroup != null)
96       {
97         featureGroup = new String(cpy.featureGroup);
98       }
99       if (cpy.otherDetails != null)
100       {
101         try
102         {
103           otherDetails = (Map<String, Object>) ((HashMap<String, Object>) cpy.otherDetails)
104                   .clone();
105         } catch (Exception e)
106         {
107           // ignore
108         }
109       }
110       if (cpy.links != null && cpy.links.size() > 0)
111       {
112         links = new Vector<String>();
113         for (int i = 0, iSize = cpy.links.size(); i < iSize; i++)
114         {
115           links.addElement(cpy.links.elementAt(i));
116         }
117       }
118     }
119   }
120
121   /**
122    * Constructor including a Status value
123    * 
124    * @param type
125    * @param desc
126    * @param status
127    * @param begin
128    * @param end
129    * @param featureGroup
130    */
131   public SequenceFeature(String type, String desc, String status,
132           int begin, int end, String featureGroup)
133   {
134     this(type, desc, begin, end, featureGroup);
135     setStatus(status);
136   }
137
138   /**
139    * Constructor
140    * 
141    * @param type
142    * @param desc
143    * @param begin
144    * @param end
145    * @param featureGroup
146    */
147   SequenceFeature(String type, String desc, int begin, int end,
148           String featureGroup)
149   {
150     this.type = type;
151     this.description = desc;
152     this.begin = begin;
153     this.end = end;
154     this.featureGroup = featureGroup;
155   }
156
157   /**
158    * Constructor including a score value
159    * 
160    * @param type
161    * @param desc
162    * @param begin
163    * @param end
164    * @param score
165    * @param featureGroup
166    */
167   public SequenceFeature(String type, String desc, int begin, int end,
168           float score, String featureGroup)
169   {
170     this(type, desc, begin, end, featureGroup);
171     this.score = score;
172   }
173
174   /**
175    * Two features are considered equal if they have the same type, group,
176    * description, start, end, phase, strand, and (if present) 'Name', ID' and
177    * 'Parent' attributes.
178    * 
179    * Note we need to check Parent to distinguish the same exon occurring in
180    * different transcripts (in Ensembl GFF). This allows assembly of transcript
181    * sequences from their component exon regions.
182    */
183   @Override
184   public boolean equals(Object o)
185   {
186     return equals(o, false);
187   }
188
189   /**
190    * Overloaded method allows the equality test to optionally ignore the
191    * 'Parent' attribute of a feature. This supports avoiding adding many
192    * superficially duplicate 'exon' or CDS features to genomic or protein
193    * sequence.
194    * 
195    * @param o
196    * @param ignoreParent
197    * @return
198    */
199   public boolean equals(Object o, boolean ignoreParent)
200   {
201     if (o == null || !(o instanceof SequenceFeature))
202     {
203       return false;
204     }
205
206     SequenceFeature sf = (SequenceFeature) o;
207     if (begin != sf.begin || end != sf.end || score != sf.score)
208     {
209       return false;
210     }
211
212     if (getStrand() != sf.getStrand())
213     {
214       return false;
215     }
216
217     if (!(type + description + featureGroup + getPhase()).equals(sf.type
218             + sf.description + sf.featureGroup + sf.getPhase()))
219     {
220       return false;
221     }
222     if (!equalAttribute(getValue("ID"), sf.getValue("ID")))
223     {
224       return false;
225     }
226     if (!equalAttribute(getValue("Name"), sf.getValue("Name")))
227     {
228       return false;
229     }
230     if (!ignoreParent)
231     {
232       if (!equalAttribute(getValue("Parent"), sf.getValue("Parent")))
233       {
234         return false;
235       }
236     }
237     return true;
238   }
239
240   /**
241    * Returns true if both values are null, are both non-null and equal
242    * 
243    * @param att1
244    * @param att2
245    * @return
246    */
247   protected static boolean equalAttribute(Object att1, Object att2)
248   {
249     if (att1 == null && att2 == null)
250     {
251       return true;
252     }
253     if (att1 != null)
254     {
255       return att1.equals(att2);
256     }
257     return att2.equals(att1);
258   }
259
260   /**
261    * DOCUMENT ME!
262    * 
263    * @return DOCUMENT ME!
264    */
265   public int getBegin()
266   {
267     return begin;
268   }
269
270   public void setBegin(int start)
271   {
272     this.begin = start;
273   }
274
275   /**
276    * DOCUMENT ME!
277    * 
278    * @return DOCUMENT ME!
279    */
280   public int getEnd()
281   {
282     return end;
283   }
284
285   public void setEnd(int end)
286   {
287     this.end = end;
288   }
289
290   /**
291    * DOCUMENT ME!
292    * 
293    * @return DOCUMENT ME!
294    */
295   public String getType()
296   {
297     return type;
298   }
299
300   public void setType(String type)
301   {
302     this.type = type;
303   }
304
305   /**
306    * DOCUMENT ME!
307    * 
308    * @return DOCUMENT ME!
309    */
310   public String getDescription()
311   {
312     return description;
313   }
314
315   public void setDescription(String desc)
316   {
317     description = desc;
318   }
319
320   public String getFeatureGroup()
321   {
322     return featureGroup;
323   }
324
325   public void setFeatureGroup(String featureGroup)
326   {
327     this.featureGroup = featureGroup;
328   }
329
330   public void addLink(String labelLink)
331   {
332     if (links == null)
333     {
334       links = new Vector<String>();
335     }
336
337     links.insertElementAt(labelLink, 0);
338   }
339
340   public float getScore()
341   {
342     return score;
343   }
344
345   public void setScore(float value)
346   {
347     score = value;
348   }
349
350   /**
351    * Used for getting values which are not in the basic set. eg STRAND, PHASE
352    * for GFF file
353    * 
354    * @param key
355    *          String
356    */
357   public Object getValue(String key)
358   {
359     if (otherDetails == null)
360     {
361       return null;
362     }
363     else
364     {
365       return otherDetails.get(key);
366     }
367   }
368
369   /**
370    * Returns a property value for the given key if known, else the specified
371    * default value
372    * 
373    * @param key
374    * @param defaultValue
375    * @return
376    */
377   public Object getValue(String key, Object defaultValue)
378   {
379     Object value = getValue(key);
380     return value == null ? defaultValue : value;
381   }
382
383   /**
384    * Used for setting values which are not in the basic set. eg STRAND, FRAME
385    * for GFF file
386    * 
387    * @param key
388    *          eg STRAND
389    * @param value
390    *          eg +
391    */
392   public void setValue(String key, Object value)
393   {
394     if (value != null)
395     {
396       if (otherDetails == null)
397       {
398         otherDetails = new HashMap<String, Object>();
399       }
400
401       otherDetails.put(key, value);
402     }
403   }
404
405   /*
406    * The following methods are added to maintain the castor Uniprot mapping file
407    * for the moment.
408    */
409   public void setStatus(String status)
410   {
411     setValue(STATUS, status);
412   }
413
414   public String getStatus()
415   {
416     return (String) getValue(STATUS);
417   }
418
419   public void setAttributes(String attr)
420   {
421     setValue(ATTRIBUTES, attr);
422   }
423
424   public String getAttributes()
425   {
426     return (String) getValue(ATTRIBUTES);
427   }
428
429   public void setPosition(int pos)
430   {
431     begin = pos;
432     end = pos;
433   }
434
435   public int getPosition()
436   {
437     return begin;
438   }
439
440   /**
441    * Return 1 for forward strand ('+' in GFF), -1 for reverse strand ('-' in
442    * GFF), and 0 for unknown or not (validly) specified
443    * 
444    * @return
445    */
446   public int getStrand()
447   {
448     int strand = 0;
449     if (otherDetails != null)
450     {
451       Object str = otherDetails.get(STRAND);
452       if ("-".equals(str))
453       {
454         strand = -1;
455       }
456       else if ("+".equals(str))
457       {
458         strand = 1;
459       }
460     }
461     return strand;
462   }
463
464   /**
465    * Set the value of strand
466    * 
467    * @param strand
468    *          should be "+" for forward, or "-" for reverse
469    */
470   public void setStrand(String strand)
471   {
472     setValue(STRAND, strand);
473   }
474
475   public void setPhase(String phase)
476   {
477     setValue(PHASE, phase);
478   }
479
480   public String getPhase()
481   {
482     return (String) getValue(PHASE);
483   }
484
485   /**
486    * Set the ordinal number of this feature on the sequence
487    * 
488    * @param num
489    */
490   public void setFeatureNumber(int num)
491   {
492     setValue(NUMBER, String.valueOf(num));
493   }
494
495   /**
496    * Returns the feature number if set, else 0. Intended for use as the ordinal
497    * position of the feature on the sequence for features of the same type.
498    * 
499    * @return
500    */
501   public int getFeatureNumber()
502   {
503     try
504     {
505       return Integer.parseInt((String) getValue(NUMBER));
506     } catch (Exception e)
507     {
508       // property absent or not numeric
509       return 0;
510     }
511   }
512
513   /**
514    * Readable representation, for debug only, not guaranteed not to change
515    * between versions
516    */
517   @Override
518   public String toString()
519   {
520     return String.format("%d %d %s %s", getBegin(), getEnd(), getType(),
521             getDescription());
522   }
523
524   /**
525    * Overridden to ensure that whenever two objects are equal, they have the
526    * same hashCode
527    */
528   @Override
529   public int hashCode()
530   {
531     String s = getType() + getDescription() + getFeatureGroup()
532             + getValue("ID") + getValue("Name") + getValue("Parent")
533             + getPhase();
534     return s.hashCode() + getBegin() + getEnd() + (int) getScore()
535             + getStrand();
536   }
537 }